End-to-End LLM Evaluation
End-to-end evaluation assesses the observable inputs and outputs of your LLM application and treats it as a black box β you only care about what goes in and what comes out, not the path the system took to get there. The shape of "input" and "output" depends entirely on what your app does:
- Tool-using agent treated as a black box β input is the user's task, output is the final answer plus the tools that were called.
- Multi-turn chatbot / support agent β input is the scenario the user is in, output is the full conversation.
- RAG / QA app β input is a question, output is the answer (and the retrieved context, if you want to score faithfulness).
- Document summarization β input is the source document, output is the summary.
- Classifier / extractor β input is a chunk of text, output is the label or the structured fields you pulled out.
- Writing assistant / rewriter β input is the draft (and any instructions), output is the rewritten text.

This page explains the concepts behind end-to-end evaluation. For the actual step-by-step walkthroughs, jump to the right flavor for your application:
- Single-Turn End-to-End Evals β for any LLM app where one input maps to one output (agents treated as a black box, RAG / QA, summarization, classifiers, etc.).
- Multi-Turn End-to-End Evals β for chatbots and conversational agents where the unit of evaluation is the whole conversation.
If you need to evaluate the internal path taken by an AI agent or long-horizon agent, use trajectory-based evaluation instead.
Treating Your App as a Black Box
In end-to-end evaluation, you only describe what's observable from outside your LLM application β the input you sent, the output that came back, and any context that was used along the way. You do not describe the retrieval algorithm, the chain of LLM calls inside an agent, or any internal reasoning steps. That's the whole point of "end-to-end": you're grading the result, not the path the system took to get there.
Concretely, the parameters you populate on a test case are the entire surface your metrics see.
For single-turn apps, you populate fields on an LLMTestCase:
inputβ what you sent into your app (the question, document, draft, task, etc.).actual_outputβ what your app produced (the answer, summary, label, rewritten text, agent's final reply).retrieval_contextβ for RAG-style apps, the chunks your retriever returned. Required by metrics likeFaithfulnessMetricandContextualRelevancyMetric.tools_calledβ for agentic apps, the tools the agent invoked. Required by metrics likeToolCorrectnessMetricandArgumentCorrectnessMetric.expected_output/expected_toolsβ optional gold references, used by reference-based metrics.contextβ optional extra background, used by some reference-based metrics.
For multi-turn apps, you populate fields on a ConversationalTestCase:
scenarioβ what the simulated user is trying to do.expected_outcomeβ what success looks like.turnsβ the sequence ofTurnobjects (each with aroleandcontent) that make up the conversation.
Notice what's not there: there's no place to describe "the retriever's prompt", "the tool argument schema", or "the inner LLM call that produced this answer." If a metric needs to score one of those things in isolation, end-to-end isn't the right fit.
Single-Turn vs Multi-Turn
Pick the flavor that matches your application:
| Single-Turn | Multi-Turn | |
|---|---|---|
| Test case | LLMTestCase | ConversationalTestCase |
| Dataset entry | Golden | ConversationalGolden |
| What's evaluated | One input β one output | A full conversation (a sequence of Turns) |
| How test cases are made | You invoke your app on each golden and build the test case from the result | The ConversationSimulator drives a synthetic user against your chatbot until the scenario plays out |
| Typical apps | Agents-as-black-box, RAG / QA, summarization, classifiers, writing assistants | Chatbots, support agents, multi-turn assistants |
| Metric base class | BaseMetric | BaseConversationalMetric |
| Walkthrough | Single-Turn E2E Evals β | Multi-Turn E2E Evals β |
The two flavors live on different test case classes because the unit of evaluation is genuinely different (one exchange vs many), and deepeval will refuse to mix them in the same test run.
End-to-End vs Trajectory-Based vs Component-Level
End-to-end, trajectory-based, and component-level evaluation use different scopes. End-to-end sees the system as a black box, trajectory-based sees the complete chain inside an agent, and component-level isolates one internal span.
In each case, you attach metrics to a different unit of work:
- End-to-end β the unit is the whole app. One test case per run of your app, scoring the final input β final output.
- Trajectory-based β the unit is the agent's complete ordered execution path, scoring how its internal steps work together.
- Component-level β the unit is each
@observe'd span. Many test cases per run of your app β one per span you've chosen to grade β each scoring the input β output of that span.
| End-to-End | Trajectory-Based | Component-Level | |
|---|---|---|---|
| What you score | The final user-visible output (the system as one black-box component) | The complete ordered chain of decisions, tool calls, and intermediate steps | Individual internal spans (retriever, tool call, sub-agent, etc.) |
| How metrics are attached | To a test case or as black-box metrics on the trace | To the complete trace through evals_iterator() | To each span, via @observe(metrics=[...]) |
| Best for | Flat applications, multi-turn conversations, or final-output quality checks | AI agents and long-horizon agents where the execution path affects quality | Complex applications where individual components need to be diagnosed or graded |
| Tracing required | No | Yes | Yes |
You don't have to choose just one. With the recommended evals iterator path, black-box metrics can score the final result, trajectory metrics can score the complete agent path, and metrics attached to individual spans with @observe(metrics=[...]) can score components in the same traced run.
When should you choose end-to-end?
Choose end-to-end evaluation when:
- Your LLM application has a "flat" architecture that fits naturally into a single
LLMTestCase(agents treated as a black box, RAG / QA, summarization, single-shot classifiers, writing assistants, etc.) - Your application is multi-turn (chatbots, support agents) and you want to score the whole conversation rather than each step.
- Your application is a complex agent, but you've concluded that component-level evaluation gives you too much noise and you'd rather grade the final outcome.
In short: you care about the result, not the path the system took to get there. Most of the quickstart is end-to-end evaluation.
Two Ways to Run a Test Run
Both single-turn and (for evaluate()) multi-turn give you a choice between two equivalent code paths:
| Approach | What it looks like | When to choose it |
|---|---|---|
evaluate() | Build a list of LLMTestCases (or ConversationalTestCases) up front, hand them to a single evaluate() call. | You want a self-contained script with no tracing dependency. |
dataset.evals_iterator() with tracing β recommended (single-turn only) | Instrument your app with @observe, then loop over goldens with the iterator, passing metrics=[...]. deepeval builds the test cases from the captured trace. | Your app is (or will be) instrumented with tracing. You also get a full per-test-case trace view on Confident AI for free. |
For new single-turn projects we recommend the iterator β same amount of code, plus traces, plus the same setup carries over to component-level evaluation later.
Multi-turn end-to-end evaluation only uses evaluate() today; the iterator form is single-turn only.
What's Next
- Walk through a single-turn end-to-end evaluation.
- Walk through a multi-turn end-to-end evaluation using the
ConversationSimulator. - Evaluate the complete internal path of an agent with trajectory-based evaluation.
- Run end-to-end evals in CI/CD pipelines using
pytestanddeepeval test run. - Compare with component-level evaluation if your app has internal structure worth grading.