A multi-step retrieval-augmented generation workflow built with LangGraph. The pipeline answers questions about company policy documents through a structured 5-node graph with a quality gate and hallucination check.
[rewrite_query] → [retrieve_docs] → [grade_relevance] → [generate_answer] → [check_hallucination]
│
┌───────────────────────┘
│ (if not grounded, max 2 retries)
└→ [generate_answer]
Nodes:
rewrite_query— rewrites the user question for better retrieval precisionretrieve_docs— vector search over company policy KB (simulated)grade_relevance— LLM filters irrelevant retrieved chunksgenerate_answer— synthesizes answer with inline citationscheck_hallucination— verifies answer is grounded in retrieved docs
Company policy Q&A — answers HR questions:
- "How many days a week can I work from home?" → cites Remote Work Policy v2.3
- "What equipment expenses can I claim?" → cites Equipment Policy v1.1
langgraph-workflow/
├── workflow.py # LangGraph graph definition
├── record_cassettes.py # Capture live pipeline runs
├── requirements.txt
└── tests/
├── cassettes/
│ ├── remote_work_policy.json # 4 LLM calls, 5 node spans
│ └── equipment_stipend.json
└── test_rag_workflow.py # Node-order + quality + budget tests
cd examples/langgraph-workflow
pip install -r requirements.txtpytest tests/ -vExpected output:
tests/test_rag_workflow.py::TestNodeExecution::test_rewrite_query_node_executed[remote_work] PASSED
tests/test_rag_workflow.py::TestNodeExecution::test_node_order_rewrite_before_retrieve[remote_work] PASSED
tests/test_rag_workflow.py::TestAnswerQuality::test_remote_work_mentions_days PASSED
tests/test_rag_workflow.py::TestAnswerQuality::test_remote_work_includes_citation PASSED
...
22 passed in 0.34s
export OPENAI_API_KEY=sk-...
python record_cassettes.py| Concept | Where |
|---|---|
LangGraphAdapter auto-capture |
record_cassettes.py |
SpanKind.AGENT_STEP inspection |
TestNodeExecution |
| Node order assertions | test_node_order_rewrite_before_retrieve |
| Conditional edge testing | test_generate_before_hallucination_check |
| Citation regex assertions | test_remote_work_includes_citation |
MockLLM.add_sequential_responses |
test_full_pipeline_with_mocks |
| Direct function testing (no graph) | test_retrieve_docs_node_direct |
The cassettes capture each node as an AGENT_STEP span. You can assert
on individual nodes without re-running the full graph:
from evalcraft import replay
from evalcraft.core.models import SpanKind
run = replay("tests/cassettes/remote_work_policy.json")
# Get all node names in execution order
node_names = [
s.name for s in run.cassette.spans
if s.kind == SpanKind.AGENT_STEP
]
print(node_names)
# ['node:rewrite_query', 'node:retrieve_docs', 'node:grade_relevance',
# 'node:generate_answer', 'node:check_hallucination']Override retrieval results to test the hallucination retry path:
from evalcraft.replay.engine import ReplayEngine
engine = ReplayEngine("tests/cassettes/remote_work_policy.json")
# If you modify the cassette so is_grounded=False in check_hallucination,
# the conditional edge should trigger a regeneration loop