← Back to Blog

🧠 Agent Evaluation Patterns — The New AI Standard

A complete guide to evaluating AI agents: tool-use correctness, trajectory efficiency, multi-turn conversations, and reflection scoring — with code examples.


🧠 Core Concept: Evaluating the "How", Not Just the "What"

With a normal LLM call, you check the final response. But with an agent, that's not enough — it can reach the right result via a terrible path (wrong tools, unnecessary loops, etc.).

You need three levels of evaluation:

🤔 So why does this matter?

In 2026, evaluation is not a research checkbox — it's a production gate. Anyone can connect an LLM to a vector database. Very few build measurable, reliable AI systems.

🔧 Tool-Use Correctness — Did it pick the right tool?

This evaluates exactly how the agent interacts with external systems — not just what it finally returns.

📊 What it measures:

💻 Code Example:

from deepeval.metrics import BaseMetric

class ToolUseCorrectnessMetric(BaseMetric):
    name = "Tool Use Correctness"
    
    def evaluate(self, agent_trace, expected_trajectory):
        """
        Agent trace: [{"tool_name": "get_diff", "args": {}}, ...]
        Expected trajectory: ["get_diff", "check_style", "suggest_fix"]
        """
        actual_tools = [step["tool_name"] for step in agent_trace]

        # 1. Check if all required tools were called
        missing = set(expected_trajectory) - set(actual_tools)

        # 2. Check if unnecessary tools were called (wasted cost)
        extra = set(actual_tools) - set(expected_trajectory)

        # 3. Check tool call schema validity
        for step in agent_trace:
            if not self._validate_schema(step["tool_name"], step["args"]):
                return False, "Invalid tool arguments"

        return len(missing) == 0 and len(extra) <= 1
    
    def _validate_schema(self, tool_name, args):
        """JSON schema validation for each tool's expected arguments."""
        # ... real code would use jsonschema.validate()...
        return True
💡 Key insight: Tool-use correctness splits into invocation (should I call a tool at all?), selection (which tool?), and structural validity (are the args right?).

📈 Trajectory Efficiency — Did it take the shortest path?

Even when the final result is correct, your agent may take an expensive, slow, or looping path. This evaluates efficiency — everything about cost and learning.

📊 What it measures:

💻 Code Example:

from collections import Counter

def evaluate_trajectory_efficiency(agent_trace, max_iterations=10):
    """Evaluates whether the agent's trajectory was efficient."""
    tool_history = [step["tool_name"] for step in agent_trace]

    # Detect repeated tool calls (looping behavior)
    tool_counts = Counter(tool_history)
    redundant_calls = sum(c - 1 for c in tool_counts.values() if c > 2)

    iterations_used = len(agent_trace)
    is_efficient = iterations_used <= max_iterations and redundant_calls == 0

    cost_score = max(0, 1.0 - (redundant_calls / max_iterations))

    return {
        "is_efficient": is_efficient,
        "iterations_used": iterations_used,
        "redundant_calls": redundant_calls,
        "cost_score": round(cost_score, 2)
    }

# Example results:
# Agent A (good):   {"is_efficient": True, "iterations_used": 4, "redundant_calls": 0, "cost_score": 1.0}
# Agent B (bad):    {"is_efficient": False, "iterations_used": 15, "redundant_calls": 8, "cost_score": 0.3}

🔍 Patterns to detect:

💬 Multi-Turn Evaluation — Does it handle conversation context?

Real agents have many conversation turns. A single-turn eval misses context carryover, clarification, substitution, and error recovery.

📊 What it measures:

🌍 Real-World Test Case:

# Test case for a customer support agent:

test_multi_turn = [
    {
        "user_query": "I need to reset my password. My email is test@example.com",
        "agent_response": "I'll help you reset your password. Sending a link to test@example.com",
        "tool_calls": [{"name": "send_password_reset", "args": {"email": "test@example.com"}}]
    },
    {
        "user_query": "Actually, my email changed. It's lktinh@gmail.com now",
        "agent_response": "Updated your email to lktinh@gmail.com. Sending the reset link there.",
        "tool_calls": [{"name": "update_email", "args": {"email": "lktinh@gmail.com"}}]
    },
    {
        "user_query": "Send it again",
        "agent_response": "Sending password reset to lktinh@gmail.com",  # ✅ Remembered!
        "tool_calls": [{"name": "send_password_reset", "args": {"email": "lktinh@gmail.com"}}]
    }
]

# Expected: memory_score=2, error_recovery=True, is_coherent=True
💡 Key insight: Multi-turn evaluation is not just about remembering facts — it's about testing whether the agent can recover from errors, clarify, and keep the conversation on track.

🔄 Reflection Scoring — Can the agent self-correct?

A good agent doesn't just find the right answer — it also knows when it's making a mistake and corrects itself. This is reflection scoring.

📊 What it measures:

🌍 Real Example (PR Assistant):

This pattern is especially important for high-stakes applications like code review, banking, or medicine — where "good enough" isn't good enough.

🏗️ Putting It All Together — The Evaluation Pipeline

In production, you run all four patterns together as a composite score. This is what you'd do in your CI/CD pipeline:

def evaluate_agent_full(agent, test_case):
    """Full agent evaluation — runs all 4 patterns on one test case."""
    result = agent.run(test_case["question"])
    trace = result.trace

    evaluations = {
        "tool_use": evaluate_tool_use_correctness(trace, test_case.expected_tools),
        "trajectory": evaluate_trajectory_efficiency(trace),
        "multi_turn": evaluate_multi_turn_conversation(trace.turns, test_case.memory),
        "reflection": evaluate_reflection(trace, test_case.failure_point),
        "final_answer": evaluate_final_output(result.output, test_case.expected_answer),
    }

    composite = (
        0.25 * evaluations["tool_use"]["score"] +
        0.20 * evaluations["trajectory"]["cost_score"] +
        0.20 * evaluations["multi_turn"]["memory_score"] +
        0.15 * evaluations["reflection"]["score"] +
        0.20 * evaluations["final_answer"]["score"]
    )

    return {
        "composite_score": round(composite, 3),
        "passed": composite >= 0.7
    }

This composite score allows you to see where your agent is strong and where it's weak — not just "passed or failed".

🔑 Here's what really matters in 2026:

1. Evaluate Retrieval Before Generation — If the retrieval fails, generation cannot recover.

2. Evaluate Groundedness — Is the answer supported by retrieved documents, or is the model hallucinating?

3. Evaluate Agent Behavior, Not Just Final Output — did the agent choose the correct tool? did it call unnecessary tools?

4. Measure Latency and Cost — Accuracy alone is not enough.

5. Test Failure Scenarios — what happens if a tool API fails?

6. Use Structured Test Sets — do not rely only on manual testing.

7. Continuous Evaluation in Production — data changes, user behavior changes.

🔚 Final Thought

In 2026, building AI agent systems is not impressive. Evaluating them properly is. Anyone can connect an LLM to a vector database. Very few build measurable, reliable AI systems.

If you want to learn more about agent evaluation, watch our expanded post on MLFlow Evaluation and DeepEval — PyTest-style Agent Testing.