SHEN YIFull-Stack & AI

Paris • Singapore • Shanghai

RAG Formation · Lesson · Foundations & measurement

Trace one question through the real code

Follow one request from the API boundary to retrieval, context assembly, generation, citation, and evaluation.

$ lesson --status
▸ course RAG Formation
▸ lesson 02 / 16
▸ phase Foundations & measurement
▸ status Complete
● build → measure → learn

Standalone lesson

Learn the full lesson and test your understanding here.

The 16-lesson RAG Formation curriculum is complete. This page contains the complete lesson content, local reference material, and instant browser exercises.

Today’s tangible win

By the end of this lesson, you'll be able to point at the exact file and function behind every stage of a single `/api/v1/qa` request.

Watch the walkthrough

1 · The question we're tracing

Question:
“Quel est le délai de clôture d'un plan d'action pour une non-conformité majeure ?”

Expected source: `PR-QA-MRD-009`

Expected fact: `45 jours`

We're not going to read every file top to bottom. Instead, we'll follow the request from the outside in, and only stop to look at the functions it actually touches.

2 · The call chain

Client

↓ `POST /api/v1/qa`

`routes/qa.py` → `ask_question(request)`

↓ delegates

`qa_service.py` → `answer_question_internal(request)`

↓ runs four retrieval paths

`search_qdrant()` · `search_meilisearch()` · `query_lightrag_context()`

↓ assembles evidence (Which function decides which source items survived?)

`utils.build_context()`

↓ derives citations (Which function converts surviving items into citations?)

`utils.sources_from_items()`

↓ builds prompt and generates

`utils.build_prompt()` → `completion.generate()`

↓ returns

`models.AnswerResponse`

Keep the request-path cheat sheet open in another tab while you work through this.

3 · What each stage actually does

StageCurrent implementationWhy it matters
Routeroutes/qa.py validates the incoming request with Pydantic, then hands off.Routes stay thin on purpose; the real logic lives elsewhere.
Orchestrateqa_service.py checks the services are up, cleans up the question, reads backend config, and kicks off retrieval.It's the coordinator for the whole request, and the best place to start an end-to-end trace.
RetrieveQdrant hands back semantic chunks and summaries, Meilisearch returns keyword matches, and LightRAG supplies graph context.If evidence is missing, check retrieval before you blame the prompt.
Assemblebuild_context() splits the budget fairly across channels and drops whole items rather than truncating them.Whatever doesn't survive this stage, the model never gets to see.
Citesources_from_items() only looks at the items that actually made it into the prompt.A citation should never point to evidence the model never saw.
Generatebuild_prompt() instructs the model to stick to the given context and refuse anything unsupported; the completion service then writes the answer.Even evidence that's right there can still get skipped, muddled, or contradicted.

4 · Reconstruct the call chain — from memory

Close the call-chain diagram in section 2. Without scrolling back to it, fill in this table for the question we're tracing — recall beats re-reading for making this stick:

StageFunction
Route__________
Orchestrate__________
Retrieve (three functions)__________
Assemble (decides what survives)__________
Cite (never cites dropped evidence)__________
Generate__________

Check yourself against section 2. Anything you couldn't recall is exactly what to re-read before moving on — that gap is the point of doing it this way round.

5 · Verify against the running system

The call chain lives in api/, which isn't part of this free course — but the running API is, via the demo stack. Ask it the traced question directly and confirm your trace against the real response, with demo/setup_demo.py up running:

curl -s -X POST http://localhost:8000/api/v1/qa \
  -H "Content-Type: application/json" \
  -d "{\"question\": \"Quel est le délai de clôture d'un plan d'action pour une non-conformité majeure ?\", \"workspace\": \"meridian_demo\"}"

In the JSON response: sources should include PR-QA-MRD-009, and answer should state 45 jours. If either is missing, you've just found a real failure to diagnose with the trace you built in section 4 — was it a retrieval miss or a generation miss?

6 · Check your reconstruction

So — which function actually does the final context assembly?

Why does `sources_from_items(context_items)` matter so much?

If `/api/v1/search` doesn't turn up `PR-QA-MRD-009`, which family of functions should you dig into first?

Mission connection

For an auditor or QA professional, an answer is only as trustworthy as the path you can explain behind it. That's exactly why a simple call map pays off when something breaks: it tells you where to look first — the index, the context budget, the citation logic, or the model's output.

Next action

Fill in all six blanks in the data-flow table. Then answer this in a single sentence:

Why might a source show up in the raw retrieval results but never make it into the final API citations?

Ask me follow-up questions about anything — any function, any stage — that's still fuzzy. Once you've filled in the six blanks and answered the final question, Lesson 2 is done — move on to whichever lesson you want next.