Skip to content

feat: auto-wrap tools to hide and inject static arguments#921

Open
edis-uipath wants to merge 2 commits into
mainfrom
wrap-tools-in-static-args
Open

feat: auto-wrap tools to hide and inject static arguments#921
edis-uipath wants to merge 2 commits into
mainfrom
wrap-tools-in-static-args

Conversation

@edis-uipath

Copy link
Copy Markdown
Contributor

What

Adds a per-tool wrapper that hides statically-configured tool parameters from the model-facing schema and injects their configured values just before the tool runs. The model never has to — and never can — populate a parameter the agent author already pinned.

This is a per-tool, agent-state-independent counterpart to the existing StaticArgsHandler. Tools without static argument_properties pass through unchanged, and the original tool is never mutated.

Key pieces

  • wrap_tool_with_static_args / wrap_tools_with_static_args (agent/tools/static_args.py)
    • Strips static params from args_schema (so the LLM no longer sees them).
    • Injects the configured values into the call kwargs before the wrapped coroutine / func runs.
    • Surfaces the now-hidden values in the tool description so otherwise-identical tools stay distinguishable to the model (e.g. a send-email tool configured for Bob vs one for Alice — no reliance on tool naming).
    • Redacts is_sensitive values to <sensitive> in the description — secrets are injected at execution but never exposed to the model or logs.
  • remove_fields_from_schema (agent/tools/schema_editing.py): drops named fields from a JSON schema's properties/required by jsonpath (skips array elements and absent fields).
  • Auto-wrapping wired into the three tool-creation entry points: create_tools_from_resources (tool_factory), create_mcp_tools_and_clients (mcp_tool), and create_a2a_tools_and_clients (a2a_tool).

Why

Static parameters (e.g. a fixed Integration Service search provider, or a configured recipient) are agent-author configuration, not model input. Previously, surfacing them in the call schema invited the model to (re)populate or override them. Hiding them removes that failure mode — but naively hiding them also erases the only signal distinguishing two tools that differ solely by a configured value. This PR resolves both: hide from the schema, inject at call time, and echo the (non-sensitive) values into the description so selection still works.

StaticArgsHandler in the ReAct llm_node is intentionally left in place for backward compatibility; the wrapper drops only the static argument_properties it bakes in, leaving non-static (argument/textBuilder/arrayBuilder) variants for the handler.

Tests

tests/agent/tools/test_wrap_static_args.py covers: static param hidden from schema + injected on call, non-static params preserved, original tool not mutated, passthrough when no static args, list mapping, remove_fields_from_schema behavior, description lists the static name+value, sensitive value redacted (literal value absent), and same-named tools getting distinct descriptions.

Verification: ruff format --check + ruff check clean, mypy clean, pytest tests/agent/tools/636 passed.

🤖 Generated with Claude Code

Add a per-tool helper that hides statically-configured tool parameters
from the model-facing schema and injects the configured values just
before the underlying tool runs, so the model never has to (and never
can) populate them.

- wrap_tool_with_static_args / wrap_tools_with_static_args
  (agent/tools/static_args.py): strip static params from args_schema,
  inject their values on call, and surface the now-hidden values in the
  tool description so otherwise-identical tools remain distinguishable
  to the model. Values flagged is_sensitive are redacted to <sensitive>.
- remove_fields_from_schema (agent/tools/schema_editing.py): drop named
  fields from a JSON schema's properties/required by jsonpath.
- Auto-wrap at the three tool-creation entry points:
  create_tools_from_resources (tool_factory), create_mcp_tools_and_clients
  (mcp_tool), and create_a2a_tools_and_clients (a2a_tool).
- StaticArgsHandler in the ReAct llm_node is left untouched for back-compat.
- Tests in tests/agent/tools/test_wrap_static_args.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 23, 2026 05:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an automatic per-tool wrapper that removes statically configured tool parameters from the LLM-facing argument schema, then injects the pinned values back into the call kwargs at execution time (with sensitive static values redacted in tool descriptions). It also wires this wrapping into the main tool creation entry points and adds tests for the behavior.

Changes:

  • Added wrap_tool_with_static_args / wrap_tools_with_static_args to hide static params from args_schema, inject them at invocation time, and append (redacted) static values to tool descriptions.
  • Added remove_fields_from_schema helper to drop named properties/required entries from JSON schemas by jsonpath.
  • Enabled auto-wrapping in tool_factory, MCP tool creation, and A2A tool creation, with a new dedicated test suite.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/agent/tools/test_wrap_static_args.py Adds tests covering schema hiding, call-time injection, non-mutation, passthrough behavior, schema removal helper, and sensitive redaction in descriptions.
src/uipath_langchain/agent/tools/tool_factory.py Wraps the tool list returned from create_tools_from_resources with the new static-args wrapper.
src/uipath_langchain/agent/tools/static_args.py Implements the new tool-wrapping logic to hide static args from schema and inject them at runtime.
src/uipath_langchain/agent/tools/schema_editing.py Adds remove_fields_from_schema to remove named schema fields/properties/required by jsonpath.
src/uipath_langchain/agent/tools/mcp/mcp_tool.py Applies static-args wrapping to MCP-created tools before returning them.
src/uipath_langchain/agent/tools/a2a/a2a_tool.py Applies static-args wrapping to A2A-created tools before returning them.

Comment on lines +315 to +323
removed: set[str] = set()
for json_path in json_paths:
try:
segments = parse_jsonpath_segments(json_path)
except Exception:
# A malformed path is simply not removable; leave the field in place.
continue
if not segments or segments[-1] == "*":
continue
Comment on lines +443 to +459
coroutine = tool.coroutine
if coroutine is not None:
_coroutine = coroutine

async def _acall(**kwargs: Any) -> Any:
return await _coroutine(**apply_static_args(inject_values, kwargs))

update["coroutine"] = _acall

func = tool.func
if func is not None:
_func = func

def _call(**kwargs: Any) -> Any:
return _func(**apply_static_args(inject_values, kwargs))

update["func"] = _call
Add tests/agent/tools/test_wrap_static_args_tool_types.py covering the
parameter shapes a configured tool can take — static-only, static+input,
all-static (no model input), no-params, no-static, large descriptions, many
params, plus sensitive-value redaction and large-value truncation — crossed
with the production tool kinds: MCP (raw dict schema), agent-resource
(pydantic schema, with and without an execution wrapper), A2A, the IXP
wrapper-only class, and a plain LangChain StructuredTool.

Also verifies metadata and graph-execution wrappers survive model_copy, the
original tool is never mutated, and a heterogeneous list wraps the static
tools while passing the rest through unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
85.7% Coverage on New Code (required ≥ 90%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants