Where we left off
Lesson 23 split this course's pipeline into ingest() (run once) and ask() (run per question), specifically so it matches how a real web service works. This lesson wires those two functions into a FastAPI app, the same pattern naive_rag Lesson 24 already covered in depth, brief here on purpose: this course's own pedagogical weight belongs to Lesson 16's error-compounding demonstration, not a second full FastAPI walkthrough. If any of this lesson's mechanics (lifespan, app.state, TestClient) feel unfamiliar, read naive_rag Lesson 24's README first, it explains each one in full; this lesson only shows what changed to fit this course's State.
What actually changed from naive_rag Lesson 24
Almost nothing structurally. The one real difference: app.state now holds this course's State NamedTuple (graph and collection) instead of a bare chromadb.Collection, because this course's ask() needs both pieces, not just one.
@asynccontextmanagerasync def lifespan(app: FastAPI): docs = sorted(NOTES_DIR.glob("*.md")) app.state.graph_state = ingest(docs) yield
@app.get("/ask", response_model=AskResponse)def ask_endpoint(q: str, k: int = 2) -> AskResponse: return AskResponse(answer=ask(q, app.state.graph_state, k))ingest() runs once at startup, same as naive_rag Lesson 24. ask_endpoint calls Lesson 23's ask() unchanged. Everything else, lifespan, TestClient, the single /ask route, is identical in shape to naive_rag Lesson 24.
Checkpoint
- This lesson is Lesson 23's
ingest()/ask()wired to FastAPI'slifespanand one route, exactlynaive_ragLesson 24's pattern. - The only real change from that lesson:
app.stateholds this course's two-fieldState, not a bareCollection, because this course'sask()needs the graph and the vector index both. - Everything running inside
/askis Lesson 23'sask(), unaware it's being called from an HTTP route instead of a script'smain().
If anything here still feels unclear, read naive_rag Lesson 24's README for the fuller walkthrough, then ask before moving to Lesson 25.