DSPy Explained: Signatures, Modules and Prompt Optimization
Understand DSPy signatures, modules and optimizers, try the current docs example, and decide when measured prompt optimization is worth the work.
Published

DSPy is worth trying when you can score a repeated AI task and hand-tuning its prompts has become a maintenance job. It moves that work into a Python program and an optimization loop; the small first-run design below budgets up to 100 single-step program passes before retries. If your business cannot agree on what a correct answer looks like, start there before adding DSPy.
Verified on 15 October 2026 against DSPy's current documentation, PyPI and Google's model pricing. PyPI lists DSPy 3.4.1, released 12 October 2026, with Python 3.10 or later, below 3.16. The package to install is dspy. PyPI package metadata.
What Is DSPy, and What Does Compile Mean?
DSPy is an open-source Python framework for defining language-model tasks as programs, then improving their prompts or, with specific optimizers, their model weights. Its useful promise is repeatable improvement against your definition of success. It does not promise that your first generated prompt will beat a carefully written one. DSPy overview.
Think of a rehearsal. The signature is the brief, the module is how the performance happens, and the metric is the judging rule. The optimizer changes something, listens to the result, and keeps useful changes. Without a judging rule, you can rehearse differently forever without knowing whether anything improved.
Signatures Describe the Job
A signature declares the inputs, outputs and task instructions. "ticket -> category" says that a ticket goes in and a category comes out. A class-based signature can add typed fields and a docstring explaining the task. Your choice of fields still matters: an optimizer cannot rescue an ambiguous business definition of “urgent.” Signature documentation.
DSPy Modules Describe How It Runs
A module implements a strategy for doing the task. dspy.Predict is the basic prediction building block. dspy.ChainOfThought adds a reasoning field before the answer. dspy.ReAct supports a reasoning-and-tool-use loop. You can compose modules in Python when one model step is insufficient. Module documentation.
For a support-ticket router, begin with Predict. Adding a tool loop to classify a short message creates more moving parts before you have demonstrated a need for them.
Compilation Is an Optimization Procedure
An optimizer changes selected parts of a program using examples and a metric. For prompt optimization, .compile() returns a program carrying revised instructions or demonstrations, meaning examples included in the prompt. It does not turn the application into a native executable or eliminate future model calls. Weight training is a separate optimizer choice. Optimizer selection guide.
DSPy Tutorial: The Current Docs' Minimal Example
The current starter program generates a haiku. That small example reveals the interface without involving a database, tools or an optimization search.
Install the verified release in your Python environment:
python -m pip install 'dspy[litellm]==3.4.1'Set GEMINI_API_KEY in your environment to a valid Google Gemini API key. Save this as first_dspy.py, then run python first_dspy.py:
import os
import dspy
lm = dspy.LM(
"gemini/gemini-2.5-flash-lite",
api_key=os.environ["GEMINI_API_KEY"],
engine="litellm",
)
dspy.configure(lm=lm)
haiku_signature = "subject -> haiku"
haiku_generator = dspy.Predict(haiku_signature)
result = haiku_generator(subject="computer science")
print(result.haiku)The final four lines are the current docs' first program. The setup follows DSPy's model configuration pattern, with Google Gemini 2.5 Flash-Lite substituted so the experiment below uses one inexpensive model. The provider string follows the LiteLLM Gemini integration; the explicit backend is supported by the DSPy LM API.
DSPy builds a prompt from the signature, sends it to the configured model and parses the returned haiku field. This is an inference example, not an optimization run. No particular poem or quality improvement is claimed here.
DSPy Prompt Optimization Needs a Metric
No metric, no meaningful optimization. A metric is a function that turns a prediction into a score. For ticket routing, it can compare the predicted category with a human-approved label. For a generated answer, correctness may require a rubric or an executable check. DSPy's metric interface accepts your scoring function; the framework does not decide what your business should reward. Metrics and evaluation.
That is why a small labelled evaluation set matters. If you are tired of changing “be concise” to “respond briefly,” collect actual failures instead: a refund request sent to technical support, a login problem mistaken for billing, or a message containing two requests. Label the intended result before looking at a new prompt's output.
For a first pilot, use 60 distinct examples, deliberately split into 20 training, 20 validation and 20 final-test examples. These are proposed experiment sizes, not DSPy requirements or proof of production reliability.
- Training examples supply material the optimizer can learn from.
- Validation examples help you compare versions and make development decisions.
- Final-test examples stay untouched until you freeze the candidate and evaluation rules.
Keep related tickets from the same customer or incident in one split. Otherwise, near-duplicates can make an optimized prompt look more general than it is. Include rare failures that matter, rather than filling every split with easy cases.

There is a precise caveat to “no metric.” You can run an unoptimized DSPy program without one, and LabeledFewShot can attach examples without scoring them. That is configuration, not evidence that a change improved your task. Nor must every task have a handwritten reference answer: an executable correctness check can supply the signal. For business classification, approved labels are usually the clearest starting point. Few-shot baseline.
DSPy Optimizers: The Names in Today's Docs
Start with the simplest optimizer that addresses a failure you can name. More search is useful only when the metric can distinguish better candidates from worse ones.
The current optimizer API index lists these exact entries: BetterTogether, BootstrapFewShot, BootstrapFewShotWithRandomSearch, BootstrapFinetune, BootstrapRS, COPRO, Ensemble, GEPA, InferRules, KNN, KNNFewShot, LabeledFewShot, MIPROv2 and SIMBA.
The entries are not all independent search algorithms. BootstrapRS aliases BootstrapFewShotWithRandomSearch; KNN is the retrieval helper used by KNNFewShot. The current selection guide also names AvatarOptimizer for agent-style programs, although it is absent from that API index. Family details, selection guide.
MIPROv2 proposes instructions and uses Bayesian optimization, a search method informed by previous scores, to explore combinations of instructions and demonstrations. SIMBA works through minibatches and proposes rules or demonstrations. Those are different approaches, not renamed versions of BootstrapFewShot. MIPROv2 API, SIMBA API.
GEPA uses a reflection model to read execution feedback and propose instruction changes. A score accompanied by a specific explanation, such as “refund requests belong to billing even when they mention a broken feature,” gives it a more useful signal than a bare failure score. The detailed docs allow numeric-only metrics, but explain that these lose the richer feedback. GEPA mechanics.
Bound the First Optimization Run
A small BootstrapFewShot experiment is easier to account for than an open-ended instruction search. The following design uses one Predict module, a deterministic label metric and the same model for teaching and prediction.
DSPy BootstrapFewShot: A Deliberately Small Pass
Prepare train.jsonl, validation.jsonl and test.jsonl, each with 20 distinct, human-labelled tickets. Each line is a JSON object with text and label; allowed labels are billing, technical, account and other. For example, a request to change an invoice address could carry the label billing. These must be your examples, not the haiku output.
Save this separate script as optimize_tickets.py and run it after setting the same API key:
import json
import os
from typing import Literal
import dspy
lm = dspy.LM(
"gemini/gemini-2.5-flash-lite",
api_key=os.environ["GEMINI_API_KEY"],
engine="litellm",
cache=False,
)
dspy.configure(lm=lm)
class RouteTicket(dspy.Signature):
"""Choose the support queue responsible for the ticket's main request."""
text: str = dspy.InputField()
label: Literal["billing", "technical", "account", "other"] = (
dspy.OutputField()
)
def load_examples(path):
with open(path, encoding="utf-8") as source:
rows = [json.loads(line) for line in source if line.strip()]
assert len(rows) == 20
return [dspy.Example(**row).with_inputs("text") for row in rows]
train = load_examples("train.jsonl")
validation = load_examples("validation.jsonl")
test = load_examples("test.jsonl")
assert len({e.text for e in train + validation + test}) == 60
def correct_label(example, prediction, trace=None):
return prediction.label == example.label
program = dspy.Predict(RouteTicket)
evaluate = dspy.Evaluate(
devset=validation, metric=correct_label, num_threads=1
)
print("Baseline validation:", evaluate(program).score)
optimizer = dspy.BootstrapFewShot(
metric=correct_label,
max_bootstrapped_demos=4,
max_labeled_demos=0,
max_rounds=1,
)
optimized = optimizer.compile(program, trainset=train)
print("Optimized validation:", evaluate(optimized).score)
# Freeze this candidate before opening the final test results.
final_test = dspy.Evaluate(
devset=test, metric=correct_label, num_threads=1
)
print("Baseline final test:", final_test(program).score)
print("Optimized final test:", final_test(optimized).score)
optimized.save("candidate_ticket_router.json")This is an adaptation using the documented BootstrapFewShot constructor and evaluation interface, not a claimed benchmark. It requests up to four successful demonstrations, allows one bootstrap attempt per training example and adds no raw labelled demonstrations. The saved file is a candidate for review, not an automatic production promotion.
The accounting for this design is:
- Baseline on validation: 20 program passes.
- Bootstrap over the training set: up to 20 teacher passes, potentially fewer when enough demonstrations pass.
- Optimized program on validation: 20 program passes.
- Both versions on the final test set: 40 program passes.
That is up to 100 single-step passes, including evaluation; compilation alone accounts for at most 20. With one successful model request per pass, no retries and no parsing fallbacks, those passes become at most 100 task-model calls. The earlier haiku sanity check is separate. An LLM judge, an agent loop, retries or another optimization attempt changes the total.

At Google's Standard paid text list price for Gemini 2.5 Flash-Lite, $0.10 per million input tokens and $0.40 per million output tokens, 100 calls averaging an assumed 1,000 input and 100 output tokens cost $0.014: 100,000 ÷ 1,000,000 × $0.10 + 10,000 ÷ 1,000,000 × $0.40. Google's pricing.
That is model-only arithmetic, not a measured invoice or spending cap. The input assumption must include DSPy's formatting and attached demonstrations; longer tickets, larger outputs and billable thinking increase the amount. There is no batch or cache discount in the estimate. Your actual provider usage is the accounting record.
DSPy Framework vs Hand-Written Prompts and Fine-Tuning
Use DSPy when you need a repeatable way to improve a measurable program. Keep a handwritten prompt when it already meets the task's requirements. Consider fine-tuning when you have evidence that changing prompts is insufficient and training a model fits your deployment constraints.
These are compatible techniques. DSPy includes BootstrapFinetune, and BetterTogether combines prompt and weight optimization. The practical difference is what you are changing and maintaining, not which label sounds more advanced. Prompt and weight optimization.
The crossover is operational: if every model change or new failure case sends an engineer back to manually editing several prompts, a scored optimization loop becomes attractive. If a short prompt already clears your acceptance criteria, replacing it with a framework has no demonstrated payoff.
Who Should Act, Wait or Skip It?
Engineers should act when they have a repeated task, a working baseline and representative failures. Ticket routing and structured extraction are useful starting points because the expected outputs can be checked. Optimize a small component before expanding the system. The wall DSPy hits is the quality of your objective: it can select a higher-scoring candidate while your metric rewards the wrong behavior.
Operators should own the labels and error priorities. A support manager may care much more about misrouting an account-access incident than an ordinary billing question. If the score treats both equally, the optimization may contradict the manager's priorities. Resolve that policy before asking an engineer to improve the score.
Buyers should ask for evidence on unseen examples. Ask which baseline was compared, which cases regressed, what the serving program now includes, and whether the workflow still meets its latency requirements. An optimizer's name is not acceptance evidence. The site's Promptfoo review is adjacent reading for teams organizing evaluation work.
A business team should skip DSPy for occasional copywriting, one-off research or a workflow that changes before examples can accumulate. Also skip it when nobody can maintain Python code or agree on a scoring rule. A good prompt, a checklist and human review can be a more proportionate system. Wait when your real problem is missing source data or an undefined process; optimizing wording will not supply either.
What's Overhyped About DSPy?
“Programming instead of prompting” does not make prompts disappear. DSPy creates and manages them; people still define the task, judge failures and decide whether the outcome is acceptable.
“Compile” does not mean the model learned permanently. With prompt optimizers, the learned artifact is instructions or demonstrations. You still call a model at inference time, and demonstrations occupy prompt space. You can save learned state as JSON and load it into a fresh program. Saving and loading.
“Automatic optimization” does not mean an automatic win. The useful comparison is against your existing approach on unseen inputs. A higher development score can reflect overfitting, a permissive judge or a dataset that misses expensive failures.
And metric calls are not provider calls. GEPA's max_metric_calls budgets scoring invocations; reflection also calls a model, and a scored program may itself make multiple requests. Its auto settings do not imply a universal invoice. GEPA budget controls.
Questions Engineers Ask About DSPy
How does DSPy work?
You declare inputs and outputs with a signature, choose or compose modules, and configure a language model. An adapter builds the messages and parses results. An optimizer can then improve selected parts using examples and a metric.
Is DSPy production ready?
Treat readiness as a property of your program. Pin the package and model configuration, preserve a baseline, test representative failures and define rollback criteria. The existence of a current release does not certify your workflow.
Is DSPy still relevant?
Yes, for repeated, measurable language-model tasks whose prompts need maintenance. Its value is smaller when a simple prompt already meets your needs or the task lacks a stable evaluation rule.
What are the key differences between LangChain and DSPy?
LangChain is a framework for building agents with model and tool integrations. DSPy's distinguishing emphasis is declaring model tasks and optimizing programs against metrics. Choose according to the problem: connecting an agent's components or systematically improving a scored task. LangChain overview.
Can DSPy be used for prompt optimization?
Yes. Depending on the optimizer, it can select demonstrations, revise instructions or search both. Prompt optimization leaves the underlying model weights unchanged; fine-tuning is a separate route.
Who created DSPy?
DSPy was developed by Omar Khattab and collaborators. Its repository is maintained under Stanford NLP; the package's research citation names the contributing authors. Project and authors.
How does DSPy Gepa work?
GEPA uses feedback from program executions to propose instruction changes through a reflection model, evaluates candidates and returns a selected program. Specific failure feedback makes the search better informed than a score alone. It does not guarantee a quality gain.
What is the full form of the DSPy framework?
DSPy stands for Declarative Self-improving Python, as stated in the official package description.
Your Next Step
On Monday, pick one recurring ticket-routing or extraction failure and ask the process owner to label representative examples. Freeze a baseline, define a scoring rule and run one bounded experiment. Review changed answers before approving the candidate. If you cannot define the score, the useful next task is agreeing on the business rule.
- Published
- Category
- Build
- Language








