fix(anthropic): strip diagnostics from the chat/completions bridge re-merge

The Anthropic /v1/messages -> chat/completions bridge
(LiteLLMMessagesToCompletionTransformationHandler._prepare_completion_kwargs)
re-merges every extra kwarg the translator didn't consume into
completion_kwargs, excluding only ANTHROPIC_ONLY_REQUEST_KEYS
(previously just {"output_config"}).

diagnostics (a client-diagnostics-only field, e.g. sent by Claude Code, with
no completion()/OpenAI equivalent) was not in that exclusion set, so it
reached completion_kwargs verbatim and propagated to non-Anthropic backends
routed through this bridge (e.g. Bedrock Converse via
additionalModelRequestFields), which reject the unrecognized field with 400
"Extra inputs are not permitted".

Deployments whose litellm_params.model resolves through the native Anthropic
Messages passthrough (AnthropicMessagesConfig.transform_anthropic_messages_request)
are unaffected — that path only forwards keys declared on
AnthropicMessagesRequestOptionalParams and already drops diagnostics. This
fix covers the bridge path taken by deployments that don't resolve to that
passthrough config (e.g. a Bedrock model string with no "claude" substring
and no supported_endpoints declaration, such as an opaque application
inference profile ARN).
This commit is contained in:
Arbin 2026-09-07 18:58:03 +08:00
parent eeb7732fc1
commit 6eda19cd13
No known key found for this signature in database
GPG key ID: 17A013CB6AD55FD9
2 changed files with 59 additions and 2 deletions

View file

@ -34,8 +34,18 @@ if TYPE_CHECKING:
from litellm.proxy._types import UserAPIKeyAuth
from litellm.router import Router
# Anthropic-only keys already mapped by the translator; strip on extra_kwargs re-merge.
ANTHROPIC_ONLY_REQUEST_KEYS: Final[frozenset[str]] = frozenset({"output_config"})
# Anthropic-only keys to strip on extra_kwargs re-merge:
# - "output_config": already mapped by the translator (output_config.effort ->
# reasoning_effort, output_config.format -> response_format).
# - "diagnostics": a client-diagnostics-only field (sent by Claude Code) with no
# completion()/OpenAI equivalent. Unlike the native Anthropic Messages passthrough
# (AnthropicMessagesConfig.transform_anthropic_messages_request), which only forwards
# keys declared on AnthropicMessagesRequestOptionalParams and so drops it silently,
# this bridge re-merges every extra kwarg the translator didn't consume. Forwarding
# it verbatim reaches non-Anthropic backends (e.g. Bedrock Converse via
# additionalModelRequestFields) as an unrecognized field and 400s with "Extra inputs
# are not permitted".
ANTHROPIC_ONLY_REQUEST_KEYS: Final[frozenset[str]] = frozenset({"output_config", "diagnostics"})
_AnthropicMessages: TypeAlias = "list[dict[str, object]]"
_AnthropicSystem: TypeAlias = "str | list[dict[str, object]] | None"

View file

@ -237,3 +237,50 @@ class TestPromptCacheOptionsForwarded:
result = _call_prepare(extra_kwargs={"prompt_cache_options": {"mode": "explicit"}}, model="gpt-5.6")
completion_kwargs = result[0] if isinstance(result, tuple) else result
assert completion_kwargs["prompt_cache_options"] == {"mode": "explicit"}
class TestDiagnosticsStrippedFromCompletionKwargs:
"""``diagnostics`` (sent by Claude Code as a client-diagnostics-only field,
with no completion()/OpenAI equivalent) must not survive the
post-translation re-merge into ``completion_kwargs`` — non-Anthropic
backends reached via this bridge (e.g. Bedrock Converse via
``additionalModelRequestFields``) reject an unrecognized field with 400
"Extra inputs are not permitted". Unlike the native Anthropic Messages
passthrough (``AnthropicMessagesConfig.transform_anthropic_messages_request``),
which only forwards keys declared on
``AnthropicMessagesRequestOptionalParams`` and so drops ``diagnostics``
silently, this bridge re-merges every extra kwarg the translator didn't
consume, so the strip has to be explicit here too."""
def test_diagnostics_is_stripped(self):
extra_kwargs = {
"custom_llm_provider": "bedrock",
"diagnostics": {"trace_id": "abc123"},
}
result = _call_prepare(extra_kwargs=extra_kwargs)
completion_kwargs = result[0] if isinstance(result, tuple) else result
assert "diagnostics" not in completion_kwargs, (
"Raw diagnostics must not be forwarded — non-Anthropic backends "
"reject it with 400 'Extra inputs are not permitted'"
)
def test_contains_diagnostics(self):
assert "diagnostics" in ANTHROPIC_ONLY_REQUEST_KEYS
def test_other_extra_kwargs_still_passed_through_alongside_diagnostics(self):
"""Regression guard: the strip must be narrow. Unrelated fields like
``timeout`` continue to flow through even when ``diagnostics`` is
also present."""
extra_kwargs = {
"custom_llm_provider": "bedrock",
"diagnostics": {"trace_id": "abc123"},
"timeout": 30,
}
result = _call_prepare(extra_kwargs=extra_kwargs)
completion_kwargs = result[0] if isinstance(result, tuple) else result
assert "diagnostics" not in completion_kwargs
assert completion_kwargs.get("timeout") == 30