From f71bbfb41e0ed67e875a4b6abf88c28a3691407e Mon Sep 17 00:00:00 2001 From: Kevin Lin Date: Mon, 25 Aug 2025 21:17:08 -0700 Subject: [PATCH] fix: patch anthropic send message tests (#4173) Co-authored-by: Sarah Wooders --- letta/llm_api/anthropic_client.py | 24 ++++++++++++++++-------- letta/schemas/message.py | 7 +++++++ tests/integration_test_send_message.py | 14 ++++++++------ 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/letta/llm_api/anthropic_client.py b/letta/llm_api/anthropic_client.py index ef54ff62..6950213f 100644 --- a/letta/llm_api/anthropic_client.py +++ b/letta/llm_api/anthropic_client.py @@ -288,13 +288,22 @@ class AnthropicClient(LLMClientBase): anthropic_tools = None thinking_enabled = False - if messages: - for message in messages: - if isinstance(message.get("content"), list): - for content_block in message["content"]: - if content_block.get("type") == "thinking": - thinking_enabled = True - break + if messages and len(messages) > 0: + # Check if the last assistant message starts with a thinking block + # Find the last assistant message + last_assistant_message = None + for message in reversed(messages): + if message.get("role") == "assistant": + last_assistant_message = message + break + + if ( + last_assistant_message + and isinstance(last_assistant_message.get("content"), list) + and len(last_assistant_message["content"]) > 0 + and last_assistant_message["content"][0].get("type") == "thinking" + ): + thinking_enabled = True try: count_params = { @@ -305,7 +314,6 @@ class AnthropicClient(LLMClientBase): if thinking_enabled: count_params["thinking"] = {"type": "enabled", "budget_tokens": 16000} - result = await client.beta.messages.count_tokens(**count_params) except: raise diff --git a/letta/schemas/message.py b/letta/schemas/message.py index 5376cd5f..2b294dad 100644 --- a/letta/schemas/message.py +++ b/letta/schemas/message.py @@ -863,6 +863,13 @@ class Message(BaseMessage): "data": content_part.data, } ) + if isinstance(content_part, TextContent): + content.append( + { + "type": "text", + "text": content_part.text, + } + ) elif text_content is not None: content.append( { diff --git a/tests/integration_test_send_message.py b/tests/integration_test_send_message.py index 344863fe..c8d794b3 100644 --- a/tests/integration_test_send_message.py +++ b/tests/integration_test_send_message.py @@ -399,7 +399,7 @@ def validate_openai_format_scrubbing(messages: List[Dict[str, Any]]) -> None: assert content is None -def validate_anthropic_format_scrubbing(messages: List[Dict[str, Any]]) -> None: +def validate_anthropic_format_scrubbing(messages: List[Dict[str, Any]], reasoning_enabled: bool) -> None: """ Validate that Anthropic/Claude format assistant messages with tool_use have no tags. Args: @@ -433,10 +433,12 @@ def validate_anthropic_format_scrubbing(messages: List[Dict[str, Any]]) -> None: # Verify that the message only contains tool_use items tool_use_items = [item for item in content_list if item.get("type") == "tool_use"] assert len(tool_use_items) > 0, "Assistant message should have at least one tool_use item" - assert len(content_list) == len(tool_use_items), ( - f"Assistant message should ONLY contain tool_use items when reasoning is disabled. " - f"Found {len(content_list)} total items but only {len(tool_use_items)} are tool_use items." - ) + + if not reasoning_enabled: + assert len(content_list) == len(tool_use_items), ( + f"Assistant message should ONLY contain tool_use items when reasoning is disabled. " + f"Found {len(content_list)} total items but only {len(tool_use_items)} are tool_use items." + ) def validate_google_format_scrubbing(contents: List[Dict[str, Any]]) -> None: @@ -1983,7 +1985,7 @@ def test_inner_thoughts_toggle_interleaved( validate_openai_format_scrubbing(messages) elif llm_config.model_endpoint_type == "anthropic": messages = response["messages"] - validate_anthropic_format_scrubbing(messages) + validate_anthropic_format_scrubbing(messages, llm_config.enable_reasoner) elif llm_config.model_endpoint_type in ["google_ai", "google_vertex"]: # Google uses 'contents' instead of 'messages' contents = response.get("contents", response.get("messages", []))