Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/agents/models/chatcmpl_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,20 @@ def ensure_assistant_message() -> ChatCompletionAssistantMessageParam:
# If we have pending thinking blocks, use them as the content
# This is required for Anthropic API tool calls with interleaved thinking
if pending_thinking_blocks:
asst["content"] = pending_thinking_blocks # type: ignore
# If there is a text content, save it to append after thinking blocks
# content type is Union[str, Iterable[ContentArrayOfContentPart], None]
if "content" in asst and isinstance(asst["content"], str):
text_content = ChatCompletionContentPartTextParam(
text=asst["content"], type="text"
)
asst["content"] = [text_content]

if "content" not in asst or asst["content"] is None:
asst["content"] = []

# Thinking blocks MUST come before any other content
# We ignore type errors because pending_thinking_blocks is not openai standard
asst["content"] = pending_thinking_blocks + asst["content"] # type: ignore
pending_thinking_blocks = None # Clear after using

tool_calls = list(asst.get("tool_calls", []))
Expand Down
17 changes: 12 additions & 5 deletions tests/test_anthropic_thinking_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,27 @@ def test_anthropic_thinking_blocks_with_tool_calls():
"Signature should be preserved in thinking block"
)

first_content = content[1]
assert first_content.get("type") == "thinking", (
second_content = content[1]
assert second_content.get("type") == "thinking", (
f"Second content must be 'thinking' type for Anthropic compatibility, "
f"but got '{first_content.get('type')}'"
f"but got '{second_content.get('type')}'"
)
expected_thinking = "We should use the city Tokyo as the city."
assert first_content.get("thinking") == expected_thinking, (
assert second_content.get("thinking") == expected_thinking, (
"Thinking content should be preserved"
)
# Signature should also be preserved
assert first_content.get("signature") == "TestSignature456", (
assert second_content.get("signature") == "TestSignature456", (
"Signature should be preserved in thinking block"
)

last_content = content[2]
assert last_content.get("type") == "text", (
f"First content must be 'text' type but got '{last_content.get('type')}'"
)
expected_text = "I'll check the weather for you."
assert last_content.get("text") == expected_text, "Content text should be preserved"

# Verify tool calls are preserved
tool_calls = assistant_msg.get("tool_calls", [])
assert len(cast(list[Any], tool_calls)) == 1, "Tool calls should be preserved"
Expand Down