DSPy 实战:把提示词当程序写

·阅读约16分钟·Evergreen Tools Team

💡 工具推荐上手 DSPy 时,用 Evergreen Tools 的 提示词模板 沉淀最佳实践、Token计算器 估算编译成本、JSON格式化 校验评估集、正则测试器 清洗标签数据!

「DSPy:编程语言模型的框架,而不是提示语言模型的框架。」这是 Stanford NLP 团队在 GitHub 上对 DSPy 的官方定义,也是 2026 年提示工程分水岭的宣言。PE Collective 在年度开发者工具评测里把 DSPy 称为「反主流选择」:当别人还在手写提示词模板时,DSPy 把提示词当成可编译的程序——你定义输入输出契约(Signature)、组装模块(Module),优化器(Optimizer)自动搜索更好的提示结构与示例。本文用四个可运行示例,讲清楚评估驱动开发(eval-driven development)如何在 2026 年取代手工调提示词。

程序化提示词

编程而非提示:评估驱动开发

一、从提示词到手写程序

传统提示工程的问题在 2026 年已经无法回避:提示词是字符串,无法测试、无法版本对比、无法自动改进。DSPy 的核心思想是「编程而非提示」——你不写 prompt 字符串,而是声明 Signature(输入输出契约),让框架负责把契约翻译成模型能理解的形式。代码示例1 是一个完整的 DSPy 分类器:TicketTriage 签名声明输入 ticket、输出 label,Module 包装成可调用程序。整个文件里没有一个手写的提示词,但模型照样准确分类。

# 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."))
# -> bug

二、ChainOfThought:推理模块化

2026 年推理模型(reasoning models)成为标配,但手写「Let's think step by step」的时代结束了。代码示例2 展示了 DSPy 的 ChainOfThought 模块:框架在内部注入推理结构,你只需要声明输入输出。PRSummarizer 同时返回 summary 和 risks 两个结构化字段,直接喂给 CI bot。PE Collective 的评测特别提到:DSPy 的编程模型(signatures、modules、optimizers)干净且可组合,评估驱动开发内建在工作流里——这正是它与传统提示词模板的本质区别。

# 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.

三、Optimizer:让框架替你调提示词

DSPy 最反直觉的能力是编译(compile):你提供评估集和指标,优化器自动搜索提示结构、指令措辞与 few-shot 示例。代码示例3 用 MIPROv2 在 GSM8K 数学数据集上优化 MathSolver——先加载训练集,定义 exact-match 指标,然后一行 compile 启动搜索。团队在困难任务上报告 10-30 个百分点的提升,超过手工调提示词的效果。这是 2026 年「提示工程师」角色演变的缩影:写提示词本身正在被 AI 自动化。

# 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 把评估集变成类似单元测试的存在:带标签的数据集放在代码旁边,每次提示词改动都是一次回归测试。换模型、换优化器、换提示结构,全部先过评估再上线。2026 年的工程纪律是:没有评估集的提示词改动不允许合入。DSPy 的 dspy.evaluate 让这条纪律可以写进 CI——准确率从 0.88 掉到 0.90 的变化,在发布前就被拦下来。

# 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%}")

五、什么时候该用 DSPy

DSPy 不是银弹。简单的一次性调用、纯聊天场景、没有评估集的探索性任务,直接调 API 更合适。但一旦你的 LLM 调用进入生产:有固定输入输出契约、需要持续维护、会被多次改动——DSPy 的编程模型就碾压字符串提示词。PE Collective 的框架选型指南把它列为「面向优化型团队」的选择:有 ML 背景、愿意接受学习曲线,DSPy 的回报是独特的。学习曲线陡峭是真实的,但 2026 年大多数团队已经开始为它买单。

六、总结

2026 年的提示工程已经分成两个阵营:手写字符串派与编程派。DSPy 代表的编程派把提示词变成可测试、可优化、可版本化的程序,用评估集作为质量闸门。从 Signature 开始,接上 ChainOfThought,用 Optimizer 编译,最后把评估写进 CI——这套工作流让提示词不再是玄学,而是工程。

编译与评估循环

Signature → Module → Optimizer → Evaluate

📌 常见问题 FAQ

DSPy 是什么?

DSPy 是 Stanford NLP 团队开源的框架,官方定位是「programming—not prompting—language models」。你声明输入输出契约(Signature)与模块(Module),优化器自动搜索更好的提示结构与示例。

DSPy 和普通提示词模板有什么区别?

提示词模板是字符串,无法测试与自动优化;DSPy 把提示词当作可编译程序,支持评估集回归测试与 MIPROv2 等优化器自动调优,团队报告在困难任务上提升 10-30 个百分点。

DSPy 支持哪些模型?

通过 dspy.LM 可以接入 OpenAI、Anthropic、Google 等商业 API,也支持本地端点与开源模型。切换模型时只需改一行配置,评估集会自动告诉你质量变化。

DSPy 的学习曲线真的很陡吗?

是的。编程模型(signature、module、optimizer)与传统提示工程心智不同,文档也仍在完善。PE Collective 建议有 ML 背景的团队优先尝试,简单场景不必强上。

什么时候不应该用 DSPy?

一次性调用、纯聊天场景、没有评估集的探索性任务。先写字符串提示词快速验证,一旦进入生产并需要持续维护,再迁移到 DSPy 的编程模型。