Optimize LLM Pipelines with DSPy in Python (2026) — stop hand-editing brittle prompts. Declare signatures, compose modules, then compile against a metric.
You already extract typed objects with Instructor and build agents with Pydantic AI. DSPy sits beside them: programmable LM pipelines you can optimize (MIPROv2 / GEPA) instead of rewriting prompts by gut feel.
TL;DR
- Signatures declare inputs/outputs — not prompt strings
- Modules (
Predict,ChainOfThought,ReAct, customModule) compose like PyTorch layers - Optimizers compile demos + instructions against your metric
- Pair with LiteLLM when you need one gateway for many providers
Install
pip install dspy
# or: uv add dspy
export OPENAI_API_KEY=sk-...
Configure an LM
import dspy
lm = dspy.LM("openai/gpt-4o-mini", api_key=__import__("os").environ["OPENAI_API_KEY"])
dspy.configure(lm=lm)
Example 1 — signature + ChainOfThought
import dspy
class AnswerQuestion(dspy.Signature):
"""Answer the question briefly and accurately."""
question: str = dspy.InputField()
answer: str = dspy.OutputField(desc="one or two sentences")
qa = dspy.ChainOfThought(AnswerQuestion)
pred = qa(question="What does DSPy optimize in an LLM pipeline?")
print(pred.answer)
# reasoning steps live on pred.reasoning when using ChainOfThought
You described what to produce. DSPy fills in the prompt machinery — and later, an optimizer can rewrite instructions and few-shots for this signature.
Example 2 — small module + MIPROv2 sketch
import dspy
class DraftThenCritique(dspy.Module):
def __init__(self):
super().__init__()
self.draft = dspy.ChainOfThought("topic -> outline")
self.critique = dspy.Predict("outline, topic -> improved_outline")
def forward(self, topic: str):
draft = self.draft(topic=topic)
return self.critique(outline=draft.outline, topic=topic)
program = DraftThenCritique()
# Tiny trainset — use real labeled examples in production
trainset = [
dspy.Example(topic="async Python", improved_outline="1) event loop 2) await 3) tasks").with_inputs("topic"),
dspy.Example(topic="HTTP caching", improved_outline="1) freshness 2) ETag 3) CDN").with_inputs("topic"),
]
def metric(example, pred, trace=None) -> float:
text = (pred.improved_outline or "").lower()
# toy metric: reward numbered steps
return 1.0 if "1)" in text and "2)" in text else 0.0
# MIPROv2: proposes instructions + demos, searches combinations
optimizer = dspy.MIPROv2(metric=metric, auto="light")
optimized = optimizer.compile(program, trainset=trainset)
result = optimized(topic="FastAPI dependency injection")
print(result.improved_outline)
# optimized.save("outline_pipeline.v1.json") # persist for later load
auto="light" keeps the search cheap for demos. For larger datasets, raise the budget or try newer optimizers such as GEPA when your metric and cost allow.
When DSPy vs Instructor vs Pydantic AI
- Instructor — one-shot schema extraction / validation
- Pydantic AI — typed agents, tools, deps
- DSPy — multi-step LM programs you want to compile against a metric
Production tips
- Keep secrets in env vars; configure LMs in one place
- Write a metric that matches product quality (exact match is rarely enough)
- Start with
auto="light"; measure before/after on a held-out set save()/load()optimized programs so you do not recompile every deploy- Log traces; treat signatures as your API contract across model swaps
Wrap-up
In 2026, serious LLM apps outgrow hand-tuned prompts. DSPy gives you signatures, composable modules, and optimizers like MIPROv2 so quality becomes a compile step — not a late-night prompt rewrite.