diff --git a/python/packages/autogen-ext/src/autogen_ext/models/anthropic/_anthropic_client.py b/python/packages/autogen-ext/src/autogen_ext/models/anthropic/_anthropic_client.py index a91d9f32b..3765c2ad2 100644 --- a/python/packages/autogen-ext/src/autogen_ext/models/anthropic/_anthropic_client.py +++ b/python/packages/autogen-ext/src/autogen_ext/models/anthropic/_anthropic_client.py @@ -432,6 +432,25 @@ class BaseAnthropicChatCompletionClient(ChatCompletionClient): self._total_usage = RequestUsage(prompt_tokens=0, completion_tokens=0) self._actual_usage = RequestUsage(prompt_tokens=0, completion_tokens=0) + def _serialize_message(self, message: MessageParam) -> Dict[str, Any]: + """Convert an Anthropic MessageParam to a JSON-serializable format.""" + if isinstance(message, dict): + result: Dict[str, Any] = {} + for key, value in message.items(): + if key == "content" and isinstance(value, list): + serialized_blocks: List[Any] = [] + for block in value: # type: ignore + if isinstance(block, BaseModel): + serialized_blocks.append(block.model_dump()) + else: + serialized_blocks.append(block) + result[key] = serialized_blocks + else: + result[key] = value + return result + else: + return {"role": "unknown", "content": str(message)} + def _merge_system_messages(self, messages: Sequence[LLMMessage]) -> Sequence[LLMMessage]: """ Merge continuous system messages into a single message. @@ -573,10 +592,11 @@ class BaseAnthropicChatCompletionClient(ChatCompletionClient): prompt_tokens=result.usage.input_tokens, completion_tokens=result.usage.output_tokens, ) + serializable_messages: List[Dict[str, Any]] = [self._serialize_message(msg) for msg in anthropic_messages] logger.info( LLMCallEvent( - messages=cast(List[Dict[str, Any]], anthropic_messages), + messages=serializable_messages, response=result.model_dump(), prompt_tokens=usage.prompt_tokens, completion_tokens=usage.completion_tokens, diff --git a/python/packages/autogen-ext/tests/models/test_anthropic_model_client.py b/python/packages/autogen-ext/tests/models/test_anthropic_model_client.py index b8a106a65..decb0441b 100644 --- a/python/packages/autogen-ext/tests/models/test_anthropic_model_client.py +++ b/python/packages/autogen-ext/tests/models/test_anthropic_model_client.py @@ -317,13 +317,9 @@ async def test_anthropic_multimodal() -> None: async def test_anthropic_serialization() -> None: """Test serialization and deserialization of component.""" - api_key = os.getenv("ANTHROPIC_API_KEY") - if not api_key: - pytest.skip("ANTHROPIC_API_KEY not found in environment variables") - client = AnthropicChatCompletionClient( model="claude-3-haiku-20240307", - api_key=api_key, + api_key="api-key", ) # Serialize and deserialize @@ -336,6 +332,42 @@ async def test_anthropic_serialization() -> None: assert isinstance(loaded_model_client, AnthropicChatCompletionClient) +@pytest.mark.asyncio +async def test_anthropic_message_serialization_with_tools(caplog: pytest.LogCaptureFixture) -> None: + """Test that complex messages with tool calls are properly serialized in logs.""" + api_key = os.getenv("ANTHROPIC_API_KEY") + if not api_key: + pytest.skip("ANTHROPIC_API_KEY not found in environment variables") + + # Use existing tools from the test file + pass_tool = FunctionTool(_pass_function, description="Process input text", name="process_text") + add_tool = FunctionTool(_add_numbers, description="Add two numbers together", name="add_numbers") + + client = AnthropicChatCompletionClient( + model="claude-3-haiku-20240307", + api_key=api_key, + ) + + # Set up logging capture - capture all loggers + with caplog.at_level(logging.INFO): + # Make a request that should trigger a tool call + await client.create( + messages=[ + SystemMessage(content="Use the tools available to help the user."), + UserMessage(content="Process the text 'hello world' using the process_text tool.", source="user"), + ], + tools=[pass_tool, add_tool], + ) + + # Look for any log containing serialized messages, not just with 'LLMCallEvent' + serialized_message_logs = [ + record for record in caplog.records if '"messages":' in str(record.msg) or "messages" in str(record.msg) + ] + + # Verify we have at least one log with serialized messages + assert len(serialized_message_logs) > 0, "No logs with serialized messages found" + + @pytest.mark.asyncio async def test_anthropic_muliple_system_message() -> None: """Test multiple system messages in a single request.""" @@ -347,7 +379,6 @@ async def test_anthropic_muliple_system_message() -> None: model="claude-3-haiku-20240307", api_key=api_key, ) - # Test multiple system messages messages: List[LLMMessage] = [ SystemMessage(content="When you say anything Start with 'FOO'"),