fix(bedrock-converse): accept Reasoning(effort=..., summary=...) dict for reasoning_effort - #29329
Conversation
Greptile SummaryThis PR fixes Bedrock Converse's
Confidence Score: 5/5Safe to merge — the change is a minimal guard relaxation with matching tests, and the coercion logic is identical to the already-merged Anthropic direct-path fix. The fix is narrow: one guard expression replaced with an equivalent dict-unpacking pattern lifted directly from No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/llms/bedrock/chat/converse_transformation.py | Relaxes the reasoning_effort guard from isinstance(value, str) to value is not None, adding dict-to-string coercion that mirrors the existing Anthropic direct-path handler; logic is correct and minimal. |
| tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py | Adds two new unit tests: one parametrized happy-path test for dict-shaped reasoning_effort on adaptive Claude 4.6/4.7 models, and one defensive test for a malformed dict (missing "effort" key). No existing tests modified, no network calls made. |
Reviews (1): Last reviewed commit: "fix(bedrock-converse): accept Reasoning(..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
@hclsys — two quick things before this gets reviewed:
Thanks! |
|
companion PR #29330 (same one-line dict coercion for the Databricks Claude adapter) — both bedrock + databricks have the same #28196 root cause that the direct Anthropic adapter already fixed via #25359. Same coercion, sequential audit of the 3 anthropic-via-X adapters in litellm. Either can merge first; the other is a stand-alone identical pattern in a different file. |
|
@krrish-berri-2 quick on both: lint is now green — your earlier comment was on the pre-ruff-fix commit; commit before/after exercised directly against from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig
cfg = AmazonConverseConfig()
non_default = {"reasoning_effort": {"effort": "low", "summary": "concise"}}
op = cfg.map_openai_params(non_default_params=non_default, optional_params={},
model="bedrock/converse/us.anthropic.claude-opus-4-7", drop_params=False)
print(op.get("thinking"), op.get("output_config"))unpatched main: this PR: and the bare string still works (regression-guarded by |
… for reasoning_effort
The direct Anthropic path at litellm/llms/anthropic/chat/transformation.py
already accepts both the bare string ("low") and the OpenAI Responses
`Reasoning(effort, summary)` dict (BerriAI#25359). Bedrock Converse's
`map_openai_params` still guarded the same parameter with
`isinstance(value, str)`, silently dropping the dict shape and producing
reasoning_tokens == 0 on Bedrock Claude after upgrading.
Coerce the dict to its `effort` string before dispatching to
`_handle_reasoning_effort_parameter`, matching the Anthropic adapter.
Related to BerriAI#28196 (the direct-Anthropic half was fixed earlier).
…params under PLR0915 CI ruff caught `Too many statements (54 > 50)` in map_openai_params after my inline dict-coercion added 5 statements. Pull the coercion into a static helper `_coerce_reasoning_effort` so the dispatch reads as one elif again.
afc5085 to
923a6cc
Compare
|
redid the branch on top of internal_staging — three commits squashed into the three logic commits, no force-push of new content. waiting on CI. |
kimnamu
left a comment
There was a problem hiding this comment.
Thanks for closing the Bedrock Converse gap here — I ran the branch locally (923a6ccb) and read through _coerce_reasoning_effort, and it checks out cleanly.
- Sibling consistency is the strongest part. The helper normalizes to the same bare-string shape the direct Anthropic adapter expects, and keeps the silent-drop-on-malformed semantics identical (
if dict → .get("effort"), then drop if not a string). Mirroring the already-merged Anthropic path (rather than introducing a fail-fast that diverges across providers) is the right call — and the docstring spelling that out is a nice touch. - Verified the edge cases directly against the helper logic:
"low"→low,{"effort":"low","summary":"concise"}→low,{"summary":"concise"}(no effort)→None,{"effort":None}→None,5→None,{}→None,None→None. No crash on any malformed shape — the OpenAI ResponsesReasoning(effort, summary)dict and the defensive cases are all covered. - The test genuinely catches the bug. Reverting just the guard back to
isinstance(value, str)(keeping the new tests) makes the happy-path parametrized case fail (the dict is dropped →thinkingnever set); restoring the fix → passes. So it's a real regression guard. The walrus-in-elifto stay under PLR0915 is tidy and the skip is safe.
Minimal, well-mirrored, good regression coverage. I'd love to see it land.
(I'm not a maintainer — just a Bedrock user who reviewed and ran this locally. Prepared with the help of an AI agent (Claude Code), human-verified.)
Companion to #28196 — the direct Anthropic path was already fixed (
litellm/llms/anthropic/chat/transformation.pyaccepts both string and dict reasoning_effort, see #25359), but Bedrock Converse'smap_openai_paramsstill had the oldisinstance(value, str)guard, silently dropping the dict shape OpenAI Responses callers send ({"effort": "low", "summary": "concise"}).Symptom:
reasoning_tokens == 0on Bedrock Claude after upgrading; same as the direct path before fix.fix
Same shape coercion as the Anthropic adapter — pull
value["effort"]out before dispatch:proof
scope
Bedrock Converse only. Vertex/Databricks Anthropic-via-partner paths use the direct AnthropicConfig path (which is already fixed), so they pick this up transitively — but I haven't audited the partner-model dispatch on those providers, leaving that as a separate triage if reports come in.