Skip to content

fix(eval): include agent final response in trajectory AgentRunHistory [FOR-1629] - #1949

Open
Chibionos wants to merge 3 commits into
mainfrom
fix/trajectory-history-agent-output
Open

Chibionos wants to merge 3 commits into
mainfrom
fix/trajectory-history-agent-output

Conversation

@Chibionos

Copy link
Copy Markdown
Contributor

Trajectory evaluators were judging runs without ever seeing what the agent said. trace_to_str only renders spans that carry tool.name, and neither trajectory prompt has a slot for the final output, so a run that answers with no tool calls sends the judge an empty AgentRunHistory. Follow up to #1901, taking the Part 2 fix and the review feedback from that PR.

What the trace shows

Teodor's cloud trace (f54be95e, Default Trajectory Evaluator) has 8 eval items against the same low-code conversational agent:

Run Tool calls AgentRunHistory Score
bed87113, c3f157bb Tools_Main_GetUserContext tool call + response rendered 10, 90
195d3d99, 1e8cd149, 1aed828d none "" 0, "no AgentRunHistory provided"
60aa9c86, 245302ad none "" 100
8b4c11a5 started, never finished n/a, evaluator never ran none

So whether a run "works" depends only on one thing: did the agent call a tool. Every zero-tool run got an empty history, and the agent did answer in all of them. The text is right there in output.uipath__agent_response_messages[].contentParts[].data.inline on the Agent output span, it just never reaches the prompt. Whether the judge then gave it 0 or 100 comes down to how the expected behavior is worded. "Must not disclose X" gets passed on nothing (the 100s are vacuous), "should report X" gets failed. The tool runs are not fully healthy either. The c3f157bb justification says "the agent's final response is not shown", so the judge is guessing there too.

Why it shows up more in cloud: these are identity-scoped refusal scenarios where the agent answers straight from the prompt without calling GetUserContext. Nothing environment-specific in the evaluator, the cloud runs just hit the zero-tool path more often.

Change

  • New eval/_helpers/agent_output.py with render_agent_output / append_agent_output. Conversational output is parsed with UiPathConversationMessageData, so camelCase and snake_case both work, and comes out as [role] text plus tool calls. Any structured fields next to the messages are kept as JSON. Everything else is indented JSON instead of str(dict).
  • Both LLMJudgeTrajectoryEvaluator and LegacyTrajectoryEvaluator always append an Agent Final Response: section after the tool history, not just when the history is empty. The c3f157bb case above is why.
  • The legacy evaluator takes the output through _get_actual_output, so target_output_key and job-attachment resolution still apply. An empty span list now renders as an empty history, not "[]".
  • None output leaves the history alone, and {} is still rendered.

Not in this PR

  • The Part 1 span flush from Test/uv 16309 span flush race + fix #1901. @mjnovice's trace of the call order holds up: execute() already flushes in its finally before the eval runtime reads, and none of these runs is "tool calls happened, history empty".
  • Suspend/resume state. Both tool runs suspend on the process tool (first root span closes at suspend, a second one opens on resume ~60s later). After resume the Evaluation and Evaluation Set Run spans never get the scores ({"scores": {}}), and the evaluator span loses execution.id. 8b4c11a5 never resumed at all. That one is its own bug and I will pick it up separately.

Testing

  • tests/evaluators/test_trajectory_agent_output.py, 14 tests using the conversational payload shape from the trace. The 4 evaluator-level tests fail on main.
  • Full packages/uipath suite: 2642 passed, 1 xfailed. ruff and mypy clean on the touched files.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 19, 2026 17:39
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T17:45:15.572410Z a1307f4 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Some conversational content is silently dropped, and attachment-backed output remains unresolved in the newer trajectory evaluator.

2 open issues
What changed in this PR

Adds agents’ final responses to trajectory judge histories, addressing incomplete evaluations for runs with or without tool calls.

Changes:

  • Adds shared rendering for conversational and structured output.
  • Updates both trajectory evaluators to append final responses.
  • Adds regression tests and bumps uipath to 2.14.42.
File Description
packages/​uipath/​uv.lock Updates the locked package version.
packages/​uipath/​tests/​evaluators/​test_trajectory_agent_output.py Tests rendering and evaluator integration.
packages/​uipath/​src/​uipath/​eval/​evaluators/​llm_judge_trajectory_evaluator.py Appends final output to trajectory history.
packages/​uipath/​src/​uipath/​eval/​evaluators/​legacy_trajectory_evaluator.py Appends resolved output and handles empty traces.
packages/​uipath/​src/​uipath/​eval/​_helpers/​agent_output.py Adds shared output-rendering helpers.
packages/​uipath/​pyproject.toml Bumps the package version.

Review effort: Balanced

Comment on lines +44 to +45
if isinstance(part.data, UiPathInlineValue) and part.data.inline:
lines.append(f"[{message.role}] {part.data.inline}")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5e50d03. External parts now render as Attachment (<mime>): <uri>, and falsy inline values like false and 0 are kept. Only None and empty strings are skipped. Added a regression test with mixed text, attachment and falsy parts.

🤖 Addressed by Claude Code

Comment on lines +84 to +88
# trace_to_str only renders tool spans; the agent's own answer lives in the output.
return append_agent_output(
trace_to_str(workload_execution.workload_trace),
workload_execution.workload_output,
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5e50d03. _get_actual_output now downloads a job-attachment URI before appending it, same idiom as output_evaluator. Test mocks the download.

🤖 Addressed by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1307f4370

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +46 to +49
for call in message.tool_calls:
lines.append(
f"[{message.role}] Tool call {call.name}: {_to_json(call.input)}"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid duplicating tool calls in the trajectory

When a conversational output includes toolCalls, the same executed calls are already rendered from workload_trace by trace_to_str, so appending them again under Agent Final Response makes the judge see each call twice. This can distort trajectory evaluations involving tool selection, sequencing, or call counts; only the final response content should be appended, or calls already represented by spans should be deduplicated.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, dropped. toolCalls on the output messages are no longer rendered, the executed calls already come from the spans via trace_to_str.

🤖 Addressed by Claude Code

Comment on lines +43 to +45
for part in message.content_parts:
if isinstance(part.data, UiPathInlineValue) and part.data.inline:
lines.append(f"[{message.role}] {part.data.inline}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve non-inline response content

When an assistant response contains both inline text and an external content part, such as a generated file attachment, this loop emits the text and silently discards the external part because only UiPathInlineValue is handled. Since the nonempty text prevents the JSON fallback, the trajectory judge cannot see that the agent supplied the attachment and may incorrectly fail behavior that requires producing or sharing a file.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same fix as the Copilot thread on this line, external parts are rendered now in 5e50d03.

🤖 Addressed by Claude Code

Chibionos and others added 3 commits October 27, 2026 09:17
… [FOR-1629]

trace_to_str renders only tool spans, so zero-tool runs reached the
trajectory judge with an empty AgentRunHistory. Append the rendered final
output in both trajectory evaluators, with conversational messages
rendered as text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se [FOR-1629]

Render external content parts and falsy inline values, skip toolCalls
(already rendered from spans), and resolve job-attachment output in the
LLM judge trajectory evaluator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Chibionos
Chibionos force-pushed the fix/trajectory-history-agent-output branch from a1307f4 to 3cfb670 Compare October 27, 2026 17:18
@github-actions

Copy link
Copy Markdown

🚨 Heads up: uipath-integrations cross-tests are FAILING 🚨

Your changes may break one or more integrations in uipath-integrations-python:

  • uipath-openai-agents
  • uipath-google-adk
  • uipath-agent-framework
  • uipath-llamaindex
  • uipath-pydantic-ai

⚠️ These checks are NOT enforced by branch protection rules. Please review the failures before merging.

🔍 Inspect the failed run →

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

🚨 Heads up: uipath-langchain cross-tests are FAILING 🚨

Your changes may break the uipath-langchain-python integration.

⚠️ These checks are NOT enforced by branch protection rules. Please review the failures before merging.

🔍 Inspect the failed run →

@AAgnihotry AAgnihotry left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code review

Clean fix with solid test coverage (14 new tests). One performance nit below -- not blocking.

Checked for bugs, CLAUDE.md compliance, git history context, prior PR comments, and code comment adherence across 5 parallel review agents + deep review + adversarial review. Slop meter: 2/100 slop, 0/100 tunnel-vision.

# Sev File:Line Headline
1 P1 llm_judge_trajectory_evaluator.py:87 _get_actual_output called twice per eval -- double attachment download

🤖 Generated with Claude Code

If this review was useful, please react with 👍. Otherwise, react with 👎.

return trace_to_str(workload_execution.workload_trace)
output = workload_execution.workload_output
if isinstance(output, str) and is_job_attachment_uri(output):
output = download_attachment_as_string(extract_attachment_id(output))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 -- Performance: _get_actual_output called twice per evaluation, causing double attachment download

The base class LLMJudgeMixin.evaluate calls _get_actual_output at two sites:

  • Line 188 inside _create_evaluation_prompt
  • Line 168 in evaluate() for the justification context dict

Before this PR, _get_actual_output was pure computation (trace_to_str()). Now it calls download_attachment_as_string -- synchronous HTTP download + temp file write/read. Every trajectory evaluation with an attachment URI will download the file twice and block the event loop twice.

Fix: Override evaluate() in BaseLLMTrajectoryEvaluator to call _get_actual_output once and pass the cached result, or use @functools.lru_cache.

Not blocking since attachment-backed trajectory evals are uncommon, but worth a follow-up.

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

test:uipath-integrations test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants