Skip to content

Commit f1ebd89

Browse files
committed
feat: update validation and remove tool name input from event
1 parent 050977d commit f1ebd89

6 files changed

Lines changed: 105 additions & 212 deletions

File tree

src/uipath_langchain/agent/react/init_node.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from uipath_langchain.agent.tools.client_side_tool import (
1010
UIPATH_CLIENT_SIDE_TOOLS_INPUT_KEY,
1111
ClientSideToolInfo,
12+
apply_tool_filter,
1213
available_client_side_tools,
13-
validate_and_apply_tool_filter,
1414
)
1515

1616
from .job_attachments import (
@@ -71,18 +71,20 @@ def graph_state_init(state: Any) -> Any:
7171
)
7272
job_attachments_dict.update(message_attachments)
7373

74-
# Validate client-side tool declarations from the exchange input
75-
if client_side_tools:
76-
client_tools_input = getattr(state, UIPATH_CLIENT_SIDE_TOOLS_INPUT_KEY, None)
77-
if client_tools_input is None:
78-
available_client_side_tools.set(None)
79-
elif not isinstance(client_tools_input, list):
80-
raise ValueError(
81-
f"'{UIPATH_CLIENT_SIDE_TOOLS_INPUT_KEY}' must be a list of tool declarations, "
82-
f"got {type(client_tools_input).__name__}."
74+
# Filter available client-side tools based on exchange input declarations
75+
if client_side_tools:
76+
client_tools_input = getattr(
77+
state, UIPATH_CLIENT_SIDE_TOOLS_INPUT_KEY, None
8378
)
84-
else:
85-
validate_and_apply_tool_filter(client_tools_input, client_side_tools)
79+
if client_tools_input is None:
80+
available_client_side_tools.set(None)
81+
elif not isinstance(client_tools_input, list):
82+
raise ValueError(
83+
f"'{UIPATH_CLIENT_SIDE_TOOLS_INPUT_KEY}' must be a list of tool names, "
84+
f"got {type(client_tools_input).__name__}."
85+
)
86+
else:
87+
apply_tool_filter(client_tools_input, client_side_tools)
8688

8789
# Calculate initial message count for tracking new messages
8890
initial_message_count = (

src/uipath_langchain/agent/tools/client_side_tool.py

Lines changed: 17 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -31,71 +31,29 @@ class ClientSideToolInfo(TypedDict):
3131
output_schema: dict[str, Any] | None
3232

3333

34-
def validate_and_apply_tool_filter(
35-
declared_tools: list[dict[str, Any]],
34+
def apply_tool_filter(
35+
declared_tools: list[str | dict[str, Any]],
3636
agent_tools: dict[str, ClientSideToolInfo],
3737
) -> None:
38-
"""Validate client-side tool declarations and set the availability filter.
38+
"""Filter available client-side tools to the intersection of declared and agent tools.
3939
40-
Compares the client's declared tools against the agent's tool definitions.
41-
Raises ValueError if required tools are missing or schemas don't match.
42-
Sets the available_client_side_tools context variable for tool functions.
40+
Extracts tool names from the client's declarations, intersects with the agent's
41+
defined client-side tools, and sets the availability filter. Unknown names are
42+
silently ignored.
4343
4444
Args:
45-
declared_tools: List of tool declarations from uipath__client_side_tools input.
46-
Each item is a dict with 'name' and optional 'inputSchema'/'outputSchema'.
47-
agent_tools: The agent's client-side tools.
48-
Dict of {tool_name: ClientSideToolInfo}.
45+
declared_tools: List of tool names (strings) or dicts with a 'name' field
46+
from uipath__client_side_tools input.
47+
agent_tools: The agent's client-side tools keyed by name.
4948
"""
50-
declared: dict[str, dict[str, Any]] = {}
51-
for i, t in enumerate(declared_tools):
52-
if isinstance(t, dict):
53-
if "name" not in t:
54-
raise ValueError(
55-
f"Client-side tool declaration at index {i} is missing required 'name' field."
56-
)
57-
name = t["name"]
58-
elif isinstance(t, str):
59-
name = t
60-
t = {"name": t}
61-
else:
62-
raise ValueError(
63-
f"Client-side tool declaration at index {i} must be a dict or string, got {type(t).__name__}."
64-
)
65-
if name in declared:
66-
raise ValueError(
67-
f"Duplicate client-side tool declaration: '{name}'."
68-
)
69-
declared[name] = t
70-
71-
required = set(agent_tools.keys())
72-
missing = required - set(declared.keys())
73-
if missing:
74-
raise ValueError(
75-
f"Missing required client-side tools: {', '.join(sorted(missing))}. "
76-
f"The client must register handlers for all client-side tools defined by the agent."
77-
)
78-
79-
for name, decl in declared.items():
80-
agent_tool = agent_tools.get(name)
81-
if agent_tool is None:
82-
continue # Unknown tool, runtime will ignore it
83-
if decl.get("inputSchema") and agent_tool.get("input_schema"):
84-
if json.dumps(decl["inputSchema"], sort_keys=True) != json.dumps(
85-
agent_tool["input_schema"], sort_keys=True
86-
):
87-
raise ValueError(
88-
f"Client-side tool '{name}' inputSchema does not match agent definition."
89-
)
90-
if decl.get("outputSchema") and agent_tool.get("output_schema"):
91-
if json.dumps(decl["outputSchema"], sort_keys=True) != json.dumps(
92-
agent_tool["output_schema"], sort_keys=True
93-
):
94-
raise ValueError(
95-
f"Client-side tool '{name}' outputSchema does not match agent definition."
96-
)
97-
98-
available_client_side_tools.set(set(declared.keys()))
49+
declared_names: set[str] = set()
50+
for t in declared_tools:
51+
if isinstance(t, str):
52+
declared_names.add(t)
53+
elif isinstance(t, dict) and "name" in t:
54+
declared_names.add(t["name"])
55+
56+
available_client_side_tools.set(declared_names & set(agent_tools.keys()))
9957

10058

10159
def create_client_side_tool(

src/uipath_langchain/chat/hitl.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,21 @@ def request_approval(
127127
"""
128128
tool_call_id: str = tool_args.pop("tool_call_id")
129129

130+
# For server-side tools, is_execution_phase=True so the bridge emits
131+
# executingToolCall at the confirmation interrupt.
132+
# For client-side tools, is_execution_phase=False here because the
133+
# execution interrupt in client_side_tool.py handles it separately.
134+
is_execution_trigger = not (tool.metadata or {}).get(
135+
IS_CONVERSATIONAL_CLIENT_SIDE_TOOL, False
136+
)
137+
130138
@durable_interrupt
131139
def ask_confirmation():
132140
return {
133141
"tool_call_id": tool_call_id,
134142
"tool_name": tool.name,
135143
"input": tool_args,
136-
"is_execution_phase": False,
144+
"is_execution_phase": is_execution_trigger,
137145
}
138146

139147
response = ask_confirmation()

src/uipath_langchain/runtime/messages.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,6 @@ async def map_current_message_to_start_tool_call_events(self):
475475
tool_call=UiPathConversationToolCallEvent(
476476
tool_call_id=tool_call["id"],
477477
executing=UiPathConversationExecutingToolCallEvent(
478-
tool_name=tool_call["name"],
479478
input=tool_call["args"],
480479
),
481480
),

0 commit comments

Comments
 (0)