A course of twelve bite-sized Jupyter notebooks — plus a bonus deep dive — that teach DSPy's abstractions from the ground up: you build a multi-hop QA program over a fictional micro-world the model has never seen, define a metric, and then watch a series of increasingly clever optimizers improve the program — reading the learned prompts at every step. The finale reruns the entire workflow on real data (HotPotQA).
Written against DSPy 3.3.0. Each notebook is self-contained (open any of them cold), takes 15–40 minutes, and states its own cost up front.
The course leans on one analogy throughout — if you know PyTorch, you already know the shape of DSPy:
| PyTorch | DSPy |
|---|---|
nn.Module / forward() | dspy.Module / forward() (define-by-run graph) |
| layer shapes | signatures — "question -> answer" (typed I/O contracts) |
| weights θ | per-predictor instructions + few-shot demos |
| loss function | metric(example, prediction) — plain Python you write |
optimizer.step() | teleprompter.compile(program, trainset=...) |
| eval loop | dspy.Evaluate(devset=..., metric=...) |
state_dict() | program.save("prog.json") — human-readable learned prompts |
| TensorBoard / profiler | MLflow tracing (or Arize Phoenix) — notebook 11 |
| # | Notebook | You learn | Est. cost* |
|---|---|---|---|
| 00 | Hello, DSPy | the big idea; dspy.LM / configure / context; caching | < $0.01 |
| 01 | Signatures & Predict | typed I/O contracts; Prediction; inspect_history | < $0.02 |
| 02 | Under the hood | adapters (signature → prompt); Example; θ = instructions + demos; save() | < $0.02 |
| 03 | Modules & the graph | custom modules; LM + plain-Python computation graphs; named_predictors | ~$0.03 |
| 04 | Data & evaluation | splits; metrics & the trace contract; Evaluate; error analysis | ~$0.05 |
| 05 | Bootstrap few-shot | the program teaches itself demos; random search; DSPy-paper map | ~$0.15–0.30 |
| 06 | Instruction optimization | COPRO; MIPROv2 (grounded proposal + Bayesian search); paper maps | ~$0.30–0.60 |
| 07 | Feedback & GEPA | feedback metrics; reflective evolution; Pareto pools; GEPA-paper map | ~$0.40–0.80 |
| 08 | Agents & inference-time | ReAct + tools; dynamic graphs; Refine / BestOfN | ~$0.05 |
| 09 | The optimizer landscape | finetuning & BetterTogether; ensembles; which optimizer when | ~$0–0.05 |
| 10 | Capstone: HotPotQA | the whole workflow on real data, end to end | ~$1–2 |
| 11 | Observability & tracing | BaseCallback hooks; MLflow traces + optimizer runs; Arize Phoenix option | ~$0.05 |
| 12 | Bonus: under the LLM layer | adapters on the wire; the format-fallback ladder; response_format protocol; constrained decoding | < $0.01 |
* at the default model (deepseek-v3.2 via OpenRouter). Whole course ≈ $2.50–5 (typically less at the default). DSPy's disk cache means re-running cells and notebooks is free.
Run them in order — concepts build strictly layer by layer — but every notebook rebuilds its world from scratch, so returning to any one of them later works fine.
Requires Python ≥ 3.10.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then edit .env: your API key, and (optionally) your models
jupyter lab
Keys & models — one .env for the whole course. Every notebook's config cell reads .env first, so the model choice is global: set MODEL once and all twelve notebooks follow (STRONG_MODEL is the pricier model a few notebooks use as a teacher/reflection LM). No .env? The config cell falls back to the defaults — openrouter/deepseek/deepseek-v3.2 and openrouter/deepseek/deepseek-v4-flash — and prompts for the key. A per-notebook override stays one uncomment away in each config cell (code beats .env).
The defaults route through OpenRouter (one key, every major model — handy for the "swap the model, keep the program" experiments; create a key at openrouter.ai → Keys). Any LiteLLM provider string works instead (openai/..., anthropic/..., gemini/..., ollama_chat/...); model slugs current as of authoring — check openrouter.ai/models if one 404s.
Caching. DSPy caches every LM call in ~/.dspy_cache (on by default). Interrupting and re-running notebooks is safe and free; only genuinely new prompts cost money.
Observability (optional). Notebook 11 teaches tracing; every other notebook carries two small optional sections right after its config cell — run the Phoenix cell or the MLflow cell (obs.enable_phoenix() / obs.enable_mlflow() + obs.mlflow_ui()) to trace that notebook, or skip both — either way the cell ends by printing the localhost link where the traces appear. The obs.py helper makes this safe to leave and return to: sessions are dated (dspy-lab-YYYY-MM-DD) so days never collide, the MLflow store is pinned inside the repo (gitignored) and falls back cleanly if a remnant file is unusable, and the UIs are found-or-started from a cell — reusing a healthy server, skipping ports held by strangers. No terminal, no accounts, no extra API keys; the only thing you do outside Jupyter is click the printed localhost link. Phoenix keeps its data in ~/.phoenix.
00-…-12-….ipynb the course (12 is an optional bonus deep dive)
.env.example copy to .env: API key + global MODEL / STRONG_MODEL choice
obs.py optional tracing helpers: dated sessions, find-or-start UIs (notebook 11)
data/corpus.json the Aldervane micro-world: 30 fictional passages (so the LM can't cheat)
data/qa.json 48 hand-written QA pairs — train 24 / dev 16 / test 8, with gold passage ids
artifacts/ created at runtime by program.save() (gitignored)
scripts/check_data.py data integrity + retrieval-guarantee checks
scripts/validate_notebooks.py structural/citation/frozen-block checks (no execution)
scripts/api_audit.py audits every dspy.* symbol used against the installed dspy
Three breaking changes in DSPy 3.x that make pre-3.x snippets fail, all covered in the notebooks: per-provider clients (dspy.OpenAI(...)) are gone — use dspy.LM("provider/model"); dspy.Assert / dspy.Suggest are gone — use dspy.Refine / dspy.BestOfN (notebook 08); TypedPredictor is gone — plain Predict handles typed signatures natively.
Wherever a component comes from research, the notebook embeds a paper concept ↔ code table:
Few labels → LabeledFewShot. Metric + ~20 examples → BootstrapFewShot (+RandomSearch). Want machine-written 0-shot prompts → MIPROv2. Your metric can explain failures in text → GEPA. Own the weights → BootstrapFinetune / BetterTogether. High stakes per call → wrap in Refine / BestOfN or Ensemble. The full decision guide is notebook 09.
17 commits
14 commits
Jupyter Notebook
92.2%
Python
7.8%
A course of twelve bite-sized Jupyter notebooks — plus a bonus deep dive — that teach DSPy's abstractions from the ground up: you build a multi-hop QA program over a fictional micro-world the model has never seen, define a metric, and then watch a series of increasingly clever optimizers improve the program — reading the learned prompts at every step. The finale reruns the entire workflow on real data (HotPotQA).
Written against DSPy 3.3.0. Each notebook is self-contained (open any of them cold), takes 15–40 minutes, and states its own cost up front.
The course leans on one analogy throughout — if you know PyTorch, you already know the shape of DSPy:
| PyTorch | DSPy |
|---|---|
nn.Module / forward() | dspy.Module / forward() (define-by-run graph) |
| layer shapes | signatures — "question -> answer" (typed I/O contracts) |
| weights θ | per-predictor instructions + few-shot demos |
| loss function | metric(example, prediction) — plain Python you write |
optimizer.step() | teleprompter.compile(program, trainset=...) |
| eval loop | dspy.Evaluate(devset=..., metric=...) |
state_dict() | program.save("prog.json") — human-readable learned prompts |
| TensorBoard / profiler | MLflow tracing (or Arize Phoenix) — notebook 11 |
| # | Notebook | You learn | Est. cost* |
|---|---|---|---|
| 00 | Hello, DSPy | the big idea; dspy.LM / configure / context; caching | < $0.01 |
| 01 | Signatures & Predict | typed I/O contracts; Prediction; inspect_history | < $0.02 |
| 02 | Under the hood | adapters (signature → prompt); Example; θ = instructions + demos; save() | < $0.02 |
| 03 | Modules & the graph | custom modules; LM + plain-Python computation graphs; named_predictors | ~$0.03 |
| 04 | Data & evaluation | splits; metrics & the trace contract; Evaluate; error analysis | ~$0.05 |
| 05 | Bootstrap few-shot | the program teaches itself demos; random search; DSPy-paper map | ~$0.15–0.30 |
| 06 | Instruction optimization | COPRO; MIPROv2 (grounded proposal + Bayesian search); paper maps | ~$0.30–0.60 |
| 07 | Feedback & GEPA | feedback metrics; reflective evolution; Pareto pools; GEPA-paper map | ~$0.40–0.80 |
| 08 | Agents & inference-time | ReAct + tools; dynamic graphs; Refine / BestOfN | ~$0.05 |
| 09 | The optimizer landscape | finetuning & BetterTogether; ensembles; which optimizer when | ~$0–0.05 |
| 10 | Capstone: HotPotQA | the whole workflow on real data, end to end | ~$1–2 |
| 11 | Observability & tracing | BaseCallback hooks; MLflow traces + optimizer runs; Arize Phoenix option | ~$0.05 |
| 12 | Bonus: under the LLM layer | adapters on the wire; the format-fallback ladder; response_format protocol; constrained decoding | < $0.01 |
* at the default model (deepseek-v3.2 via OpenRouter). Whole course ≈ $2.50–5 (typically less at the default). DSPy's disk cache means re-running cells and notebooks is free.
Run them in order — concepts build strictly layer by layer — but every notebook rebuilds its world from scratch, so returning to any one of them later works fine.
Requires Python ≥ 3.10.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then edit .env: your API key, and (optionally) your models
jupyter lab
Keys & models — one .env for the whole course. Every notebook's config cell reads .env first, so the model choice is global: set MODEL once and all twelve notebooks follow (STRONG_MODEL is the pricier model a few notebooks use as a teacher/reflection LM). No .env? The config cell falls back to the defaults — openrouter/deepseek/deepseek-v3.2 and openrouter/deepseek/deepseek-v4-flash — and prompts for the key. A per-notebook override stays one uncomment away in each config cell (code beats .env).
The defaults route through OpenRouter (one key, every major model — handy for the "swap the model, keep the program" experiments; create a key at openrouter.ai → Keys). Any LiteLLM provider string works instead (openai/..., anthropic/..., gemini/..., ollama_chat/...); model slugs current as of authoring — check openrouter.ai/models if one 404s.
Caching. DSPy caches every LM call in ~/.dspy_cache (on by default). Interrupting and re-running notebooks is safe and free; only genuinely new prompts cost money.
Observability (optional). Notebook 11 teaches tracing; every other notebook carries two small optional sections right after its config cell — run the Phoenix cell or the MLflow cell (obs.enable_phoenix() / obs.enable_mlflow() + obs.mlflow_ui()) to trace that notebook, or skip both — either way the cell ends by printing the localhost link where the traces appear. The obs.py helper makes this safe to leave and return to: sessions are dated (dspy-lab-YYYY-MM-DD) so days never collide, the MLflow store is pinned inside the repo (gitignored) and falls back cleanly if a remnant file is unusable, and the UIs are found-or-started from a cell — reusing a healthy server, skipping ports held by strangers. No terminal, no accounts, no extra API keys; the only thing you do outside Jupyter is click the printed localhost link. Phoenix keeps its data in ~/.phoenix.
00-…-12-….ipynb the course (12 is an optional bonus deep dive)
.env.example copy to .env: API key + global MODEL / STRONG_MODEL choice
obs.py optional tracing helpers: dated sessions, find-or-start UIs (notebook 11)
data/corpus.json the Aldervane micro-world: 30 fictional passages (so the LM can't cheat)
data/qa.json 48 hand-written QA pairs — train 24 / dev 16 / test 8, with gold passage ids
artifacts/ created at runtime by program.save() (gitignored)
scripts/check_data.py data integrity + retrieval-guarantee checks
scripts/validate_notebooks.py structural/citation/frozen-block checks (no execution)
scripts/api_audit.py audits every dspy.* symbol used against the installed dspy
Three breaking changes in DSPy 3.x that make pre-3.x snippets fail, all covered in the notebooks: per-provider clients (dspy.OpenAI(...)) are gone — use dspy.LM("provider/model"); dspy.Assert / dspy.Suggest are gone — use dspy.Refine / dspy.BestOfN (notebook 08); TypedPredictor is gone — plain Predict handles typed signatures natively.
Wherever a component comes from research, the notebook embeds a paper concept ↔ code table:
Few labels → LabeledFewShot. Metric + ~20 examples → BootstrapFewShot (+RandomSearch). Want machine-written 0-shot prompts → MIPROv2. Your metric can explain failures in text → GEPA. Own the weights → BootstrapFinetune / BetterTogether. High stakes per call → wrap in Refine / BestOfN or Ensemble. The full decision guide is notebook 09.
17 commits
14 commits
Jupyter Notebook
92.2%
Python
7.8%