fix(anthropic-adapter): strip output_config for non-Anthropic backends - #22727
fix(anthropic-adapter): strip output_config for non-Anthropic backends#22727cfdude wants to merge 6 commits into
Conversation
…messages adapter When routing non-Anthropic models (e.g. Amazon Bedrock Nova Pro) through the Anthropic-compatible /v1/messages endpoint, `_prepare_completion_kwargs` was forwarding the client-supplied max_tokens directly to the underlying provider without checking the model's actual output token limit. This caused hard failures for models like Amazon Nova Pro (10,000 token limit) when clients such as Claude Code send large max_tokens values that are valid for other providers (e.g. 64,000 for Anthropic Claude). Fix: look up max_output_tokens via litellm.get_model_info() before building the request_data dict and cap accordingly. The lookup uses custom_llm_provider from extra_kwargs (already resolved by the outer anthropic_messages_handler via get_llm_provider) with a fallback to inferring the provider from the model string. Tested with bedrock/converse/us.amazon.nova-pro-v1:0 routed via LiteLLM proxy: requests sending max_tokens=16000 are now silently capped to 10000 and succeed instead of returning HTTP 400.
- Simplify two-try-block approach into a single try block: if no explicit provider in extra_kwargs, infer it via get_llm_provider() in the same path rather than a separate fallback block. This eliminates the misleading _capped/_lookup_succeeded flag entirely. - Use explicit `is not None` check instead of truthy check on max_output_tokens, consistent with get_modified_max_tokens pattern in token_counter.py and correct for hypothetical zero-limit models. - Add unit tests covering all capping scenarios: - max_tokens capped when it exceeds the model limit - max_tokens unchanged when within the limit - max_tokens unchanged when equal to the limit - provider inferred from model string when absent from extra_kwargs - resilient when get_model_info raises - no cap when max_output_tokens key is missing from model_info - no cap when max_output_tokens is explicitly None - explicit provider is used without calling get_llm_provider
Emit a debug log when the adapter silently reduces max_tokens to the model's output limit, consistent with the get_modified_max_tokens pattern in token_counter.py. Aids debugging when users observe shorter completions than expected.
output_config is an Anthropic Claude-specific parameter that has no equivalent in other model providers. When routing through the experimental pass-through adapter to non-Anthropic backends (e.g. Amazon Nova Pro on Bedrock, Llama, Mistral), the parameter is forwarded verbatim and causes HTTP 400 "extraneous key" errors. Strip output_config from the request when the target provider is not an Anthropic Claude model. The thinking parameter is intentionally excluded from stripping because some non-Anthropic models (e.g. Qwen) support reasoning/thinking natively and the adapter already handles translation for those cases.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds two defensive features to the Anthropic experimental pass-through adapter: (1) capping
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/llms/anthropic/experimental_pass_through/adapters/handler.py | Adds max_tokens capping against model limits and strips output_config for non-Anthropic backends. Logic is sound for covered providers; Vertex AI gap already flagged in prior review. |
| tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_handler.py | New unit tests for max_tokens capping and output_config stripping. The _call helper has a falsy-dict bug that prevents the provider-inference fallback test from exercising the intended code path. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["_prepare_completion_kwargs()"] --> B["Cap max_tokens via get_model_info()"]
B --> C["Translate request to OpenAI format"]
C --> D{"Is target Anthropic Claude?"}
D -->|"provider == 'anthropic'"| E["Keep output_config"]
D -->|"provider == 'bedrock' && model contains 'anthropic.claude'"| E
D -->|"Non-Anthropic backend"| F["Strip output_config from excluded_keys"]
E --> G["Merge extra_kwargs into completion_kwargs"]
F --> G
G --> H["Route to litellm.completion()"]
Last reviewed commit: 3647cac
Tests cover strip/pass-through behavior for output_config based on target provider: stripped for non-Anthropic backends (Bedrock Nova Pro, OpenAI, etc.), preserved for Anthropic direct and Bedrock-hosted Claude.
| _anthropic_only_params = {"output_config"} | ||
| _target_provider = (extra_kwargs or {}).get("custom_llm_provider", "") | ||
| _is_anthropic_claude = _target_provider in ( | ||
| "anthropic", | ||
| ) or ( | ||
| _target_provider == "bedrock" | ||
| and "anthropic.claude" in completion_kwargs.get("model", "") | ||
| ) |
There was a problem hiding this comment.
Missing Vertex AI Claude detection
The _is_anthropic_claude check only covers the "anthropic" and "bedrock" providers, but Vertex AI also hosts Anthropic Claude models (e.g. vertex_ai/claude-sonnet-4). For those, custom_llm_provider would be "vertex_ai" and the model string would contain "claude" but not "anthropic.claude". This means output_config would be incorrectly stripped for Vertex AI Claude models.
There is already an existing helper LiteLLMAnthropicMessagesAdapter.is_anthropic_claude_model() in the adjacent transformation.py (line 653) that correctly handles all three providers by checking for "anthropic" or "claude" in the model string. Consider reusing that helper, or extending this check to cover Vertex AI:
| _anthropic_only_params = {"output_config"} | |
| _target_provider = (extra_kwargs or {}).get("custom_llm_provider", "") | |
| _is_anthropic_claude = _target_provider in ( | |
| "anthropic", | |
| ) or ( | |
| _target_provider == "bedrock" | |
| and "anthropic.claude" in completion_kwargs.get("model", "") | |
| ) | |
| _anthropic_only_params = {"output_config"} | |
| _target_provider = (extra_kwargs or {}).get("custom_llm_provider", "") | |
| _is_anthropic_claude = _target_provider in ( | |
| "anthropic", | |
| ) or ( | |
| _target_provider == "bedrock" | |
| and "anthropic.claude" in completion_kwargs.get("model", "") | |
| ) or ( | |
| _target_provider == "vertex_ai" | |
| and "claude" in completion_kwargs.get("model", "").lower() | |
| ) |
| """ | ||
| Unit tests for LiteLLMMessagesToCompletionTransformationHandler._prepare_completion_kwargs, | ||
| specifically the max_tokens capping logic added to prevent HTTP 400 errors from providers | ||
| with strict output token limits (e.g. Amazon Nova Pro: 10,000 tokens). | ||
| """ |
There was a problem hiding this comment.
Missing tests for output_config stripping
The PR title and description focus on stripping output_config for non-Anthropic backends, but the test file only covers the max_tokens capping logic. The PR description itself notes "Unit tests to be added covering strip/no-strip behavior per provider" as unchecked. Please add tests verifying:
output_configis stripped when routing to non-Anthropic backends (e.g. Bedrock Nova Pro)output_configis preserved when routing to Anthropic Claude (direct API)output_configis preserved when routing to Bedrock-hosted Anthropic Claudeoutput_configis preserved when routing to Vertex AI Claude
Context Used: Rule from dashboard - What: Ensure that any PR claiming to fix an issue includes evidence that the issue is resolved, such... (source)
| max_tokens=max_tokens, | ||
| messages=MESSAGES, | ||
| model=MODEL, | ||
| extra_kwargs=extra_kwargs or {"custom_llm_provider": PROVIDER}, |
There was a problem hiding this comment.
Helper masks empty dict argument
extra_kwargs or {default} treats {} as falsy in Python, so passing extra_kwargs={} silently substitutes the default dict that includes the provider. This means test_fallback_infers_provider_when_not_in_extra_kwargs (line 62) never exercises the fallback inference path — get_llm_provider is never invoked because the provider key is always present. The test passes only because get_model_info is mocked to return the capped value regardless of how the provider was resolved.
Use an explicit None check instead:
| extra_kwargs=extra_kwargs or {"custom_llm_provider": PROVIDER}, | |
| extra_kwargs=extra_kwargs if extra_kwargs is not None else {"custom_llm_provider": PROVIDER}, |
…ept it Resolves the silent strip of Anthropic Structured Outputs across the Vertex AI Claude transformation paths and the Anthropic-adapter re-merge. Consolidates and supersedes four stalled community PRs addressing overlapping aspects of the same root bug: - #23475 (Vertex AI Claude blanket-strip removal) - #23396 (Vertex AI Claude conditional passthrough) - #23706 (Anthropic adapter exclude output_config from non-Anthropic backends) - #22727 (Anthropic adapter strip output_config for non-Anthropic backends) Closes / addresses: #23380 (Vertex AI Claude output_config drop), related: #26423, #25079, #24549, #25971, #25957, #26163, #24856. What was broken --------------- * Vertex AI Claude paths called ``data.pop("output_config")`` and ``data.pop("output_format")`` unconditionally even when Vertex accepted those fields. Callers asking for Structured Outputs got a 200 with prose and never knew the schema constraints had been silently dropped (often masked for months by permissive fallback parsers). * The ``/v1/messages`` -> ``/chat/completions`` adapter (``LiteLLMMessagesToCompletionTransformationHandler``) re-merged the raw Anthropic-shaped ``output_config`` into ``completion_kwargs`` AFTER the translator already mapped its meaningful parts to ``response_format`` / ``reasoning_effort``. Non-Anthropic backends (Azure OpenAI, Fireworks, Bedrock Nova, etc.) then 400'd with "Extra inputs are not permitted". Approach -------- Vertex AI Claude (chat-completion + experimental_pass_through paths): Replace the unconditional pop with a sanitizer ``_sanitize_vertex_anthropic_output_params`` that strips only the Vertex-unsupported keys (today: ``effort``) from ``output_config`` while forwarding ``format`` and the legacy top-level ``output_format``. Defensive: non-dict ``output_config`` values are dropped to avoid sending malformed payloads downstream. Greptile P1 from PR #23396 addressed: when ``output_config`` carries both ``format`` and ``effort``, the prior conditional pass-through forwarded ``effort`` and reproduced the 400. The new helper filters per-key. Anthropic ``/v1/messages`` adapter: Add ``output_config`` to a named module-level constant ``ANTHROPIC_ONLY_REQUEST_KEYS`` and wire it into ``excluded_keys`` so the post-translation re-merge skips re-adding the raw key. This fixes the 400 on non-Anthropic backends and avoids the conflicting duplicate (``response_format`` + raw ``output_config``) on Anthropic-family backends. Greptile P2 from PR #23706 addressed: the constant gives reviewers one grep target instead of an inline literal that silently grows. Greptile P2 from PR #22727 addressed: ``extra_kwargs or {}`` is replaced with explicit ``is None`` checks so empty-dict callers no longer skip the fallback path. Tests ----- * tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/ test_vertex_ai_partner_models_anthropic_transformation.py: - 5 new/updated cases plus a direct unit test for ``_sanitize_vertex_anthropic_output_params``. - Updated ``test_vertex_ai_claude_sonnet_4_5_structured_output_fix`` so its mock-injected ``output_format`` is asserted to FLOW THROUGH (the original test asserted the now-buggy strip behavior). * tests/test_litellm/llms/anthropic/experimental_pass_through/ adapters/test_handler_output_config_passthrough.py (new): - Constant export sanity, output_config strip with ``effort`` only, output_config strip with ``format`` only, regression guard that unrelated extras still flow, explicit-empty-dict path, and the ``extra_kwargs=None`` no-crash path. Test-quality fixes incorporated from Greptile review on the superseded PRs: * No ``inspect.getsource`` source-text assertions (PR #24114 / #23475). * ``sys.path`` insertion is anchored to ``__file__`` (PR #23706). * Assertion messages are positional, not tuple (PR #24114-class bug). * No ``or {}`` masking explicit empty dicts in helper signatures (PR #22727). Verified locally: 26/26 pass with this commit. The new tests fail (or fail to import) on ``main`` without it. Out of scope ------------ * The ``max_tokens`` capping logic from PR #22727 — independent concern, deserves its own PR with a focused test plan. * Architectural rework of the ``excluded_keys`` mechanism (Greptile P2 on PR #23706 noted point-fix growth). The named constant gives maintainers a clear place to extend; a registry-based approach would be a follow-up. Co-Authored-By: netbrah <netbrah> Co-Authored-By: s-zx <s-zx> Co-Authored-By: invoicepulse <invoicepulse> Co-Authored-By: cfdude <cfdude> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ept it Resolves the silent strip of Anthropic Structured Outputs across the Vertex AI Claude transformation paths and the Anthropic-adapter re-merge. Consolidates and supersedes four stalled community PRs addressing overlapping aspects of the same root bug: - BerriAI#23475 (Vertex AI Claude blanket-strip removal) - BerriAI#23396 (Vertex AI Claude conditional passthrough) - BerriAI#23706 (Anthropic adapter exclude output_config from non-Anthropic backends) - BerriAI#22727 (Anthropic adapter strip output_config for non-Anthropic backends) Closes / addresses: BerriAI#23380 (Vertex AI Claude output_config drop), related: BerriAI#26423, BerriAI#25079, BerriAI#24549, BerriAI#25971, BerriAI#25957, BerriAI#26163, BerriAI#24856. What was broken --------------- * Vertex AI Claude paths called ``data.pop("output_config")`` and ``data.pop("output_format")`` unconditionally even when Vertex accepted those fields. Callers asking for Structured Outputs got a 200 with prose and never knew the schema constraints had been silently dropped (often masked for months by permissive fallback parsers). * The ``/v1/messages`` -> ``/chat/completions`` adapter (``LiteLLMMessagesToCompletionTransformationHandler``) re-merged the raw Anthropic-shaped ``output_config`` into ``completion_kwargs`` AFTER the translator already mapped its meaningful parts to ``response_format`` / ``reasoning_effort``. Non-Anthropic backends (Azure OpenAI, Fireworks, Bedrock Nova, etc.) then 400'd with "Extra inputs are not permitted". Approach -------- Vertex AI Claude (chat-completion + experimental_pass_through paths): Replace the unconditional pop with a sanitizer ``_sanitize_vertex_anthropic_output_params`` that strips only the Vertex-unsupported keys (today: ``effort``) from ``output_config`` while forwarding ``format`` and the legacy top-level ``output_format``. Defensive: non-dict ``output_config`` values are dropped to avoid sending malformed payloads downstream. Greptile P1 from PR BerriAI#23396 addressed: when ``output_config`` carries both ``format`` and ``effort``, the prior conditional pass-through forwarded ``effort`` and reproduced the 400. The new helper filters per-key. Anthropic ``/v1/messages`` adapter: Add ``output_config`` to a named module-level constant ``ANTHROPIC_ONLY_REQUEST_KEYS`` and wire it into ``excluded_keys`` so the post-translation re-merge skips re-adding the raw key. This fixes the 400 on non-Anthropic backends and avoids the conflicting duplicate (``response_format`` + raw ``output_config``) on Anthropic-family backends. Greptile P2 from PR BerriAI#23706 addressed: the constant gives reviewers one grep target instead of an inline literal that silently grows. Greptile P2 from PR BerriAI#22727 addressed: ``extra_kwargs or {}`` is replaced with explicit ``is None`` checks so empty-dict callers no longer skip the fallback path. Tests ----- * tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/ test_vertex_ai_partner_models_anthropic_transformation.py: - 5 new/updated cases plus a direct unit test for ``_sanitize_vertex_anthropic_output_params``. - Updated ``test_vertex_ai_claude_sonnet_4_5_structured_output_fix`` so its mock-injected ``output_format`` is asserted to FLOW THROUGH (the original test asserted the now-buggy strip behavior). * tests/test_litellm/llms/anthropic/experimental_pass_through/ adapters/test_handler_output_config_passthrough.py (new): - Constant export sanity, output_config strip with ``effort`` only, output_config strip with ``format`` only, regression guard that unrelated extras still flow, explicit-empty-dict path, and the ``extra_kwargs=None`` no-crash path. Test-quality fixes incorporated from Greptile review on the superseded PRs: * No ``inspect.getsource`` source-text assertions (PR BerriAI#24114 / BerriAI#23475). * ``sys.path`` insertion is anchored to ``__file__`` (PR BerriAI#23706). * Assertion messages are positional, not tuple (PR BerriAI#24114-class bug). * No ``or {}`` masking explicit empty dicts in helper signatures (PR BerriAI#22727). Verified locally: 26/26 pass with this commit. The new tests fail (or fail to import) on ``main`` without it. Out of scope ------------ * The ``max_tokens`` capping logic from PR BerriAI#22727 — independent concern, deserves its own PR with a focused test plan. * Architectural rework of the ``excluded_keys`` mechanism (Greptile P2 on PR BerriAI#23706 noted point-fix growth). The named constant gives maintainers a clear place to extend; a registry-based approach would be a follow-up. Co-Authored-By: netbrah <netbrah> Co-Authored-By: s-zx <s-zx> Co-Authored-By: invoicepulse <invoicepulse> Co-Authored-By: cfdude <cfdude> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
…ept it Resolves the silent strip of Anthropic Structured Outputs across the Vertex AI Claude transformation paths and the Anthropic-adapter re-merge. Consolidates and supersedes four stalled community PRs addressing overlapping aspects of the same root bug: - BerriAI#23475 (Vertex AI Claude blanket-strip removal) - BerriAI#23396 (Vertex AI Claude conditional passthrough) - BerriAI#23706 (Anthropic adapter exclude output_config from non-Anthropic backends) - BerriAI#22727 (Anthropic adapter strip output_config for non-Anthropic backends) Closes / addresses: BerriAI#23380 (Vertex AI Claude output_config drop), related: BerriAI#26423, BerriAI#25079, BerriAI#24549, BerriAI#25971, BerriAI#25957, BerriAI#26163, BerriAI#24856. What was broken --------------- * Vertex AI Claude paths called ``data.pop("output_config")`` and ``data.pop("output_format")`` unconditionally even when Vertex accepted those fields. Callers asking for Structured Outputs got a 200 with prose and never knew the schema constraints had been silently dropped (often masked for months by permissive fallback parsers). * The ``/v1/messages`` -> ``/chat/completions`` adapter (``LiteLLMMessagesToCompletionTransformationHandler``) re-merged the raw Anthropic-shaped ``output_config`` into ``completion_kwargs`` AFTER the translator already mapped its meaningful parts to ``response_format`` / ``reasoning_effort``. Non-Anthropic backends (Azure OpenAI, Fireworks, Bedrock Nova, etc.) then 400'd with "Extra inputs are not permitted". Approach -------- Vertex AI Claude (chat-completion + experimental_pass_through paths): Replace the unconditional pop with a sanitizer ``_sanitize_vertex_anthropic_output_params`` that strips only the Vertex-unsupported keys (today: ``effort``) from ``output_config`` while forwarding ``format`` and the legacy top-level ``output_format``. Defensive: non-dict ``output_config`` values are dropped to avoid sending malformed payloads downstream. Greptile P1 from PR BerriAI#23396 addressed: when ``output_config`` carries both ``format`` and ``effort``, the prior conditional pass-through forwarded ``effort`` and reproduced the 400. The new helper filters per-key. Anthropic ``/v1/messages`` adapter: Add ``output_config`` to a named module-level constant ``ANTHROPIC_ONLY_REQUEST_KEYS`` and wire it into ``excluded_keys`` so the post-translation re-merge skips re-adding the raw key. This fixes the 400 on non-Anthropic backends and avoids the conflicting duplicate (``response_format`` + raw ``output_config``) on Anthropic-family backends. Greptile P2 from PR BerriAI#23706 addressed: the constant gives reviewers one grep target instead of an inline literal that silently grows. Greptile P2 from PR BerriAI#22727 addressed: ``extra_kwargs or {}`` is replaced with explicit ``is None`` checks so empty-dict callers no longer skip the fallback path. Tests ----- * tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/ test_vertex_ai_partner_models_anthropic_transformation.py: - 5 new/updated cases plus a direct unit test for ``_sanitize_vertex_anthropic_output_params``. - Updated ``test_vertex_ai_claude_sonnet_4_5_structured_output_fix`` so its mock-injected ``output_format`` is asserted to FLOW THROUGH (the original test asserted the now-buggy strip behavior). * tests/test_litellm/llms/anthropic/experimental_pass_through/ adapters/test_handler_output_config_passthrough.py (new): - Constant export sanity, output_config strip with ``effort`` only, output_config strip with ``format`` only, regression guard that unrelated extras still flow, explicit-empty-dict path, and the ``extra_kwargs=None`` no-crash path. Test-quality fixes incorporated from Greptile review on the superseded PRs: * No ``inspect.getsource`` source-text assertions (PR BerriAI#24114 / BerriAI#23475). * ``sys.path`` insertion is anchored to ``__file__`` (PR BerriAI#23706). * Assertion messages are positional, not tuple (PR BerriAI#24114-class bug). * No ``or {}`` masking explicit empty dicts in helper signatures (PR BerriAI#22727). Verified locally: 26/26 pass with this commit. The new tests fail (or fail to import) on ``main`` without it. Out of scope ------------ * The ``max_tokens`` capping logic from PR BerriAI#22727 — independent concern, deserves its own PR with a focused test plan. * Architectural rework of the ``excluded_keys`` mechanism (Greptile P2 on PR BerriAI#23706 noted point-fix growth). The named constant gives maintainers a clear place to extend; a registry-based approach would be a follow-up. Co-Authored-By: netbrah <netbrah> Co-Authored-By: s-zx <s-zx> Co-Authored-By: invoicepulse <invoicepulse> Co-Authored-By: cfdude <cfdude>
…sthrough-consolidated fix(adapters,vertex): pass output_config through to backends that accept it (closes BerriAI#23380, supersedes BerriAI#23475/BerriAI#23396/BerriAI#23706/BerriAI#22727)
Summary
output_configis an Anthropic Claude-specific parameter with no equivalent in other model providersextraneous key [output_config] is not permittederrorsoutput_configfrom the request when the target provider is not an Anthropic Claude model (direct Anthropic API or Bedrock-hosted Anthropic Claude)thinkingis intentionally not stripped — some non-Anthropic models (e.g. Qwen) support reasoning natively and the adapter already handles translation for thoseTest plan
bedrock/converse/us.amazon.nova-pro-v1:0via LiteLLM proxy — previously failed withextraneous key [output_config], now succeedsoutput_configpassthrough to native Anthropic Claude models is unaffected