diff --git a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py index 98c181f1526..02da45b07e1 100644 --- a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py +++ b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py @@ -696,11 +696,34 @@ def _prepare_messages_for_anthropic(self, messages: Sequence[Message]) -> list[d This skips the first message if it is a system message, as Anthropic expects system instructions as a separate parameter. + + Anthropic's API requires that the conversation ends with a user message. + If the last message is from the assistant, a synthetic user turn is + appended when it will not break Anthropic tool_use/tool_result pairing. """ # first system message is passed as instructions if messages and isinstance(messages[0], Message) and messages[0].role == "system": - return [self._prepare_message_for_anthropic(msg) for msg in messages[1:]] - return [self._prepare_message_for_anthropic(msg) for msg in messages] + msgs = list(messages[1:]) + else: + msgs = list(messages) + + result = [self._prepare_message_for_anthropic(msg) for msg in msgs] + + # Anthropic requires the conversation to end with a user message. + # Append a synthetic user turn so chained agent outputs work as + # valid context for the next agent without rewriting the assistant message. + if result and result[-1].get("role") == "assistant" and not self._message_has_tool_use(result[-1]): + result.append({"role": "user", "content": "Continue"}) + + return result + + def _message_has_tool_use(self, message: dict[str, Any]) -> bool: + """Return whether an Anthropic message contains tool_use blocks.""" + content = message.get("content") + return isinstance(content, list) and any( + isinstance(item, dict) and item.get("type") in {"tool_use", "mcp_tool_use", "server_tool_use"} + for item in content + ) def _prepare_message_for_anthropic(self, message: Message) -> dict[str, Any]: """Prepare a Message for the Anthropic client. diff --git a/python/packages/anthropic/tests/test_anthropic_client.py b/python/packages/anthropic/tests/test_anthropic_client.py index 0cfec3423c1..3e82ebb1685 100644 --- a/python/packages/anthropic/tests/test_anthropic_client.py +++ b/python/packages/anthropic/tests/test_anthropic_client.py @@ -618,9 +618,37 @@ def test_prepare_messages_for_anthropic_without_system( result = client._prepare_messages_for_anthropic(messages) - assert len(result) == 2 + assert len(result) == 3 assert result[0]["role"] == "user" assert result[1]["role"] == "assistant" + assert result[2]["role"] == "user" + assert result[2]["content"] == "Continue" + + +def test_prepare_messages_for_anthropic_does_not_append_after_tool_use( + mock_anthropic_client: MagicMock, +) -> None: + """Do not append plain user text after assistant tool_use blocks.""" + client = create_test_anthropic_client(mock_anthropic_client) + messages = [ + Message(role="user", contents=["What's the weather?"]), + Message( + role="assistant", + contents=[ + Content.from_function_call( + call_id="call_123", + name="get_weather", + arguments={"location": "Seattle"}, + ) + ], + ), + ] + + result = client._prepare_messages_for_anthropic(messages) + + assert len(result) == 2 + assert result[1]["role"] == "assistant" + assert result[1]["content"][0]["type"] == "tool_use" # Tool Conversion Tests