Skip to content

fix(agent): show static args in conversation tool call events [UV-17565] - #1180

Draft
andrewwan-uipath wants to merge 5 commits into
mainfrom
fix/conversation-tool-call-static-args
Draft

andrewwan-uipath wants to merge 5 commits into
mainfrom
fix/conversation-tool-call-static-args

Conversation

@andrewwan-uipath

@andrewwan-uipath andrewwan-uipath commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Problem

UV-17565: when a conversational agent's tool argument is bound with an argument expression, the tool runs with the bound value, but the chat and the Studio Web debug window show the value the model inferred.

Static args are applied to the tool calls after the model call (StaticArgsHandler.apply_to_response), but startToolCall / executingToolCall are built from the streamed chunks, which still carry the model's args.

Fix

  • StaticArgsHandler also keeps display values per tool, with sensitive values masked as <hidden>.
  • The LLM node passes them in the model run metadata. LangGraph forwards that metadata with each messages stream chunk.
  • The runtime passes the stream metadata to map_event, and the mapper applies the values to the tool call args when building the tool call events.

Testing

  • New mapper tests for the streamed and full-message paths.
  • A handler test for the display values and masking.
  • An end-to-end LLM node test that streams through a real LangGraph graph. It fails on main with LLM-GUESS-123.
  • ruff, mypy and the full test suite pass locally.

🤖 Generated with Claude Code

Argument expressions (static args) are applied to the tool calls after the
model call, but startToolCall/executingToolCall are built from the streamed
chunks, so the chat and debug window showed the model-inferred args instead
of the ones the tool is called with.

The LLM node now passes the resolved display values (sensitive values masked)
in the model run metadata, and the message mapper applies them when building
the tool call events.

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

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.

🟡 Changes recommended

Confirmation approval can overwrite sensitive server-side tool arguments with the literal <hidden> placeholder, causing tools to receive masked values instead of their bound secrets.

1 open issue
What changed in this PR

This PR makes conversational tool-call events display resolved static arguments rather than model-inferred values, aligning chat and Studio Web debugging with tool execution.

Changes:

  • Pass static-argument display values through model-run metadata, masking sensitive values.
  • Apply those values when mapping streamed and full-message tool-call events.
  • Add regression coverage and bump the package version to 0.18.52.
File Description
uv.lock Update the locked package version.
tests/​runtime/​test_runtime_unit.py Update the mapper mock to accept metadata.
tests/​runtime/​test_chat_message_mapper.py Test display overrides and tool isolation.
tests/​agent/​tools/​test_static_args.py Test display values and sensitive-value masking.
tests/​agent/​react/​test_llm_node.py Add a LangGraph streaming integration test.
src/​uipath_langchain/​runtime/​runtime.py Forward stream metadata to the event mapper.
src/​uipath_langchain/​runtime/​messages.py Apply static display values to tool-call events.
src/​uipath_langchain/​agent/​tools/​static_args.py Cache display values with sensitive arguments masked.
src/​uipath_langchain/​agent/​react/​llm_node.py Attach display values to model-run metadata.
src/​uipath_langchain/​agent/​contracts/​static_args.py Define the shared metadata key.
pyproject.toml Bump the package version to 0.18.52.

Review effort: Balanced

The tool call events show sensitive static args masked, so the approval input
may carry the placeholder or drop the arg. Restore sensitive static args from
the server-side call args so the tool still gets the real value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restore all static args from the server-side call args on approval, so an
edited arg (e.g. host) can't redirect a restored secret. Keep sensitive static
args masked in the approved args and cancel metadata sent back to the model
and client.

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

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.

🟡 Changes recommended

Edited confirmations can leak restored secrets into conversation output and subsequent model input, while empty wildcard arrays can leave bound tool inputs unrestored.

2 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Restore bound values for empty wildcard arrays

src/​uipath_langchain/​chat/​hitl.py:169

For a sensitive binding such as $['files'][*], approving {"files": []} leaves the list empty instead of restoring its bound value, so the tool executes without that input. apply_static_args() already handles this JSONPath limitation explicitly. Handle empty wildcard arrays in the restoration helper while preserving unrelated approved edits, and add a confirmation regression test.

Review effort: Balanced

Comment on lines 198 to 204
# Mutate call args so the tool executes with the approved values
call["args"] = approved_args
return ConfirmationResult(
cancelled=None,
args_modified=approved_args != original_args,
approved_args=approved_args,
)
Restore a [*] static path as the whole array, and remove a static path the
server-side call args don't have instead of keeping the client's value.

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

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.

🟡 Changes recommended

The restore logic can revert approved dynamic-child edits and overwrite distinct nested arrays with the first match, causing data loss and incorrect argument-modification tracking.

3 open issues
1 resolved since last review

Review effort: Balanced

Comment thread src/uipath_langchain/chat/hitl.py Outdated
Comment on lines +186 to +188
argument_properties = getattr(tool, "argument_properties", None) or {}
restored = copy.deepcopy(approved_args)
for json_path in argument_properties:
Comment thread src/uipath_langchain/chat/hitl.py Outdated
expr = parse(restore_path)
matches = expr.find(original_args)
if matches:
expr.update_or_create(restored, copy.deepcopy(matches[0].value))
…call

Restore static args on approval with the same dedup and apply_static_args
semantics used on the model's call, so objectBuilder markers don't revert
edits to dynamic children and nested [*] paths keep each array's length.

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

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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