fix: handle missing tool_call_id in Anthropic message conversion (#8381)
* 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] <datadog-official[bot]@users.noreply.github.com> Co-Authored-By: Letta <noreply@letta.com> * 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 <kianjones9@users.noreply.github.com> 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> --------- Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com> Co-authored-by: datadog-official[bot] <datadog-official[bot]@users.noreply.github.com> Co-authored-by: Letta <noreply@letta.com> Co-authored-by: Kian Jones <11655409+kianjones9@users.noreply.github.com>
This commit is contained in:
committed by
Caren Thomas
parent
d2e19bbc05
commit
b559bf8403
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user