Skip to content

fix(anthropic): thread real provider through capability probes instead of pinning anthropic - #32874

Merged
mateo-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_thread_provider_capability_probes
Jul 11, 2026
Merged

fix(anthropic): thread real provider through capability probes instead of pinning anthropic#32874
mateo-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_thread_provider_capability_probes

Conversation

@mateo-berri

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

Copy link
Copy Markdown
Contributor

Relevant issues

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

Unit-level proof, reproduced before the fix and passing after. Before (base commit 109193f, verified by stashing the litellm/ changes and running the new regression tests against the unmodified code): with supports_adaptive_thinking flipped to false on the exact global.anthropic.claude-opus-4-8 cost-map entry, the Bedrock Invoke /v1/messages transformation still emitted thinking.type='adaptive', so test_bedrock_messages_thinking_shape_follows_exact_bedrock_entry_flag failed with AssertionError: assert 'adaptive' == 'enabled', and both TestCapabilityProbeUsesCallerProvider tests failed. After (commit 02fa810): all three regression tests pass, and the full mapped suites for every touched module pass (2,518 passed across the anthropic, bedrock, databricks, azure_ai and vertex partner-model test trees; the single failure in test_vertex_ai_partner_models_anthropic_messages_config.py is a missing vertexai package in the local env and fails identically on base)

The branch has since been rebased twice onto litellm_internal_staging: first onto a4199d3 (picking up #32831, #32833, #32840, #32867, #32875, #32882 and #32752; the same sweep plus the tests those PRs added passed, 2,616 passed with the same single pre-existing env failure), then onto f604034 after the companion #32873 merged, giving the current head abc3893. On that final head the mapped suites for every touched module plus the full anthropic test tree pass again (1,223 passed; the 16 failures are pre-existing local-env artifacts, async mock awaits in modules this PR does not modify, unrelated to these changes)

Live proxy demonstration (real AWS Bedrock, no mocks)

All runs below are live end to end: a proxy on localhost making real SigV4-signed calls to AWS Bedrock us-west-2 against the real us.anthropic.claude-sonnet-4-6 inference profile, costing real money. The proxy is started with LITELLM_LOCAL_MODEL_COST_MAP=True so the locally edited cost map is exactly what it reads. Sonnet 4.6 accepts both thinking payload shapes, which is why all runs return real 200s; the observable difference is the outbound request body in the --detailed_debug log

The operator override, flipping supports_adaptive_thinking to false on the exact Bedrock entry:

python3 -c "import json; p='litellm/model_prices_and_context_window_backup.json'; d=json.load(open(p)); d['us.anthropic.claude-sonnet-4-6']['supports_adaptive_thinking']=False; json.dump(d, open(p,'w'), indent=4)"

qa_config.yaml:

model_list:
  - model_name: sonnet-46
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-4-6
      aws_region_name: us-west-2

Proxy start (AWS credentials and LITELLM_MASTER_KEY exported in the shell, AWS_BEARER_TOKEN_BEDROCK unset):

LITELLM_LOCAL_MODEL_COST_MAP=True LITELLM_MODE=PRODUCTION .venv/bin/python litellm/proxy/proxy_cli.py --config qa_config.yaml --port $PORT --detailed_debug 2>&1 | tee run.log

The identical request sent in every run:

curl -sS -w '\nHTTP %{http_code}\n' http://127.0.0.1:$PORT/v1/chat/completions \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "content-type: application/json" \
  -d '{"model":"sonnet-46","messages":[{"role":"user","content":"What is 2+2? Answer with just the number"}],"reasoning_effort":"low","max_tokens":4096}'

Before, at 109193f (the merge base). The request succeeds, but the outbound Bedrock body still uses adaptive thinking; the false on the exact Bedrock entry is silently ignored because the probe pins custom_llm_provider="anthropic"

{"id":"chatcmpl-08cf4847-94d7-45f4-8210-3fcc3c1d2f64","created":1783741885,"model":"sonnet-46","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"4","role":"assistant"}}],"usage":{"completion_tokens":5,"prompt_tokens":19,"total_tokens":24,"completion_tokens_details":{"reasoning_tokens":0,"text_tokens":5},"prompt_tokens_details":{"cached_tokens":0,"text_tokens":19,"cache_creation_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}
HTTP 200

Outbound request in the before log:

POST Request Sent from LiteLLM:
curl -X POST \
https://bedrock-runtime.us-west-2.amazonaws.com/model/us.anthropic.claude-sonnet-4-6/converse \
-H 'Content-Type: application/json' -H 'X-Amz-Date: 20260711T035124Z' -H 'X-Amz-Security-Token: IQ****==' -H 'Authorization: AW****76' -H 'Content-Length: 243' \
-d '{"messages": [{"role": "user", "content": [{"text": "What is 2+2? Answer with just the number"}]}], "inferenceConfig": {"maxTokens": 4096}, "additionalModelRequestFields": {"thinking": {"type": "adaptive"}, "output_config": {"effort": "low"}}}'

After, captured at the final head abc3893 (re-run after the review follow-up commit and both rebases; the first after-capture at 02fa810 behaved identically). Same override, same request; the outbound body now uses manual thinking with a budget derived from reasoning_effort, proving the bedrock-namespaced exact entry drives the probe

{"id":"chatcmpl-ca9fb4a2-b3f5-40fb-90bd-804a51744491","created":1783798769,"model":"sonnet-46","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"4","role":"assistant","reasoning_content":"4","thinking_blocks":[{"type":"thinking","thinking":"4","signature":"EsoBCmcIDxABGAIqQOjn...(truncated)"}],"provider_specific_fields":{"reasoningContentBlocks":[{"reasoningText":{"signature":"EsoBCmcIDxABGAIqQOjn...(truncated)","text":"4"}}]}}}],"usage":{"completion_tokens":13,"prompt_tokens":48,"total_tokens":61,"completion_tokens_details":{"reasoning_tokens":1,"text_tokens":12},"prompt_tokens_details":{"cached_tokens":0,"text_tokens":48,"cache_creation_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}
HTTP 200

Outbound request in the after log:

POST Request Sent from LiteLLM:
curl -X POST \
https://bedrock-runtime.us-west-2.amazonaws.com/model/us.anthropic.claude-sonnet-4-6/converse \
-H 'Content-Type: application/json' -H 'X-Amz-Date: 20260711T193928Z' -H 'X-Amz-Security-Token: IQ****==' -H 'Authorization: AW****' -H 'Content-Length: 229' \
-d '{"messages": [{"role": "user", "content": [{"text": "What is 2+2? Answer with just the number"}]}], "inferenceConfig": {"maxTokens": 4096}, "additionalModelRequestFields": {"thinking": {"type": "enabled", "budget_tokens": 1024}}}'

Control pair with the override reverted (git checkout -- litellm/model_prices_and_context_window_backup.json, entry back to its committed supports_adaptive_thinking: true): the identical request returned HTTP 200 with "thinking": {"type": "adaptive"}, "output_config": {"effort": "low"} in the outbound body at 109193f, at 02fa810 and again at the final head abc3893 (X-Amz-Date 20260711T194022Z in the control log). The PR changes which cost-map entry is authoritative for the probe; default behavior is unchanged

Type

🐛 Bug Fix

Changes

AnthropicModelInfo._supports_model_capability pinned custom_llm_provider="anthropic" on every capability probe. For provider-prefixed ids this made the probe ignore the exact cost-map entry entirely: _check_provider_match rejected e.g. the Bedrock-namespaced global.anthropic.claude-opus-4-8 entry against the pinned "anthropic", and the anthropic-scoped fallback-generalization rule answered instead. Verified live, setting supports_adaptive_thinking to false on that entry changed nothing, because the rule short-circuits True before the raw-entry backstop is consulted. Operators therefore had no per-entry override, and the documented precedence of "exact entry beats rule" was silently violated on provider-prefixed ids

This PR threads the caller's real provider through the probe chain as a required custom_llm_provider parameter: _supports_model_capability, _is_adaptive_thinking_model, is_effort_used, get_anthropic_beta_list, _supports_effort_level, _validate_effort_for_model, _model_supports_effort_param, _map_reasoning_effort (kept separate from its existing llm_provider error tag, whose values like bedrock_converse are not valid lookup providers), and the /v1/messages pass-through translation helpers. _get_model_capability (the raw model-cost walk with its alias ladder) stays provider-less and keeps its place as the backstop after the provider-aware lookup

Call sites now pass their own namespace: "bedrock" in the Converse and Invoke chat transformations and the Bedrock Invoke /v1/messages transformation (via a custom_llm_provider property override on AmazonAnthropicClaudeMessagesConfig), "databricks" in the Databricks chat transformation, "vertex_ai" in the Vertex partner-model transformation and output_params_utils, and "anthropic" in the direct Anthropic paths. AnthropicMessagesConfig gained a custom_llm_provider property returning "anthropic" so the shared pass-through helpers thread whichever subclass invokes them; subclasses other than Bedrock Invoke keep their previous effective namespace. The Claude Platform on AWS config passes "anthropic" for its is_effort_used call because its model ids are Anthropic-native (claude-opus-4-8 style; the claude_platform/ route prefix is stripped before transformation) and no claude_platform namespace exists in the cost map, so its entries resolve under "anthropic"; this preserves current behavior. The azure_ai config needed no explicit change: get_anthropic_headers only consumes precomputed booleans and never probes, while the Azure probes flow through the inherited AnthropicConfig methods which now use the instance's custom_llm_provider ("azure_ai"), where exact azure_ai/claude-* entries exist. The supports_mid_conversation_system probe that #32831 added to the Bedrock Invoke /v1/messages config already passes "bedrock" straight to _supports_factory, so it needed no change on rebase and resolves against bedrock-namespaced exact entries like the rest. The adaptive-to-legacy downgrade helper #32867 added to the pass-through base (_translate_adaptive_effort_for_non_adaptive_model) landed while this PR was in flight with the old provider-less probe calls; it is threaded here the same way as the other pass-through helpers

Behavior: for mapped models nothing changes except that exact-entry flags now win, which is the fix. For unmapped ids under non-anthropic providers most pre-#32873 fallback rules were anthropic-scoped, so capability probes returned False (except where the bedrock-scoped 4.8+ rule from #32831 matched); the companion fallback-generalizations rework #32873 (splitting rules into routing plus provider-neutral capability kinds) has since merged and this branch is rebased past it, so provider-neutral capability rules now answer for unmapped ids under every provider. No real shipped model is unmapped today, so the gap was confined to not-yet-released model ids

Review follow-up: several config subclasses never overrode the inherited custom_llm_provider property, so their probes still read the anthropic namespace and operator overrides on their own cost-map entries were ignored. The /v1/messages configs for Azure AI, Vertex partner models, GitHub Copilot and JSON-configured OpenAI-compatible providers plus the Databricks chat config now override it, returning "azure_ai", "vertex_ai", "github_copilot", the provider's slug and "databricks" respectively. _supports_model_capability also treats the provider-aware lookup as authoritative when it resolves an explicit flag, so supports_adaptive_thinking: false on an exact provider-namespaced entry like azure_ai/claude-opus-4-8 now beats the anthropic-scoped fallback rule; regression tests flip that flag on the azure_ai/ and vertex_ai/ entries and assert the emitted thinking shape switches from adaptive to manual, and they fail without the overrides. The Claude Platform /v1/messages config keeps the inherited "anthropic" deliberately, matching its chat sibling's probes, since its ids are Anthropic-native with no provider-namespaced entries; the generic OpenAI-like messages config also keeps "anthropic" because it has no class-level namespace. A follow-up commit (80294a9) takes a reviewer nit and consolidates the repeated self.custom_llm_provider or "anthropic" fallback in the chat and messages configs into a single _resolved_provider property, so future call sites cannot forget the fallback; no behavior change


Note

Medium Risk
Changes how reasoning and adaptive-thinking payloads are built across Bedrock, Vertex, Azure, and other Anthropic-compatible routes; unmapped models on non-anthropic providers may probe differently until related fallback work lands.

Overview
Anthropic-related capability probes no longer hard-code custom_llm_provider="anthropic". They take the caller's provider so Bedrock, Vertex, Azure AI, Databricks, and similar paths read provider-namespaced cost-map entries when deciding adaptive thinking, effort tiers, output_config, and beta headers.

_supports_model_capability now resolves flags via get_llm_provider + _get_model_info_helper first; an explicit supports_*: false on the exact provider entry wins over anthropic-scoped fallback rules. Shared helpers (_map_reasoning_effort, effort validation, /v1/messages translation) and Bedrock/Vertex/Databricks call sites pass "bedrock", "vertex_ai", "databricks", etc. Several configs expose custom_llm_provider on the subclass (azure_ai, Bedrock messages, GitHub Copilot, JSON OpenAI-like providers) so pass-through transforms probe the right namespace.

Observable effect: operator overrides on per-provider model JSON (e.g. flipping supports_adaptive_thinking) actually change outbound thinking / output_config instead of being ignored; default mapped behavior is unchanged when flags match.

Reviewed by Cursor Bugbot for commit abc3893. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Improved Anthropic reasoning and adaptive-thinking support across Anthropic-compatible providers.
    • Model capabilities are now detected using the correct provider-specific configuration.
    • Corrected thinking payloads, effort handling, validation, and beta-header behavior for Bedrock, Azure AI, Databricks, Vertex AI, GitHub Copilot, and other integrations.
    • Added regression coverage for provider-specific thinking behavior and capability detection.

Loading
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