fix(bedrock): normalize Messages system role and adaptive-thinking for Claude Invoke - #31364
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR updates Bedrock and Anthropic reasoning transforms for Claude adaptive thinking. The main changes are:
Confidence Score: 5/5The changes are well-scoped to Bedrock/Anthropic transformation paths and shared reasoning-effort conversion helpers. Regression coverage exercises the affected payload-shaping and threshold behavior, and no blocking correctness issues were identified in the reviewed changes.
What T-Rex did
|
Greptile SummaryThis PR fixes two Bedrock Invoke failures for newer Claude models:
Confidence Score: 4/5Safe to merge; both fixes address real Bedrock 400 rejections, the models are correctly registered in the cost map, and the new tests are mock-only with thorough scenario coverage. The system-role normalization and adaptive-thinking injection are logically correct, the call ordering in transform_anthropic_messages_request is right (normalization before TTL stripping), and the test suite covers happy-path, merge, and legacy-budget conversion cases. The two new name-string helpers follow a pre-existing pattern rather than the preferred model-map-only approach, and a minor falsy-zero guard in the budget conversion could silently discard a caller-supplied budget_tokens=0. litellm/llms/anthropic/common_utils.py for the new name-based model helpers; litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py for the budget_tokens guard.
|
| Filename | Overview |
|---|---|
| litellm/llms/anthropic/common_utils.py | Adds _is_claude_4_8_model and _is_claude_fable_5_model name-based helpers and wires them into _is_adaptive_thinking_model as fallbacks for unmapped provider routing IDs; both models are already registered in model_prices_and_context_window.json so the primary detection path works for known aliases. |
| litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py | Adds system-role normalization (moves role:system messages to the top-level system field) and rewires clear_thinking handling to emit thinking.type=adaptive + output_config.effort for adaptive-thinking models instead of the rejected enabled shape; logic is correct with one minor falsy-zero guard worth tightening. |
| tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py | Adds parametrized tests for _is_claude_4_8_model, _is_claude_fable_5_model, adaptive-thinking detection for Fable 5, and a name-fallback test for unmapped aliases; coverage is thorough and tests use the local cost map fixture correctly. |
| tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py | Adds end-to-end and unit tests for adaptive-thinking injection, legacy enabled to adaptive conversion, non-adaptive model preservation, and system-role normalization including merge into a pre-existing system field; all new tests are mock-only with no network calls. |
Reviews (2): Last reviewed commit: "style(bedrock): use builtin generics in ..." | Re-trigger Greptile
…ng conversion
The clear_thinking_20251015 -> adaptive conversion resolved the thinking
budget with `thinking.get("budget_tokens") or BEDROCK_MIN_THINKING_BUDGET_TOKENS`,
which treats a caller-supplied `budget_tokens=0` as missing and silently
substitutes the Bedrock minimum. Resolve the budget with an explicit
`is not None` check so an explicit 0 is honored.
|
Addressed both points from the 4/5 review in fb1fcb8. Falsy-zero guard (A): fixed. The clear_thinking -> adaptive conversion previously resolved the budget with Name-based helpers (B): keeping |
…Invoke _ensure_thinking_for_clear_thinking_context_management returns early when _supports_extended_thinking_on_bedrock(model) is False, so the adaptive-thinking injection never runs for models absent from that gate. Opus 4.8 slips through on the incidental "opus-4" substring, but Fable 5 had no matching pattern, so a clear_thinking_20251015 request on Fable 5 reached Bedrock with an unsupported context-management edit and no thinking field; the exact 400 this path exists to prevent. Add the fable-5 patterns to the gate so Fable 5 (mapped ids and unmapped aliases) gets thinking.type=adaptive + output_config.effort like the other adaptive models. Extend the adaptive-injection regression test to cover Fable 5 (a mapped id and an unmapped alias) so it fails without the gate entry, and add focused coverage for the budget->effort tiers, the disabled/enabled/adaptive thinking branches, output_config.effort preservation, and list/dict system-role normalization. Also normalize the Invoke transformation module and its test to line-length 88 so ruff format --check (CI format-check) passes.
|
Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 396fbfa. Configure here.
…ve for thinking detection Replace the per-version name helpers (_is_claude_4_6/4_7/4_8_model, _is_claude_fable_5_model) with cost-map-flag-first detection. _is_adaptive_thinking_model now reads supports_adaptive_thinking from the model cost map and falls back to a single generalized family-version regex (_claude_version_at_least(model, 4, 6)) only when a model is unmapped, instead of hard-coding each new Claude release. Wire supports_adaptive_thinking through ProviderSpecificModelInfo and ModelInfo so the cost map flag actually surfaces at lookup time. Reroute the Bedrock Invoke extended-thinking gate and the two anthropic/chat/transformation.py call sites through _is_adaptive_thinking_model. Known gap left to the fallback_generalizations work (#29718): unmapped Fable 5 aliases have no parseable minor version, so they defer to the cost map and are not detected until a mapped entry or a generalization rule exists. Covered by an explicit regression test.
…ve thinking from cost map only The prior commit kept a regex (_claude_version_at_least) as a fallback when an id resolved to no cost-map entry. Remove it: _is_adaptive_thinking_model now reads supports_adaptive_thinking and nothing else, so "which Claude versions think adaptively" lives entirely in the model cost map, and a new adaptive release is a JSON edit rather than a Python edit. To keep the flag authoritative across the id forms the Bedrock Invoke and anthropic paths actually see, backfill supports_adaptive_thinking=true on every adaptive Claude entry that was missing it (Opus 4.6/4.7 and Sonnet 4.6 across region/provider aliases) in both the root and bundled cost maps, and generalize _model_map_lookup_candidates to normalize an id to its base cost-map key: strip a Bedrock version suffix (-v1:0 fully, or just the :0 inference-profile minor so the -v1-keyed 4.6 entries resolve), strip a dated-release suffix (-20260219), and rewrite a dotted family version (4.6 -> 4-6). This is id normalization feeding the lookup, not capability-by-name. Tests load the PR-local cost map (the flags are not on main until merge) and cover each normalization path plus the unmapped-alias deferral to fallback_generalizations (#29718).
…ppings Route every reasoning_effort <-> thinking-budget conversion through the DEFAULT_REASONING_EFFORT_*_THINKING_BUDGET constants so the numbers stay in sync across providers. The five constants are now 2000/5000/10000/20000/40000 Add reasoning_effort_from_thinking_budget() in litellm_core_utils/reasoning_effort_utils.py and route the three OpenAI-style forward maps (anthropic adapters, responses adapters, hosted_vllm) through it. The bedrock invoke and experimental messages adaptive maps now reference the constants directly; the only behavior change is the xhigh threshold moving from 24000 to 20000. Reverse maps and the cross-provider test grid read the same constants
|
Generated by Claude Code |
…h budget The single-sourced DEFAULT_REASONING_EFFORT_*_THINKING_BUDGET thresholds moved high from 4096 to 10000. The live reasoning_effort grid sends budget-mode requests with max_tokens=8192, so reasoning_effort=high now produces budget_tokens=10000 > max_tokens and every provider returns 'max_tokens must be greater than thinking.budget_tokens'. Derive a shared BUDGET_MODE_MAX_TOKENS (2x the high budget) for the spec and the request builder so the ceiling always clears the largest 200-expected tier. Also resolve the inherited base test_reasoning_effort assertion off the same high-budget constant instead of the stale 4096 literal so it tracks the source of truth.
|
Generated by Claude Code |
The single-sourcing refactor moved the shared effort<->budget thresholds up (low 1024->2000, medium 2048->5000, high 4096->10000, xhigh 8192->20000, max 16384->40000). That silently changes the effort->budget direction: a caller who sets reasoning_effort together with a max_tokens that used to sit above the old per-tier budget but below the new one now trips the provider's "max_tokens must be greater than thinking.budget_tokens" 400. It spans every backend that derives a budget from an effort (Anthropic, Gemini/Vertex, hosted vLLM), not just Bedrock. Restore the constants to their pre-PR values while keeping every backend reading from the shared DEFAULT_REASONING_EFFORT_*_THINKING_BUDGET constants, so the mapping stays single-sourced without the behavior change. Tests that pinned the raised thresholds now derive their boundaries from the same constants.
|
On the P1 about That helper is the budget -> OpenAI-style The other half of the concern, the "24000 to 20000" threshold move not being delivered, no longer applies: the latest commit reverts the five Generated by Claude Code |
|
Generated by Claude Code |
…e shared constant The cross-provider translation tests pinned reasoning_effort="high" to a literal budget_tokens=10000, the raised value. Point them at DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET so they track the single source instead of a magic number.
|
Generated by Claude Code |
…drock ids The model-map candidate normalization applied each suffix strip independently to the original id, so the real Bedrock shape "<base>-<YYYYMMDD>-v1:0" never reduced to its base cost-map key: stripping the version left the date, and the dated-suffix regex is anchored to the end so it could not fire while the version was still present. An adaptive Claude model invoked by its full dated+versioned id (e.g. us.anthropic.claude-sonnet-4-6-20251101-v1:0) therefore resolved to supports_adaptive_thinking=null and was treated as non-adaptive, reaching Bedrock with the rejected thinking.type=enabled shape, the exact 400 this path prevents. Add a composed normalization that rewrites the dotted family version, then peels the -vN:rev version suffix, then the -YYYYMMDD dated suffix, so the combined form resolves to its base key. Regression tests pin the combined suffix on sonnet-4-6 and opus-4-8 across provider/region prefixes.
|
Both P1s from the latest review: Provider/date/suffix aliases remaining non-adaptive (P1 #1): valid, fixed in 1580ca3. You were right. The candidate normalization applied each suffix strip independently to the original id, so the real Bedrock shape Pass-through budget-to-effort thresholds (P1 #2): intentional, not a regression to preserve. This one is a deliberate design decision rather than an oversight. The branch now restores the five The budget -> effort direction you flagged (Anthropic Generated by Claude Code |
|
Generated by Claude Code |
…tants and format common_utils The constant revert restored the effort<->budget thresholds to their pre-PR values (1024/2048/4096/8192/16384) and single-sourced the reverse budget->effort ladder through reasoning_effort_from_thinking_budget, but several tests still pinned the briefly-raised values and the old hardcoded reverse buckets, so the "All Other Providers" shard failed Derive the anthropic chat effort->budget assertions from the shared DEFAULT_REASONING_EFFORT_*_THINKING_BUDGET constants, and update the experimental pass-through and responses adapter expectations to the single-sourced reverse ladder (budget 1024 -> low, 5000 -> high) Also run ruff format --line-length 88 over anthropic/common_utils.py so the CI format-check, which checks the whole changed file, passes
|
On the P1 "Mapped Bedrock/provider adaptive Claude IDs still resolve as non-adaptive": this is a pre-merge measurement artifact, not a code defect, and it resolves once the flag lives where it will at runtime after merge.
Pin the branch-local map and the discrepancy disappears. With So there is no id form that resolves false once the flag is in the map the runtime actually reads post-merge; the only state where it reads false is the pre-merge remote fetch against Separately, pushed 5cf377a to fix unrelated CI on the prior head: the Generated by Claude Code |
|
Generated by Claude Code |
|
Confirming the re-surfaced P1 on 5cf377a is the same pre-merge artifact, now checked against the exact new ids in the latest probe. Under the branch-local map (
Each normalizes to a base key that carries the flag (for example the sonnet forms fold to Generated by Claude Code |
|
bugbot run Generated by Claude Code |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
bugbot run Generated by Claude Code |
|
I ignored the P1 by trex because trex made a mistake and did not use local map. That's why it thinks it's broken but it's actually not |
…r Claude Invoke (BerriAI#31364) * fix(bedrock): normalize Messages system role and adaptive-thinking for Claude Invoke * style(bedrock): use builtin generics in new Invoke helpers to clear UP006 gate * fix(bedrock): honor explicit thinking budget_tokens=0 in clear_thinking conversion The clear_thinking_20251015 -> adaptive conversion resolved the thinking budget with `thinking.get("budget_tokens") or BEDROCK_MIN_THINKING_BUDGET_TOKENS`, which treats a caller-supplied `budget_tokens=0` as missing and silently substitutes the Bedrock minimum. Resolve the budget with an explicit `is not None` check so an explicit 0 is honored. * fix(bedrock): gate Fable 5 into clear_thinking adaptive injection on Invoke _ensure_thinking_for_clear_thinking_context_management returns early when _supports_extended_thinking_on_bedrock(model) is False, so the adaptive-thinking injection never runs for models absent from that gate. Opus 4.8 slips through on the incidental "opus-4" substring, but Fable 5 had no matching pattern, so a clear_thinking_20251015 request on Fable 5 reached Bedrock with an unsupported context-management edit and no thinking field; the exact 400 this path exists to prevent. Add the fable-5 patterns to the gate so Fable 5 (mapped ids and unmapped aliases) gets thinking.type=adaptive + output_config.effort like the other adaptive models. Extend the adaptive-injection regression test to cover Fable 5 (a mapped id and an unmapped alias) so it fails without the gate entry, and add focused coverage for the budget->effort tiers, the disabled/enabled/adaptive thinking branches, output_config.effort preservation, and list/dict system-role normalization. Also normalize the Invoke transformation module and its test to line-length 88 so ruff format --check (CI format-check) passes. * refactor(anthropic): make supports_adaptive_thinking flag authoritative for thinking detection Replace the per-version name helpers (_is_claude_4_6/4_7/4_8_model, _is_claude_fable_5_model) with cost-map-flag-first detection. _is_adaptive_thinking_model now reads supports_adaptive_thinking from the model cost map and falls back to a single generalized family-version regex (_claude_version_at_least(model, 4, 6)) only when a model is unmapped, instead of hard-coding each new Claude release. Wire supports_adaptive_thinking through ProviderSpecificModelInfo and ModelInfo so the cost map flag actually surfaces at lookup time. Reroute the Bedrock Invoke extended-thinking gate and the two anthropic/chat/transformation.py call sites through _is_adaptive_thinking_model. Known gap left to the fallback_generalizations work (BerriAI#29718): unmapped Fable 5 aliases have no parseable minor version, so they defer to the cost map and are not detected until a mapped entry or a generalization rule exists. Covered by an explicit regression test. * refactor(anthropic): drop name-based version fallback; resolve adaptive thinking from cost map only The prior commit kept a regex (_claude_version_at_least) as a fallback when an id resolved to no cost-map entry. Remove it: _is_adaptive_thinking_model now reads supports_adaptive_thinking and nothing else, so "which Claude versions think adaptively" lives entirely in the model cost map, and a new adaptive release is a JSON edit rather than a Python edit. To keep the flag authoritative across the id forms the Bedrock Invoke and anthropic paths actually see, backfill supports_adaptive_thinking=true on every adaptive Claude entry that was missing it (Opus 4.6/4.7 and Sonnet 4.6 across region/provider aliases) in both the root and bundled cost maps, and generalize _model_map_lookup_candidates to normalize an id to its base cost-map key: strip a Bedrock version suffix (-v1:0 fully, or just the :0 inference-profile minor so the -v1-keyed 4.6 entries resolve), strip a dated-release suffix (-20260219), and rewrite a dotted family version (4.6 -> 4-6). This is id normalization feeding the lookup, not capability-by-name. Tests load the PR-local cost map (the flags are not on main until merge) and cover each normalization path plus the unmapped-alias deferral to fallback_generalizations (BerriAI#29718). * refactor(reasoning_effort): single-source effort<->thinking-budget mappings Route every reasoning_effort <-> thinking-budget conversion through the DEFAULT_REASONING_EFFORT_*_THINKING_BUDGET constants so the numbers stay in sync across providers. The five constants are now 2000/5000/10000/20000/40000 Add reasoning_effort_from_thinking_budget() in litellm_core_utils/reasoning_effort_utils.py and route the three OpenAI-style forward maps (anthropic adapters, responses adapters, hosted_vllm) through it. The bedrock invoke and experimental messages adaptive maps now reference the constants directly; the only behavior change is the xhigh threshold moving from 24000 to 20000. Reverse maps and the cross-provider test grid read the same constants * test(reasoning_effort): lift budget-mode max_tokens above the new high budget The single-sourced DEFAULT_REASONING_EFFORT_*_THINKING_BUDGET thresholds moved high from 4096 to 10000. The live reasoning_effort grid sends budget-mode requests with max_tokens=8192, so reasoning_effort=high now produces budget_tokens=10000 > max_tokens and every provider returns 'max_tokens must be greater than thinking.budget_tokens'. Derive a shared BUDGET_MODE_MAX_TOKENS (2x the high budget) for the spec and the request builder so the ceiling always clears the largest 200-expected tier. Also resolve the inherited base test_reasoning_effort assertion off the same high-budget constant instead of the stale 4096 literal so it tracks the source of truth. * fix(reasoning_effort): keep effort<->budget thresholds at pre-PR values The single-sourcing refactor moved the shared effort<->budget thresholds up (low 1024->2000, medium 2048->5000, high 4096->10000, xhigh 8192->20000, max 16384->40000). That silently changes the effort->budget direction: a caller who sets reasoning_effort together with a max_tokens that used to sit above the old per-tier budget but below the new one now trips the provider's "max_tokens must be greater than thinking.budget_tokens" 400. It spans every backend that derives a budget from an effort (Anthropic, Gemini/Vertex, hosted vLLM), not just Bedrock. Restore the constants to their pre-PR values while keeping every backend reading from the shared DEFAULT_REASONING_EFFORT_*_THINKING_BUDGET constants, so the mapping stays single-sourced without the behavior change. Tests that pinned the raised thresholds now derive their boundaries from the same constants. * test(reasoning_effort): derive high effort->budget assertions from the shared constant The cross-provider translation tests pinned reasoning_effort="high" to a literal budget_tokens=10000, the raised value. Point them at DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET so they track the single source instead of a magic number. * fix(anthropic): resolve adaptive flag for combined dated+versioned Bedrock ids The model-map candidate normalization applied each suffix strip independently to the original id, so the real Bedrock shape "<base>-<YYYYMMDD>-v1:0" never reduced to its base cost-map key: stripping the version left the date, and the dated-suffix regex is anchored to the end so it could not fire while the version was still present. An adaptive Claude model invoked by its full dated+versioned id (e.g. us.anthropic.claude-sonnet-4-6-20251101-v1:0) therefore resolved to supports_adaptive_thinking=null and was treated as non-adaptive, reaching Bedrock with the rejected thinking.type=enabled shape, the exact 400 this path prevents. Add a composed normalization that rewrites the dotted family version, then peels the -vN:rev version suffix, then the -YYYYMMDD dated suffix, so the combined form resolves to its base key. Regression tests pin the combined suffix on sonnet-4-6 and opus-4-8 across provider/region prefixes. * fix(reasoning_effort): align budget<->effort tests with reverted constants and format common_utils The constant revert restored the effort<->budget thresholds to their pre-PR values (1024/2048/4096/8192/16384) and single-sourced the reverse budget->effort ladder through reasoning_effort_from_thinking_budget, but several tests still pinned the briefly-raised values and the old hardcoded reverse buckets, so the "All Other Providers" shard failed Derive the anthropic chat effort->budget assertions from the shared DEFAULT_REASONING_EFFORT_*_THINKING_BUDGET constants, and update the experimental pass-through and responses adapter expectations to the single-sourced reverse ladder (budget 1024 -> low, 5000 -> high) Also run ruff format --line-length 88 over anthropic/common_utils.py so the CI format-check, which checks the whole changed file, passes
|
bugbot run |
There was a problem hiding this comment.
✅ 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 5cf377a. Configure here.
|
bugbot run |
There was a problem hiding this comment.
✅ 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 5cf377a. Configure here.
Relevant issues
N/A
Linear ticket
N/A
Pre-Submission checklist
make test-unit@greptileaiand received a Confidence Score of at least 4/5Screenshots / Proof of Fix
Reproduced live against real Bedrock on a local proxy with
bedrock-invoke-opus-4-8mapped tobedrock/global.anthropic.claude-opus-4-8, an adaptive-thinking model. Both payloads are what Claude Code emits through the Anthropic Messages endpoint when pointed at a Bedrock Invoke Claude model. "Before" runslitellm_internal_staging; "after" runs this branchSystem-role normalization, a
role: "system"entry insidemessagesthat Invoke rejects in favor of the top-levelsystemfield:Before, HTTP 400:
{"message":"messages.0: use the top-level 'system' parameter for the initial system prompt"}After, HTTP 200 with
"Hi there friend"; the outgoing Invoke body hoists the entry into"system":[{"type":"text","text":"You are terse. Reply in 3 words."}]Adaptive-thinking for clear_thinking, a
clear_thinking_20251015edit with no top-levelthinkingfield (the shape Claude Code sends):Before, HTTP 400:
{"message":"\"thinking.type.enabled\" is not supported for this model. Use \"thinking.type.adaptive\" and \"output_config.effort\" to control thinking behavior."}After, HTTP 200 with
"2+2 = **4**"; the outgoing Invoke body carries"thinking":{"type":"adaptive"},"output_config":{"effort":"low"}instead of the rejected enabled shapeAn explicit
thinking:{"type":"enabled","budget_tokens":N}is unaffected and already returns HTTP 200 on both branches, since the existing transform converts that to adaptive plus effort; the slice this PR closes is theclear_thinkingedit with no thinking fieldType
🐛 Bug Fix
Changes
Normalize Bedrock Messages
role: "system"entries before Invoke, and make adaptive-thinking detection driven by the model cost map'ssupports_adaptive_thinkingflag instead of a name helper per Claude release. Regression coverage lives intest_anthropic_common_utils.pyandtest_anthropic_claude3_transformation.py. Part of upstreaming a downstream patch series maintained against 1.85.1The adaptive-thinking injection for
clear_thinking_20251015is gated behind_supports_extended_thinking_on_bedrock. That gate, and_is_adaptive_thinking_model, previously hard-coded a substring helper per release (_is_claude_4_6/4_7/4_8_model,_is_claude_fable_5_model); any model not yet listed (Fable 5, or Opus 4.8 outside the incidentalopus-4substring) returned early and reached Bedrock with an unsupported context-management edit and no thinking field, the exact 400 this path exists to prevent_is_adaptive_thinking_modelreadssupports_adaptive_thinking(newly threaded throughProviderSpecificModelInfoandModelInfo) and nothing else, so "which Claude versions think adaptively" lives entirely in the model cost map; a new adaptive release is a JSON edit, not a Python edit. To keep that flag authoritative across the id forms the Bedrock Invoke and anthropic paths actually see,supports_adaptive_thinking=trueis backfilled on every adaptive Claude entry that was missing it (Opus 4.6/4.7 and Sonnet 4.6 across region/provider aliases) in both the root and bundled cost maps, and_model_map_lookup_candidatesis generalized to normalize an id down to its base cost-map key: strip a Bedrock version suffix (-v1:0fully, or just the:0inference-profile minor so the-v1-keyed 4.6 entries resolve), strip a dated-release suffix (-20260219), and rewrite a dotted family version (4.6->4-6). That is id normalization feeding the lookup, not capability-by-name. The Bedrock gate and the twoanthropic/chat/transformation.pycall sites route through_is_adaptive_thinking_model, and the per-version helpers plus the version-regex fallback are deletedOne gap is deferred to the
fallback_generalizationswork in #29718: an alias absent from the cost map (a future release, or a preview suffix likeclaude-fable-5-preview) is treated as non-adaptive until a mapped entry or a generalization rule exists. An explicit regression test documents thisThe Invoke transformation module and its test stay normalized to line-length 88 so
ruff format --check(the CIformat-checkstep) passesThis branch also collapses every reasoning_effort <-> thinking-budget conversion onto one authoritative source without moving any of the numbers. The five
DEFAULT_REASONING_EFFORT_{LOW,MEDIUM,HIGH,XHIGH,MAX}_THINKING_BUDGETconstants keep their pre-existing values (1024/2048/4096/8192/16384) and are the single place those numbers live in either direction. A newreasoning_effort_from_thinking_budgetinlitellm/litellm_core_utils/reasoning_effort_utils.pyholds the OpenAI-style forward ladder once, and the three previously identical copies in the anthropic adapters, responses adapters, and hosted_vllm transforms now call it. The Bedrock Invoke_effort_from_thinking_budgetand the experimental messages adaptive map reference the constants directly instead of their own literals. The reverse (effort -> budget) maps inanthropic/chat/transformation.pyand Gemini, plus the cross-provider grid intests/llm_translation/reasoning_effort_grid/grid_spec.py, already read the same constants, so the conversion is now single-sourced in both directions and can no longer drift, with no effort<->budget threshold changing. An earlier revision of this branch raised those constants (high 4096->10000, etc.); that was reverted because it shifted the effort->budget direction and could pushthinking.budget_tokenspast a caller'smax_tokenson providers that rejectmax_tokens <= budget_tokensNote
Medium Risk
Touches request shaping for Bedrock Invoke and global adaptive-thinking gating; wrong alias resolution or clear_thinking injection could change wire payloads for Claude Messages traffic, though behavior is heavily regression-tested.
Overview
Fixes Bedrock Invoke Anthropic Messages failures when Claude Code sends
role: "system"insidemessagesorclear_thinking_20251015without athinkingfield on adaptive models.Bedrock Invoke: Hoists in-message
systemroles into the top-levelsystemfield before the request is built. Forclear_thinking_20251015, adaptive models getthinking.type=adaptiveplusoutput_config.effort(from budget tiers) instead of the rejectedthinking.type=enabledshape; legacy non-adaptive models still use enabled +budget_tokens, with explicitbudget_tokens=0preserved.Adaptive thinking detection: Drops per-version name helpers (
_is_claude_4_6/4_7_model, etc.)._is_adaptive_thinking_modelnow reads onlysupports_adaptive_thinkingfrom the cost map, with richer_model_map_lookup_candidates(Bedrock-v1:0, dated suffixes,4.6→4-6, etc.) so routed aliases resolve. That flag is added on affected Claude entries in both cost-map JSON files and threaded throughModelInfo/get_model_info.Reasoning effort ↔ budget: Adds
reasoning_effort_from_thinking_budgetand routes duplicate ladder logic in anthropic pass-through adapters, responses adapters, and hosted_vllm through it; Bedrock clear-thinking effort mapping uses the sameDEFAULT_REASONING_EFFORT_*_THINKING_BUDGETconstants (values unchanged).Tests and the reasoning-effort grid are updated to use those constants; unmapped aliases stay non-adaptive until the cost map or #29718 generalizations cover them.
Reviewed by Cursor Bugbot for commit 5cf377a. Bugbot is set up for automated code reviews on this repo. Configure here.