DSPy in 2026: Treating Prompts as Programs
💡 Tool Tip:When adopting DSPy, use Evergreen Tools' Prompt Templates to capture best practices, Token Counter to estimate compile costs, JSON Formatter to validate evaluation sets, and Regex Tester to clean label data!
“DSPy: The framework for programming—not prompting—language models.” That is the official tagline on the Stanford NLP team's GitHub repo, and it is the declaration of a prompt-engineering watershed in 2026. PE Collective's annual developer tools review calls DSPy the contrarian pick: while everyone else hand-writes prompt templates, DSPy treats prompts as compilable programs — you declare input/output contracts (Signatures), assemble Modules, and Optimizers automatically search for better prompt structure and examples. This guide uses four runnable samples to show why eval-driven development replaced hand-tuning prompts in 2026.
Program, don't prompt: eval-driven development
1. From Prompt Strings to Programs
The problem with traditional prompt engineering became undeniable in 2026: prompts are strings, and strings cannot be tested, diffed, or improved automatically. DSPy's core idea is programming rather than prompting — you declare a Signature (input/output contract) and the framework handles translating that contract into something the model understands. Code sample 1 is a complete DSPy classifier: the TicketTriage signature declares ticket as input and label as output, and the Module wraps it into a callable program. There is not a single hand-written prompt in the file, yet the model classifies accurately.
# dspy_basics.py — the hello world of programming language models
# pip install dspy
import dspy
# Pick a model: OpenAI, Anthropic, or a local endpoint
lm = dspy.LM("openai/gpt-5.6-luna", temperature=0.2)
dspy.configure(lm=lm)
# A Signature declares INPUT -> OUTPUT contract. No prompt string needed.
class TicketTriage(dspy.Signature):
"""Classify a support ticket into bug, feature, or question."""
ticket: str = dspy.InputField()
label: str = dspy.OutputField(desc="one of: bug, feature, question")
# A Module wraps the signature into a callable program
class Triage(dspy.Module):
def __init__(self):
super().__init__()
self.classify = dspy.Predict(TicketTriage)
def forward(self, ticket: str) -> str:
return self.classify(ticket=ticket).label
triage = Triage()
print(triage("The app crashes when I upload a PNG larger than 5MB."))
# -> bug2. ChainOfThought: Reasoning as a Module
Reasoning models became table stakes in 2026, but hand-writing 'Let's think step by step' is over. Code sample 2 shows DSPy's ChainOfThought module: the framework injects reasoning structure internally, and you only declare inputs and outputs. PRSummarizer returns summary and risks as structured fields, ready to feed a CI bot. PE Collective's review specifically notes that DSPy's programming model (signatures, modules, optimizers) is clean and composable, and eval-driven development is built into the workflow — which is exactly what separates it from traditional prompt templates.
# chain_of_thought.py — reasoning modules, no hand-written CoT prompt
import dspy
class SummarizeWithReasons(dspy.Signature):
"""Summarize a PR description and list the three riskiest changes."""
description: str = dspy.InputField()
summary: str = dspy.OutputField()
risks: list[str] = dspy.OutputField()
class PRSummarizer(dspy.Module):
def __init__(self):
super().__init__()
# ChainOfThought injects "Let's think step by step" style reasoning
# internally — you never paste a reasoning prompt yourself.
self.reason = dspy.ChainOfThought(SummarizeWithReasons)
def forward(self, description: str):
out = self.reason(description=description)
return out.summary, out.risks
summarizer = PRSummarizer()
summary, risks = summarizer("Adds OAuth login, bumps the API SDK, refactors auth.ts")
# summary and risks come back as structured fields, ready for your CI bot.3. Optimizers: Let the Framework Tune Your Prompts
DSPy's most counterintuitive ability is compilation: give it an evaluation set and a metric, and the optimizer automatically searches prompt structure, instruction wording, and few-shot examples. Code sample 3 uses MIPROv2 on the GSM8K math dataset to optimize a MathSolver — load the training set, define an exact-match metric, and one compile call starts the search. Teams report 10-30 point gains on hard tasks over hand-tuned prompts. This is the 2026 evolution of the 'prompt engineer' role in miniature: writing prompts is itself being automated.
# optimize.py — let the framework write better prompts than you can
import dspy
from dspy.datasets import gsm8k
class MathSolver(dspy.Module):
def __init__(self):
super().__init__()
self.solve = dspy.ChainOfThought("question -> answer")
def forward(self, question: str) -> str:
return self.solve(question=question).answer
# 1. Load an evaluation set (GSM8K math problems)
trainset, devset = gsm8k.load()[:200], gsm8k.load()[200:400]
# 2. Define the metric: exact-match on the answer
def metric(gold, pred, trace=None):
return gold.answer.strip() == pred.answer.strip()
# 3. Compile: the optimizer searches prompt structure + few-shot demos
optimizer = dspy.MIPROv2(metric=metric, auto="light")
program = optimizer.compile(MathSolver(), trainset=trainset, max_bootstrapped_demos=8)
# 4. Evaluate on held-out dev set
score = dspy.evaluate(program, devset=devset, metric=metric)
print(f"Dev accuracy: {score:.2%}")
# Teams report 10-30 point gains vs hand-written prompts on hard tasks.4. Evaluation Sets: The New Unit Tests for Prompts
Code sample 4 turns evaluation sets into something akin to unit tests: a labeled dataset lives next to your code, and every prompt change becomes a regression test. Swap models, swap optimizers, swap prompt structure — everything goes through evaluation before shipping. The 2026 engineering discipline is simple: prompt changes without an evaluation set do not merge. DSPy's dspy.evaluate lets you wire that discipline into CI, catching a drop from 0.88 to 0.90 before it reaches production.
# eval_harness.py — evaluation sets are the new unit tests
import dspy
import json
# Keep a labeled dataset next to your code, like tests
DATASET = json.load(open("triage_eval.json")) # [{"ticket": ..., "label": ...}]
def triage_metric(gold, pred, trace=None):
return gold.label == pred.label
examples = [dspy.Example(ticket=d["ticket"], label=d["label"]) for d in DATASET]
examples = [e.with_inputs("ticket") for e in examples]
# Every prompt change becomes a regression test:
# - Before optimization: 0.88
# - After MIPROv2 compile: 0.93
# - After swapping the LM: 0.90 <- caught before production!
score = dspy.evaluate(triage, devset=examples, metric=triage_metric)
print(f"Triage accuracy: {score:.2%}")5. When to Reach for DSPy
DSPy is not a silver bullet. For one-off calls, pure chat scenarios, and exploratory tasks without an evaluation set, calling the API directly is the right move. But the moment your LLM calls enter production — fixed contracts, ongoing maintenance, repeated changes — the programming model beats string prompts. PE Collective's framework guide lists DSPy for optimization-minded teams: if you have an ML background and accept the learning curve, the payoff is unique. The steep curve is real, but in 2026 most serious teams are already paying it.
6. Summary
Prompt engineering in 2026 has split into two camps: the string-writers and the programmers. DSPy's programming camp turns prompts into testable, optimizable, versionable programs with evaluation sets as the quality gate. Start with a Signature, add ChainOfThought, compile with an Optimizer, and wire evaluation into CI — this workflow turns prompts from folklore into engineering.
Signature → Module → Optimizer → Evaluate
📌 Frequently Asked Questions
What is DSPy?
DSPy is an open-source framework from the Stanford NLP team, positioned as 'programming—not prompting—language models.' You declare input/output contracts (Signatures) and Modules, and optimizers automatically search for better prompt structure and examples.
How is DSPy different from prompt templates?
Prompt templates are strings that cannot be tested or optimized automatically; DSPy treats prompts as compilable programs with evaluation-set regression tests and optimizers like MIPROv2. Teams report 10-30 point gains on hard tasks.
Which models does DSPy support?
dspy.LM connects to OpenAI, Anthropic, Google, and other commercial APIs, plus local endpoints and open-source models. Switching models is a one-line change, and your evaluation set tells you immediately if quality moved.
Is the DSPy learning curve really steep?
Yes. The programming model (signatures, modules, optimizers) differs from traditional prompt engineering, and docs still have gaps. PE Collective recommends it for teams with ML backgrounds; simple scenarios don't need it.
When should I NOT use DSPy?
For one-off calls, pure chat scenarios, and exploratory tasks without evaluation sets. Validate quickly with string prompts first, then migrate to DSPy's programming model once the call enters production and needs ongoing maintenance.