fix(anthropic): handle mid-conversation system messages (#26773)
Co-authored-by: Xinyuan Tong <115166877+JustinTong0323@users.noreply.github.com>
This commit is contained in:
co-authored by
Xinyuan Tong
parent
c65f4ea692
commit
b4dda8b3ce
@@ -124,7 +124,7 @@ AnthropicContentBlock = Annotated[
|
||||
|
||||
|
||||
class AnthropicMessage(BaseModel):
|
||||
role: Literal["user", "assistant"]
|
||||
role: Literal["user", "assistant", "system"]
|
||||
content: Union[str, list[AnthropicContentBlock]]
|
||||
|
||||
|
||||
@@ -379,6 +379,64 @@ class AnthropicMessagesRequest(BaseModel):
|
||||
output_config: Optional[AnthropicOutputConfig] = None
|
||||
betas: Optional[list[str]] = None
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def move_mid_conversation_system_messages(cls, values: dict) -> dict:
|
||||
"""Fold mid-conversation ``role: "system"`` turns into the top-level
|
||||
``system`` field — some clients (e.g. Claude Code) emit them there."""
|
||||
messages = values.get("messages", [])
|
||||
if not messages:
|
||||
return values
|
||||
|
||||
clean_messages = []
|
||||
extracted_system_texts = []
|
||||
|
||||
for msg in messages:
|
||||
# ``mode="before"`` sees raw dicts (HTTP path) but also already-
|
||||
# constructed ``AnthropicMessage`` objects (programmatic path, e.g.
|
||||
# ``handle_count_tokens``), so normalize to a dict first.
|
||||
if isinstance(msg, BaseModel):
|
||||
msg = msg.model_dump()
|
||||
if msg.get("role") == "system":
|
||||
content = msg.get("content", "")
|
||||
if isinstance(content, str) and content.strip():
|
||||
extracted_system_texts.append(content.strip())
|
||||
elif isinstance(content, list):
|
||||
for block in content:
|
||||
if isinstance(block, dict) and block.get("type") == "text":
|
||||
text = block.get("text", "").strip()
|
||||
if text:
|
||||
extracted_system_texts.append(text)
|
||||
else:
|
||||
clean_messages.append(msg)
|
||||
|
||||
if extracted_system_texts:
|
||||
existing_system = values.get("system")
|
||||
combined_system = []
|
||||
|
||||
if existing_system:
|
||||
if isinstance(existing_system, str):
|
||||
if existing_system.strip():
|
||||
combined_system.append(existing_system.strip())
|
||||
elif isinstance(existing_system, list):
|
||||
for block in existing_system:
|
||||
if isinstance(block, BaseModel):
|
||||
block = block.model_dump()
|
||||
if isinstance(block, dict) and block.get("type") == "text":
|
||||
text = block.get("text", "").strip()
|
||||
if text:
|
||||
combined_system.append(text)
|
||||
|
||||
combined_system.extend(extracted_system_texts)
|
||||
|
||||
# Join into a string — ``system`` is ``str | list[AnthropicContentBlock]``,
|
||||
# so a ``list[str]`` would fail validation.
|
||||
if combined_system:
|
||||
values["system"] = "\n".join(combined_system)
|
||||
|
||||
values["messages"] = clean_messages
|
||||
return values
|
||||
|
||||
@field_validator("model")
|
||||
@classmethod
|
||||
def _validate_model(cls, v):
|
||||
|
||||
Reference in New Issue
Block a user