Where we left off
Every lesson so far re-captions all four images and re-embeds all nine records on every single run. That was fine for four images; it stops being fine the moment a corpus has hundreds. Captioning is a vision call per image (Lesson 4), the most expensive step this course's pipeline has, more expensive than embedding a text chunk, since it sends a full image and asks for a paragraph back rather than a short string and a vector. This lesson applies naive_rag Lesson 13's fix (save to JSON, load instead of rebuild) to that specific cost.
The code, piece by piece
def save_store(store: list[dict], path: Path) -> None: serializable = [ {**record, "image_path": str(record["image_path"]) if record["image_path"] else None} for record in store ] path.write_text(json.dumps(serializable))One wrinkle naive_rag Lesson 13 never had: image_path is a Path object, and json.dumps doesn't know how to serialize one. Converting it to a plain string (None stays None) before saving, and back to a Path after loading, is the entire adaptation needed, everything else about persistence (a record is JSON-serializable, a vector is just a list of floats) is identical to the text-only version.
def load_store(path: Path) -> list[dict]: records = json.loads(path.read_text()) for record in records: if record["image_path"] is not None: record["image_path"] = Path(record["image_path"]) return recordsThe reverse conversion, run once at load time, restoring image_path to a real Path so Lesson 8's generation code (record["image_path"].read_bytes()) keeps working unmodified against a loaded store, exactly as it does against a freshly built one.
Checkpoint
- Captioning is the most expensive step this course's pipeline has, one vision call per image; persisting the store avoids re-paying it on every run, the same motivation
naive_ragLesson 13 had for text embeddings, now mattering even more. Pathobjects need converting to and from plain strings around a JSON round-trip; everything else about the record survives unchanged.- This is still a single flat file, adequate for this course's small fixture set; Lesson 20 replaces it with
chromadb, which persists automatically and scales past what a JSON file comfortably handles.
If anything here still feels unclear, ask before moving to Lesson 13.