先说清楚:DSPy 不是又一个提示词模板

手写提示词的麻烦不在写,而在维护:换模型、加字段、改输出格式,整段措辞都得推倒重来,而且没有对照能证明「改完更好」。DSPy 换了个做法——用 Signature 声明输入输出、用 Module 组合流程、用 Optimizer 拿一批例子自动挑提示词与 few-shot 示例。你写的是程序结构和数据,措辞交给编译器。当前版本 3.4(2026-09 发布),核心包就是 dspy 一个。

第一步:装好,接一个模型

pip install -U dspy

import dspy

# 云端:显式传 key;也可以指向本地 vLLM / Ollama 的 OpenAI 兼容端点
lm = dspy.LM('openai/gpt-6', api_key='...')
# lm = dspy.LM('openai/local-model', api_base='http://localhost:8000/v1', api_key='local')
dspy.configure(lm=lm)

如果模型在本地跑,把 api_base 指到 vLLM 或 Ollama 的 OpenAI 兼容接口即可。注意选支持结构化输出、函数调用的模型,否则优化器拿不到稳定结果;密钥一律走环境变量,别写进代码或提交进仓库。

第二步:把任务写成 Signature 与 Module

class ClassifyTicket(dspy.Signature):
    """判断工单属于哪一类,并给出是否需要人工介入。"""
    text: str = dspy.InputField()
    category: str = dspy.OutputField(desc="退款/物流/账号/其他")
    needs_human: bool = dspy.OutputField()

class Triage(dspy.Module):
    def __init__(self):
        super().__init__()
        self.classify = dspy.Predict(ClassifyTicket)

    def forward(self, text):
        return self.classify(text=text)

Signature 用类型标注和文档串说明契约:描述里写清「输出只能是哪几类」,但别把全部业务规则都堆成自然语言,那是留给优化器去补的。先用最朴素的 dspy.Predict,跑通之后再考虑换成 ChainOfThought 之类更重的模块。

第三步:备一份小评测集,再留一批验收样本

rows = [(text, category, needs_human), ...]  # 从真实数据里抽
examples = [
    dspy.Example(text=t, category=c, needs_human=h).with_inputs('text')
    for t, c, h in rows
]
trainset, devset = examples[:60], examples[60:90]

几十条就够起步,关键是来自真实数据、覆盖容易出错的那几类。划分出参与优化的集合之后,另外再留一批既没参与优化、也没参与调参的样本做最终验收——用同一批数据自证,等于没有验收。

第四步:选一个优化器跑起来

从简单开始:BootstrapFewShot 只自动挑 few-shot 示例,便宜、见效快;MIPROv2 会同时搜索指令与示例,通常效果更好但更费 token;追求榜单式效果还可以考虑 GEPA。它们都能从 dspy 顶层或 dspy.teleprompt 导入。

def metric(example, pred, trace=None):
    return (pred.category == example.category) and (pred.needs_human == example.needs_human)

from dspy.teleprompt import BootstrapFewShot
optimizer = BootstrapFewShot(metric=metric, max_bootstrapped_demos=4)
compiled = optimizer.compile(Triage(), trainset=trainset)

compiled.save('triage.json')   # 之后重新加载即可复用这版「编译」结果

第五步:把「优化前 vs 优化后」摆在一张表上

  • 用同一个 dev 集对比优化前后:通过率、逐条失败项、耗时、token 成本都要记
  • 看逐条失败,别只看均值:均值持平但某类样本全错,才是真正要处理的问题
  • 把优化器最终选出的指令与示例存进版本管理,它才是可复现的产物,而不是「跑过一次」
  • 换模型要重跑:优化出来的提示词是跟着模型走的,换模型后原样照搬经常不成立

第六步:边界与几个坑

  • 优化有成本:MIPROv2 这类会大量调模型,先用小集合跑通再放大,别一上来就烧 token
  • 小样本容易过拟合:几十条能起步,但结论要谨慎,条件允许时把评测集做到上百条
  • 它不替代领域知识和工具:能靠检索、规则、外部 API 解决的事,先交给它们,别全压给模型
  • 版本迭代快:3.x 的 API 变动不少,锁版本、读对应版本的文档,别照抄旧教程
  • 本地模型要能给出结构化输出(JSON / 函数调用),否则优化过程会不稳定
  • 提示词与评测数据都可能含敏感信息:脱敏后再进仓库,团队才能一起用
一句话:DSPy 把「调提示词」从手工艺变成可编译、可对照的工程——它的价值不在省下写提示词的时间,而在让每一次改动都有基线可查。