mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix(anthropic): don't mutate read-only requests and tighten system list typing
- copy the request before stripping the Claude Code identity so a read-only wire-body mapping (shadow evaluation) no longer raises TypeError on pop/assign - annotate the system-list helper as list[object] instead of bare list - drop a comment that restated the equality check it precedes - add a regression test for the read-only request path
This commit is contained in:
parent
b1d6264a03
commit
49de19e6e3
4 changed files with 36 additions and 7 deletions
|
|
@ -143,7 +143,9 @@ def strip_claude_code_identity(text: str) -> str | None:
|
|||
return text
|
||||
|
||||
|
||||
def strip_claude_code_identity_from_system(system_param: str | list | None) -> str | list | None:
|
||||
def strip_claude_code_identity_from_system(
|
||||
system_param: str | list[object] | None,
|
||||
) -> str | list[object] | None:
|
||||
"""Strip Claude Code's self-identification sentence from a full system parameter.
|
||||
|
||||
Unlike :func:`strip_claude_code_identity`, which operates on a single text
|
||||
|
|
@ -169,7 +171,6 @@ def strip_claude_code_identity_from_system(system_param: str | list | None) -> s
|
|||
stripped_text = strip_claude_code_identity(text)
|
||||
if stripped_text is None:
|
||||
continue
|
||||
# Only copy the block when the text changed.
|
||||
if stripped_text == text:
|
||||
filtered_list.append(content_block)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1239,10 +1239,14 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
system_param: Final = anthropic_message_request.get("system")
|
||||
if system_param is not None:
|
||||
stripped_system = strip_claude_code_identity_from_system(system_param)
|
||||
if stripped_system is None:
|
||||
anthropic_message_request.pop("system", None)
|
||||
elif stripped_system != system_param:
|
||||
anthropic_message_request["system"] = stripped_system
|
||||
if stripped_system is None or stripped_system != system_param:
|
||||
# This adapter is also invoked with a read-only wire-body mapping
|
||||
# (shadow evaluation); mutate a copy, never the caller's request.
|
||||
anthropic_message_request = cast(AnthropicMessagesRequest, dict(anthropic_message_request))
|
||||
if stripped_system is None:
|
||||
anthropic_message_request.pop("system", None)
|
||||
else:
|
||||
anthropic_message_request["system"] = stripped_system
|
||||
|
||||
## CONVERT ANTHROPIC MESSAGES TO OPENAI
|
||||
messages_list: Final[list[AllAnthropicPassThroughMessageValues]] = cast(
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
return False
|
||||
|
||||
@staticmethod
|
||||
def _strip_claude_code_identity_from_system(system_param) -> str | list | None:
|
||||
def _strip_claude_code_identity_from_system(system_param: str | list[object] | None) -> str | list[object] | None:
|
||||
"""
|
||||
Strip Claude Code's self-identification sentence from system parameter.
|
||||
|
||||
|
|
|
|||
|
|
@ -2900,6 +2900,30 @@ def test_translate_anthropic_to_openai_strips_claude_code_identity(system, expec
|
|||
assert system_messages[0]["content"] == expected_system_content
|
||||
|
||||
|
||||
def test_translate_anthropic_to_openai_strips_claude_code_identity_read_only_request():
|
||||
"""Identity stripping must not mutate a read-only request (shadow evaluation passes a
|
||||
MappingProxyType, so the adapter must copy before rewriting ``system``)."""
|
||||
from types import MappingProxyType
|
||||
|
||||
adapter = LiteLLMAnthropicMessagesAdapter()
|
||||
request = MappingProxyType(
|
||||
{
|
||||
"model": "hosted_vllm/kimi-k3",
|
||||
"max_tokens": 1024,
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"system": f"{CLAUDE_CODE_IDENTITY}\nYou are an interactive agent.",
|
||||
}
|
||||
)
|
||||
|
||||
openai_request, _ = adapter.translate_anthropic_to_openai(
|
||||
anthropic_message_request=request,
|
||||
custom_llm_provider="hosted_vllm",
|
||||
)
|
||||
|
||||
system_messages = [m for m in openai_request["messages"] if m["role"] == "system"]
|
||||
assert [m["content"] for m in system_messages] == ["You are an interactive agent."]
|
||||
|
||||
|
||||
def test_translate_openai_content_to_anthropic_reasoning_content_without_thinking_blocks():
|
||||
"""
|
||||
Test that reasoning_content is converted to thinking block when thinking_blocks is not present.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue