fix(anthropic): handle mid-conversation system messages (#26773)

Co-authored-by: Xinyuan Tong <115166877+JustinTong0323@users.noreply.github.com>
This commit is contained in:
Jairo David Campaña Rosero
2026-06-21 04:34:47 +00:00
committed by GitHub
co-authored by Xinyuan Tong
parent c65f4ea692
commit b4dda8b3ce
3 changed files with 167 additions and 1 deletions
@@ -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):