Multi-Turn Evals: Scoring Long Conversations End-to-End
Learn how to evaluate multi-turn agent conversations with trajectory scoring, context tracking, and end-to-end metrics that go beyond single-turn accuracy.
Published on • September 15, 2026
AI Assistant

Single-turn evals are straightforward: give the model a prompt, check the output. But real agent conversations span dozens of turns with branching decisions, tool calls, and evolving context. Multi-turn evals measure whether your agent maintains coherence, remembers critical information, and reaches correct conclusions across an entire session.
Why Single-Turn Evals Fall Short
A chatbot might score 95% on individual Q&A benchmarks but still fail catastrophically in production because:
- It contradicts itself after turn 10
- It forgets constraints established in turn 2
- It repeats the same tool call that already failed
- It loses track of the user’s goal when the conversation branches
Multi-turn evals capture these failure modes by scoring the entire trajectory, not isolated snapshots.
Anatomy of a Multi-Turn Eval
The Golden Trajectory
A golden trajectory is a reference conversation that represents ideal behavior:
golden_trajectory = {
"task": "Book a flight from NYC to London for next Tuesday under $800",
"turns": [
{"role": "user", "content": "I need to fly from NYC to London next Tuesday"},
{"role": "assistant", "content": "Let me search for flights...", "tool_calls": [{"name": "search_flights", "args": {"origin": "NYC", "destination": "LHR", "date": "2026-10-14"}}]},
{"role": "tool", "content": "[3 results: $650, $720, $890]"},
{"role": "assistant", "content": "Found 3 options. The $650 and $720 options are within your budget. The $650 flight has a 2-hour layover in Dublin."},
{"role": "user", "content": "I prefer nonstop if possible"},
{"role": "assistant", "content": "Let me filter for nonstop flights...", "tool_calls": [{"name": "search_flights", "args": {"origin": "NYC", "destination": "LHR", "date": "2026-10-14", "nonstop": True}}]},
{"role": "assistant", "content": "The only nonstop option is $890, which exceeds your $800 budget. The best within budget is the $720 flight with a 1-hour layover. Shall I book that?"}
],
"expected_outcome": "Recommended the $720 non-exact-match flight with explanation"
}
Scoring Dimensions
Multi-turn evals require scoring across multiple dimensions simultaneously:
from dataclasses import dataclass
from typing import Optional
@dataclass
class TurnScore:
relevance: float # Does this turn advance the task?
coherence: float # Is it consistent with previous turns?
tool_accuracy: float # Were the right tools called with correct args?
memory: float # Does it recall earlier context?
@dataclass
class TrajectoryScore:
task_completion: float # Did the agent finish the task?
turn_scores: list[TurnScore]
total_turns: int
efficiency: float # Did it minimize unnecessary turns?
overall: float # Weighted composite
Implementation with LangGraph
LangGraph’s checkpoint system makes it natural to evaluate multi-turn flows:
from langgraph.graph import StateGraph, MessagesState
from langgraph.checkpoint.memory import MemorySaver
from langsmith import evaluate, Client
# Define your agent graph
def agent_node(state: MessagesState):
# Your agent logic here
response = call_llm(state["messages"])
return {"messages": [response]}
def tool_node(state: MessagesState):
# Execute tool calls
results = execute_tools(state["messages"])
return {"messages": results}
graph = StateGraph(MessagesState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
graph.add_conditional_edges("agent", should_use_tools, {"yes": "tools", "no": "__end__"})
graph.add_edge("tools", "agent")
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
# Define multi-turn eval target
def eval_target(inputs: dict) -> dict:
config = {"configurable": {"thread_id": inputs["session_id"]}}
for turn in inputs["conversation"]:
result = app.invoke({"messages": [{"role": "user", "content": turn}]}, config)
return {"messages": result["messages"]}
# Custom evaluator for trajectory quality
def trajectory_scorer(run, example):
messages = run.outputs["messages"]
golden = example.outputs["expected_trajectory"]
# Score task completion
task_complete = check_task_completion(messages, golden["task"])
# Score context retention
context_score = check_context_retention(messages, golden["constraints"])
# Score tool usage efficiency
tool_score = check_tool_efficiency(messages, golden["expected_tools"])
return {
"key": "trajectory_score",
"score": (task_complete + context_score + tool_score) / 3,
"comment": f"Task: {task_complete}, Context: {context_score}, Tools: {tool_score}"
}
# Run evaluation
results = evaluate(
eval_target,
data="multi-turn-test-set",
evaluators=[trajectory_scorer],
experiment_prefix="v2-agent-eval"
)
Key Metrics for Multi-Turn Evals
Context Retention Score
Did the agent remember constraints from early turns?
def context_retention_score(messages: list, constraints: list[str]) -> float:
scores = []
for constraint in constraints:
# Check if constraint is referenced in later turns
mentioned = any(
constraint.lower() in msg.content.lower()
for msg in messages[3:] # Skip first 3 turns
if hasattr(msg, 'content')
)
scores.append(1.0 if mentioned else 0.0)
return sum(scores) / len(scores) if scores else 0.0
Turn Efficiency Ratio
Did the agent reach the conclusion without wasting turns?
def efficiency_ratio(actual_turns: int, optimal_turns: int) -> float:
if actual_turns <= optimal_turns:
return 1.0
# Penalize excess turns logarithmically
import math
excess = actual_turns - optimal_turns
return max(0, 1.0 - math.log2(excess + 1) / 10)
Coherence Across Branches
When the user changes direction, does the agent adapt without losing track?
def coherence_score(messages: list) -> float:
contradictions = 0
for i, msg in enumerate(messages):
if msg.role == "assistant":
for j, earlier in enumerate(messages[:i]):
if earlier.role == "assistant":
if contains_contradiction(earlier.content, msg.content):
contradictions += 1
total_assistant_turns = sum(1 for m in messages if m.role == "assistant")
return max(0, 1.0 - (contradictions / max(total_assistant_turns, 1)))
Building a Multi-Turn Eval Dataset
Curate Diverse Scenarios
Your eval set should cover:
- Happy path: Straightforward task completion
- Constraint changes: User modifies requirements mid-conversation
- Tool failures: Agent must recover from bad tool responses
- Ambiguous queries: Agent must ask clarifying questions
- Multi-step tasks: Requires 5+ tool calls to complete
eval_dataset = [
{
"scenario": "constraint_change",
"conversation": [
"I want a red sedan under $30K",
"Actually, make it an SUV",
"Does it have leather seats?"
],
"expected_behavior": "Adapt to SUV, maintain price constraint, check features"
},
{
"scenario": "tool_failure_recovery",
"conversation": [
"What's the weather in Paris?",
"The API returned an error, try again",
"Never mind, just tell me the forecast for tomorrow"
],
"expected_behavior": "Retry once, then offer alternative approach"
}
]
Production Evaluation Pipeline
# GitHub Actions integration
name: Agent Evals
on:
pull_request:
paths:
- 'src/agent/**'
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run multi-turn evals
run: |
python -m eval.run_multi_turn \
--dataset golden-trajectories.json \
--threshold 0.85 \
--fail-on-regression
- name: Comment PR with results
uses: actions/github-script@v7
with:
script: |
const results = require('./eval/results.json');
github.rest.issues.createComment({
issue_number: context.issue.number,
body: `## Agent Eval Results\n- Trajectory Score: ${results.avg_score}\n- Context Retention: ${results.context_score}\n- Task Completion: ${results.task_score}`
});
Common Pitfalls
- Over-fitting to golden trajectories: Agent might take a different but equally valid path. Score outcomes, not exact steps.
- Ignoring cost: A 3-turn solution at $0.01/turn beats a 10-turn solution at $0.005/turn.
- Static eval sets: Update your eval data as your agent evolves. Stale evals give false confidence.
- Missing edge cases: Always include conversations where the agent should refuse or escalate.
Multi-turn evals are computationally expensive but irreplaceable for production agents. Start with 50-100 curated trajectories covering your critical paths, and expand from there.