Tool deep diveOptimization

DSPy: write the program, let an optimizer write the prompts

An MIT-licensed Python framework where you declare typed signatures and modules, define a metric, and let optimizers such as GEPA and MIPROv2 rewrite instructions and examples, or fine-tune weights, against your data.

Checked 13 Oct 202610 min read

Vendor
Stanford NLP and the DSPy open-source community
Licence
MIT
Category
Optimization
Latest release
3.4.1 · 12 Oct 2026 · Release notes (opens in a new tab)

Best for

Pipelines with a measurable metric and labelled examples, where you want better prompts, cheaper models or a model swap

Avoid when

You have no metric or fewer than a few dozen labelled examples, or a hand-written prompt on your current model already passes

Learn it hands-onDSPy: write the program, let an optimizer write the prompts is taught in Experiments and Prompt Optimization, with machine-verified labs.
On this page
  1. What it is and where it sits
  2. When to pick it
  3. How it works
  4. Members only: Set it up
  5. Members only: Production configuration
  6. Members only: Wiring it into the stack
  7. Members only: Operating it
  8. Members only: Alternatives compared
  9. Members only: Learn it hands-on

DSPy replaces hand-tuned prompt strings with small Python programs. You declare what each step takes and returns, compose steps into a module, write a function that scores an output, and run an optimizer. The optimizer runs your program on examples, keeps what scores well, and writes the instructions and few-shot examples for you. When a new model ships, you rerun the optimizer instead of rewriting prompts by hand.

What it is and where it sits#

DSPy is an open-source Python framework from the Stanford NLP group, MIT licensed. Version 3.4.1 shipped on 12 October 2026 and supports Python 3.10 to 3.15. It calls models through LiteLLM, so dspy.LM("openai/..."), dspy.LM("anthropic/...") or a local OpenAI-compatible server all work the same way.

Four ideas carry the whole framework:

  • Signatures declare a step's inputs and outputs with types: ticket: str in, team: Literal["billing", "technical", "account", "other"] and urgent: bool out. The docstring is the starting instruction. You never write the prompt template.
  • Modules decide how a signature is called. dspy.Predict asks directly, dspy.ChainOfThought adds a reasoning field first, dspy.ReAct runs a tool loop, and ProgramOfThought, CodeAct, MultiChainComparison, BestOfN, Refine and Parallel cover other patterns. You compose them in a dspy.Module with a forward() method like any Python class.
  • Metrics are plain functions that take the labelled example and the prediction and return a bool, a float, or dspy.Prediction(score=..., feedback=...).
  • Optimizers (DSPy also calls them teleprompters) tune one or more of three things: the instructions, the few-shot demos, or the model weights.

In the stack, DSPy is the optimization layer. It sits above the gateway and the model, next to your eval tools: promptfoo, Braintrust or Inspect AI measure a prompt; DSPy searches for a better one against the same kind of metric.

When to pick it#

DSPy earns its keep when three things are true: you can score an output automatically, you have labelled examples (a few dozen at least, a few hundred is better), and the prompt drives cost or quality.

  • Moving to a smaller or cheaper model. The usual story: optimize a pipeline on a small model with a strong model as the instruction writer, and match or beat the hand-prompted large model. DSPy's own GEPA tutorial reports a nano-sized model climbing from 78% to 90% on its task metric, above the large model's 82% baseline.
  • Swapping models. A new model release becomes a rerun of the optimizer and an eval, not a week of prompt edits.
  • Multi-step pipelines. Retrieval, classification and generation steps tuned together, where fixing one prompt by hand breaks another.
  • Classifiers and extractors with clear labels. Exact-match metrics make optimization cheap and reliable.

Skip it, or wait, when:

  • You cannot write a metric. No metric, no optimization. Write the eval first with promptfoo or Braintrust.
  • You have a handful of examples. Optimizers overfit tiny sets, especially demo-based ones.
  • The current prompt already passes. If a hand-written prompt on your current model meets the bar at an acceptable cost, compiling costs money for no gain.
  • You need a fixed, auditable prompt for compliance review. You can still review the compiled instructions (they are saved as JSON), but they change every time you recompile.

The nearest alternatives are hand-iteration in a playground with an eval suite (fine for one prompt), vendor prompt-improver features in hosted tools such as Braintrust's Loop, and fine-tuning a model directly.

How it works#

1. A dataset of 120 test cases feeds two prompt variants, A and B. 2. Code checks and an LLM judge score every output. 3. Combined scores: production 80, prompt A 74, prompt B 89. 4. The CI gate requires a score above production. Prompt B passes and ships. Prompt A is a regression and is blocked. 5. Production traces flow back into the dataset as new test cases.traces become new test casesDataset120 test casesPrompt AshorterPrompt Badds examplesCode checksformat, factsLLM judgerubric scoreCombined score, 0 to 100Production80Prompt A74Prompt B89CI gatemust beat production (80)B · shipsA · blockedProductionserves prompt B

You build a program, measure it, compile it, and measure again on data the optimizer never saw:

  1. Configure the model. dspy.configure(lm=dspy.LM("openai/gpt-6-luna"), track_usage=True) sets the default. dspy.context(lm=...) overrides it for a block. Calls are cached on disk and in memory by default.
  2. Split the data. A training set feeds the optimizer, a validation set picks the winning candidate, and a test set reports the result. GEPA's guide says to keep most data in training and make validation the smallest sample that still represents real traffic.
  3. Evaluate the baseline. dspy.Evaluate(devset=test, metric=metric, num_threads=8) runs the program in parallel and returns an EvaluationResult whose .score is a percentage and whose .results holds every (example, prediction, score) triple. A metric that raises scores failure_score (0 by default) instead of stopping the run.
  4. Compile. optimizer.compile(program, trainset=train, valset=val) returns a new, optimized copy and leaves the original untouched. Compiled sub-modules are marked so a second optimizer on an outer program leaves them alone.
  5. Save and serve. optimized.save("triage.json") writes the instructions, demos and model settings as reviewable JSON. Load it into a freshly built program in production; compile once, serve many times.

The optimizers available in 3.4, grouped by what they tune, from DSPy's own selection guide:

OptimizerTunesUse it when
LabeledFewShotDemos, no model callsThe honest baseline
BootstrapFewShotDemos from passing tracesFirst thing to try; cheap
BootstrapFewShotWithRandomSearch (BootstrapRS)Demos, searched over seedsDemo quality varies between runs
KNNFewShotDemos picked per input at run timeInputs vary widely
COPROInstructionsWording is wrong, demos are fine
MIPROv2Instructions and demos together, Bayesian searchBoth are weak and you have budget
GEPAInstructions, by reflecting on written feedbackYou can explain failures in words
SIMBARules and demos aimed at the worst minibatch examplesFailures share a nameable pattern
InferRulesReadable rules appended to instructionsYou want the learned rules spelled out
BootstrapFinetuneModel weightsPrompt-only has plateaued and the model is tunable
BetterTogetherPrompts and weights in sequenceBoth levers, composed
EnsembleCombines compiled programsSeveral good candidates to vote
AvatarOptimizerAgent instructions from good and bad examplesPass or fail agent tasks

GEPA is the only optimizer that reads the feedback text from your metric. MIPROv2 and COPRO treat the metric as a single number. The docs' rule of thumb: demo tuning tends to overfit small sets, instruction tuning tends to generalize, and fine-tuning is the last lever, not the first.

Set it up

This program optimizes a support-ticket triage step with GEPA. Put a few hundred labelled tickets in tickets.csv with the columns ticket , team and urgent . The whole flow (baseline, GEPA compile with auto="light" , evaluation, JSON save and reload) was run end to end ...

Members only below this line

Get the full DSPy: write the program, let an optimizer write the prompts deep dive free

What you get on this page:

  • 6 members sections

Free. You also get Stratiflux Weekly: one email a week, unsubscribe anytime.

Also in the members part

  1. Production configuration
  2. Wiring it into the stack
  3. Operating it
  4. Alternatives compared
  5. Learn it hands-on

Sources

Checked against these on 13 Oct 2026.

  1. DSPy repository, releases and licence (opens in a new tab)github.com
  2. DSPy docs: choosing an optimizer (opens in a new tab)github.com
  3. DSPy docs: prompt optimizing with GEPA (opens in a new tab)github.com
  4. DSPy docs: metrics and evaluation (opens in a new tab)github.com
  5. DSPy docs: saving and loading (opens in a new tab)github.com
  6. DSPy docs: FAQ (when DSPy fits, compile cost) (opens in a new tab)github.com
  7. DSPy docs: cheatsheet (caching, usage tracking) (opens in a new tab)github.com
  8. GEPA: reflective prompt evolution (paper) (opens in a new tab)arxiv.org
  9. DSPy on PyPI (opens in a new tab)pypi.org