diff --git a/src/uipath_langchain/runtime/messages.py b/src/uipath_langchain/runtime/messages.py index 1164fcd0..428699c5 100644 --- a/src/uipath_langchain/runtime/messages.py +++ b/src/uipath_langchain/runtime/messages.py @@ -110,9 +110,8 @@ def _map_messages_internal( self, messages: list[UiPathConversationMessage] ) -> list[BaseMessage]: """ - Converts UiPathConversationMessage list to LangChain messages (UserMessage/AIMessage/ToolMessage list). + Converts UiPathConversationMessage list to LangChain messages (UserMessage/AIMessage list). - All content parts are combined into content_blocks - - Tool calls are converted to LangChain ToolCall format, with results stored as ToolMessage - Metadata includes message_id, role, timestamps """ converted_messages: list[BaseMessage] = [] @@ -179,7 +178,6 @@ def _map_messages_internal( elif role == "assistant": # Convert tool calls to LangChain format tool_calls: list[ToolCall] = [] - tool_messages: list[ToolMessage] = [] if uipath_message.tool_calls: for uipath_tool_call in uipath_message.tool_calls: tool_call = ToolCall( @@ -189,34 +187,6 @@ def _map_messages_internal( ) tool_calls.append(tool_call) - tool_call_output = ( - uipath_tool_call.result.output - if uipath_tool_call.result - else None - ) - tool_call_status = ( - "success" - if uipath_tool_call.result - and not uipath_tool_call.result.is_error - else "error" - ) - - # Serialize output to string if needed - if tool_call_output is None: - content = "" - elif isinstance(tool_call_output, str): - content = tool_call_output - else: - content = json.dumps(tool_call_output) - - tool_messages.append( - ToolMessage( - content=content, - status=tool_call_status, - tool_call_id=uipath_tool_call.tool_call_id, - ) - ) - # Ideally we pass in content_blocks here rather than string content, but when doing so, OpenAI errors unless a msg_ prefix is used for content-block IDs. # When needed, we can switch to content_blocks but need to work out a common ID strategy across models for the content-block IDs. converted_messages.append( @@ -230,7 +200,6 @@ def _map_messages_internal( additional_kwargs=metadata, ) ) - converted_messages.extend(tool_messages) return converted_messages diff --git a/tests/runtime/chat_message_mapper.py b/tests/runtime/chat_message_mapper.py index 554646b3..2881a868 100644 --- a/tests/runtime/chat_message_mapper.py +++ b/tests/runtime/chat_message_mapper.py @@ -301,7 +301,7 @@ def test_map_messages_handles_assistant_message_without_tool_calls(self): assert msg.additional_kwargs["message_id"] == "msg-1" def test_map_messages_handles_tool_calls_without_results(self): - """Should include tool calls without results with empty content and error status.""" + """Should include tool calls on AIMessage without creating ToolMessages.""" mapper = UiPathChatMessagesMapper("test-runtime", None) from uipath.core.chat import UiPathConversationToolCall @@ -335,22 +335,16 @@ def test_map_messages_handles_tool_calls_without_results(self): result = mapper.map_messages([uipath_msg]) - # AIMessage + ToolMessage - assert len(result) == 2 + # Only AIMessage - ToolMessages are produced by tool_node during execution + assert len(result) == 1 ai_msg = result[0] assert isinstance(ai_msg, AIMessage) assert len(ai_msg.tool_calls) == 1 assert ai_msg.tool_calls[0]["id"] == "call-123" assert ai_msg.tool_calls[0]["name"] == "search_database" - tool_msg = result[1] - assert isinstance(tool_msg, ToolMessage) - assert tool_msg.tool_call_id == "call-123" - assert tool_msg.content == "" # Empty content for tool without result - assert tool_msg.status == "error" # Error status for tool without result - def test_map_messages_includes_tool_calls_with_results(self): - """Should create AIMessage with tool_calls AND ToolMessage for completed tool calls.""" + """Should create AIMessage with tool_calls but no ToolMessages.""" mapper = UiPathChatMessagesMapper("test-runtime", None) from uipath.core.chat import ( UiPathConversationToolCall, @@ -391,8 +385,8 @@ def test_map_messages_includes_tool_calls_with_results(self): result = mapper.map_messages([uipath_msg]) - # Should have AIMessage + ToolMessage - assert len(result) == 2 + # Only AIMessage - ToolMessages are produced by tool_node during execution + assert len(result) == 1 # Check AIMessage ai_msg = result[0] @@ -406,15 +400,8 @@ def test_map_messages_includes_tool_calls_with_results(self): assert tool_call["args"] == {"query": "test query"} assert tool_call["id"] == "call-123" - # Check ToolMessage - tool_msg = result[1] - assert isinstance(tool_msg, ToolMessage) - assert tool_msg.tool_call_id == "call-123" - assert tool_msg.content == '{"results": ["item1", "item2"]}' - assert tool_msg.status == "success" - def test_map_messages_includes_tool_calls_with_error_results(self): - """Should create ToolMessage with error status for failed tool calls.""" + """Should create AIMessage with tool_calls for failed tool calls, no ToolMessages.""" mapper = UiPathChatMessagesMapper("test-runtime", None) from uipath.core.chat import ( UiPathConversationToolCall, @@ -446,14 +433,14 @@ def test_map_messages_includes_tool_calls_with_error_results(self): result = mapper.map_messages([uipath_msg]) - assert len(result) == 2 - tool_msg = result[1] - assert isinstance(tool_msg, ToolMessage) - assert tool_msg.status == "error" - assert tool_msg.content == "Tool execution failed" + assert len(result) == 1 + ai_msg = result[0] + assert isinstance(ai_msg, AIMessage) + assert len(ai_msg.tool_calls) == 1 + assert ai_msg.tool_calls[0]["id"] == "call-456" def test_map_messages_includes_tool_calls_with_string_output(self): - """Should handle string output in tool results without JSON serialization.""" + """Should create AIMessage with tool_calls for string output results.""" mapper = UiPathChatMessagesMapper("test-runtime", None) from uipath.core.chat import ( UiPathConversationToolCall, @@ -485,13 +472,14 @@ def test_map_messages_includes_tool_calls_with_string_output(self): result = mapper.map_messages([uipath_msg]) - assert len(result) == 2 - tool_msg = result[1] - assert isinstance(tool_msg, ToolMessage) - assert tool_msg.content == "plain text result" + assert len(result) == 1 + ai_msg = result[0] + assert isinstance(ai_msg, AIMessage) + assert len(ai_msg.tool_calls) == 1 + assert ai_msg.tool_calls[0]["id"] == "call-789" def test_map_messages_includes_tool_calls_with_none_output(self): - """Should handle None output in tool results as empty string.""" + """Should create AIMessage with tool_calls for None output results.""" mapper = UiPathChatMessagesMapper("test-runtime", None) from uipath.core.chat import ( UiPathConversationToolCall, @@ -523,13 +511,14 @@ def test_map_messages_includes_tool_calls_with_none_output(self): result = mapper.map_messages([uipath_msg]) - assert len(result) == 2 - tool_msg = result[1] - assert isinstance(tool_msg, ToolMessage) - assert tool_msg.content == "" + assert len(result) == 1 + ai_msg = result[0] + assert isinstance(ai_msg, AIMessage) + assert len(ai_msg.tool_calls) == 1 + assert ai_msg.tool_calls[0]["id"] == "call-999" def test_map_messages_includes_multiple_tool_calls_with_mixed_results(self): - """Should handle multiple tool calls, some with results and some without.""" + """Should handle multiple tool calls on AIMessage, no ToolMessages created.""" mapper = UiPathChatMessagesMapper("test-runtime", None) from uipath.core.chat import ( UiPathConversationToolCall, @@ -571,8 +560,8 @@ def test_map_messages_includes_multiple_tool_calls_with_mixed_results(self): result = mapper.map_messages([uipath_msg]) - # Should have AIMessage + 2 ToolMessages (for both tool calls) - assert len(result) == 3 + # Only AIMessage - ToolMessages are produced by tool_node during execution + assert len(result) == 1 ai_msg = result[0] assert isinstance(ai_msg, AIMessage) @@ -583,20 +572,6 @@ def test_map_messages_includes_multiple_tool_calls_with_mixed_results(self): assert ai_msg.tool_calls[1]["id"] == "call-2" assert ai_msg.tool_calls[1]["name"] == "tool_without_result" - # First ToolMessage (with result) - tool_msg_1 = result[1] - assert isinstance(tool_msg_1, ToolMessage) - assert tool_msg_1.tool_call_id == "call-1" - assert tool_msg_1.content == '{"status": "done"}' - assert tool_msg_1.status == "success" - - # Second ToolMessage (without result) - tool_msg_2 = result[2] - assert isinstance(tool_msg_2, ToolMessage) - assert tool_msg_2.tool_call_id == "call-2" - assert tool_msg_2.content == "" # Empty content for tool without result - assert tool_msg_2.status == "error" # Error status for tool without result - def test_map_messages_handles_tool_calls_without_input(self): """Should handle tool call with None input and with result.""" mapper = UiPathChatMessagesMapper("test-runtime", None) @@ -630,7 +605,7 @@ def test_map_messages_handles_tool_calls_without_input(self): result = mapper.map_messages([uipath_msg]) - assert len(result) == 2 + assert len(result) == 1 msg = result[0] assert isinstance(msg, AIMessage) assert len(msg.tool_calls) == 1