Skip to content

fix(bedrock): preserve adaptive thinking effort through the /v1/messages bridge - #36507

Merged
mateo-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_bedrock_converse_thinking_stream_fix
Aug 11, 2026
Merged

fix(bedrock): preserve adaptive thinking effort through the /v1/messages bridge#36507
mateo-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_bedrock_converse_thinking_stream_fix

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Claude Code thinking on Bedrock Converse Opus 4.7 streamed zero thinking blocks
  • The /v1/messages bridge forwarded thinking: {"type": "adaptive"} but dropped output_config: {"effort": ...}
  • Bedrock Converse returns no reasoningContent for adaptive thinking without an effort tier

How it solves it:

  • The bridge's thinking translator now forwards the effort part of output_config for Bedrock targets
  • output_config becomes a supported openai param for reasoning Claude and ARN models on Converse
  • map_openai_params carries it through with the model's Bedrock effort ceiling applied
  • Application inference profile ARNs forward the effort verbatim, since the alias hides the model from the ceiling and capability gates
  • Re-enables the skipped e2e compat cell that catches this

RCA doc: https://app.notion.com/p/3b943b8acdab8197b710e0da01ce3773

User Flow

Before: a Claude Code user on Opus 4.7 via Bedrock (Converse) never sees extended thinking

  1. Claude Code sends POST http://localhost:4000/v1/messages with model claude-opus-4-7-bedrock-converse, thinking: {"type": "adaptive"} and output_config: {"effort": "max"} (what --effort max produces)
  2. The stream contains a single content_block_start of type text and no thinking block at all, so the CLI renders a plain answer with no reasoning
  3. The compat matrix cell fails with no thinking content block observed in stream-json events

After: the same request streams a thinking block before the text

  1. Claude Code sends the identical request
  2. The stream opens with content_block_start index 0 of type thinking (with its signature), then the text block at index 1
  3. The compat cell passes for all three Bedrock Converse tiers

Relevant issues

RCA doc: https://app.notion.com/p/3b943b8acdab8197b710e0da01ce3773

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Live proxy against real AWS Bedrock in us-east-1, booted with litellm --config tests/e2e/claude_code/test_config.yaml --host 127.0.0.1 --port 4110. Same curl before and after

$ curl -sN http://127.0.0.1:4110/v1/messages \
  -H "Authorization: Bearer sk-local-rca" -H "content-type: application/json" \
  -d '{"model": "claude-opus-4-7-bedrock-converse", "max_tokens": 2048, "stream": true,
       "thinking": {"type": "adaptive"}, "output_config": {"effort": "max"},
       "messages": [{"role": "user", "content": "I have a 3-gallon jug and a 5-gallon jug. How can I measure exactly 4 gallons of water? Think through the steps carefully."}]}'

Before, at commit d8762bf (base), the stream has no thinking block:

data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
...
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"input_tokens": 60, "output_tokens": 701}}

After, at commit 929ee52 (this PR), identical request:

data: {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "thinking": "", "signature": "Er8MCnMIEBABGAIqQBFiVd4S...[redacted]"}}
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "Er8MCnMIEBABGAIqQBFiVd4S...[redacted]"}}
data: {"type": "content_block_stop", "index": 0}
data: {"type": "content_block_start", "index": 1, "content_block": {"type": "text", "text": ""}}
...
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"input_tokens": 60, "output_tokens": 1311}}

The e2e compat cell this PR re-enables, driving the real claude CLI with --effort max against that same proxy, goes from [claude-opus-4-7-bedrock-converse] no thinking content block observed in stream-json events at d8762bf to all green at 929ee52:

$ LITELLM_PROXY_URL=http://127.0.0.1:4110 LITELLM_MASTER_KEY=*** \
    pytest tests/e2e/claude_code/thinking/test_bedrock_converse.py -q
[compat] per-provider breakdown:
  bedrock_converse     pass=3
1 passed in 15.89s

The regression tests were verified in both directions: with the litellm/ changes stashed, the adapter effort-preservation test (both model shapes), both converse param-mapping cases, and the ARN supported-param test fail; with them restored, all 276 tests across the two touched files pass

Commit 0f41365 extends this to opaque application inference profile ARNs, which passed the new param mapping but were still dropped at request serialization because the alias cannot resolve to an anthropic base model. The new serialization test (thinking plus effort both landing in additionalModelRequestFields for a profile ARN) fails at 929ee52 and passes with the commit

QA run at head 0f41365 (2026-08-11). Real Claude Code CLI v2.1.227 driven interactively under tmux, before leg proxied at the merge base d8762bf and after leg at 0f41365, each with its own worktree, venv, env file, and port, real AWS Bedrock traffic throughout. Both legs ran the identical session, differing only in the proxy's commit:

ANTHROPIC_BASE_URL=http://127.0.0.1:<port> ANTHROPIC_AUTH_TOKEN=sk-local-qa \
  claude --model claude-opus-4-7-bedrock-converse --effort max
> I have a 3-gallon jug and a 5-gallon jug. How can I measure exactly 4 gallons
  of water? Think through the steps carefully.

Before, proxy at d8762bf (port 24576): the answer streams with no thinking block anywhere. The client's own session transcript records the assistant message as a single text block

assistant blocks: {'text': 1}    (zero blocks of type "thinking", output in 9s)

After, proxy at 0f41365 (port 35886): the thinking block arrives with its signature ahead of the text, on both turns of the session

assistant#1  model=converse/us.anthropic.claude-opus-4-7  blocks=[('thinking', 0 chars, signature)]  output_tokens=1314
assistant#2  blocks=[('text', 927 chars)]

Control on the already-green invoke cell (claude-opus-4-7-bedrock-invoke, same proxy build, port 44637), pinning what parity means for a real user:

block 1: type=thinking, thinking="" (0 chars), signature present (1116 chars)
block 2: type=text, 688 chars       output_tokens=710, both requests 200

QA caveats, all observed live and none caused or worsened by this PR: on both Bedrock routes Opus 4.7 adaptive thinking arrives signature-only, so the interactive TUI shows no visible reasoning text on Converse or on the reference-good Invoke column, and billed output_tokens exceed the delivered text on both; this PR brings Converse to exact parity with Invoke, which is what the matrix cell measures. Separately, every Converse turn produced a hidden 400 before the 200 (Bedrock rejects output_config.format on Claude Code's structured-output side request) identically at base and at head, and not at all on Invoke; that is the pre-existing follow-up already listed under Caveats

Type

🐛 Bug Fix

Caveats (if any)

  • output_config forwarding is scoped to Bedrock-destined models: other bridged providers (e.g. openrouter) accept thinking but reject the raw param when drop_params is off
  • For profile ARNs the effort ships unvalidated and Bedrock enforces it, matching how thinking already flows for them; a profile fronting a non-Anthropic model would get a loud provider error instead of a silent drop
  • Follow-up, not bundled here: Bedrock Converse rejects output_config.format on Claude Code's title-generation side request

QA runbook

  • tests/e2e/claude_code/thinking/test_bedrock_converse.py::test_thinking_bedrock_converse - the proxy preserves the adaptive-thinking effort tier so Bedrock Converse streams a thinking block on every probed Claude tier (needs AWS Bedrock credentials in env)
    • Boot the proxy: litellm --config tests/e2e/claude_code/test_config.yaml --port 4000
    • POST /v1/messages with model claude-opus-4-7-bedrock-converse, thinking: {"type": "adaptive"} and output_config: {"effort": "max"} (exact body in Proof of Fix above)
    • Expect the stream to open with a content_block_start of type thinking before the text block
    • Run the e2e cell above with the claude CLI installed, expect bedrock_converse pass=3
    • Sanity check: this test makes sense to add and is not hand-wavey (e.g., assert actual expected spend instead of just spend > 0) or potentially flaky

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

…ges bridge

Claude Code drives Opus 4.7 with thinking {"type": "adaptive"} plus
output_config {"effort": "max"}. The anthropic-to-openai adapter
forwarded thinking verbatim for Claude models but dropped output_config,
and Bedrock Converse streams zero reasoningContent blocks for adaptive
thinking without an effort tier. Forward the effort subset of
output_config for Bedrock targets, accept it in the converse supported
params, and map it with the model's effort ceiling applied. Re-enable
the skipped e2e compat cell that catches this
@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR preserves adaptive-thinking effort across the Anthropic Messages to Bedrock Converse bridge, including opaque application inference profile ARNs

  • Forwards the effort-bearing portion of output_config from the Messages adapter
  • Maps and applies known Bedrock model effort ceilings before request serialization
  • Sends effort through additionalModelRequestFields when an application profile hides the underlying model
  • Adds unit coverage and re-enables the Bedrock Converse compatibility test

Confidence Score: 5/5

The PR appears safe to merge

The previously reported application inference profile issue is fixed: current serialization recognizes the opaque ARN and preserves effort in the additional Bedrock request fields, with focused regression coverage

Important Files Changed

Filename Overview
litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py Forwards non-format output configuration only for Bedrock-bound Claude requests while retaining separate structured-output translation
litellm/llms/bedrock/chat/converse_transformation.py Supports explicit output configuration, applies known model ceilings, and completes serialization for opaque application inference profile ARNs
litellm/types/llms/openai.py Adds the bridged output configuration field to the chat completion request type
tests/e2e/claude_code/thinking/test_bedrock_converse.py Re-enables the end-to-end compatibility case that verifies Bedrock Converse emits thinking content
tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py Covers effort preservation, format separation, and exclusion for non-Bedrock providers
tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py Covers supported-parameter mapping, effort ceilings, and application profile ARN request serialization

Reviews (2): Last reviewed commit: "fix(bedrock): forward output_config effo..." | Re-trigger Greptile

Comment thread litellm/llms/bedrock/chat/converse_transformation.py
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_bedrock_converse_thinking_stream_fix (0f41365) with litellm_internal_staging (b0fac57)

Open in CodSpeed

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0f41365. Configure here.

@mateo-berri
mateo-berri merged commit 7b34a6c into litellm_internal_staging Aug 11, 2026
84 checks passed
@mateo-berri
mateo-berri deleted the litellm_bedrock_converse_thinking_stream_fix branch August 11, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants