Skip to content

Agent Evaluation

Agents are non-deterministic and complex — you can't test them like regular code. Evaluation means measuring success rate, tracking failure modes, and iteratively improving. This covers metrics, benchmarking, and building evaluation pipelines.

Table of Contents

Installation / Setup

pip install langchain-openai

Core Syntax

Agent evaluation dimensions

Dimension What to measure How
Task completion Did the agent finish the task? Binary pass/fail
Correctness Is the answer correct? Compare to ground truth
Efficiency How many steps/tools used? Count steps, API calls
Safety Did it avoid harmful actions? Check guardrails held
Cost Token usage, API calls Track usage per run
Latency Time to complete Wall-clock measurement

Failure modes

Failure mode Description Detection
Hallucination Agent makes up facts Compare to sources
Tool misuse Wrong tool or wrong args Log tool calls
Infinite loop Agent never finishes Step count limit
Wrong plan Steps don't lead to solution Check intermediate results
Context overflow Too many tokens Token count check
Permission error Tries disallowed actions Guardrail logs
Slow response Takes too long Timeout check

Basic evaluation

def evaluate_agent(agent, test_cases):
    results = {"passed": 0, "failed": 0, "errors": []}

    for case in test_cases:
        try:
            response = agent.invoke({"messages": [("human", case["input"])]})
            answer = response["messages"][-1].content

            # check correctness
            if case.get("expected"):
                correct = case["expected"].lower() in answer.lower()
            else:
                correct = len(answer) > 0   # basic sanity check

            if correct:
                results["passed"] += 1
            else:
                results["failed"] += 1
                results["errors"].append({
                    "input": case["input"],
                    "expected": case.get("expected"),
                    "got": answer[:200],
                })
        except Exception as e:
            results["failed"] += 1
            results["errors"].append({"input": case["input"], "error": str(e)})

    results["success_rate"] = results["passed"] / len(test_cases)
    return results

Common Operations

Track efficiency metrics

def evaluate_efficiency(agent, test_cases):
    metrics = []
    for case in test_cases:
        response = agent.invoke({"messages": [("human", case["input"])]})
        messages = response["messages"]

        tool_calls = sum(1 for m in messages if hasattr(m, "tool_calls") and m.tool_calls)
        total_tokens = sum(m.usage_metadata.get("total_tokens", 0)
                          for m in messages if hasattr(m, "usage_metadata"))

        metrics.append({
            "steps": len(messages),
            "tool_calls": tool_calls,
            "tokens": total_tokens,
        })

    return {
        "avg_steps": sum(m["steps"] for m in metrics) / len(metrics),
        "avg_tool_calls": sum(m["tool_calls"] for m in metrics) / len(metrics),
        "avg_tokens": sum(m["tokens"] for m in metrics) / len(metrics),
    }

LLM-as-judge evaluation

from openai import OpenAI

client = OpenAI(api_key="YOUR_API_KEY")

def judge_response(query, response, reference=None):
    prompt = f"""Rate this agent response on a scale of 1-5:
Query: {query}
Response: {response}
{"Reference: " + reference if reference else ""}

Score (1-5) and brief explanation:"""

    result = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.0,
    )
    return result.choices[0].message.content

Track failure modes

def categorize_failures(errors):
    categories = {
        "hallucination": [], "tool_misuse": [], "wrong_plan": [],
        "slow": [], "error": [],
    }
    for err in errors:
        if "hallucin" in str(err).lower():
            categories["hallucination"].append(err)
        elif "tool" in str(err).lower():
            categories["tool_misuse"].append(err)
        elif "timeout" in str(err).lower():
            categories["slow"].append(err)
        else:
            categories["error"].append(err)
    return categories

Practical Examples

Example 1 — End-to-end agent evaluation

import time

def full_evaluation(agent, test_cases):
    results = {
        "total": len(test_cases),
        "passed": 0, "failed": 0,
        "times": [], "tokens": [], "scores": [],
        "failures": [],
    }

    for case in test_cases:
        start = time.time()
        try:
            response = agent.invoke({"messages": [("human", case["input"])]})
            elapsed = time.time() - start
            answer = response["messages"][-1].content

            score = judge_response(case["input"], answer, case.get("expected"))

            results["times"].append(elapsed)
            results["scores"].append(score)
            if "4" in score or "5" in score:
                results["passed"] += 1
            else:
                results["failed"] += 1
                results["failures"].append({"input": case["input"], "score": score})
        except Exception as e:
            results["failed"] += 1
            results["failures"].append({"input": case["input"], "error": str(e)})

    results["avg_time"] = sum(results["times"]) / len(results["times"])
    results["success_rate"] = results["passed"] / results["total"]
    return results

Example 2 — Regression testing

def regression_test(agent, test_cases, threshold=0.8):
    """Ensure quality doesn't drop below threshold."""
    results = full_evaluation(agent, test_cases)

    assert results["success_rate"] >= threshold, (
        f"Regression detected: {results['success_rate']:.2%} < {threshold:.2%}"
    )
    print(f"✓ Pass rate: {results['success_rate']:.2%} (threshold: {threshold:.2%})")
    return results

Example 3 — A/B testing agents

def ab_test(agent_a, agent_b, test_cases, n_runs=5):
    """Compare two agent versions."""
    scores_a, scores_b = [], []

    for _ in range(n_runs):
        for case in test_cases:
            r_a = agent_a.invoke({"messages": [("human", case["input"])]})
            r_b = agent_b.invoke({"messages": [("human", case["input"])]})

            s_a = float(judge_response(case["input"], r_a["messages"][-1].content).split()[0])
            s_b = float(judge_response(case["input"], r_b["messages"][-1].content).split()[0])

            scores_a.append(s_a)
            scores_b.append(s_b)

    avg_a = sum(scores_a) / len(scores_a)
    avg_b = sum(scores_b) / len(scores_b)
    print(f"Agent A: {avg_a:.2f}, Agent B: {avg_b:.2f}")
    return "A" if avg_a > avg_b else "B"

Common Patterns

Pattern: golden dataset

# maintain a set of known-good test cases
golden_dataset = [
    {"input": "What is Python?", "expected": "programming language", "category": "factual"},
    {"input": "Sort this list", "expected": "sorted list", "category": "code"},
    {"input": "Summarize this document", "expected": "summary", "category": "analysis"},
]

Pattern: monitor in production

def production_monitor(query, response, metadata):
    """Log production metrics for monitoring."""
    log_entry = {
        "query": query,
        "response_length": len(response),
        "tools_used": metadata.get("tool_calls", 0),
        "latency_ms": metadata.get("latency", 0),
        "tokens": metadata.get("tokens", 0),
    }
    # send to monitoring system
    save_to_log(log_entry)

Pattern: iterative improvement

def improve_from_failures(agent, failures, llm):
    """Analyze failures and suggest improvements."""
    failure_summary = "\n".join(f"- {f['input']}: {f.get('error', 'wrong answer')}"
                                for f in failures[:10])

    suggestions = llm.invoke(
        f"""Based on these agent failures, suggest improvements:
{failure_summary}

Suggest specific changes to tools, prompts, or routing."""
    ).content
    return suggestions

Best Practices

  • Build a golden test set — 20–50 cases covering common scenarios.
  • Track success rate over time — catch regressions.
  • Log everything — queries, responses, tool calls, latencies.
  • Use LLM-as-judge for subjective quality; exact match for factual tasks.
  • Test edge cases — empty inputs, adversarial queries, long inputs.
  • Monitor in production — real-world data reveals unexpected failures.
  • Iterate from failures — analyze and fix the most common failure modes first.

Common Mistakes

Mistake Fix
No evaluation at all Build a test set; measure before shipping
Testing only happy paths Add edge cases and failure scenarios
Ignoring efficiency Track steps, tokens, latency
Manual evaluation only Automate with LLM-as-judge
Not monitoring production Log metrics continuously

Troubleshooting

Symptom Cause / Fix
Low success rate Check tool descriptions, prompt quality
Agent too slow Reduce steps, use faster model
High cost Track token usage; optimize prompts
Inconsistent results Fix temperature; add deterministic fallback

Official Documentation

Last Updated

2026-09-06 — Initial version.