Skip to content

Commit 9f99874

Browse files
fix: interaction between static args and HITL argument rewrite (#722)
1 parent 5749acb commit 9f99874

File tree

15 files changed

+371
-546
lines changed

15 files changed

+371
-546
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.9.8"
3+
version = "0.9.9"
44
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath_langchain/agent/react/llm_node.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,22 @@
88
AnyMessage,
99
ToolCall,
1010
)
11-
from langchain_core.tools import BaseTool, StructuredTool
11+
from langchain_core.tools import BaseTool
1212
from pydantic import BaseModel
1313
from uipath.agent.react import RAISE_ERROR_TOOL
1414
from uipath.runtime.errors import UiPathErrorCategory
1515

16-
from uipath_langchain.agent.tools.static_args import (
17-
apply_static_argument_properties_to_schema,
18-
)
1916
from uipath_langchain.chat.handlers import get_payload_handler
2017

2118
from ..exceptions import AgentRuntimeError, AgentRuntimeErrorCode
2219
from ..messages.message_utils import replace_tool_calls
20+
from ..tools.static_args import StaticArgsHandler
2321
from .constants import (
2422
DEFAULT_MAX_CONSECUTIVE_THINKING_MESSAGES,
2523
DEFAULT_MAX_LLM_MESSAGES,
2624
)
2725
from .types import FLOW_CONTROL_TOOLS, AgentGraphState
28-
from .utils import count_consecutive_thinking_messages, extract_input_data_from_state
26+
from .utils import count_consecutive_thinking_messages
2927

3028

3129
def _filter_control_flow_tool_calls(
@@ -81,6 +79,7 @@ def create_llm_node(
8179
"""
8280
bindable_tools = list(tools) if tools else []
8381
payload_handler = get_payload_handler(model)
82+
static_args_handler = StaticArgsHandler()
8483

8584
async def llm_node(state: StateT):
8685
messages: list[AnyMessage] = state.messages
@@ -93,8 +92,8 @@ async def llm_node(state: StateT):
9392
category=UiPathErrorCategory.USER,
9493
)
9594

96-
static_schema_tools = _apply_tool_argument_properties(
97-
bindable_tools, state, input_schema
95+
static_schema_tools = static_args_handler.initialize(
96+
bindable_tools, state, input_schema or type(state)
9897
)
9998
current_tool_choice: Literal["auto", "any"] = tool_choice
10099
if current_tool_choice == "auto" and (
@@ -131,22 +130,7 @@ async def llm_node(state: StateT):
131130
if len(filtered_tool_calls) != len(response.tool_calls):
132131
response = replace_tool_calls(response, filtered_tool_calls)
133132

133+
static_args_handler.apply_to_response(response.tool_calls)
134134
return {"messages": [response]}
135135

136136
return llm_node
137-
138-
139-
def _apply_tool_argument_properties(
140-
tools: list[BaseTool],
141-
state: StateT,
142-
input_schema: type[InputT] | None = None,
143-
) -> list[BaseTool]:
144-
"""Apply dynamic schema modifications to tools based on their argument_properties."""
145-
146-
agent_input = extract_input_data_from_state(state, input_schema or type(state))
147-
return [
148-
apply_static_argument_properties_to_schema(tool, agent_input)
149-
if isinstance(tool, StructuredTool)
150-
else tool
151-
for tool in tools
152-
]

src/uipath_langchain/agent/tools/context_tool.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import logging
44
import uuid
5-
from typing import Any, Optional, cast
5+
from typing import Any, Optional
66

77
from jsonpath_ng import parse # type: ignore[import-untyped]
88
from langchain_core.documents import Document
@@ -40,17 +40,13 @@
4040
from uipath_langchain.agent.tools.internal_tools.schema_utils import (
4141
BATCH_TRANSFORM_OUTPUT_SCHEMA,
4242
)
43-
from uipath_langchain.agent.tools.static_args import (
44-
ArgumentPropertiesMixin,
45-
handle_static_args,
46-
)
43+
from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType
4744
from uipath_langchain.retrievers import ContextGroundingRetriever
4845

4946
from .structured_tool_with_argument_properties import (
5047
StructuredToolWithArgumentProperties,
5148
)
5249
from .structured_tool_with_output_type import StructuredToolWithOutputType
53-
from .tool_node import ToolWrapperReturnType
5450
from .utils import sanitize_tool_name
5551

5652
logger = logging.getLogger(__name__)
@@ -251,9 +247,6 @@ async def context_semantic_search_wrapper(
251247
call: ToolCall,
252248
state: AgentGraphState,
253249
) -> ToolWrapperReturnType:
254-
call["args"] = handle_static_args(
255-
cast(ArgumentPropertiesMixin, tool), state, call["args"]
256-
)
257250
nonlocal _resolved_arg_folder_prefix
258251
_resolved_arg_folder_prefix = _resolve_folder_path_prefix_from_state(
259252
resource, dict(state)
@@ -379,9 +372,6 @@ async def context_deep_rag_wrapper(
379372
state: AgentGraphState,
380373
) -> ToolWrapperReturnType:
381374
nonlocal _resolved_arg_folder_prefix
382-
call["args"] = handle_static_args(
383-
cast(ArgumentPropertiesMixin, tool), state, call["args"]
384-
)
385375
_resolved_arg_folder_prefix = _resolve_folder_path_prefix_from_state(
386376
resource, dict(state)
387377
)
@@ -533,9 +523,6 @@ async def context_batch_transform_wrapper(
533523
call: ToolCall,
534524
state: AgentGraphState,
535525
) -> ToolWrapperReturnType:
536-
call["args"] = handle_static_args(
537-
cast(ArgumentPropertiesMixin, tool), state, call["args"]
538-
)
539526
nonlocal _resolved_arg_folder_prefix
540527
_resolved_arg_folder_prefix = _resolve_folder_path_prefix_from_state(
541528
resource, dict(state)
@@ -557,7 +544,7 @@ async def context_batch_transform_wrapper(
557544
"output_schema": output_model,
558545
},
559546
)
560-
tool.set_tool_wrappers(awrapper=context_batch_transform_wrapper)
547+
tool.set_tool_wrappers(awrapper=job_attachment_wrapper)
561548
return tool
562549

563550

src/uipath_langchain/agent/tools/escalation_tool.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
from uipath_langchain._utils import get_execution_folder_path
2424
from uipath_langchain._utils.durable_interrupt import durable_interrupt
2525
from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
26-
from uipath_langchain.agent.tools.static_args import (
27-
handle_static_args,
28-
)
2926
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
3027
StructuredToolWithArgumentProperties,
3128
)
@@ -261,7 +258,6 @@ async def escalation_wrapper(
261258
tool.metadata["_call_id"] = call.get("id")
262259
tool.metadata["_call_args"] = dict(call.get("args", {}))
263260

264-
call["args"] = handle_static_args(resource, state, call["args"])
265261
result = await tool.ainvoke(call["args"])
266262

267263
if result["action"] == EscalationAction.END:

src/uipath_langchain/agent/tools/integration_tool.py

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33
import copy
44
import re
5-
from typing import Any, cast
5+
from typing import Any
66

7-
from langchain.tools import BaseTool
8-
from langchain_core.messages import ToolCall
97
from langchain_core.tools import StructuredTool
108
from uipath.agent.models.agent import (
119
AgentIntegrationToolParameter,
@@ -26,16 +24,8 @@
2624
raise_for_enriched,
2725
)
2826
from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
29-
from uipath_langchain.agent.react.types import AgentGraphState
30-
from uipath_langchain.agent.tools.static_args import (
31-
ArgumentPropertiesMixin,
32-
handle_static_args,
33-
)
34-
from uipath_langchain.agent.tools.tool_node import (
35-
ToolWrapperReturnType,
36-
)
3727

38-
from .schema_editing import strip_matching_enums
28+
from .schema_editing import strip_enum
3929
from .structured_tool_with_argument_properties import (
4030
StructuredToolWithArgumentProperties,
4131
)
@@ -149,15 +139,14 @@ def _is_param_name_to_jsonpath(param_name: str) -> str:
149139
return "$" + "".join(parts)
150140

151141

152-
def strip_template_enums_from_schema(
142+
def strip_enums_from_schema(
153143
schema: dict[str, Any],
154144
parameters: list[AgentIntegrationToolParameter],
155145
) -> dict[str, Any]:
156-
"""Remove {{template}} enum values only from argument-variant parameter fields.
146+
"""Remove enum constraints from fields in the schema that were configured with static args.
157147
158-
For each parameter with fieldVariant 'argument', navigates the schema to the
159-
corresponding field (supporting nested objects, arrays, and $ref resolution)
160-
and strips enum values matching the {{...}} pattern.
148+
We strip them so that the tool's args_schema does not enforce them at
149+
validation time; we add our own enum constraints only visible to the LLM.
161150
162151
The function deep-copies the schema so the original is never mutated.
163152
@@ -166,17 +155,14 @@ def strip_template_enums_from_schema(
166155
parameters: List of integration tool parameters from resource.properties.
167156
168157
Returns:
169-
A cleaned copy of the schema with template enum values removed
170-
only from argument-variant fields.
158+
A cleaned copy of the schema with enum constraints removed
159+
from configured fields.
171160
"""
172161
schema = copy.deepcopy(schema)
173162

174163
for param in parameters:
175-
if param.field_variant != "argument":
176-
continue
177-
178164
segments = _param_name_to_segments(param.name)
179-
strip_matching_enums(schema, segments, _TEMPLATE_PATTERN)
165+
strip_enum(schema, segments)
180166

181167
return schema
182168

@@ -309,7 +295,7 @@ def create_integration_tool(
309295

310296
activity_metadata = convert_to_activity_metadata(resource)
311297

312-
cleaned_input_schema = strip_template_enums_from_schema(
298+
cleaned_input_schema = strip_enums_from_schema(
313299
resource.input_schema, resource.properties.parameters
314300
)
315301
input_model = create_model(cleaned_input_schema)
@@ -351,16 +337,6 @@ async def integration_tool_fn(**kwargs: Any):
351337

352338
return result
353339

354-
async def integration_tool_wrapper(
355-
tool: BaseTool,
356-
call: ToolCall,
357-
state: AgentGraphState,
358-
) -> ToolWrapperReturnType:
359-
call["args"] = handle_static_args(
360-
cast(ArgumentPropertiesMixin, tool), state, call["args"]
361-
)
362-
return await tool.ainvoke(call)
363-
364340
tool = StructuredToolWithArgumentProperties(
365341
name=tool_name,
366342
description=resource.description,
@@ -375,6 +351,5 @@ async def integration_tool_wrapper(
375351
},
376352
argument_properties=argument_properties,
377353
)
378-
tool.set_tool_wrappers(awrapper=integration_tool_wrapper)
379354

380355
return tool

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
HumanMessage,
1212
SystemMessage,
1313
)
14-
from langchain_core.messages.tool import ToolCall
1514
from langchain_core.runnables.config import var_child_runnable_config
16-
from langchain_core.tools import BaseTool, StructuredTool
15+
from langchain_core.tools import StructuredTool
1716
from uipath.agent.models.agent import (
1817
AgentInternalToolResourceConfig,
1918
)
@@ -27,12 +26,9 @@
2726
)
2827
from uipath_langchain.agent.multimodal import FileInfo, build_file_content_block
2928
from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
30-
from uipath_langchain.agent.react.types import AgentGraphState
31-
from uipath_langchain.agent.tools.static_args import handle_static_args
3229
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
3330
StructuredToolWithArgumentProperties,
3431
)
35-
from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType
3632
from uipath_langchain.agent.tools.utils import sanitize_tool_name
3733
from uipath_langchain.chat.helpers import (
3834
append_content_blocks_to_message,
@@ -107,14 +103,6 @@ async def tool_fn(**kwargs: Any):
107103

108104
job_attachment_wrapper = get_job_attachment_wrapper(output_type=output_model)
109105

110-
async def analyze_file_tool_wrapper(
111-
tool: BaseTool,
112-
call: ToolCall,
113-
state: AgentGraphState,
114-
) -> ToolWrapperReturnType:
115-
call["args"] = handle_static_args(resource, state, call["args"])
116-
return await job_attachment_wrapper(tool, call, state)
117-
118106
tool = StructuredToolWithArgumentProperties(
119107
name=tool_name,
120108
description=resource.description,
@@ -129,7 +117,7 @@ async def analyze_file_tool_wrapper(
129117
"output_schema": output_model,
130118
},
131119
)
132-
tool.set_tool_wrappers(awrapper=analyze_file_tool_wrapper)
120+
tool.set_tool_wrappers(awrapper=job_attachment_wrapper)
133121
return tool
134122

135123

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
from typing import Any
55

66
from langchain_core.language_models import BaseChatModel
7-
from langchain_core.messages.tool import ToolCall
8-
from langchain_core.tools import BaseTool, StructuredTool
7+
from langchain_core.tools import StructuredTool
98
from uipath.agent.models.agent import (
109
AgentInternalBatchTransformToolProperties,
1110
AgentInternalToolResourceConfig,
@@ -32,16 +31,13 @@
3231
)
3332
from uipath_langchain.agent.exceptions import AgentStartupError, AgentStartupErrorCode
3433
from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
35-
from uipath_langchain.agent.react.types import AgentGraphState
3634
from uipath_langchain.agent.tools.internal_tools.schema_utils import (
3735
BATCH_TRANSFORM_OUTPUT_SCHEMA,
3836
add_query_field_to_schema,
3937
)
40-
from uipath_langchain.agent.tools.static_args import handle_static_args
4138
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
4239
StructuredToolWithArgumentProperties,
4340
)
44-
from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType
4541
from uipath_langchain.agent.tools.utils import sanitize_tool_name
4642

4743

@@ -195,14 +191,6 @@ async def upload_result_attachment():
195191

196192
job_attachment_wrapper = get_job_attachment_wrapper(output_type=output_model)
197193

198-
async def batch_transform_tool_wrapper(
199-
tool: BaseTool,
200-
call: ToolCall,
201-
state: AgentGraphState,
202-
) -> ToolWrapperReturnType:
203-
call["args"] = handle_static_args(resource, state, call["args"])
204-
return await job_attachment_wrapper(tool, call, state)
205-
206194
tool = StructuredToolWithArgumentProperties(
207195
name=tool_name,
208196
description=resource.description,
@@ -224,5 +212,5 @@ async def batch_transform_tool_wrapper(
224212
**({"static_query": static_query} if is_query_static else {}),
225213
},
226214
)
227-
tool.set_tool_wrappers(awrapper=batch_transform_tool_wrapper)
215+
tool.set_tool_wrappers(awrapper=job_attachment_wrapper)
228216
return tool

0 commit comments

Comments
 (0)