Agent Eval¶
Agents are harder to evaluate than chat models. Trajectories matter. Tool calls matter. Final state matters more than text.
Rapid Recall
Three flavors of agent eval: (1) Functional correctness = did the agent actually accomplish the task (final state matches expected). (2) Trajectory eval = step efficiency, tool selection accuracy, unnecessary actions, recovery. (3) Tool-call eval = right tool, right arguments, right order, with IoU-style metrics. Elo for multi-version comparison. SWE-bench Verified is the canonical coding-agent benchmark (real GitHub issues with hidden tests; top agents at ~50-70% pass rate early 2026). LangSmith + agentevals is the 2026 stack: tracing automatic via LANGSMITH_TRACING=true, evaluators as Python functions, pytest gating.
Why agent eval is hard¶
Agents have trajectories, not just outputs. A chat model produces one answer; an agent produces a sequence of tool calls, observations, and reasoning steps. Evaluation needs to consider:
- The final answer.
- The path taken to get there.
- Whether intermediate tool calls were correct.
- Whether the agent recovered from failures.
Plus the reward signal is often sparse — many agent tasks have one binary success criterion at the end (tests pass / don't pass), with no per-step grading.
Agentic functional correctness¶
For agent systems: did the agent actually accomplish the task? Binary outcome, not text quality.
Examples¶
| Task | Text quality metric | Functional correctness |
|---|---|---|
| "Book a flight" | Does response say it booked? | Is flight actually booked in system? |
| "Refund customer" | Does response confirm refund? | Did refund process in payment API? |
| "Fix the bug" | Does code look good? | Do tests pass? |
| "Write the report" | Is it readable? | Does it contain all required sections + correct data? |
Implementation¶
Build a mock environment with instrumented tools. Tools log success/failure. Eval harness compares final state to expected state.
class MockEnv:
def __init__(self):
self.state = {"flights_booked": [], "refunds_issued": []}
def book_flight(self, flight_id):
self.state["flights_booked"].append(flight_id)
return {"status": "success", "confirmation": "ABC123"}
def assert_task_complete(self, expected):
return self.state == expected
def eval_agent(agent, task, expected_final_state):
env = MockEnv()
agent.run(task, tools=env)
return env.assert_task_complete(expected_final_state)
SWE-bench — the canonical agentic eval¶
Real GitHub issues with hidden tests. Agent gets issue description + repo. Runs, makes changes. Pass if tests pass. Hard benchmark, top agents (Claude 4, GPT-5) hover around 50-70% pass rate on SWE-bench Verified as of early 2026.
2026 benchmarks and the gaming problem¶
The structural shift: benchmarks the industry relied on two years ago are saturated. MMLU went from ~35% in 2021 to 99% in 2026 → measures nothing. GSM8K, HumanEval, basic MMLU are in the graveyard. Old benchmarks are dead; what carries signal in 2026:
| Domain | Live benchmarks |
|---|---|
| Reasoning / knowledge | GPQA Diamond (grad science), HLE (Humanity's Last Exam, built to stay unsaturated, <50%, damning calibration: confident when wrong), ARC-AGI-2/3 |
| Coding | SWE-bench Verified (real GitHub issues, ~80% frontier), LiveCodeBench (contamination-resistant via post-cutoff date-stamping) |
| Math | AIME 2025 / 2026 |
| Human preference | Arena Elo (LMSYS) |
Agent benchmarks are a distinct category:
- GAIA — general assistant, multi-step web/file/reasoning (~75% top agents).
- τ²-bench — tool-agent-user with policy adherence; the reliability benchmark (even top models <50%,
pass^8<25%). - OSWorld — real desktop computer use. WebArena — browser. Terminal-Bench — DevOps.
- METR HCAST / Time Horizons — longest task an agent finishes 50% of the time.
The four gaming mechanisms — don't conflate them¶
- Contamination — test set leaks into training. Removing contaminated GSM8K dropped accuracy up to 13%. Defense: post-cutoff / held-out benchmarks.
- Scaffold / harness inflation — same model swings 30+ points by orchestration (Opus 4.5: 80.9% Verified vs 45.9% Pro / SEAL — same model, the harness differs). Vendor numbers aren't directly comparable.
- Benchmark design flaws — Berkeley got ~100% on several by exploiting validation infra (public answers on HuggingFace, agent code runs in the evaluator's environment). SWE-bench-Verified: a wrong patch can still pass tests.
- Selective reporting — best-of-N, best scaffold, single favorable runs inflate by 5–15 points.
How to read them. Benchmarks are diagnostic tests, not verdicts (a cholesterol test doesn't predict blood pressure). No single benchmark — use a portfolio, and the only one that matters for you is a custom eval of 100–200 cases from your real workload. Extra traps: effective context is only 50–65% of advertised (RULER finding); single-run success ≠ reliability (pass^k collapse). Use public benchmarks to shortlist and a custom multi-run domain eval to decide. Progress is real; the magnitude in any vendor launch is inflated.
Trajectory evaluation¶
Between text quality and functional correctness: evaluate the path the agent took.
Metrics:
- Step efficiency: # steps vs optimal.
- Tool selection accuracy: did the agent pick the right tool at each step?
- Unnecessary actions: did it do redundant work?
- Recovery: after a failure, did it adapt or get stuck?
Useful for agent debugging even when functional correctness passes, an agent that "works" but takes 20 steps when 5 would do is a cost disaster.
Trajectory eval pipeline¶
flowchart LR
T[Test case<br/>task + expected trajectory] --> R[Run agent]
R --> CT[Captured trace<br/>tool calls + args + results]
CT --> J[Trajectory judge<br/>LLM-as-judge or rule-based]
J --> S[Scores<br/>step efficiency, tool IoU, recovery]
Tool-call evaluation: right tool, args, order¶
Three sub-metrics:
| Metric | What it asks | How to measure |
|---|---|---|
| Tool-name IoU | Of the expected tool calls, what fraction did the agent make? | |expected ∩ actual| / |expected ∪ actual| over tool names |
| Argument correctness | Were the arguments to each tool call correct? | LLM judge on (expected_args, actual_args) per call |
| Order correctness | Did the agent call tools in the right sequence? | Edit distance between expected and actual order |
def tool_call_iou(expected_calls, actual_calls):
"""Set IoU over (tool_name) tuples."""
e = {(c["name"]) for c in expected_calls}
a = {(c["name"]) for c in actual_calls}
return len(e & a) / max(1, len(e | a))
For coding agents, this maps cleanly to: did the agent run the right view/grep/edit/bash sequence to fix the bug?
Parallel vs sequential analysis¶
When an agent emits multiple tool calls per turn (parallel), evaluation must check:
- Were they actually parallelizable? (no data dependencies between them)
- Did the agent parallelize when it should have? (anti-pattern: serializing 3 independent web searches)
- Did it serialize when it should have? (data dependency missed → race condition)
LLM-as-judge for trajectories¶
from agentevals.trajectory.llm import create_trajectory_llm_as_judge
trajectory_judge = create_trajectory_llm_as_judge(model="openai:gpt-5-mini")
score = trajectory_judge(
outputs=actual_trajectory_messages,
reference_outputs=expected_trajectory_messages,
)
# score["score"] in [0, 1]; score["comment"] explains the rating
The judge sees the full message history (system, user, assistant tool calls, tool results) and rates: did the agent's path make sense, did it use the right tools, did it recover from failures?
Elo rating for multi-version eval¶
Classic win-loss rating from chess. Elo converts a rating difference into an expected win probability, then nudges ratings by the surprise (actual − expected). Big surprise → big change; expected result → tiny change.
Expected: E_A = 1 / (1 + 10^((R_B − R_A)/400)) # every 400 points = 10× the odds; equal ratings = 0.5
Update: R_A_new = R_A + K · (S_A − E_A) # S_A: win=1, loss=0, tie=0.5; K = learning rate
# (S_A − E_A) is the surprise; Elo is zero-sum
Worked example: both at 1500, K=32, A beats B → E_A=0.5, R_A=1516, R_B=1484 (A gains 16, B loses 16). A 1700 model tying a 1300 model actually loses points (was expected to win, scored only 0.5).
Applied to LLM systems:
- Collect queries (production traffic or golden set).
- For each query, run two or more candidate systems.
- Have a judge (LLM or human) pick winner.
- Update Elo ratings.
- After 1000+ comparisons, ratings stabilize.
Why it beats absolute scoring:
- Sensitive to small differences (A is 55% preferred to B is meaningful).
- Unified scale across many variants.
- Robust to judge calibration drift (comparisons are relative).
Two wrinkles when you use Elo on a fixed offline set¶
Sequential Elo is order-dependent — bad for a fixed test set. Two fixes:
- Shuffle and average over many orderings (what LMSYS Arena does).
- Use Bradley-Terry / logistic regression — the order-independent batch MLE of the same model. Elo is the online SGD approximation; Bradley-Terry is the closed-form MLE.
With only 2 models, Elo is overkill — just report win-rate + confidence interval. Elo earns its keep with many models. And watch LLM-judge bias (position, length, self-preference) — Elo faithfully propagates it.
def update_elo(rating_a, rating_b, outcome, K=32):
"""outcome: 1 if A wins, 0 if B wins, 0.5 if tie."""
expected_a = 1 / (1 + 10**((rating_b - rating_a) / 400))
new_a = rating_a + K * (outcome - expected_a)
new_b = rating_b + K * ((1 - outcome) - (1 - expected_a))
return new_a, new_b
Used in LMSYS Chatbot Arena, Copilot Arena. Your edge in interviews: propose Elo for multi-version rollouts.
The LangSmith + agentevals stack¶
Because LangGraph is a LangChain product, LangSmith integration is essentially free — set two environment variables and every graph run is traced automatically:
import os
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "ls-..."
# Now graph.invoke(...) produces a full trace in LangSmith:
# every node, every LLM call, every tool call, with timing + tokens.
No manual instrumentation, the Pregel runtime emits a span per node automatically.
Regression suite pattern¶
from langsmith import Client
from agentevals.trajectory.llm import create_trajectory_llm_as_judge
client = Client()
dataset = client.create_dataset("agent-regression")
client.create_examples(dataset_id=dataset.id, examples=[
{"inputs": {"question": "weather in Delhi"},
"outputs": {"reference_answer": "38C clear"}},
])
trajectory_judge = create_trajectory_llm_as_judge(model="openai:gpt-5-mini")
def run_agent(inputs):
return graph.invoke({"messages": [HumanMessage(inputs["question"])]})
client.evaluate(
run_agent,
data="agent-regression",
evaluators=[trajectory_judge],
experiment_prefix="v2-prompt-change",
)
Pytest gating¶
import pytest
from langsmith import testing as t
@pytest.mark.langsmith
def test_agent_books_correct_flight():
result = run_agent({"question": "cheapest flight BLR to BOM tomorrow"})
t.log_outputs(result)
score = trajectory_judge(outputs=result["messages"], reference_outputs=...)
assert score["score"] > 0.8
Set thresholds, fail the pipeline when scores drop, bringing the same rigor as deterministic unit tests to agent quality.
Production sampling: online evaluators¶
LangSmith's online evaluators run on a sampled % of production traces. You configure them in the LangSmith UI, pick a sample rate, attach an evaluator, get continuous quality signals on real traffic. No code in your graph.
What's uniquely testable in LangGraph¶
| What | How LangGraph makes it easy |
|---|---|
| Did the right node run? | Trace shows every node execution |
| Did the router decide correctly? | Router is a pure function, unit test it |
| Trajectory (which path through the graph) | The sequence of nodes is in the trace |
| Tool-call correctness | Tool nodes are traced with args + results |
| Did the loop terminate correctly? | Step count + recursion limit visible in trace |
| State at each step | Checkpointer history = every intermediate state |
The last one is powerful: because the checkpointer stores every superstep's state (time travel), you can replay exactly what the agent saw at each decision point during debugging.
Interview Questions¶
Q5: Explain Elo rating and why it's better than absolute scoring for LLM eval.
Elo updates ratings based on pairwise win/loss outcomes. Applied to LLMs: run candidate A and B on same query, judge picks winner, update ratings. Advantages: sensitive to small differences (A preferred 55% of the time is meaningful), relative scale robust to judge calibration, unified across many variants. Example: LMSYS Chatbot Arena uses Elo with human voters; internal eval can use LLM-as-judge head-to-head.
Q7: Agentic functional correctness vs text quality — when does each win?
Text quality wins for conversational AI where the outcome is "user feels helped." Functional correctness wins for agentic systems with measurable outcomes: booking, purchasing, fixing code, processing data. Rule: if the task has a ground-truth final state, measure functional correctness. If the task is open-ended (write an essay, explain a concept), measure text quality with LLM-as-judge or human eval.
Q10: Design eval for a multi-agent system end-to-end.
Three planes. (1) Per-agent unit eval: each agent on its specific task with golden samples. (2) Trajectory eval: did the system reach correct final state? Measure functional correctness, step efficiency, tool selection accuracy. (3) Interaction eval: count unnecessary handoffs, redundant work, failure recovery. Layer it with: offline golden set on every PR, shadow A/B on production traffic, weekly human review sample, daily dashboard on CSAT + resolution + cost + latency.