Tutorial: RAG End to End
Goal: see every stage of the RAG pipeline with your own eyes — chunks in SQLite, retrieval scores, the evaluator's verdict on both an answerable and an unanswerable question, and tenant isolation. 20 minutes.
Prereq: Your First Agent. Concepts: RAG Overview.
1 · Create a knowledge agent and a corpus
curl -X POST http://localhost:8001/agents/ -H "Content-Type: application/json" -d '{
"name": "KB Agent", "type": "Customer Support",
"system_prompt": "You answer questions from the company knowledge base.",
"enabled_tools": "document_search"
}'Assume id = 4. Build a test document big enough to chunk (>800 chars):
python - <<'EOF'
faq = """Support tiers. Premium customers receive a 4-hour response SLA on all tickets.
Standard customers receive a 24-hour response SLA. Enterprise plans include a dedicated
technical account manager and quarterly reviews. """ * 3
policy = """Refund policy. Annual subscriptions may be refunded pro-rata within 60 days
of purchase. Monthly subscriptions are non-refundable once the billing period starts.
Refunds are processed to the original payment method within 5 business days. """ * 3
open("kb.txt", "w", encoding="utf-8").write(faq + policy)
EOF2 · Ingest and inspect the artifacts
curl -X POST http://localhost:8001/agents/4/documents/upload -F "file=@kb.txt" | jqThe response reports chunks_count (expect 2–3 for ~1.5 KB at 800/100 chunking). Now inspect both stores' records:
# SQLite: the chunks with their Chroma vector IDs
curl http://localhost:8001/agents/4/chunks | jq \
'.[] | {chunk_index, first_words: .chunk_text[0:50]}'
sqlite3 backend/app/agentops.db \
"SELECT chunk_index, vector_id FROM document_chunks WHERE agent_id=4;"Note the deterministic IDs (agent_4_document_N_chunk_i) — the SQLite↔Chroma bridge. Look for a chunk boundary mid-sentence: that's why the 100-char overlap exists.
3 · Raw retrieval — see what the evaluator will see
curl -X POST http://localhost:8001/agents/4/documents/search \
-H "Content-Type: application/json" \
-d '{"query": "how fast do premium customers get answers", "top_k": 2}' | jq \
'.results[] | {score, chunk_index, preview: .chunk_text[0:60]}'Two things to internalize: the query shares no keywords with "4-hour response SLA" yet matches — that's semantic retrieval; and score is a distance (lower = better), not a percentage (Retrieval).
4 · The graph answers…
curl -X POST http://localhost:8001/agents/4/documents/ask \
-H "Content-Type: application/json" \
-d '{"question": "What is the refund window for annual subscriptions?", "top_k": 3}' | jqInspect all three response layers: answer (grounded — "60 days… pro-rata"), sources (which chunks), retrieval_evaluation (has_answer: true + the evaluator's reason). The path taken: retrieve → evaluate → answer (Corrective RAG).
5 · …and refuses
curl -X POST http://localhost:8001/agents/4/documents/ask \
-H "Content-Type: application/json" \
-d '{"question": "What is the company vacation policy?", "top_k": 3}' | jqThe answer is the fixed refusal string; sources still lists the nearest (irrelevant) chunks and retrieval_evaluation.reason explains why they weren't enough. This is the point of corrective RAG: related-but-insufficient context gets refused, not hallucinated — and the refusal was cheaper than an answer (evaluator only, no generation — cost profile).
6 · Prove tenant isolation
Ask the same refund question as a different agent (your agent 2 or 3 — grant it document_search if needed):
curl -X POST http://localhost:8001/agents/3/documents/ask \
-H "Content-Type: application/json" \
-d '{"question": "What is the refund window for annual subscriptions?", "top_k": 3}' | jq \
'{answer, sources}'Refusal with empty (or foreign-corpus) sources — agent 3 cannot see agent 4's vectors because every query filters where={"agent_id": ...} (the isolation primitive).
7 · Check the telemetry trail
curl http://localhost:8001/agents/4/tool-calls | jq \
'.[] | {tool_name, success, latency_ms}'You'll see document_search and langgraph_corrective_rag entries — but notice no ToolCall for the upload and no cost fields on any of them: two documented observability gaps you now recognize on sight (coverage map).
What you learned
Chunking artifacts and the dual-store design · semantic retrieval and distance scores · both graph paths and their costs · tenant isolation by metadata filter · where RAG telemetry is thinner than the rest of the platform.
Next: Stage 4 of the Learning Guide — the frontend — or start shipping with the Guides.