Skip to content

Commit a075df1

Browse files
docs: add LangGraph integration documentation page
1 parent ed01b8e commit a075df1

1 file changed

Lines changed: 251 additions & 0 deletions

File tree

docs/integrations/langgraph.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# LangGraph Integration
2+
3+
groundlens provides a LangGraph-aware callback handler that automatically scores every LLM call in an agentic pipeline, builds a structured execution trace, and generates self-contained HTML triage reports.
4+
5+
## Installation
6+
7+
```bash
8+
pip install "groundlens[langgraph]"
9+
```
10+
11+
This installs `langgraph` and `langchain-core`.
12+
13+
## GroundlensLangGraphCallback
14+
15+
The callback intercepts every LLM call within a LangGraph execution, evaluates each response with groundlens, and accumulates the results into a structured `AgentTrace`.
16+
17+
### Basic Usage
18+
19+
```python
20+
from langgraph.graph import StateGraph
21+
from groundlens.integrations.langgraph import GroundlensLangGraphCallback
22+
23+
gl = GroundlensLangGraphCallback()
24+
25+
# Pass the callback in the LangGraph config
26+
result = app.invoke(
27+
{"question": "What is the capital of France?"},
28+
config={"callbacks": [gl]},
29+
)
30+
31+
# Get the execution trace
32+
trace = gl.get_trace()
33+
print(trace.summary())
34+
```
35+
36+
Output:
37+
38+
```
39+
Agent completed: 3 steps (523ms)
40+
✓ 2 trusted ⚠ 0 review ✗ 1 flagged
41+
Flagged: fact_check (DGI=0.180)
42+
```
43+
44+
### Configuration
45+
46+
```python
47+
gl = GroundlensLangGraphCallback(
48+
groundlens_model="all-MiniLM-L6-v2", # Embedding model
49+
context_key="context", # Metadata key for explicit context
50+
)
51+
```
52+
53+
| Parameter | Default | Description |
54+
|---|---|---|
55+
| `groundlens_model` | `"all-MiniLM-L6-v2"` | Sentence-transformer model for scoring |
56+
| `context_key` | `"context"` | Metadata key for explicit context override |
57+
58+
### Auto Context Detection
59+
60+
The callback automatically detects grounding context:
61+
62+
1. **Explicit context** — If metadata contains the `context_key`, that value is used.
63+
2. **Tool output** — If a tool call produced output before the LLM call, the tool output becomes the grounding context (SGI scoring).
64+
3. **No context** — When neither is available, DGI (ungrounded) scoring is applied.
65+
66+
This means tool-augmented nodes automatically get SGI scoring, while reasoning nodes that generate without retrieval get DGI scoring — no configuration required.
67+
68+
### Graph Node Tracking
69+
70+
The callback tracks which LangGraph node produced each LLM call. Node names are resolved from:
71+
72+
1. The `langgraph_node` key in chain metadata.
73+
2. The chain's serialized `name` field (excluding internal wrappers like `RunnableSequence`).
74+
75+
This enables per-node triage in the execution trace.
76+
77+
### Resetting State
78+
79+
To reuse the same callback across multiple invocations, call `reset()` between runs:
80+
81+
```python
82+
gl = GroundlensLangGraphCallback()
83+
84+
result1 = app.invoke(input1, config={"callbacks": [gl]})
85+
trace1 = gl.get_trace()
86+
87+
gl.reset()
88+
89+
result2 = app.invoke(input2, config={"callbacks": [gl]})
90+
trace2 = gl.get_trace()
91+
```
92+
93+
## AgentTrace
94+
95+
The `AgentTrace` object accumulates all evaluated steps and provides aggregate statistics, summaries, and export methods.
96+
97+
### Properties
98+
99+
| Property | Type | Description |
100+
|---|---|---|
101+
| `steps` | `list[AgentStep]` | Ordered list of evaluated steps |
102+
| `total_steps` | `int` | Total number of evaluated steps |
103+
| `flagged_steps` | `int` | Steps triaged as flagged |
104+
| `review_steps` | `int` | Steps triaged as needing review |
105+
| `trusted_steps` | `int` | Steps triaged as trusted |
106+
| `total_duration_ms` | `float` | Total LLM call time in milliseconds |
107+
108+
### Triage Summary
109+
110+
```python
111+
trace = gl.get_trace()
112+
print(trace.summary())
113+
```
114+
115+
The summary shows step counts by triage category and lists any flagged or review steps with their node name and score.
116+
117+
### Triage Loop
118+
119+
Iterate over steps to build custom handling logic:
120+
121+
```python
122+
trace = gl.get_trace()
123+
124+
for step in trace.steps:
125+
if step.triage == "flagged":
126+
print(f"{step.node_name}: {step.score.explanation}")
127+
elif step.triage == "review":
128+
print(f"? {step.node_name}: needs human review")
129+
else:
130+
print(f"{step.node_name}: trusted")
131+
```
132+
133+
### JSON Export
134+
135+
```python
136+
# As a Python dict
137+
data = trace.to_dict()
138+
139+
# As a JSON string
140+
json_str = trace.to_json(indent=2)
141+
```
142+
143+
### HTML Report
144+
145+
Generate a self-contained HTML triage report:
146+
147+
```python
148+
# Write to file
149+
trace.to_html("report.html")
150+
151+
# Or get the HTML string
152+
html = trace.to_html()
153+
```
154+
155+
The HTML report includes a visual summary of all steps with color-coded triage status, scores, timing, and full input/output text.
156+
157+
## AgentStep
158+
159+
Each step in the trace is an `AgentStep` dataclass with the following fields:
160+
161+
| Field | Type | Description |
162+
|---|---|---|
163+
| `node_name` | `str` | LangGraph node that produced this LLM call |
164+
| `step_index` | `int` | Execution order (0-based) |
165+
| `input_text` | `str` | Prompt sent to the LLM |
166+
| `output_text` | `str` | LLM response text |
167+
| `context` | `str \| None` | Grounding context (tool output), or `None` for DGI |
168+
| `score` | `GroundlensScore` | The groundlens evaluation result |
169+
| `triage` | `str` | `"trusted"`, `"review"`, or `"flagged"` |
170+
| `method` | `str` | `"sgi"` or `"dgi"` |
171+
| `duration_ms` | `float` | LLM call latency in milliseconds |
172+
173+
## Triage Classification
174+
175+
Each step is classified into one of three triage categories:
176+
177+
| Category | Condition | Meaning |
178+
|---|---|---|
179+
| **trusted** | `normalized >= 0.6` and not flagged | Response is well-grounded |
180+
| **review** | `normalized < 0.6` and not flagged | Low confidence — human review recommended |
181+
| **flagged** | `score.flagged == True` | Likely hallucination detected |
182+
183+
## Logging
184+
185+
The callback uses Python's `logging` module under the `groundlens.integrations.langgraph.callback` logger:
186+
187+
- **WARNING** — Flagged responses (likely hallucinations)
188+
- **INFO** — Passing responses
189+
- **DEBUG** — Lifecycle events (chain start/end, tool start/end, LLM start)
190+
- **ERROR** — Chain, tool, or LLM errors
191+
192+
```python
193+
import logging
194+
logging.basicConfig(level=logging.INFO)
195+
196+
gl = GroundlensLangGraphCallback()
197+
# Now groundlens callback events appear in logs
198+
```
199+
200+
## Complete Example
201+
202+
```python
203+
from langgraph.graph import StateGraph, END
204+
from langchain_openai import ChatOpenAI
205+
from groundlens.integrations.langgraph import GroundlensLangGraphCallback
206+
207+
# Build your LangGraph agent
208+
builder = StateGraph(dict)
209+
210+
def research(state):
211+
llm = ChatOpenAI()
212+
result = llm.invoke(state["question"])
213+
return {"research": result.content}
214+
215+
def synthesize(state):
216+
llm = ChatOpenAI()
217+
result = llm.invoke(f"Summarize: {state['research']}")
218+
return {"answer": result.content}
219+
220+
builder.add_node("research", research)
221+
builder.add_node("synthesize", synthesize)
222+
builder.set_entry_point("research")
223+
builder.add_edge("research", "synthesize")
224+
builder.add_edge("synthesize", END)
225+
app = builder.compile()
226+
227+
# Run with groundlens monitoring
228+
gl = GroundlensLangGraphCallback()
229+
result = app.invoke(
230+
{"question": "What are the latest advances in quantum computing?"},
231+
config={"callbacks": [gl]},
232+
)
233+
234+
# Inspect the trace
235+
trace = gl.get_trace()
236+
print(trace.summary())
237+
238+
# Export report
239+
trace.to_html("agent_report.html")
240+
241+
# Programmatic triage
242+
for step in trace.steps:
243+
if step.triage == "flagged":
244+
print(f"Node '{step.node_name}' flagged: {step.score.explanation}")
245+
```
246+
247+
## Why This Matters for Agentic AI
248+
249+
Multi-step agents make hallucination detection harder because errors compound across nodes. A hallucinated fact from an early reasoning step can propagate through tool calls and synthesis, producing a final answer that *looks* well-supported but rests on fabricated premises.
250+
251+
groundlens addresses this by scoring each LLM call independently at the node level. The triage trace shows exactly *where* in the pipeline confidence dropped, enabling targeted human review instead of blanket distrust of the entire output.

0 commit comments

Comments
 (0)