Skip to content

fix(evals): include agent output in AgentRunHistory [AE-2147] - #1894

Open
rakesh-uipath wants to merge 3 commits into
mainfrom
fix/ae-2147-trajectory-agent-output
Open

fix(evals): include agent output in AgentRunHistory [AE-2147]#1894
rakesh-uipath wants to merge 3 commits into
mainfrom
fix/ae-2147-trajectory-agent-output

Conversation

@rakesh-uipath

@rakesh-uipath rakesh-uipath commented Sep 10, 2026

Copy link
Copy Markdown

Fixes AE-2147 — reported by James Dickson in #help-agent-builder, filed by Maria Vimer.

The bug

{{AgentRunHistory}} reaches the trajectory judge as a tool-call transcript with the agent's own output missing, so the judge grades an incomplete trace.

It is real, and it is one predicate:

https://github.com/UiPath/uipath-python/blob/30f6bec2/packages/uipath/src/uipath/eval/_helpers/evaluators_helpers.py#L653

for span in workload_trace:
    if span.attributes and (tool_name := span.attributes.get(TOOL_NAME_ATTR)):

A span without tool.name is skipped, and the docstring said so out loud: "Only processes tool spans". The workload's own answer lives on WorkloadExecution.workload_output (populated in runtime.py:1143) and never entered the string.

There is no second channel for it. On BaseLLMTrajectoryEvaluator the actual-output placeholder is the history:

actual_output_placeholder: str = "{{AgentRunHistory}}"   # llm_judge_trajectory_evaluator.py:63
...
return trace_to_str(workload_execution.workload_trace)   # line 83 — output not passed

So the judge never saw the output at all — while its own system prompt promises it "a trace/history of the agent's actions and outputs".

Reproduced against James's run (one Web_Search tool span, agent answered correctly). Before:

[2026-08-26 18:46:40] LLM Response:
  Agent Selected 1 Tool(s):

  Tool: Web_Search
  Arguments: {}

[2026-08-26 18:46:47] Tool Call Response - Web_Search:
{}

Byte-for-byte what his screenshot shows. The judge scored it 10% with the justification "It never produced a user-facing answer identifying Argentina as the most recent World Cup winner" — while the Actual Output panel on the same screen read {"search_results_answer": "Argentina won the most recent FIFA World Cup (Qatar 2022)..."}. The agent was right and got marked down for the serialiser's omission.

The fix

trace_to_str takes the workload output and appends it as a final block; both trajectory evaluators pass it.

[2026-08-26 18:46:47] Tool Call Response - Web_Search:
{}

Agent Output:
{"search_results_answer": "Argentina won the most recent FIFA World Cup (Qatar 2022), ..."}

Fixed in LLMJudgeTrajectoryEvaluator / LLMJudgeTrajectorySimulationEvaluator and in LegacyTrajectoryEvaluator, which had the same defect through the same helper.

Deliberately not changed

Intermediate assistant messages are still not rendered. LLM spans carry the full system prompt, and test_legacy_trajectory_prompt_uses_compact_tool_history exists precisely to keep a 10k-char prompt out of the judge's context. Surfacing intermediate reasoning needs a bounded projection of the LLM spans, not a filter removal — worth doing, separate change. James's other asks in that thread (documenting what each {{...}} binds to in the evaluator UI, eval-driven judge authoring) are product work, not this.

workload_output=None keeps the old behaviour, so no existing caller changes shape.

Tests

packages/uipath/tests/evaluators/test_trajectory_agent_output.py — reproduces the AE-2147 run through both evaluators and asserts the answer reaches the prompt. Both fail on main:

E  AssertionError: assert 'Argentina won the most recent FIFA World Cup (Qatar 2022).' in
   '...Tool Call Response - Web_Search:\n{}\n'

Plus five cases on trace_to_str itself in test_evaluator_helpers.py (ordering, str vs dict output, omitted output, empty output).

Verification

Ran locally, all from packages/uipath:

  • uv run pytest — green, exit 0
  • uv run ruff check . — All checks passed
  • uv run ruff format --check . — 335 files already formatted
  • uv run mypy --config-file pyproject.toml . — no issues in 335 source files
  • uv run python scripts/lint_httpx_client.py — clean

Version bumped 2.14.14 → 2.14.15; 2.14.14 is already on PyPI so check-version-availability would have failed otherwise.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings September 10, 2026 15:42
@github-actions github-actions Bot added test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-runtime test:uipath-integrations labels Sep 10, 2026

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

The legacy evaluator still omits supplied output for runs with no trace spans.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Fixes {{AgentRunHistory}} so trajectory evaluators include the agent’s final output.

Changes:

  • Appends workload output to serialized traces.
  • Updates modern and legacy trajectory evaluators.
  • Adds regression tests.
  • Bumps version to 2.14.15.
File summaries
File Summary
packages/uipath/uv.lock Updates locked package metadata.
packages/uipath/tests/evaluators/test_trajectory_agent_output.py Adds evaluator regression coverage.
packages/uipath/tests/evaluators/test_evaluator_helpers.py Tests trace output serialization.
packages/uipath/src/uipath/eval/evaluators/llm_judge_trajectory_evaluator.py Passes agent output into judge history.
packages/uipath/src/uipath/eval/evaluators/legacy_trajectory_evaluator.py Passes output into legacy history, but empty-trace runs still need correction.
packages/uipath/src/uipath/eval/_helpers/evaluators_helpers.py Serializes and appends agent output.
packages/uipath/pyproject.toml Bumps the package version.
Review details
  • Files reviewed: 6/7 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +140 to +144
@@ -139,7 +141,7 @@ def _create_evaluation_prompt(
and agent_run_history
and isinstance(agent_run_history[0], ReadableSpan)
):
agent_run_history = trace_to_str(agent_run_history)
agent_run_history = trace_to_str(agent_run_history, workload_output)
@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 →

rakesh-uipath added a commit that referenced this pull request Sep 10, 2026
The span-list guard in LegacyTrajectoryEvaluator required a first
element, so a run that answered without calling a tool fell through to
str([]) and lost {{AgentRunHistory}} all over again -- the same omission
AE-2147 is about, just on the no-tool path. Route an empty span list
through trace_to_str whenever there is an output to append, and keep the
old '[]' rendering when there is none.

Found by Copilot review on #1894.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rakesh-uipath and others added 3 commits September 10, 2026 10:33
trace_to_str() rendered only spans carrying a `tool.name` attribute, so
`{{AgentRunHistory}}` reached the trajectory judge as a tool-call
transcript with the agent's own answer missing. The trajectory judges
have no second channel for it — `actual_output_placeholder` IS
`{{AgentRunHistory}}` — so the judge graded every run as if the agent
had produced nothing, and marked correct runs down for "never produced
an answer".

Pass the workload output through to trace_to_str and append it as a
final "Agent Output" block, from both the LLM-judge and the legacy
trajectory evaluator. LLM spans stay unrendered on purpose: they carry
the full system prompt and would swamp the judge's context
(test_legacy_trajectory_prompt_uses_compact_tool_history pins that).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sonar's quality gate wanted 90% on new code and got 85.7%. The two
uncovered lines were the non-JSON-serialisable fallback in
_format_workload_output and the new argument inside
LegacyTrajectoryEvaluator.evaluate, which no test reached.

The second one is worth having regardless: it asserts the agent output
lands in the prompt the LLM is actually sent, not just in the string
_create_evaluation_prompt returns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The span-list guard in LegacyTrajectoryEvaluator required a first
element, so a run that answered without calling a tool fell through to
str([]) and lost {{AgentRunHistory}} all over again -- the same omission
AE-2147 is about, just on the no-tool path. Route an empty span list
through trace_to_str whenever there is an output to append, and keep the
old '[]' rendering when there is none.

Found by Copilot review on #1894.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rakesh-uipath
rakesh-uipath force-pushed the fix/ae-2147-trajectory-agent-output branch from 6f87333 to a6a24fe Compare September 10, 2026 17:34
@sonarqubecloud

Copy link
Copy Markdown

@Chibionos Chibionos 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.

/deep-review — PR #1894 · fix(evals): include agent output in AgentRunHistory [AE-2147]

Branch: fix/ae-2147-trajectory-agent-outputmain | Size: +309/−12 across 7 files

Verdict

Approve — correct, minimal, well-tested bugfix; both lenses (architecture + runtime) clean, Python domain pillar clean, pytest/ruff check/ruff format --check/mypy all pass on the changed files.

Slop meter

🎚️ Slop Meter — PR #1894

Verdict: 🟢 clean
Slop axis: 2/100 · Tunnel-vision axis: 0/100
Diff: +309 LOC across 7 files

Only signal: hardcoded_version (2 hits, pyproject.toml + uv.lock) — the legitimate version bump described in the PR body (2.14.14 already published to PyPI). No other slop or tunnel-vision signals fired.

Codex recheck

Codex CLI is on PATH but the exec call failed with "You hit your spend cap set by the owner of your workspace" (not the documented "not on PATH" case). Treated as unavailable for this run — proceeding on local two-lens findings only (Codex not available this run — stronger guarantee once budget/config is fixed).

Findings index (all inline below)

# Sev File:Line Headline
No P0/P1/P2 findings

(No finding bodies in the summary — every body would be an inline comment anchored to a line; there are none to anchor this cycle.)

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