What this is
No new concepts in this lesson. This is the course's capstone: a small, real FastAPI service built entirely out of ideas from Lessons 1 through 24, combined into one thing. If you can read lesson.py and understand why every piece is there, you've mastered this course. If any piece feels unfamiliar, that's a sign to revisit the lesson it came from.
What it does
Ingests all five fixture notes and all five image sources (four standalone figures plus the one embedded in circuit-board-notebook.pdf) into a chromadb collection at startup, ten records total, each carrying modality, source, and image_path/image_bytes metadata. Serves GET /ask, which retrieves across both modalities, re-attaches the original image at generation time whenever the best match is one, cites whether each fact came from a text note or an image, and returns the original image's filename alongside the answer when relevant. Serves GET /image/{filename} to fetch that file directly.
Where each piece came from
def extract_images_from_pdf(pdf_path: Path) -> list[bytes]:Lesson 17: the one image source in this fixture set that isn't a standalone .png.
metadatas = [{"modality": ..., "source": ..., "image_path": ...}, ...]Lesson 22's State shape, unchanged: one chromadb collection, both modalities, image_path an empty string for text records.
if metadata["image_path"]: image_bytes = Path(metadata["image_path"]).read_bytes() parts.append(types.Part.from_bytes(...))Lesson 8 (re-attach the original image at generation time) plus Lesson 14 (modality-labeled citation prompt), running against chromadb metadata instead of a Python dict's image_path is not None field.
@app.get("/image/{filename}")def get_image(filename: str) -> FileResponse:Lesson 24, unchanged: serve the actual retrieved image back to the caller, not just a description of it.
Try this yourself
Without looking anything up:
- Add a new fixture note with no image at all, confirm ingestion and retrieval both work unmodified, on purpose, not every document needs every modality.
- Extend
get_image(or add a new route) to serve the PDF-extracted image back to a caller too, given the bytes are already extracted at ingest time, where would you cache them so a request doesn't need to re-parse the PDF every time? - Run
uvicorn lesson:app --reloadfrom this folder and hitGET /ask?q=...from a browser orcurl, confirm it behaves identically to theTestClientcalls in the script.
This is where Multimodal RAG, built by extending naive_rag's exact pipeline with one new idea (captioning-then-embed), ends up: a small, real, citation-aware, cross-modal service. Lesson 26 is a short, code-free look at where this specific architecture still falls short.