From caa3f8b28d318f0273660628b190e1ca9b4adad0 Mon Sep 17 00:00:00 2001 From: Matthew Zhou Date: Wed, 8 Oct 2025 15:53:46 -0700 Subject: [PATCH] feat: Double write to ToolReturnMessage [LET-5332] (#5266) Double write to ToolReturnMessage --- letta/schemas/message.py | 11 ++++++++ letta/server/rest_api/interface.py | 42 +++++++++++++++++++++++++----- letta/server/server.py | 22 ++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/letta/schemas/message.py b/letta/schemas/message.py index 31173aa7..71f6d328 100644 --- a/letta/schemas/message.py +++ b/letta/schemas/message.py @@ -647,6 +647,16 @@ class Message(BaseMessage): Returns: Configured ToolReturnMessage instance """ + from letta.schemas.letta_message import ToolReturn as ToolReturnSchema + + tool_return_obj = ToolReturnSchema( + tool_return=message_text, + status=status, + tool_call_id=tool_call_id, + stdout=stdout, + stderr=stderr, + ) + return ToolReturnMessage( id=self.id, date=self.created_at, @@ -655,6 +665,7 @@ class Message(BaseMessage): tool_call_id=tool_call_id, stdout=stdout, stderr=stderr, + tool_returns=[tool_return_obj], name=self.name, otid=Message.generate_otid_from_id(self.id, otid_index), sender_id=self.sender_id, diff --git a/letta/server/rest_api/interface.py b/letta/server/rest_api/interface.py index c9ca5166..bda4567d 100644 --- a/letta/server/rest_api/interface.py +++ b/letta/server/rest_api/interface.py @@ -1303,14 +1303,29 @@ class StreamingServerInterface(AgentChunkStreamingInterface): # Skip this tool call receipt return else: + from letta.schemas.letta_message import ToolReturn as ToolReturnSchema + + status = msg_obj.tool_returns[0].status if msg_obj.tool_returns else "success" + stdout = msg_obj.tool_returns[0].stdout if msg_obj.tool_returns else [] + stderr = msg_obj.tool_returns[0].stderr if msg_obj.tool_returns else [] + + tool_return_obj = ToolReturnSchema( + tool_return=msg, + status=status, + tool_call_id=msg_obj.tool_call_id, + stdout=stdout, + stderr=stderr, + ) + new_message = ToolReturnMessage( id=msg_obj.id, date=msg_obj.created_at, tool_return=msg, - status=msg_obj.tool_returns[0].status if msg_obj.tool_returns else "success", + status=status, tool_call_id=msg_obj.tool_call_id, - stdout=msg_obj.tool_returns[0].stdout if msg_obj.tool_returns else [], - stderr=msg_obj.tool_returns[0].stderr if msg_obj.tool_returns else [], + stdout=stdout, + stderr=stderr, + tool_returns=[tool_return_obj], name=msg_obj.name, otid=Message.generate_otid_from_id(msg_obj.id, chunk_index) if chunk_index is not None else None, ) @@ -1319,14 +1334,29 @@ class StreamingServerInterface(AgentChunkStreamingInterface): msg = msg.replace("Error: ", "", 1) # new_message = {"function_return": msg, "status": "error"} assert msg_obj.tool_call_id is not None + from letta.schemas.letta_message import ToolReturn as ToolReturnSchema + + status = msg_obj.tool_returns[0].status if msg_obj.tool_returns else "error" + stdout = msg_obj.tool_returns[0].stdout if msg_obj.tool_returns else [] + stderr = msg_obj.tool_returns[0].stderr if msg_obj.tool_returns else [] + + tool_return_obj = ToolReturnSchema( + tool_return=msg, + status=status, + tool_call_id=msg_obj.tool_call_id, + stdout=stdout, + stderr=stderr, + ) + new_message = ToolReturnMessage( id=msg_obj.id, date=msg_obj.created_at, tool_return=msg, - status=msg_obj.tool_returns[0].status if msg_obj.tool_returns else "error", + status=status, tool_call_id=msg_obj.tool_call_id, - stdout=msg_obj.tool_returns[0].stdout if msg_obj.tool_returns else [], - stderr=msg_obj.tool_returns[0].stderr if msg_obj.tool_returns else [], + stdout=stdout, + stderr=stderr, + tool_returns=[tool_return_obj], name=msg_obj.name, otid=Message.generate_otid_from_id(msg_obj.id, chunk_index) if chunk_index is not None else None, ) diff --git a/letta/server/server.py b/letta/server/server.py index 69c298a0..0ed583c1 100644 --- a/letta/server/server.py +++ b/letta/server/server.py @@ -1239,6 +1239,16 @@ class SyncServer(object): function_args=tool_args, tool=tool, ) + from letta.schemas.letta_message import ToolReturn as ToolReturnSchema + + tool_return_obj = ToolReturnSchema( + tool_return=str(tool_execution_result.func_return), + status=tool_execution_result.status, + tool_call_id="null", + stdout=tool_execution_result.stdout, + stderr=tool_execution_result.stderr, + ) + return ToolReturnMessage( id="null", tool_call_id="null", @@ -1247,10 +1257,21 @@ class SyncServer(object): tool_return=str(tool_execution_result.func_return), stdout=tool_execution_result.stdout, stderr=tool_execution_result.stderr, + tool_returns=[tool_return_obj], ) except Exception as e: func_return = get_friendly_error_msg(function_name=tool.name, exception_name=type(e).__name__, exception_message=str(e)) + from letta.schemas.letta_message import ToolReturn as ToolReturnSchema + + tool_return_obj = ToolReturnSchema( + tool_return=func_return, + status="error", + tool_call_id="null", + stdout=[], + stderr=[traceback.format_exc()], + ) + return ToolReturnMessage( id="null", tool_call_id="null", @@ -1259,6 +1280,7 @@ class SyncServer(object): tool_return=func_return, stdout=[], stderr=[traceback.format_exc()], + tool_returns=[tool_return_obj], ) # MCP wrappers