一个被低估的工具:DSPy,把写 Prompt 变成编译
一个被低估的工具:DSPy,把写 Prompt 变成编译
斯坦福大学开源的AI框架
开场:先看一个让手写党沉默的战绩
DSPy 官网首页挂着一组数字:同一个 RAG 管线,编译前 F1 0.41,编译后 0.63。全程一行 prompt 都没有手写。
另一个例子来自 Shopify。他们用 DSPy 优化商品元数据抽取,每千次调用成本降了两个数量级。
如果你每天都在跟 prompt 搏斗,这组数字值得多看两眼。
我第一次听说 DSPy 时,下意识把它归类成"又一个 LangChain 式编排框架",随手关掉了文档。后来在搜索产线上评估 query 改写节点,手写 prompt 改了 3 版,线上指标纹丝不动,我才回头认真读它。
读完只有一个感受:这个工具被严重低估了。它的核心价值在编译这一层,编排只是最外围的壳。它把你写的 prompt 从文本变成可被数据编译的参数,再用优化器自动搜索更优解。
这篇文章不讲概念罗列。我以"优化器 + 蒸馏"两条主线,把它在搜索产线上真正值钱的地方拆开,附一份本地验证过可跑的示例。
一、为什么被低估:它跟编排框架站的位置不一样
先说一个侧面证据。有人搜"DSPy 入门",搜到一篇号称《DSPy 从入门到劝退》的文章,通篇在讲音频滤波和实时信号处理。
作者把 DSPy 当成了数字信号处理库。DSP 三个字母惹的祸,全文内容全部作废。
这个反例说明两件事:中文世界关于 DSPy 的优质资料确实稀缺;它的名字还自带误导。连资料都这么容易跑偏,工具被低估也就不奇怪了。
大多数人低估 DSPy,原因在于拿错了参照系。
LangChain 解决的是"怎么把组件接起来",LangGraph 解决的是"有状态的 Agent 图怎么跑",DSPy 解决的是另一个问题:prompt 本身怎么被自动优化。
一句话定位:DSPy 是 LLM 程序的编译器。
类比 PyTorch 你就懂了。PyTorch 里你定义网络结构,写 loss,优化器自动调权重。
DSPy 里你声明输入输出契约,写评分函数,优化器自动调 prompt 指令和示例。模型权重被梯度更新,prompt 被编译器更新,心智模型完全一致。
这套心智模型对应四个概念:
| 概念 | 作用 | PyTorch 类比 |
|---|---|---|
| Signature 签名 | 声明输入输出契约:context, question -> answer |
定义网络层 |
| Module 模块 | 同一签名换执行策略:Predict / ChainOfThought / ReAct | nn.Module 积木 |
| Metric 指标 | 你写的评分函数,优化的一切都对着它来 | loss / 评测指标 |
| Optimizer 优化器 | 自动搜更优指令与示例,甚至生成微调数据 | 训练器 |
关键在最后一行。Metric 和 Optimizer 的存在,把"调 prompt"从手艺活变成了数据活。
二、优化器:编译器的心脏
写 prompt 这么多年,你最清楚问题在哪:没有度量,全是手感。
同样一段话术,换个模型效果就漂。你说不清是指令不够细,还是缺示例,还是输出格式吃了亏。DSPy 把这个问题拆成了可优化的对象:指令文本、few-shot 示例、甚至结构化输出模板。
优化器替你干的事,就是在训练集上反复试这些对象的不同变体,用 metric 判分,留下得分最高的组合。
官方 demo 里有个直观战绩:分类任务 baseline 62%,GEPA 编译后 89%。没换模型,没加数据,只编译了 prompt。
优化器家族按你的预算和任务形态选:
| 优化器 | 特点 | 什么时候用 |
|---|---|---|
| BootstrapFewShot | 快速起步,自动挑示例 | 刚接入、验证链路时 |
| GEPA | 遗传算法 + 帕累托,反射式,省预算 | 预算紧、需要文本化反馈 |
| SIMBA | 长尾难例自省,适合 agentic 长任务 | 难样本集中攻坚 |
| MIPROv2 | 贝叶斯搜指令 + 示例 | 预算充裕时追求上限 |
| GRPO | 真正的强化学习 | 代价最大,极少场景值得 |
换优化器只需改一行代码,这点后面示例里会看到。
给一句产线提醒:metric 决定一切。评分函数设计失误,优化器会把错误行为刷到极致,这叫过拟合到错误目标。对策是用线上可验证信号当判分依据,比如点击、成交、人工抽检。
上线前再在留出集上复测。垃圾 metric 进,垃圾 prompt 出,这条铁律在编译世界里同样成立。
三、蒸馏:被低估里最被低估的
如果说优化器是被低估的,那蒸馏就是被低估里最被低估的。
DSPy 官方把 fine-tune 工作流当一等公民。编译器跑一遍,副产品是一批高质量数据。这批数据能直接反哺小模型微调,原理上是把大模型当 teacher,编译过程产出 few-shot 与训练语料,喂给本地小模型。
这条路径对你的本地部署策略价值极大,具体讲三点。
第一,编译产物里的 demos 是优化器自动挑的 few-shot。它替你做了人工精选示例的活,挑出来的还是经过 metric 验证的版本。
第二,把产物导出成对话格式,就是一份微调语料。云端强模型当 teacher 跑一次编译,得到的数据集够本地 14B 微调用一轮。
第三,换模型不重写 prompt。DSPy 换 LM 只需改一行配置再重编译,这个能力对 Gemma 微调版与云 Qwen 并存的场景是刚需。prompt 层不再跟着模型绑定,漂移问题从源头被拆掉。
官方教程里这套流程叫 teacher-student 蒸馏:强模型在编译期产数据,弱模型在微调期学数据。你在本地 GPU 上部署量化模型时,最缺的就是高质量训练数据,DSPy 恰好是这条生产线的开关。
四、示例:query 改写编译 + 蒸馏导出
下面这段代码我本地跑通了,dspy 3.3.1,Windows + Python 3.13。任务选搜索产线最常见的 query 改写加意图分类,共 3 段。
第 1 段:声明签名,0 行手写 prompt
import dspy
# 配置语言模型。编译期建议用云端强模型当 teacher:
lm = dspy.LM("openai/gpt-4o-mini", api_key=os.environ["OPENAI_API_KEY"])
dspy.configure(lm=lm)
class QueryRewrite(dspy.Signature):
"""把用户口语化的搜索词改写成规范商品查询词,并识别意图。"""
raw_query: str = dspy.InputField(desc="用户输入的原始搜索词,可能带口语与错别字")
rewritten: str = dspy.OutputField(desc="改写后的规范搜索词,用于召回")
intent: str = dspy.OutputField(desc="用户意图,只能是 buy / compare / browse")
# 训练集:线上 query 日志里随手捞 4 条,20 到 50 条起步效果更稳
trainset = [
dspy.Example(raw_query="想买个降噪好点的耳机", rewritten="降噪耳机", intent="buy").with_inputs("raw_query"),
dspy.Example(raw_query="手机 华为和小米哪个好", rewritten="华为手机 小米手机 对比", intent="compare").with_inputs("raw_query"),
dspy.Example(raw_query="有没有便宜点的跑步鞋", rewritten="跑步鞋 实惠", intent="browse").with_inputs("raw_query"),
dspy.Example(raw_query="iPhone 16", rewritten="iPhone 16", intent="browse").with_inputs("raw_query"),
]
注意到这里没有写任何 prompt 指令。签名里的 docstring 和字段 desc 就是全部输入。
第 2 段:写 metric,编译,保存产物
def metric(gold, pred, trace=None):
if gold.intent != pred.intent:
return False
# 简化判分:改写后的核心词需与标注有交集
gold_words = set(gold.rewritten.replace(" ", "").split())
pred_words = set(pred.rewritten.replace(" ", "").split())
return len(gold_words & pred_words) >= 1
# 快速起步用 BootstrapFewShot;预算足可换 MIPROv2(metric=metric)
optimizer = dspy.BootstrapFewShot(metric=metric, max_bootstrapped_demos=2, max_labeled_demos=2)
compiled = optimizer.compile(dspy.Predict(QueryRewrite), trainset=trainset)
compiled.save("query_rewrite.json")
编译完成后,产物里多了优化器自动挑选的 few-shot。我本地打印出来的实际结构长这样:
{
"demos": [
{"raw_query": "iPhone 16", "rewritten": "iPhone 16", "intent": "browse"},
{"raw_query": "想买个降噪好点的耳机", "rewritten": "降噪耳机", "intent": "buy"}
]
}
这 2 条示例是框架从训练集里筛出来、经 metric 验证过的。你没写任何"参考如下示例"的话术,它自动替你完成了示例选择。
编译产物是纯数据。save 出来是 json,load 回去就能当普通 Predict 用。编译期和运行期完全分离,线上调用零优化开销,这也是它能跨模型迁移的根基。
第 3 段:蒸馏导出,产物直接变微调语料
import json
with open("query_rewrite.json", encoding="utf-8") as f:
artifact = json.load(f)
rows = []
for d in artifact["demos"]:
rows.append(
{
"messages": [
{"role": "user", "content": d["raw_query"]},
{"role": "assistant", "content": f"{d['rewritten']}(意图:{d['intent']})"},
]
}
)
with open("finetune_data.jsonl", "w", encoding="utf-8") as f:
for row in rows:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
跑完你就得到一份标准的 SFT 语料,messages 三件套格式,可直接喂给本地模型微调。
这是蒸馏最朴素也最实用的形态:teacher 模型在编译期生产的高质量输出,被结构化沉淀成训练数据。要规模化,就把无标注 query 批量丢给编译后的程序生成改写结果,凑几千条再微调。
完整闭环是这样的:云端强模型当 teacher 编译一小批种子数据,产出指令与示例。用这批编译结果给海量无标注 query 打标,得到训练集。本地小模型在这份数据上微调,上线推理。
换场景就换 teacher 重编译,流水线复用。
这套路径里,优化器负责把 teacher 的能力压进 prompt,蒸馏负责把 teacher 的能力传给学生。前者解决当下调用质量,后者沉淀成可复用的模型资产。
两个环节共享同一套签名与 metric,切换成本只有一行代码。
五、算账:本地 16GB 显卡能扛编译吗
直接给结论:编译阶段用云端,推理阶段用本地。
优化器的成本结构是"对每条样本发起多次 LLM 调用试 prompt 变体"。粗算一版:
| 方案 | 量级估算 | 可行性 |
|---|---|---|
| 云端小模型 GEPA | 官网实证 200 样本约 $2.18 | 编译主力 |
| 云端小模型 MIPROv2 | 50 样本约 $5 到 20 | 预算充裕时 |
| 本地 14B 量化 | 200 样本约 160 万 token,10 小时以上 | 占用整卡,不划算 |
编译产物是稳定格式,save / load 后线上推理就是普通 LLM 调用,随时切回本地 14B 或云 Qwen。云端编译、本地推理,两头的好处都拿到。
这就是"云端训练、本地部署"策略的完整闭环,与在本地 GPU 上跑量化 Agent 的路线天然咬合。
六、边界:哪里别用它
先给一张自检清单,判断你的任务适不适合上 DSPy:
| 条件 | 说明 |
|---|---|
| 多步骤 LLM 管线 | 单次调用也能用,但管线化节点收益最大 |
| 有明确质量指标 | 能写出 metric,F1、准确率、命中率都行 |
| 有可评估样本 | 最少 20 到 50 条标注起步 |
| 质量随 prompt 漂移 | 换模型效果就变,说明有编译空间 |
四条全中,值得试点。缺样本或缺指标,先补数据再谈编译。
DSPy 解决不了所有问题,下面这些场景请绕行。
纯聊天、开放式创作这类没有明确 metric 的任务,优化器无的放矢。一次性原型、结构还没定型的项目,先手写 prompt 跑通再迁移。长时有状态 Agent、审批流、断点续跑,那是 LangGraph 的主场,DSPy 的 ReAct 没有持久状态。
社区共识有一条经验法则:先手写 prompt 把流程跑通上线,攒真实输入输出,等你知道"好"长什么样了,再把最痛的那几个节点迁到 DSPy 编译。
它精炼的是已经能跑的东西。探索该做什么,那是另一类工具的事。
收尾:把编译器装进口袋
回到开头的战绩。RAG F1 从 0.41 到 0.63,分类从 62% 到 89%,背后是同一件事:把 prompt 从文本变成参数,用数据驱动它。
低估它的人,把它当成又一个编排框架。真正用过的人,把它当成生产 prompt 的流水线。
我的下一步是把搜索链路里 query 理解节点迁过去试点,先攒 50 条标注,跑一版 GEPA 看增量。它的优化器家族和蒸馏能力,值得每一个被 prompt 调参折磨过的人重新评估。
本篇完整示例代码(含 DummyLM 零成本可跑版)已打包。关注我的公众号,在公众号回复 dspy 领取。