fix(anthropic): accept a tool_calls turn without content in count validation

The chat translation merged in #47363 counts an assistant tool_calls turn,
but validate_request still rejects that turn when it carries no content
key. OpenAI leaves content optional there, and an SDK message dumped with
exclude_none drops the key entirely, so a caller going through the config
or the handler directly gets a ValueError on a turn every other layer now
accepts

Treat tool_calls as the content for that check. A turn with neither is
still refused

Relates to #26324
This commit is contained in:
Armen Petrosyan 2026-10-26 11:53:15 +02:00
parent d18efdfe13
commit fac1192aa5
2 changed files with 35 additions and 1 deletions

View file

@ -488,5 +488,5 @@ class AnthropicCountTokensConfig:
if "role" not in message:
raise ValueError(f"Message {i} must have a 'role' field")
if "content" not in message:
if "content" not in message and "tool_calls" not in message:
raise ValueError(f"Message {i} must have a 'content' field")

View file

@ -1019,3 +1019,37 @@ async def test_counter_leaves_out_context_management_without_edits(context_manag
ANTHROPIC_BETA_HEADER_VALUES.COMPACT_2026_01_12.value,
ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_MANAGEMENT_2025_06_27.value,
}
@pytest.mark.parametrize("config_type", (AnthropicCountTokensConfig, AzureAIAnthropicCountTokensConfig))
def test_validate_request_accepts_a_tool_call_turn_without_content(
config_type: type[AnthropicCountTokensConfig],
) -> None:
"""OpenAI leaves ``content`` optional on an assistant ``tool_calls`` turn, and an SDK dump that drops
None values leaves the key out, so the shape the chat translation already counts is not rejected here."""
validated: Final = config_type().validate_request(
model="claude-opus-5-5",
messages=[
{"role": "user", "content": "Weather in SF?"},
{
"role": "assistant",
"tool_calls": [
{"id": "call-1", "type": "function", "function": {"name": "get_weather", "arguments": "{}"}}
],
},
],
)
assert validated is None
@pytest.mark.parametrize("config_type", (AnthropicCountTokensConfig, AzureAIAnthropicCountTokensConfig))
def test_validate_request_still_requires_content_on_a_plain_turn(
config_type: type[AnthropicCountTokensConfig],
) -> None:
"""A turn that carries neither content nor tool calls has nothing to count and is still refused."""
with pytest.raises(ValueError, match="Message 1 must have a 'content' field"):
config_type().validate_request(
model="claude-opus-5-5",
messages=[{"role": "user", "content": "Weather in SF?"}, {"role": "assistant"}],
)