Files
2026-07-13 13:32:05 +08:00

451 lines
17 KiB
Plaintext

---
id: langchain
title: LangChain
sidebar_label: LangChain
---
<IntegrationTagsDisplayer native={true} cicdEvals={true} traceability={true} />
[LangChain](https://www.langchain.com/) 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.
<AgentTraceTerminal
title="langchain_agent · deepeval"
ariaLabel="Example LangChain agent trace with per-step metric scores"
lines={[
{ kind: "cmd", name: "deepeval test run test_langchain_agent.py" },
{ kind: "blank" },
{ kind: "root", prefix: "●", name: "test_langchain_agent" },
{ kind: "blank", prefix: "│" },
{
kind: "agent",
prefix: "└─",
name: "math_agent",
metric: "Task Completion",
score: "0.96",
duration: "170ms",
pass: true,
},
{
kind: "llm",
prefix: " ├─",
name: "gpt-4o-mini · choose_tool",
metric: "G-Eval",
score: "0.44",
duration: "58ms",
pass: false,
},
{
kind: "tool",
prefix: " ├─",
name: "multiply(a=8, b=6)",
duration: "24ms",
},
{
kind: "llm",
prefix: " └─",
name: "gpt-4o-mini · final_answer",
metric: "Faithfulness",
score: "0.95",
duration: "66ms",
pass: true,
},
{ kind: "blank" },
{
kind: "summary",
name: "Trace score 0.78 · 2/3 metrics passed",
pass: false,
},
]}
/>
`deepeval`'s LangChain integration enables you to:
- **Trace any LangChain run** — pass `CallbackHandler(...)` through `config={"callbacks": [...]}` per call.
- **Evaluate traces or individual components** with `deepeval` metrics.
- **Run evals from scripts or CI/CD** — same callback, different surfaces.
- **Customize trace and span data** through callback kwargs, LangChain metadata, and `deepeval`'s tool decorator.
## Getting Started
<Steps>
<Step>
### Installation
```bash
pip install -U deepeval langchain langchain-openai
```
LangChain is instrumented per-call: you decide which runs are traced by passing `CallbackHandler(...)` into LangChain's runtime config.
</Step>
<Step>
### Instrument and evaluate
Create a `CallbackHandler` and pass it to the agent's `invoke` method.
```python title="langchain_agent.py" showLineNumbers
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 LangChain callback.
for golden in dataset.evals_iterator():
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler(metrics=[TaskCompletionMetric()])]},
)
```
Done ✅. You've run your first eval with full traceability into LangChain via `deepeval`.
</Step>
</Steps>
## 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 spans** — `create_agent(...)` runs and any nested runnable steps.
- **LLM spans** — chat model and completion calls.
- **Tool spans** — tool calls and function executions.
- **Retriever spans** — retriever calls, when your app uses retrieval.
```text
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 answer
```
The 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 (pytest)
Use the `deepeval` pytest integration. Each parametrized test invocation becomes one LangChain run; failing metrics fail the test, which fails the build.
```python title="test_langchain_agent.py" showLineNumbers
import pytest
from langchain.agents import create_agent
from deepeval import assert_test
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.")
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:
```bash
deepeval test run test_langchain_agent.py
```
### In a script
Use `EvaluationDataset` + `evals_iterator(...)`. Each `Golden` becomes one LangChain run; metrics score the resulting trace through the callback.
```python title="langchain_agent.py" showLineNumbers
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():
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler(metrics=[TaskCompletionMetric()])]},
)
```
## Applying metrics to components
Passing `metrics=[...]` to `CallbackHandler` evaluates the overall LangChain run. To evaluate a component instead, attach metrics where LangChain creates that component.
### Agent spans (sub-agents)
Wrap the invocation in `with next_agent_span(metrics=[...]):`. The `CallbackHandler` drains the staged metric onto the **first agent span** it opens inside the `with` block — useful for scoring a sub-agent (e.g. an agent invoked as a tool, or a nested `create_agent` run) in isolation.
```python title="langchain_agent.py" showLineNumbers
from langchain.agents import create_agent
from deepeval.integrations.langchain import CallbackHandler
from deepeval.metrics import TaskCompletionMetric
from deepeval.tracing import next_agent_span
...
agent = create_agent(model="openai:gpt-4o-mini", tools=[multiply], system_prompt="Be concise.")
for golden in dataset.evals_iterator():
with next_agent_span(metrics=[TaskCompletionMetric()]):
agent.invoke(
{"messages": [{"role": "user", "content": golden.input}]},
config={"callbacks": [CallbackHandler()]},
)
```
The same one-shot semantic as `next_llm_span` applies: only the first agent span in the run picks up the staged metric.
### LLM calls
Wrap the invocation in `with next_llm_span(metrics=[...]):`. The `CallbackHandler` drains the staged metric onto the **first LLM span** it opens inside the `with` block; later LLM calls in the same run get nothing. This is the same one-shot semantic used by `next_*_span` in the Pydantic AI / Strands / AgentCore / Google ADK integrations.
```python title="langchain_agent.py" showLineNumbers
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()]},
)
```
:::caution[One-shot per run]
`next_llm_span` stages a metric for the **first** LLM span LangChain opens inside the `with` block. Later LLM calls in the same `agent.invoke(...)` — e.g. the tool-choice turn followed by the final-answer turn — won't receive the staged metric. To score every LLM call, drive the loop yourself (`next_llm_span` per call) or score the run end-to-end with trace-level metrics on `CallbackHandler(metrics=[...])`.
:::
For deterministic tool calls, use tool spans for traceability, inputs, outputs, and metadata. Avoid attaching metrics directly to tool spans.
### Retriever calls
Wrap the invocation in `with next_retriever_span(...)` to stage a metric (or a Confident AI `metric_collection`) on the **first retriever span** LangChain opens inside the `with` block.
```python title="langchain_agent.py" showLineNumbers
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()]},
)
```
`next_retriever_span` accepts the same `metrics=[...]` / `metric_collection=...` kwargs as `next_llm_span`. The same one-shot semantic applies: only the first retriever span in the run picks up the staged config.
## Customizing trace and span data
LangChain is instrumented per-call through callbacks, so customization happens at the callback or span-staging boundary.
- Use `CallbackHandler(...)` kwargs for trace-level defaults like `name`, `tags`, `metadata`, `thread_id`, and `user_id`.
- Use `next_agent_span(...)` / `next_llm_span(...)` / `next_retriever_span(...)` / `next_tool_span(...)` to stage component-level fields (metrics, metric collections, test cases, custom span metadata) onto the next span the callback opens.
- Use tool spans for deterministic traceability, inputs, outputs, and metadata.
```python title="langchain_agent.py" showLineNumbers
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(...)`, `next_*_span(...)`, and `deepeval`'s tool decorator — 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.
### Evaluate subagents with `next_*_span`
`next_*_span(metrics=[...])` stages a metric for the next matching span the `CallbackHandler` opens. Use this when you want to evaluate a subagent or model step instead of the full run. Pick the helper that matches the span you want to score: `next_agent_span(...)` or `next_llm_span(...)`.
```python title="langchain_agent.py" showLineNumbers
from langchain.agents import create_agent
from deepeval.integrations.langchain import CallbackHandler
from deepeval.metrics import TaskCompletionMetric
from deepeval.tracing import next_agent_span
...
agent = create_agent(model="openai:gpt-4o-mini", tools=[multiply], system_prompt="Be concise.")
def run_agent(prompt: str):
with next_agent_span(metrics=[TaskCompletionMetric()]):
return agent.invoke(
{"messages": [{"role": "user", "content": prompt}]},
config={"callbacks": [CallbackHandler()]},
)
```
#### No trace-level metrics required
Trace-level metrics are end-to-end metrics: they score the whole trace. They are not strictly necessary here because the `TaskCompletionMetric` is staged for the agent span, so CI/CD and scripts only need to run the agent inside the staging block.
This is how you'd run it:
<Tabs items={["CI/CD", "Scripts"]}>
<Tab value="CI/CD">
```python title="test_langchain_agent.py" showLineNumbers
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)
```
```bash
deepeval test run test_langchain_agent.py
```
</Tab>
<Tab value="Scripts">
```python title="langchain_agent.py" showLineNumbers
...
for golden in dataset.evals_iterator():
run_agent(golden.input)
```
</Tab>
</Tabs>
### 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.
```python title="langchain_agent.py" showLineNumbers
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].content
```
## API 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` | Metrics applied to the LangChain run. |
| `metric_collection` | `str` | Metric collection applied to the LangChain run. |
| `test_case_id` | `str` | Optional test case identifier. |
| `turn_id` | `str` | Optional turn identifier for conversational traces. |
For native tracing helpers (`@observe`, `with trace(...)`, `update_current_trace`, `update_current_span`) see the [tracing reference](/docs/evaluation-llm-tracing).
## FAQs
<FAQs
qas={[
{
question: "Can I evaluate a sub-agent inside my LangChain agent run?",
answer: (
<>
Yes. Stage a metric with{" "}
<code>with next_agent_span(metrics=[...])</code> right before{" "}
<code>agent.invoke(...)</code>, and the <code>CallbackHandler</code>{" "}
drains it onto that sub-agent's span — scoring the sub-agent in
isolation without touching the parent. It's one-shot per run, so to
score every step you drive the loop yourself or use trace-level
metrics on <code>CallbackHandler(metrics=[...])</code>.
</>
),
},
{
question: "Can I gate CI/CD on my LangChain agent's metrics?",
answer: (
<>
Yes. Pass a <code>CallbackHandler()</code> into the agent's{" "}
<code>config</code> inside a parametrized <code>pytest</code> test,
then call <code>assert_test(golden=golden, metrics=[...])</code> and
run <code>deepeval test run</code> so a failing metric fails the
build.
</>
),
},
{
question: "Can I see these LangChain traces in a cloud UI?",
answer: (
<>
Yes, optionally. After <code>deepeval login</code>,{" "}
<a href="https://www.confident-ai.com">Confident AI</a> renders every
agent, LLM, tool, and retriever span produced by the{" "}
<code>CallbackHandler</code> in a shared dashboard — no code changes.
</>
),
},
{
question: "Can I monitor a LangChain app in production?",
answer: (
<>
Yes. Keep the <code>CallbackHandler</code> in your production calls and
set <code>thread_id</code> / <code>user_id</code> for grouping; when
logged into Confident AI those live traces support{" "}
<a href="https://www.confident-ai.com/docs/llm-tracing/online-evals">
online evals
</a>{" "}
on real traffic.
</>
),
},
]}
/>