diff --git a/src/agents/models/chatcmpl_converter.py b/src/agents/models/chatcmpl_converter.py index 0ece1664b..f17e8f126 100644 --- a/src/agents/models/chatcmpl_converter.py +++ b/src/agents/models/chatcmpl_converter.py @@ -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", [])) diff --git a/tests/test_anthropic_thinking_blocks.py b/tests/test_anthropic_thinking_blocks.py index 35446efe4..8fbc59833 100644 --- a/tests/test_anthropic_thinking_blocks.py +++ b/tests/test_anthropic_thinking_blocks.py @@ -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"