---
url: 'https://adk.nht.io/batteries/llm/bedrock-converse.md'
description: >-
  AWS Bedrock's native Converse API over plain HTTPS: content-block grammar,
  strict role alternation, bearer-token auth without the AWS SDK, and explicit
  repair policies for auditing what Bedrock actually enforces.
---

# AWS Bedrock Converse

## LLM summary — AWS Bedrock Converse

* [`BedrockConverseAdapter`](https://adk.nht.io/api/@nhtio/adk/batteries/llm/bedrock_converse/adapter/classes/BedrockConverseAdapter) from `@nhtio/adk/batteries/llm/bedrock_converse`. Drives Bedrock's native `POST {baseURL}/model/{modelId}/converse` (and `/converse-stream`). Dependency-light: plain HTTPS over `fetch`, no AWS SDK peer, no SigV4 signer.
* Auth: Bedrock API key (`ABSK…`) sent as `Authorization: Bearer <apiKey>` against `bedrock-runtime.<region>.amazonaws.com`.
* Required option: `model`. Optional: `apiKey`, `region` (default `us-east-1`), `baseURL`, `stream`, `maxTokens`, `temperature`, `topP`, `stopSequences`, `toolChoice`, `additionalModelRequestFields`, `alternationPolicy` (`'merge'` | `'filler'` | `'reject'`, default `'merge'`), `timeoutMs`, `retry`, `fetch`, `tokenEncoding`, `spoolStore`, `bucketOrder`, `thoughtSurfacing`, `unsupportedMediaPolicy`, `localToolCallParser`, `onRawGeneration`, `onPromptAssembled`, `helpers`, `forgeToolsFilter`.
* `STASH_KEY` is `'bedrockConverse'`. Resolution order: constructor baseline → executor-call overrides → `ctx.stash.bedrockConverse`, re-validated each iteration.
* Wire shape: content-BLOCK protocol (`content[]` of `{text}`, `{image}`, `{toolUse}`, `{toolResult}`, `{reasoningContent}`). One assistant turn carries prose and a tool call together. Tool results ride a **`user`** turn. System blocks live at top-level `system[]` (no `system` role). Roles must strictly alternate (`user` ↔ `assistant`).
* `toolConfig` invariant: required whenever any `toolUse` or `toolResult` block appears in history, and `tools[]` must be non-empty. When replaying past tool calls without current tools, the battery backfills declarations from history.
* Schema sanitizer: recursively strips unsupported keywords (`$schema`, `$ref`, `definitions`, `additionalProperties`, `patternProperties`, `allOf`, `anyOf`, `oneOf`, `not`, `format`, etc.). `toolUseId` sanitized to `[A-Za-z0-9_-]{1,64}`.
* Exceptions: `E_INVALID_BEDROCK_CONVERSE_OPTIONS` (529), `E_CONVERSE_REQUEST_FAILED` (502), `E_CONVERSE_MISSING_TOOL_CONFIG` (400), `E_CONVERSE_ALTERNATION_VIOLATION` (400), `E_CONVERSE_STREAM_ERROR` (502), `E_CONVERSE_INVALID_TOOL_INPUT` (422).

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`](https://adk.nht.io/api/@nhtio/adk/batteries/llm/bedrock_converse/exceptions/variables/E_CONVERSE_MISSING_TOOL_CONFIG) when a tool block lacks configuration, or
  [`E_CONVERSE_ALTERNATION_VIOLATION`](https://adk.nht.io/api/@nhtio/adk/batteries/llm/bedrock_converse/exceptions/variables/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](/batteries/validation/api-surface-scope) 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`](https://adk.nht.io/api/@nhtio/adk/batteries/llm/bedrock_converse/exceptions/variables/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.
