用 DSPy 将提示词调优变成可评测流程
DSPy 通过类型化输入输出定义任务,用模块选择直接调用、推理或工具执行策略,再依据样例与评分函数优化提示词。文章介绍三类核心组件,帮助将人工试改提示词转为可测试的代码流程。
AI 深度解读
改一版提示词,挑几条输出看看,感觉不错就上线。等模型更新、边界案例出现,再重新调。DSPy 想把这套循环变成可评估的程序:开发者定义任务、运行策略和评分标准,优化器负责寻找更合适的指令与示例。
这篇文章值得读的地方,是把「提示词改得好不好」变成了工程问题。不过,文中的代码是入门示例,模型迁移流程也不是基准测试。真正需要学走的,是如何组织任务、评估和发布门槛。
不手写长提示词,任务要怎么表达
DSPy 是 Stanford NLP 的开源 Python 框架,采用 MIT 许可证。按原文提供的信息,DSPy 3.4.1 于 2026 年 10 月发布,需要 Python 3.10 或更新版本,安装命令是 pip install dspy。
它把 LLM 程序拆成三部分。
Signature 声明输入和输出,相当于给模型任务写接口契约。它支持 Literal、Optional、列表,甚至 dspy.Image。Module 决定执行策略:dspy.Predict 直接调用,dspy.ChainOfThought 增加逐步推理,dspy.ReAct 加入工具与推理循环。Optimizer 则根据样本和评分函数,调整指令或少样本示例。
下面的工单分类器,展示了任务契约如何落到代码里:
from typing import Literal
import dspy
class TriageTicket(dspy.Signature):
"""Classify a customer support ticket for routing."""
ticket: str = dspy.InputField()
category: Literal["billing", "bug", "account", "feature_request"] = dspy.OutputField()
urgency: Literal["low", "medium", "high"] = dspy.OutputField()
triage = dspy.ChainOfThought(TriageTicket)
pred = triage(ticket="I was charged twice for my subscription this month.")
print(pred.category, pred.urgency) # 例如 billing high
print(pred.reasoning) # ChainOfThought 增加 reasoning 字段
要留意 Literal 那两行:类别和紧急程度都有明确候选值,模型与解析器据此处理输出。但字段声明不能保证业务判断正确,仍然需要评估。
所谓「没有提示词」,也不能理解成指令消失了。类的 docstring 会成为指令,字段名和类型会参与输出格式定义。快速实验还可以写 dspy.Predict("question -> answer")。运行 dspy.inspect_history(n=1),就能看到完整消息和原始响应。
模型通过 provider/model 标识,例如 openai/gpt-5-nano。原文列出的路由包括 Anthropic 的 anthropic/...、Google AI Studio 的 gemini/...、Azure OpenAI、Vertex AI,以及本地 Ollama 的 ollama_chat/llama3.2:1b。OpenAI-compatible 服务可以通过 api_base 和 api_key 接入。
dspy.configure(lm=lm) 设置进程默认模型;局部切换使用 with dspy.context(lm=other_lm):。任务定义与模型选择分开,后续才容易比较不同模型。
优化器能调提示词,但谁来定义「更好」
评分函数才是优化器真正执行的需求。
原文建议从历史工单取 50-200 条标注数据。用 dspy.Example(...).with_inputs("ticket") 指定输入字段,category 和 urgency 留作评分标签。
示例评分很直接:类别、紧急程度各占一半,两项都对得 1.0,只对一项得 0.5。生成示范时,如果 trace is not None,则只接受两项都正确的结果。dspy.Evaluate 使用 num_threads=4、display_progress=True 执行评估,返回的 .score 是百分数。
文中的 50.0 只是示意输出,没有证据表明它是作者实测的基线。
BootstrapFewShot 会运行训练样本,保留评分通过的执行轨迹,再把它们放进提示词作为示范。示例设置 max_bootstrapped_demos=4、max_labeled_demos=4。包内还提供 MIPROv2 和 GEPA。
GEPA 更进一步:评分函数不仅返回分数,还能说明错在哪里,让反思模型据此改写指令。下面这段代码展示了这条反馈链:
def triage_feedback(example, pred, trace=None, pred_name=None, pred_trace=None):
problems = []
if example.category != pred.category:
problems.append(f"category should be {example.category}, got {pred.category}")
if example.urgency != pred.urgency:
problems.append(f"urgency should be {example.urgency}, got {pred.urgency}")
score = 1.0 - len(problems) / 2
return dspy.Prediction(score=score, feedback="; ".join(problems) or None)
gepa = dspy.GEPA(
metric=triage_feedback,
reflection_lm=dspy.LM("openai/gpt-5.4"), # 用更强的模型编写指令
auto="light", # 预算可选 "light"、"medium"、"heavy"
num_threads=2,
)
optimized = gepa.compile(triage, trainset=trainset, valset=devset)
要注意最后一行:trainset 为反思更新提供材料,valset 用于选择最终候选程序。两者之外,还要保留独立测试集。
这里也有业务前提。类别与紧急程度各占一半,是示例作者的选择。假如漏掉高紧急度工单的代价远高于普通分类错误,这个评分就没有完整表达需求。优化器可能提高平均分,却仍然放过最昂贵的错误。
换便宜模型之前,先把发布条件写下来
原文给出的迁移流程很实用:固定 TriageTicket,导出 200 条已解决工单,按最终类别和紧急程度标注,再拆成训练、验证和独立测试集。
先测大模型与小模型的基线,再让小模型执行任务、大模型担任 reflection_lm,通过 GEPA 优化。只有小模型最终测试分数至少达到大模型基线,才发布 triage_small_v1.json。
作者明确说了,这是示例工作流,不是基准测试。
文中引用 DSPy 文档的生产案例:Shopify 把单提示词 GPT-5 任务迁移到经 GEPA 优化的小型 Qwen 模型,报告成本约降至原来的 1/75,可靠性约为原来的 2 倍;Dropbox 则报告通过优化器迁移到更小模型,并让程序准确率翻倍。原文没有交代完整数据集、指标口径和对照配置,不能据此推算普通团队的收益。
优化结果通过 optimized.save("triage_v1.json") 保存,服务端重新创建模块,再用 triage.load("triage_v1.json") 加载。JSON 包含指令、示范以及保存时的 DSPy、Python 版本,适合随代码提交并审查差异。
运行细节同样会影响判断。DSPy 默认启用内存和磁盘缓存,重复输入可能直接拿到旧答案。测试采样时,可以用 dspy.LM(..., cache=False),或调用 dspy.configure_cache(enable_disk_cache=False, enable_memory_cache=False)。
优化过程会多次调用模型,建议从 auto="light" 开始,让 num_threads 适配服务商限流,并通过 dspy.configure(lm=lm, track_usage=True) 查看每次预测的 token 用量。编译后还应运行 dspy.inspect_history(),或打开 JSON 检查指令是否夹带数据、编码了不希望出现的偏差。
原文列出 3.3.0、3.4.0、3.4.1 分别在 2026 年 8、9、10 月发布,提醒生产环境固定 dspy== 的具体版本,升级前查看迁移说明。
我的判断
我认可它把注意力从措辞转向评估。对工单分类这类输入、输出和标签比较明确的任务,这比挑几条结果凭感觉判断更容易复查,也更方便比较模型。
但原文没有给出作者自己的完整实验,也没有证明 DSPy 一定优于「手写提示词加评估脚本」。如果任务简单、示例少、提示词很短,后者可能已经够用。DSPy 的吸引力在于把任务声明、执行策略、优化和状态保存组织成一套流程,代价是引入框架与优化调用成本。
数据划分也不能只做表面功夫。入门代码直接按顺序切半,没有讨论重复工单、时间分布和类别不均衡。评估集如果不能代表真实流量,分数再精确也可能误导决策。
还有一个容易忽略的细节:示例流程先用测试集比较两个模型,再做最终验收。如果团队反复根据测试结果修改方案,这个测试集也会逐渐参与决策。应给最终验收保留边界,避免把同一批样本磨成另一套训练材料。
能拿来做什么
- 给已有的工单分类功能补一份人工确认的评估集,先明确类别错误和紧急程度错误各自的代价,再决定评分方式。
- 在 Node API 或前端管理后台接入分类能力时,可以把 DSPy 放在 Python 服务中负责分类,接口只返回业务所需字段;先验证接入收益,再增加部署复杂度。
- 准备更换模型时,固定任务和发布门槛,分别记录测试分数、调用成本与延迟。优化器运行成功,不等于新模型已经满足上线条件。
- 把优化后的 JSON 纳入版本管理,审查指令和示范的变化,同时固定 DSPy 版本;重新评估模型时,检查缓存是否影响了观察结果。