Skip to content

Commit 2ef0f8a

Browse files
committed
fix: remove tool message mapping and fix tool result
1 parent d23ba31 commit 2ef0f8a

4 files changed

Lines changed: 31 additions & 87 deletions

File tree

src/uipath_langchain/agent/tools/internal_tools/analyze_files_tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ async def tool_fn(**kwargs: Any):
8888
result = await non_streaming_llm.ainvoke(messages, config=config)
8989

9090
analysis_result = extract_text_content(result)
91-
return analysis_result
91+
return {"analysisResult": analysis_result}
9292

9393
job_attachment_wrapper = get_job_attachment_wrapper(output_type=output_model)
9494

src/uipath_langchain/runtime/messages.py

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,8 @@ def _map_messages_internal(
110110
self, messages: list[UiPathConversationMessage]
111111
) -> list[BaseMessage]:
112112
"""
113-
Converts UiPathConversationMessage list to LangChain messages (UserMessage/AIMessage/ToolMessage list).
113+
Converts UiPathConversationMessage list to LangChain messages (UserMessage/AIMessage list).
114114
- All content parts are combined into content_blocks
115-
- Tool calls are converted to LangChain ToolCall format, with results stored as ToolMessage
116115
- Metadata includes message_id, role, timestamps
117116
"""
118117
converted_messages: list[BaseMessage] = []
@@ -179,7 +178,6 @@ def _map_messages_internal(
179178
elif role == "assistant":
180179
# Convert tool calls to LangChain format
181180
tool_calls: list[ToolCall] = []
182-
tool_messages: list[ToolMessage] = []
183181
if uipath_message.tool_calls:
184182
for uipath_tool_call in uipath_message.tool_calls:
185183
tool_call = ToolCall(
@@ -189,34 +187,6 @@ def _map_messages_internal(
189187
)
190188
tool_calls.append(tool_call)
191189

192-
tool_call_output = (
193-
uipath_tool_call.result.output
194-
if uipath_tool_call.result
195-
else None
196-
)
197-
tool_call_status = (
198-
"success"
199-
if uipath_tool_call.result
200-
and not uipath_tool_call.result.is_error
201-
else "error"
202-
)
203-
204-
# Serialize output to string if needed
205-
if tool_call_output is None:
206-
content = ""
207-
elif isinstance(tool_call_output, str):
208-
content = tool_call_output
209-
else:
210-
content = json.dumps(tool_call_output)
211-
212-
tool_messages.append(
213-
ToolMessage(
214-
content=content,
215-
status=tool_call_status,
216-
tool_call_id=uipath_tool_call.tool_call_id,
217-
)
218-
)
219-
220190
# 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.
221191
# When needed, we can switch to content_blocks but need to work out a common ID strategy across models for the content-block IDs.
222192
converted_messages.append(
@@ -230,7 +200,6 @@ def _map_messages_internal(
230200
additional_kwargs=metadata,
231201
)
232202
)
233-
converted_messages.extend(tool_messages)
234203

235204
return converted_messages
236205

tests/agent/tools/internal_tools/test_analyze_files_tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ async def test_create_analyze_file_tool_success(
132132
)
133133

134134
# Verify calls
135-
assert result == "Analyzed result"
135+
assert result == {"analysisResult": "Analyzed result"}
136136
mock_resolve_attachments.assert_called_once()
137137
mock_add_files.assert_called_once()
138138
mock_llm.ainvoke.assert_called_once()

tests/runtime/chat_message_mapper.py

Lines changed: 28 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ def test_map_messages_handles_assistant_message_without_tool_calls(self):
301301
assert msg.additional_kwargs["message_id"] == "msg-1"
302302

303303
def test_map_messages_handles_tool_calls_without_results(self):
304-
"""Should include tool calls without results with empty content and error status."""
304+
"""Should include tool calls on AIMessage without creating ToolMessages."""
305305
mapper = UiPathChatMessagesMapper("test-runtime", None)
306306
from uipath.core.chat import UiPathConversationToolCall
307307

@@ -335,22 +335,16 @@ def test_map_messages_handles_tool_calls_without_results(self):
335335

336336
result = mapper.map_messages([uipath_msg])
337337

338-
# AIMessage + ToolMessage
339-
assert len(result) == 2
338+
# Only AIMessage - ToolMessages are produced by tool_node during execution
339+
assert len(result) == 1
340340
ai_msg = result[0]
341341
assert isinstance(ai_msg, AIMessage)
342342
assert len(ai_msg.tool_calls) == 1
343343
assert ai_msg.tool_calls[0]["id"] == "call-123"
344344
assert ai_msg.tool_calls[0]["name"] == "search_database"
345345

346-
tool_msg = result[1]
347-
assert isinstance(tool_msg, ToolMessage)
348-
assert tool_msg.tool_call_id == "call-123"
349-
assert tool_msg.content == "" # Empty content for tool without result
350-
assert tool_msg.status == "error" # Error status for tool without result
351-
352346
def test_map_messages_includes_tool_calls_with_results(self):
353-
"""Should create AIMessage with tool_calls AND ToolMessage for completed tool calls."""
347+
"""Should create AIMessage with tool_calls but no ToolMessages."""
354348
mapper = UiPathChatMessagesMapper("test-runtime", None)
355349
from uipath.core.chat import (
356350
UiPathConversationToolCall,
@@ -391,8 +385,8 @@ def test_map_messages_includes_tool_calls_with_results(self):
391385

392386
result = mapper.map_messages([uipath_msg])
393387

394-
# Should have AIMessage + ToolMessage
395-
assert len(result) == 2
388+
# Only AIMessage - ToolMessages are produced by tool_node during execution
389+
assert len(result) == 1
396390

397391
# Check AIMessage
398392
ai_msg = result[0]
@@ -406,15 +400,8 @@ def test_map_messages_includes_tool_calls_with_results(self):
406400
assert tool_call["args"] == {"query": "test query"}
407401
assert tool_call["id"] == "call-123"
408402

409-
# Check ToolMessage
410-
tool_msg = result[1]
411-
assert isinstance(tool_msg, ToolMessage)
412-
assert tool_msg.tool_call_id == "call-123"
413-
assert tool_msg.content == '{"results": ["item1", "item2"]}'
414-
assert tool_msg.status == "success"
415-
416403
def test_map_messages_includes_tool_calls_with_error_results(self):
417-
"""Should create ToolMessage with error status for failed tool calls."""
404+
"""Should create AIMessage with tool_calls for failed tool calls, no ToolMessages."""
418405
mapper = UiPathChatMessagesMapper("test-runtime", None)
419406
from uipath.core.chat import (
420407
UiPathConversationToolCall,
@@ -446,14 +433,14 @@ def test_map_messages_includes_tool_calls_with_error_results(self):
446433

447434
result = mapper.map_messages([uipath_msg])
448435

449-
assert len(result) == 2
450-
tool_msg = result[1]
451-
assert isinstance(tool_msg, ToolMessage)
452-
assert tool_msg.status == "error"
453-
assert tool_msg.content == "Tool execution failed"
436+
assert len(result) == 1
437+
ai_msg = result[0]
438+
assert isinstance(ai_msg, AIMessage)
439+
assert len(ai_msg.tool_calls) == 1
440+
assert ai_msg.tool_calls[0]["id"] == "call-456"
454441

455442
def test_map_messages_includes_tool_calls_with_string_output(self):
456-
"""Should handle string output in tool results without JSON serialization."""
443+
"""Should create AIMessage with tool_calls for string output results."""
457444
mapper = UiPathChatMessagesMapper("test-runtime", None)
458445
from uipath.core.chat import (
459446
UiPathConversationToolCall,
@@ -485,13 +472,14 @@ def test_map_messages_includes_tool_calls_with_string_output(self):
485472

486473
result = mapper.map_messages([uipath_msg])
487474

488-
assert len(result) == 2
489-
tool_msg = result[1]
490-
assert isinstance(tool_msg, ToolMessage)
491-
assert tool_msg.content == "plain text result"
475+
assert len(result) == 1
476+
ai_msg = result[0]
477+
assert isinstance(ai_msg, AIMessage)
478+
assert len(ai_msg.tool_calls) == 1
479+
assert ai_msg.tool_calls[0]["id"] == "call-789"
492480

493481
def test_map_messages_includes_tool_calls_with_none_output(self):
494-
"""Should handle None output in tool results as empty string."""
482+
"""Should create AIMessage with tool_calls for None output results."""
495483
mapper = UiPathChatMessagesMapper("test-runtime", None)
496484
from uipath.core.chat import (
497485
UiPathConversationToolCall,
@@ -523,13 +511,14 @@ def test_map_messages_includes_tool_calls_with_none_output(self):
523511

524512
result = mapper.map_messages([uipath_msg])
525513

526-
assert len(result) == 2
527-
tool_msg = result[1]
528-
assert isinstance(tool_msg, ToolMessage)
529-
assert tool_msg.content == ""
514+
assert len(result) == 1
515+
ai_msg = result[0]
516+
assert isinstance(ai_msg, AIMessage)
517+
assert len(ai_msg.tool_calls) == 1
518+
assert ai_msg.tool_calls[0]["id"] == "call-999"
530519

531520
def test_map_messages_includes_multiple_tool_calls_with_mixed_results(self):
532-
"""Should handle multiple tool calls, some with results and some without."""
521+
"""Should handle multiple tool calls on AIMessage, no ToolMessages created."""
533522
mapper = UiPathChatMessagesMapper("test-runtime", None)
534523
from uipath.core.chat import (
535524
UiPathConversationToolCall,
@@ -571,8 +560,8 @@ def test_map_messages_includes_multiple_tool_calls_with_mixed_results(self):
571560

572561
result = mapper.map_messages([uipath_msg])
573562

574-
# Should have AIMessage + 2 ToolMessages (for both tool calls)
575-
assert len(result) == 3
563+
# Only AIMessage - ToolMessages are produced by tool_node during execution
564+
assert len(result) == 1
576565

577566
ai_msg = result[0]
578567
assert isinstance(ai_msg, AIMessage)
@@ -583,20 +572,6 @@ def test_map_messages_includes_multiple_tool_calls_with_mixed_results(self):
583572
assert ai_msg.tool_calls[1]["id"] == "call-2"
584573
assert ai_msg.tool_calls[1]["name"] == "tool_without_result"
585574

586-
# First ToolMessage (with result)
587-
tool_msg_1 = result[1]
588-
assert isinstance(tool_msg_1, ToolMessage)
589-
assert tool_msg_1.tool_call_id == "call-1"
590-
assert tool_msg_1.content == '{"status": "done"}'
591-
assert tool_msg_1.status == "success"
592-
593-
# Second ToolMessage (without result)
594-
tool_msg_2 = result[2]
595-
assert isinstance(tool_msg_2, ToolMessage)
596-
assert tool_msg_2.tool_call_id == "call-2"
597-
assert tool_msg_2.content == "" # Empty content for tool without result
598-
assert tool_msg_2.status == "error" # Error status for tool without result
599-
600575
def test_map_messages_handles_tool_calls_without_input(self):
601576
"""Should handle tool call with None input and with result."""
602577
mapper = UiPathChatMessagesMapper("test-runtime", None)
@@ -630,7 +605,7 @@ def test_map_messages_handles_tool_calls_without_input(self):
630605

631606
result = mapper.map_messages([uipath_msg])
632607

633-
assert len(result) == 2
608+
assert len(result) == 1
634609
msg = result[0]
635610
assert isinstance(msg, AIMessage)
636611
assert len(msg.tool_calls) == 1

0 commit comments

Comments
 (0)