mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
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:
parent
eeb7732fc1
commit
6eda19cd13
2 changed files with 59 additions and 2 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue