Repository navigation
Conversation
The multi-turn trajectory metric shows the judge each turn's final reply under the name of the first agent that acted in the turn. After a transfer that is the agent that handed off, not the one that answered, so a billing_agent reply was shown as "AGENT (router)" and a rubric about which agent answers was judged on the wrong name. Label the reply with the author of the event that carried it. That event stays in the invocation events with its content moved to final_response, so it is the last agent event left without content; when there is none, the last agent event is used.
lxcxjxhx
left a comment
There was a problem hiding this comment.
The reverse scan is the right way to find the event whose content was moved: convert_events_to_eval_invocations empties exactly one agent event, and looking from the end skips anything that came after it. The docstring names that dependency, which is what makes the heuristic readable.
The risk worth naming is that this is a heuristic coupled to another function's internals. get_final_response_author is correct because convert_events_to_eval_invocations moves the content off the final-response event and leaves the event in place. Nothing enforces that: if the conversion later changes to drop the event entirely, or to keep the content and copy it, this function silently returns a different author rather than failing. The docstring says what it assumes; a test that constructs the "content moved off the last agent event" shape directly would keep the assumption honest.
Concretely: a test that builds InvocationEvents with [user, agent_a(content), agent_b(no content)] and asserts agent_b — plus one where a content-bearing event follows the empty one, asserting the empty one still wins. Those two pin the scan order and the "first from the end" choice; without them, a future change to the loop passes review by looking plausible.
Checked and correct: the user filter is case-insensitive, so "User" / "USER" are excluded too; and an invocation with no agent events returns None rather than raising, which is what the callers need.
Minor: agent_events[-1].author if agent_events else None at the end could be agent_events[-1].author guarded by the earlier if not agent_events — as written it re-checks a condition the loop above already established, but it is harmless and clearer than nesting.
Link to Issue or Description of Change
2. Or, if no issue exists, describe the change:
Problem:
rubric_based_multi_turn_trajectory_quality_v1builds a dialogue for the judgein which every line carries the agent that produced it. The line for a turn's
final reply is labelled with
invocation_events[0].author, the first agentthat acted in the turn. After a transfer that is the agent that handed off,
not the one that answered, so the judge reads a sub-agent's reply as the root
agent's.
A rubric about which agent answers, for example "the router never answers a
billing question itself", is then judged on the wrong name. The tool call
lines of the same turn are labelled correctly, so the dialogue contradicts
itself.
Repro (no model calls; a scripted
BaseLlmmakesroutercalltransfer_to_agentandbilling_agentanswer; the session events go throughEvaluationGenerator.convert_events_to_eval_invocationsand then into theevaluator's dialogue):
Solution:
Label the reply with the author of the event that carried it.
convert_events_to_eval_invocationskeeps that event ininvocation_eventsand moves its content to
final_response, so it is the last agent event leftwithout content. A new helper in
llm_as_judge_utils.py,get_final_response_author, returns its author.The helper does not simply take the last event: a usage report or another
agent's tool call can follow the reply, and a hand-written invocation can hold
a user event. An invocation with no event for the reply falls back to its last
agent event, and one without agent events keeps the existing "agent" label.
For a single agent the label is the same as before.
Testing Plan
Unit Tests:
Added tests for the helper in
test_llm_as_judge_utils.py(after a transfer,with a later usage report or tool call of another agent, with no event for the
reply, with no agent events) and
test_final_reply_is_attributed_to_the_agent_that_wrote_it, which checks thewhole dialogue for a router to billing_agent transfer.
Before the fix the dialogue test fails (1 failed, 11 passed). After, on Python
3.11 and 3.13:
pre-commit runover the commit passes, andmypyreports no issues in thetwo changed modules.
toxover the whole suite was not run.Manual End-to-End (E2E) Tests:
The scripted two-agent run above, after the fix:
Checklist
Additional context
rubric_based_final_response_quality_v1picks the agent the same way when itlooks up developer instructions for the judge. That is left out to keep this
to one concern and can follow as a separate PR.