LangChain
LangChain is an open-source framework for building LLM applications with models, prompts, tools, retrievers, and agents (via create_agent).
The deepeval integration traces LangChain runs through a CallbackHandler that you pass into LangChain's config. Every agent run, model call, tool call, and retriever call becomes a span you can inspect, without rewriting your LangChain app.
deepeval's LangChain integration enables you to:
- Trace any LangChain run โ pass a
CallbackHandlerthrough LangChain's callbacks config per call. - Evaluate the complete ordered agent trajectory โ score the sequence across all applicable agent, LLM, tool, and retriever spans.
- Evaluate traces or individual components with
deepevalmetrics. - Run evals from scripts or CI/CD โ same callback, different surfaces.
- Customize trace and span data through callback kwargs and
next_*_spanstaging.
Getting Started
Installation
pip install -U deepeval langchain langchain-openaiLangChain is instrumented per-call: you decide which runs are traced by passing CallbackHandler(...) into LangChain's runtime config.
Instrument and evaluate
Create a CallbackHandler and pass it to the agent's invoke method.
from langchain.agents import create_agent
from deepeval.integrations.langchain import CallbackHandler
from deepeval.dataset import EvaluationDataset, Golden
from deepeval.metrics import TaskCompletionMetric
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[multiply],
system_prompt="Be concise.",
)
# Goldens are the inputs you want to evaluate.
dataset = EvaluationDataset(goldens=[Golden(input="What is 8 multiplied by 6?")])
# The `TaskCompletionMetric` is passed into the `evals_iterator`.
for golden in dataset.evals_iterator(metrics=[TaskCompletionMetric()]):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)Done โ
. You've run your first eval with full traceability into LangChain via deepeval.
What gets traced
Each LangChain call that receives a CallbackHandler produces a trace โ the end-to-end unit your user observes. Inside that trace are component spans for each callback LangChain emits:
- Agent span โ the top-level
create_agent/ runnableinvoke(...)call (the root of the tree). - LLM spans โ chat model and completion calls.
- Tool spans โ tool calls and function executions.
- Retriever spans โ retriever calls, when your app uses retrieval.
Trace โ what the user observes
โโโ Agent: math_agent โ one create_agent invoke(...) call
โโโ LLM: gpt-4o-mini โ component span: model chooses a tool
โโโ Tool: multiply โ component span: tool input + output
โโโ LLM: gpt-4o-mini โ component span: final answerThe trace and its component spans are independently evaluable.
Running evals
There are two surfaces for running evals against a LangChain app. Pick by where you want results to surface โ your terminal during development, or your CI pipeline as a pass/fail gate.
In CI/CD
Use the deepeval pytest integration. Each parametrized test invocation becomes one LangChain run; failing metrics fail the test, which fails the build.
import pytest
from langchain.agents import create_agent
from deepeval.integrations.langchain import CallbackHandler
from deepeval.dataset import EvaluationDataset, Golden
from deepeval.metrics import TaskCompletionMetric
from deepeval import assert_test
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
agent = create_agent(model="openai:gpt-4o-mini", tools=[multiply], system_prompt="Be concise.")
dataset = EvaluationDataset(goldens=[
Golden(input="What is 8 multiplied by 6?"),
Golden(input="What is 7 multiplied by 9?"),
])
@pytest.mark.parametrize("golden", dataset.goldens)
def test_langchain_agent(golden: Golden):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)
assert_test(golden=golden, metrics=[TaskCompletionMetric()])Run it with:
deepeval test run test_langchain_agent.pyIn a script
Use EvaluationDataset + evals_iterator. Each Golden becomes one LangChain run, and metrics passed to the iterator score the resulting trace end-to-end.
dataset = EvaluationDataset(goldens=[
Golden(input="What is 8 multiplied by 6?"),
Golden(input="What is 7 multiplied by 9?"),
])
for golden in dataset.evals_iterator(metrics=[TaskCompletionMetric()]):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)Evaluate agent trajectories
Trajectory evaluation scores the complete ordered trace: the agent's decisions, model calls, tool use, and other applicable spans in the sequence they occurred. This is distinct from evaluating only the final output or attaching a metric to an individual component span.
Pass trajectory metrics to evals_iterator so each instrumented LangChain run is evaluated at trace scope:
from deepeval.metrics import TaskCompletionMetric, StepEfficiencyMetric, PlanAdherenceMetric
from deepeval.integrations.langchain import CallbackHandler
from deepeval.dataset import EvaluationDataset, Golden
...
dataset = EvaluationDataset(goldens=[Golden(input="What is 8 multiplied by 6?")])
metrics = [TaskCompletionMetric(), StepEfficiencyMetric(), PlanAdherenceMetric()]
for golden in dataset.evals_iterator(metrics=metrics):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)See the trajectory evaluation guide for metric selection and interpretation. To score one step instead of the ordered run, apply metrics to components next.
Applying metrics to components
Passing metrics to evals_iterator evaluates the overall LangChain run. To evaluate a component instead, stage metrics onto the next span the callback opens.
Agent spans
Wrap the invocation in next_agent_span. The CallbackHandler drains the staged metric onto the root agent span opened by invoke(...) โ useful when you want a span-level score on the agent itself rather than the whole trace.
from deepeval.integrations.langchain import CallbackHandler
from deepeval.metrics import TaskCompletionMetric
from deepeval.tracing import next_agent_span
...
for golden in dataset.evals_iterator():
with next_agent_span(metrics=[TaskCompletionMetric()]):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)LLM calls
Wrap the invocation in next_llm_span. The CallbackHandler drains the staged metric onto the first LLM span it opens inside the block; later LLM calls in the same run get nothing.
from langchain.agents import create_agent
from deepeval.integrations.langchain import CallbackHandler
from deepeval.metrics import AnswerRelevancyMetric
from deepeval.tracing import next_llm_span
...
agent = create_agent(model="openai:gpt-4o-mini", tools=[multiply], system_prompt="Be concise.")
for golden in dataset.evals_iterator():
with next_llm_span(metrics=[AnswerRelevancyMetric()]):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)Retriever calls
Wrap the invocation in next_retriever_span to stage a metric (or a Confident AI metric_collection) on the first retriever span LangChain opens inside the block.
from deepeval.integrations.langchain import CallbackHandler
from deepeval.tracing import next_retriever_span
...
for golden in dataset.evals_iterator():
with next_retriever_span(metric_collection="retriever_v1"):
chain.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)Tool calls
For deterministic tool calls, use tool spans for traceability, inputs, outputs, and metadata. Prefer attaching metrics at the LLM or trace level rather than on tools.
Use deepeval's patched tool decorator when you want to attach a metric collection (or metrics) from the tool definition:
from deepeval.integrations.langchain import tool
@tool(metric_collection="tools_v1")
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * bCustomizing trace and span data
LangChain is instrumented per-call through callbacks, so customization happens at the callback or span-staging boundary.
- Use
CallbackHandlerkwargs for trace-level defaults likename,tags,metadata,thread_id, anduser_id. - Use
next_agent_span/next_llm_span/next_retriever_span/next_tool_spanto stage component-level fields onto the next span the callback opens. - Use tool bodies (or Python's patched
tooldecorator) for deterministic traceability, inputs, outputs, and metadata.
callback = CallbackHandler(
name="math-agent",
tags=["langchain", "math"],
metadata={"team": "support"},
user_id="user-123",
)
agent.invoke(
{"messages": [{"role": "user", "content": "What is 8 multiplied by 6?"}]},
config={"callbacks": [callback]},
)Advanced patterns
The primitives above โ CallbackHandler and next_*_span โ compose around one boundary: LangChain owns the callback lifecycle, and your code chooses where to stage component config for the next span the callback opens.
Score every matching span
Where next_*_span is one-shot, a scope-wide context applies to every matching span. Use this when an agent makes several model calls per run and you want all of them scored.
from deepeval.tracing import LlmSpanContext, trace
from deepeval.metrics import AnswerRelevancyMetric
...
with trace(llm_span_context=LlmSpanContext(metrics=[AnswerRelevancyMetric()])):
agent.invoke(
{"messages": [{"role": "user", "content": prompt}]},
config={"callbacks": [CallbackHandler()]},
)Evaluate components without trace-level metrics
Stage a metric on an LLM (or retriever) span, then run CI/CD or scripts without also passing metrics to the iterator / matcher. Trace-level metrics are end-to-end metrics: they are not strictly necessary when the component already carries the metric.
from deepeval.metrics import AnswerRelevancyMetric
from deepeval.tracing import next_llm_span
...
def run_agent(prompt: str):
with next_llm_span(metrics=[AnswerRelevancyMetric()]):
return agent.invoke(
{"messages": [{"role": "user", "content": prompt}]},
config={"callbacks": [CallbackHandler()]},
)No trace-level metrics required
This is how you'd run it:
import pytest
from deepeval import assert_test
...
@pytest.mark.parametrize("golden", dataset.goldens)
def test_component_metrics(golden: Golden):
run_agent(golden.input)
assert_test(golden=golden)deepeval test run test_langchain_agent.py...
for golden in dataset.evals_iterator():
run_agent(golden.input)Wrap a LangChain run in @observe
When the LangChain call is part of a larger operation, decorate the outer function with @observe. LangChain spans nest under your observed span when the callback runs inside it.
from deepeval.tracing import observe
...
@observe(name="respond_to_user")
def respond_to_user(prompt: str) -> str:
result = agent.invoke(
{"messages": [{"role": "user", "content": prompt}]},
config={"callbacks": [CallbackHandler()]},
)
return result["messages"][-1].contentAPI reference
CallbackHandler accepts the following trace-level kwargs. Each one is a default for runs that use that callback.
| Kwarg | Type | Description |
|---|---|---|
name | str | Default trace name. |
tags | list[str] | Tags applied to traces produced by this callback. |
metadata | dict | Trace metadata applied when the callback starts a trace. |
thread_id | str | Groups related runs into a single trace thread. |
user_id | str | Actor identifier for the trace. |
metrics | list | Trace-level metrics for the run. Prefer evals_iterator in eval scripts. |
metric_collection | str | Trace-level metric collection, for online evals on live traffic. |
test_case_id | str | Optional test case identifier. |
turn_id | str | Optional turn identifier for conversational traces. |
For native tracing helpers (@observe, update_current_trace, update_current_span) see the tracing reference.
FAQs
Can I evaluate a component inside my LangChain agent run?
next_agent_span / next_llm_span (or the retriever / tool helpers) around agent.invoke(...). The CallbackHandler drains it onto the first matching span. It is one-shot per run, so to score every step use a scope-wide context (trace(...)) or trace-level metrics on evals_iterator.Can I gate CI/CD on my LangChain agent's metrics?
CallbackHandler() into the agent's config inside a parametrized pytest test, then call assert_test(golden=golden, metrics=[...]) and run deepeval test run.Can I see these LangChain traces in a cloud UI?
deepeval login, Confident AI renders every agent, LLM, tool, and retriever span produced by the CallbackHandler in a shared dashboard โ no code changes.Can I monitor a LangChain app in production?
CallbackHandler in your production calls and set thread_id / user_id for grouping; when logged into Confident AI those live traces support online evals on real traffic.