From b559bf8403f59f9cefe6bc61a8b60276e4f76f72 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:10:05 -0800 Subject: [PATCH] fix: handle missing tool_call_id in Anthropic message conversion (#8381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: handle missing tool_call_id in Anthropic message conversion - Add null check for self.tool_returns before iterating - Fall back to message's tool_call_id when tool_return.tool_call_id is None - Improve error message to show actual tool name from message.name - Only raise error if no valid tool_call_id is available from either source This fixes the error "Anthropic API requires tool_use_id to be set" that occurs when a ToolReturn object in the database doesn't have tool_call_id set, by using the message-level tool_call_id as a fallback. Fixes #8379 🤖 Generated with [Letta Code](https://letta.com) Co-authored-by: datadog-official[bot] Co-Authored-By: Letta * fix: restrict tool_call_id fallback to single tool returns The message-level `self.tool_call_id` is set to the first tool return's ID for legacy compatibility. For parallel tool calls with multiple tool_returns, using this as a fallback would incorrectly assign the first tool return's ID to all subsequent returns missing their own ID. This change: - Only allows the fallback when there's exactly one tool return - For multiple tool returns, each must have its own ID or raise an error - Adds tool return index to error messages for better debugging Co-authored-by: Kian Jones 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta --------- Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com> Co-authored-by: datadog-official[bot] Co-authored-by: Letta Co-authored-by: Kian Jones <11655409+kianjones9@users.noreply.github.com> --- letta/schemas/message.py | 55 +++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/letta/schemas/message.py b/letta/schemas/message.py index b5d92466..d7a82772 100644 --- a/letta/schemas/message.py +++ b/letta/schemas/message.py @@ -1731,29 +1731,42 @@ class Message(BaseMessage): elif self.role == "tool": # NOTE: Anthropic uses role "user" for "tool" responses content = [] - for tool_return in self.tool_returns: - if not tool_return.tool_call_id: - from letta.log import get_logger + # Handle the case where tool_returns is None or empty + if self.tool_returns: + # For single tool returns, we can use the message's tool_call_id as fallback + # since self.tool_call_id == tool_returns[0].tool_call_id for legacy compatibility. + # For multiple tool returns (parallel tool calls), each must have its own ID + # to correctly map results to their corresponding tool invocations. + use_message_fallback = len(self.tool_returns) == 1 + for idx, tool_return in enumerate(self.tool_returns): + # Get tool_call_id from tool_return; only use message fallback for single returns + resolved_tool_call_id = tool_return.tool_call_id + if not resolved_tool_call_id and use_message_fallback: + resolved_tool_call_id = self.tool_call_id + if not resolved_tool_call_id: + from letta.log import get_logger - logger = get_logger(__name__) - logger.error( - f"Missing tool_call_id in tool return. " - f"Message ID: {self.id}, " - f"Tool name: {getattr(tool_return, 'name', 'unknown')}, " - f"Tool return: {tool_return}" + logger = get_logger(__name__) + logger.error( + f"Missing tool_call_id in tool return and no fallback available. " + f"Message ID: {self.id}, " + f"Tool name: {self.name or 'unknown'}, " + f"Tool return index: {idx}/{len(self.tool_returns)}, " + f"Tool return status: {tool_return.status}" + ) + raise TypeError( + f"Anthropic API requires tool_use_id to be set. " + f"Message ID: {self.id}, Tool: {self.name or 'unknown'}, " + f"Tool return index: {idx}/{len(self.tool_returns)}" + ) + func_response = truncate_tool_return(tool_return.func_response, tool_return_truncation_chars) + content.append( + { + "type": "tool_result", + "tool_use_id": resolved_tool_call_id, + "content": func_response, + } ) - raise TypeError( - f"Anthropic API requires tool_use_id to be set. " - f"Message ID: {self.id}, Tool: {getattr(tool_return, 'name', 'unknown')}" - ) - func_response = truncate_tool_return(tool_return.func_response, tool_return_truncation_chars) - content.append( - { - "type": "tool_result", - "tool_use_id": tool_return.tool_call_id, - "content": func_response, - } - ) if content: anthropic_message = { "role": "user",