fix(adapters,vertex): pass output_config through to backends that accept it (closes #23380, supersedes #23475/#23396/#23706/#22727) - #26439
Conversation
[Infra] Promote internal staging to main
…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 PR adds parameter filtering and translation logic for Anthropic's Status: 0 open Posted by Veria AI · 2026-04-24T16:05:06.898Z |
Greptile SummaryThis PR fixes silent stripping of Confidence Score: 5/5Safe to merge; fixes a confirmed silent-drop bug with correct per-key sanitization, good test coverage, and only P2 findings No P0 or P1 issues found. The two P2 findings (None/absent key ambiguity in the sanitizer, and falsy-vs-None check in the translator) have no realistic impact on production inputs. The PR includes 26 passing tests, reversed test assertions are intentional and correctly reflect the fixed behavior, and the shared helper avoids the duplicate-logic problem of the superseded PRs. output_params_utils.py (None vs absent key edge case) and transformation.py adapter (falsy output_format check semantics)
|
| Filename | Overview |
|---|---|
| litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/output_params_utils.py | New shared sanitization helper for Vertex AI Claude; correctly filters unsupported sub-keys (effort) while preserving supported ones (format); minor edge case: .get() cannot distinguish absent key from key set to None, leaving null values in the dict |
| litellm/llms/anthropic/experimental_pass_through/adapters/handler.py | Adds ANTHROPIC_ONLY_REQUEST_KEYS constant and wires output_config into excluded_keys to prevent the raw Anthropic field from leaking into completion_kwargs; None-check fix for extra_kwargs is correct |
| litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py | Extends _translate_output_format_to_openai to accept output_config.format as a fallback when output_format is absent; uses falsy check (if not output_format:) which silently treats empty-dict output_format the same as absent |
| litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/transformation.py | Replaces unconditional pop() of output_config/output_format with call to sanitize_vertex_anthropic_output_params; clean delegation to shared helper |
| litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py | Mirrors the chat-completion path change — replaces unconditional pop() with sanitize_vertex_anthropic_output_params; one source of truth for both transformation paths |
| tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_output_config_passthrough.py | New test file covering all key scenarios: strip, translate, precedence, empty/None extra_kwargs, and constant export; behavioral tests rather than source inspection; no real network calls |
| tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py | Existing tests updated to reflect corrected behavior (output_format now forwarded, effort dropped); assertions reversed from prior strip-everything behavior — valid since the stripping was the bug; new tests added for per-key sanitization scenarios |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Client sends output_config] --> B{Which path?}
B --> C[Vertex AI Claude\nchat-completion path]
B --> D[Vertex AI Claude\nMessages pass-through path]
B --> E[Anthropic adapter\n/v1/messages → /chat/completions]
C --> F[sanitize_vertex_anthropic_output_params]
D --> F
F --> G{output_config sub-keys}
G -->|effort only| H[Drop output_config entirely\navoid Vertex 400]
G -->|format only| I[Forward output_config as-is\nVertex accepts format]
G -->|format + effort| J[Strip effort, keep format\nForward sanitized output_config]
G -->|non-dict| K[Drop defensively]
E --> L[Extract output_config\ninto request_data]
L --> M[translate_anthropic_to_openai\nmaps format→response_format\neffort→reasoning_effort]
M --> N[excluded_keys filter\nANTHROPIC_ONLY_REQUEST_KEYS\nprevents raw output_config\nre-merge into completion_kwargs]
N --> O[completion_kwargs has\nresponse_format / reasoning_effort\nbut NOT raw output_config]
Reviews (2): Last reviewed commit: "fix: address Greptile review feedback on..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Three concerns raised by bot reviewers, all addressed:
1. CodeQL cyclic-import warning
``experimental_pass_through/transformation.py`` imported from the
parent ``..transformation`` module, which CodeQL flagged as a
potential cycle. Extracted the helper into a new leaf module
``vertex_ai_partner_models/anthropic/output_params_utils.py`` that
has no heavy imports of its own. Both transformation files now
import from it cleanly. Renamed the helper from the underscore-
prefixed ``_sanitize_vertex_anthropic_output_params`` to the
public ``sanitize_vertex_anthropic_output_params`` since it is now
shared across modules.
2. Greptile P2: redundant ``None`` guard on ``extra_kwargs``
``handler.py`` had two ``extra_kwargs = extra_kwargs if ... else {}``
coercions; the second was a no-op because line 220 already
coerced. Removed the second one and added a NOTE comment so future
readers understand ``extra_kwargs`` is guaranteed non-None at the
point of use.
3. Greptile P2: misleading "already translated" docstring
The docstring claimed the translator above mapped
``output_config.format`` to ``response_format``, but Greptile
correctly traced the code and found that only the legacy top-level
``output_format`` was being translated — ``output_config.format``
was being silently dropped on the adapter path. Two-part fix:
a. Code: extended ``_translate_output_format_to_openai`` to accept
both shapes (top-level ``output_format`` AND
``output_config.format`` sub-key). Top-level still takes
precedence when both are supplied. This means callers using the
newer Anthropic Structured Outputs API now have their schema
properly forwarded to non-Anthropic backends as
``response_format``.
b. Tests: rewrote the misleading docstring to describe what
actually happens, plus added two new tests:
* ``test_output_format_top_level_still_translates`` —
regression guard for the legacy path
* ``test_output_format_takes_precedence_over_output_config_format``
— documents the precedence rule explicitly
Tests: 28/28 pass (was 26/26 before; +2 for the new translation
behavior + precedence). All run in ~0.5s, no real network calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@greptile-apps, can you please re-review? |
|
@dkindlund — could you add a screenshot or short video showing that this change works as expected? It really helps reviewers verify the fix quickly. Thanks! |
|
@mateo-berri didn't we recently work on this? is this still a relevant PR? |
|
We merged a PR for Bedrock but not Vertex. This is a legit value add. I can take over reviewing |
|
@mateo-berri assigned to you |
mateo-berri
left a comment
There was a problem hiding this comment.
LGTM. Thank you for the contribution!
b55a0f0
into
BerriAI:litellm_oss_staging_04_25_2026
Three concerns raised by bot reviewers, all addressed:
1. CodeQL cyclic-import warning
``experimental_pass_through/transformation.py`` imported from the
parent ``..transformation`` module, which CodeQL flagged as a
potential cycle. Extracted the helper into a new leaf module
``vertex_ai_partner_models/anthropic/output_params_utils.py`` that
has no heavy imports of its own. Both transformation files now
import from it cleanly. Renamed the helper from the underscore-
prefixed ``_sanitize_vertex_anthropic_output_params`` to the
public ``sanitize_vertex_anthropic_output_params`` since it is now
shared across modules.
2. Greptile P2: redundant ``None`` guard on ``extra_kwargs``
``handler.py`` had two ``extra_kwargs = extra_kwargs if ... else {}``
coercions; the second was a no-op because line 220 already
coerced. Removed the second one and added a NOTE comment so future
readers understand ``extra_kwargs`` is guaranteed non-None at the
point of use.
3. Greptile P2: misleading "already translated" docstring
The docstring claimed the translator above mapped
``output_config.format`` to ``response_format``, but Greptile
correctly traced the code and found that only the legacy top-level
``output_format`` was being translated — ``output_config.format``
was being silently dropped on the adapter path. Two-part fix:
a. Code: extended ``_translate_output_format_to_openai`` to accept
both shapes (top-level ``output_format`` AND
``output_config.format`` sub-key). Top-level still takes
precedence when both are supplied. This means callers using the
newer Anthropic Structured Outputs API now have their schema
properly forwarded to non-Anthropic backends as
``response_format``.
b. Tests: rewrote the misleading docstring to describe what
actually happens, plus added two new tests:
* ``test_output_format_top_level_still_translates`` —
regression guard for the legacy path
* ``test_output_format_takes_precedence_over_output_config_format``
— documents the precedence rule explicitly
Tests: 28/28 pass (was 26/26 before; +2 for the new translation
behavior + precedence). All run in ~0.5s, no real network calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three concerns raised by bot reviewers, all addressed:
1. CodeQL cyclic-import warning
``experimental_pass_through/transformation.py`` imported from the
parent ``..transformation`` module, which CodeQL flagged as a
potential cycle. Extracted the helper into a new leaf module
``vertex_ai_partner_models/anthropic/output_params_utils.py`` that
has no heavy imports of its own. Both transformation files now
import from it cleanly. Renamed the helper from the underscore-
prefixed ``_sanitize_vertex_anthropic_output_params`` to the
public ``sanitize_vertex_anthropic_output_params`` since it is now
shared across modules.
2. Greptile P2: redundant ``None`` guard on ``extra_kwargs``
``handler.py`` had two ``extra_kwargs = extra_kwargs if ... else {}``
coercions; the second was a no-op because line 220 already
coerced. Removed the second one and added a NOTE comment so future
readers understand ``extra_kwargs`` is guaranteed non-None at the
point of use.
3. Greptile P2: misleading "already translated" docstring
The docstring claimed the translator above mapped
``output_config.format`` to ``response_format``, but Greptile
correctly traced the code and found that only the legacy top-level
``output_format`` was being translated — ``output_config.format``
was being silently dropped on the adapter path. Two-part fix:
a. Code: extended ``_translate_output_format_to_openai`` to accept
both shapes (top-level ``output_format`` AND
``output_config.format`` sub-key). Top-level still takes
precedence when both are supplied. This means callers using the
newer Anthropic Structured Outputs API now have their schema
properly forwarded to non-Anthropic backends as
``response_format``.
b. Tests: rewrote the misleading docstring to describe what
actually happens, plus added two new tests:
* ``test_output_format_top_level_still_translates`` —
regression guard for the legacy path
* ``test_output_format_takes_precedence_over_output_config_format``
— documents the precedence rule explicitly
Tests: 28/28 pass (was 26/26 before; +2 for the new translation
behavior + precedence). All run in ~0.5s, no real network calls.
…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
Pass
output_configthrough to backends that accept it (Vertex AI Claude, Anthropic-direct) and stop forwarding the raw Anthropic-shaped key into OpenAI-format completion calls where it triggers 400 "Extra inputs are not permitted." Consolidates four stalled community PRs that addressed overlapping aspects of the same root bug, incorporates every Greptile review concern that was outstanding on those PRs, and closes one functional gap they all missed (format+effortmixed payload on Vertex).Closes / supersedes
Original authors credited via
Co-Authored-Bytrailers: @netbrah, @s-zx, @invoicepulse, @cfdude.Related issues (not closed by this PR but reference the same cluster)
#26423, #25079, #24549, #25971, #25957, #26163, #24856
What was broken
Anthropic accepts
output_configand validates the schema. The bug reporter's empirical confirmation:Approach
Vertex AI Claude (both transformation paths)
Replace
data.pop("output_config", None)with a sanitizer that strips only the Vertex-unsupported keys (today:effort) fromoutput_config, forwardingformatand the legacy top-leveloutput_format. Helper lives in the chat-completion module and is imported by the Messages module so there's one source of truth.The
_VERTEX_UNSUPPORTED_OUTPUT_CONFIG_KEYSconstant gives maintainers one place to extend as Vertex parity drifts.Anthropic adapter (
/v1/messages→/chat/completions)Add
output_configto a named module-level constantANTHROPIC_ONLY_REQUEST_KEYSand wire it intoexcluded_keys. The translator above the re-merge has already mappedoutput_config.format→response_formatandoutput_config.effort→reasoning_effortfor non-Claude targets. Re-adding the raw key was either redundant (Anthropic-family target sees bothresponse_formatAND a duplicateoutput_config) or harmful (non-Anthropic target rejects unknown field).Also replaces
extra_kwargs or {}withextra_kwargs if extra_kwargs is not None else {}so callers passing an explicit empty dict don't get a default substituted (Greptile P2 on PR #22727 — the masked-empty-dict bug that hid the fallback inference path from being tested).Greptile feedback addressed (every concern from the four superseded PRs)
output_formatpass-through" — said the field flowed through OpenAI-compat when actually only via mock injectionoutput_formatflows through the mock path explicitly and notes thatmap_openai_paramsstill uses tools for the OpenAI-compat patheffortkey leaks through whenformatis also present" — combined payload would still 400 on Vertex_sanitize_vertex_anthropic_output_paramsfilters per-key, soformat+effortbecomesformatonly. Dedicated test:test_vertex_ai_anthropic_output_config_format_plus_effort_strips_only_effort*_droppedtests asserted the old strip behavior)ANTHROPIC_ONLY_REQUEST_KEYS+ comment instructing future maintainers where to extendsys.path.insertworking-directory-relative path"os.path.join(os.path.dirname(__file__), ...)anchored to file locationasync_complete_streaming_responsetriggers unconditional early return"async_success_handler"_prepare_completion_kwargsdirectly and inspect its return value_is_anthropic_claudeonly checkedanthropic/bedrockoutput_configstripping"test_handler_output_config_passthrough.pycovers strip with effort-only, strip with format-only, regression guard for unrelated extras, empty-extra-kwargs, None-extra-kwargsextra_kwargs or {default}substitutes default for{}if extra_kwargs is not None else {}and dedicated testtest_explicit_empty_dict_does_not_substitute_defaultinspect.getsourcesource-text tests are fragile"Tests
26/26 pass with this commit. The new tests fail (or fail to import) on
mainwithout it.Manual reproduction
Out of scope (deliberately deferred)
max_tokenscapping from PR fix(anthropic-adapter): strip output_config for non-Anthropic backends #22727 — independent concern. Should land as its own PR with focused tests for Bedrock Nova / strict-limit providers.excluded_keys— Greptile flagged the point-fix-growth pattern. Named constant addresses the maintainability concern; a registry-based approach can come as a follow-up./v1/messagesstrip investigation — the bug report mentions a strip on Anthropic-direct that I could not reproduce via code reading. The transformation path explicitly forwardsoutput_config. If users still see the strip on Anthropic-direct after this PR lands, it's a separate downstream issue worth a fresh bug report.Test plan
make test-unitfor the touched test filesmainwithout this commitblack --checkclean🤖 Generated with Claude Code