Skip to content

Commit 528de50

Browse files
committed
fix: tool confirmation for coded agents (create_agent)
Three fixes for coded agent tool confirmation: 1. hitl.py: @requires_approval now stamps REQUIRE_CONVERSATIONAL_CONFIRMATION metadata so the runtime can discover confirmation tools. 2. runtime.py: _get_tool_confirmation_info handles both graph shapes — UiPathToolNode (low-code, bound.tool) and LangGraph ToolNode (create_agent, bound.tools_by_name). 3. messages.py: seed chunk accumulator with an empty AIMessageChunk instead of the incoming message. Prevents self-merge on the first streaming chunk which doubled tool names (e.g. search_websearch_web) and broke the confirmation set lookup.
1 parent da450a3 commit 528de50

File tree

3 files changed

+31
-13
lines changed

3 files changed

+31
-13
lines changed

src/uipath_langchain/chat/hitl.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ def wrapper(**tool_args: Any) -> Any:
221221
return_direct=return_direct,
222222
)
223223

224+
if result.metadata is None:
225+
result.metadata = {}
226+
result.metadata[REQUIRE_CONVERSATIONAL_CONFIRMATION] = True
227+
224228
_created_tool.append(result)
225229
return result
226230

src/uipath_langchain/runtime/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ async def map_ai_message_chunk_to_events(
323323

324324
# For every new message_id, start a new message
325325
if message.id not in self.seen_message_ids:
326-
self.current_message = message
326+
self.current_message = AIMessageChunk(content="", id=message.id)
327327
self.seen_message_ids.add(message.id)
328328
self._citation_stream_processor = CitationStreamProcessor()
329329
events.append(self.map_to_message_start_event(message.id))

src/uipath_langchain/runtime/runtime.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -494,20 +494,34 @@ def _get_tool_confirmation_info(self) -> tuple[set[str], dict[str, Any]]:
494494
"""Single pass over graph nodes to collect confirmation tool names and schemas."""
495495
names: set[str] = set()
496496
schemas: dict[str, Any] = {}
497+
498+
def _record(tool: Any, fallback_name: str) -> None:
499+
metadata = getattr(tool, "metadata", None) or {}
500+
if not metadata.get(REQUIRE_CONVERSATIONAL_CONFIRMATION):
501+
return
502+
tool_name = getattr(tool, "name", fallback_name)
503+
names.add(tool_name)
504+
tool_call_schema = getattr(tool, "tool_call_schema", None)
505+
if tool_call_schema is not None:
506+
schemas[tool_name] = tool_call_schema.model_json_schema()
507+
else:
508+
schemas[tool_name] = {}
509+
497510
for node_name, node_spec in self.graph.nodes.items():
498-
# langgraph's processing node.bound -> runnable.tool -> baseTool (if tool node)
499-
tool = getattr(getattr(node_spec, "bound", None), "tool", None)
500-
if tool is None:
511+
bound = getattr(node_spec, "bound", None)
512+
if bound is None:
501513
continue
502-
metadata = getattr(tool, "metadata", None) or {}
503-
if metadata.get(REQUIRE_CONVERSATIONAL_CONFIRMATION):
504-
tool_name = getattr(tool, "name", node_name)
505-
names.add(tool_name)
506-
tool_call_schema = getattr(tool, "tool_call_schema", None)
507-
if tool_call_schema is not None:
508-
schemas[tool_name] = tool_call_schema.model_json_schema()
509-
else:
510-
schemas[tool_name] = {}
514+
# Low-code: single-tool UiPathToolNode
515+
tool = getattr(bound, "tool", None)
516+
if tool is not None:
517+
_record(tool, node_name)
518+
continue
519+
# Coded: multi-tool ToolNode (create_agent)
520+
tools_by_name = getattr(bound, "tools_by_name", None)
521+
if isinstance(tools_by_name, dict):
522+
for fallback_name, tool in tools_by_name.items():
523+
_record(tool, fallback_name)
524+
511525
return names, schemas
512526

513527
def _is_middleware_node(self, node_name: str) -> bool:

0 commit comments

Comments
 (0)