Skip to content
4 min read · 776 words

AWS Bedrock Converse

Bedrock Converse is not an OpenAI clone. It is a content-block protocol with strict structural rules: turns contain typed block arrays (content[]), prose and tool calls share a single assistant turn, tool results arrive on user turns, system instructions live in a top-level system[] array, and roles must strictly alternate.

This battery drives that native wire directly over plain HTTPS with a Bedrock API key. It does not depend on @aws-sdk/client-bedrock-runtime or a SigV4 request signer.

ts
import { BedrockConverseAdapter } from '@nhtio/adk/batteries/llm/bedrock_converse'

const executor = new BedrockConverseAdapter({
  model: 'us.amazon.nova-2-lite-v1:0',
  apiKey: process.env.AWS_BEARER_TOKEN_BEDROCK,
  region: 'us-east-1',
})

// executor.executor() is the DispatchExecutorFn you hand to TurnRunner.

What this wire buys you

  • No AWS SDK footprint. Bedrock supports Bearer-token authentication (Authorization: Bearer ABSK…) directly against bedrock-runtime.<region>.amazonaws.com. Skipping the AWS SDK and SigV4 signing keeps the battery light, fast to bundle, and runnable anywhere standard fetch exists.
  • Native content-block structure. Rather than forcing tool invocations into separate synthetic messages, Converse bundles prose and a {toolUse} block in one assistant turn. When the model introduces a call with reasoning or instructions, both arrive together in the turn that generated them.
  • Auditable role alternation. Gateways fronting Converse silently merge consecutive same-role turns to satisfy Bedrock's alternation check. The alternationPolicy option lets you choose how history is repaired: 'merge' (the default, lossless block concatenation), 'filler' (synthetic opposite-role turns), or 'reject' (send history untouched so Converse's own error surfaces).
  • Converse-specific exception classification. When Bedrock rejects a payload with an undifferentiated HTTP 400 ValidationException, the adapter inspects the response body and surfaces actionable exceptions: E_CONVERSE_MISSING_TOOL_CONFIG when a tool block lacks configuration, or E_CONVERSE_ALTERNATION_VIOLATION when roles fail to alternate under alternationPolicy: 'reject'.

Converse is a translator

Before treating a Converse response as the definitive behavior of an underlying model, remember one detail: Converse is itself a translation layer.

AWS accepts a single content-block schema and translates it into the native wire formats of Amazon Nova, Anthropic Claude, Meta Llama, and Mistral. A passing payload or an unexpected rejection through Converse is a verdict about AWS's translation grammar, not necessarily the raw model underneath it.

This distinction is why alternationPolicy: 'reject' exists. When auditing whether turn state conforms to a vendor's actual rules, an upstream repair applied before dispatch masks non-conforming turns and makes client normalisation look like vendor tolerance. Read the doctrine in Which API surface a rule applies to before interpreting validation results obtained through this battery.

The gotchas, because they will get you

The toolConfig replay trap. Bedrock enforces two constraints that are in direct tension:

  1. toolConfig must be defined whenever any {toolUse} or {toolResult} block appears anywhere in messages — even during pure history replay when no tools are offered on the current turn.
  2. An empty tools: [] array inside toolConfig is an immediate validation error.

If you replay past turns that used a tool no longer available in the registry, Converse will reject the call if toolConfig is omitted and reject it if tools is empty. The adapter resolves this automatically: it scans history, identifies prior tool calls missing from the registry, and synthesizes placeholder declarations with permissive object schemas so Bedrock's correlation check passes. If you override buildConverseRequest, you must preserve this behavior or Bedrock will reject the transcript.

Consecutive same-role turns fail without repair. Converse rejects two consecutive user turns or two consecutive assistant turns with an HTTP 400. In the ADK, consecutive same-role messages can happen easily when tools return multiple results, when system messages are stripped, or when users send follow-up prompts. The default alternationPolicy: 'merge' folds consecutive same-role turns into a single turn by concatenating their content[] blocks. If you switch to 'reject', any non-alternating history will throw E_CONVERSE_ALTERNATION_VIOLATION.

JSON-Schema dialect limitations. Converse does not support the full JSON Schema specification for tool definitions. Keywords like $schema, $ref, definitions, additionalProperties, patternProperties, allOf, anyOf, oneOf, not, and format trigger validation errors that fail to name the offending property. The adapter automatically runs schemas through sanitizeConverseSchema to prune unsupported keys, and guarantees an empty schema falls back to { type: 'object', properties: {} } (which Converse requires).

Tool identifiers are charset-restricted. Bedrock requires toolUseId to match ^[A-Za-z0-9_-]{1,64}$. ADK's own identifiers conform, but an external one carrying colons, dots, or spaces is rejected — and the error does not name the id as the cause, which is what makes this expensive to diagnose. The adapter runs caller-supplied ids through sanitizeToolUseId first, replacing every out-of-charset character with _ and truncating at 64.

Images only, no native audio or video. Converse accepts inline image payloads (png, jpeg, gif, webp) in content[]. Audio and video attachments cannot be mapped to native Converse blocks; they are governed by unsupportedMediaPolicy and will throw or log according to your configuration.