人工智能实践(语言智能)
第3讲:提示词

3.4 DSPy:把 prompt 当程序

Signature / Module / Teleprompter 三层抽象,BootstrapFewShot 与 MIPRO 编译器如何让 prompt 自我优化

为什么需要 DSPy

问题陈述(Khattab et al., ICLR 2024 Spotlight):现有 LM 流水线通常使用硬编码的"提示模板"——冗长指令和示例字符串——通过手工反复试错得到。这种做法脆弱且不可扩展,概念上等同于"手动调分类器的权重"。

DSPy 的核心洞见借鉴自深度学习的历史——PyTorch 不要求你手写反向传播,你只需要声明模型结构,优化器会接管权重更新。那么:

类比:PyTorch 把"神经网络的权重优化"自动化;DSPy 把"LM 流水线的 prompt 优化"自动化。

三层抽象

DSPy 给出三个面向自动优化的抽象:

抽象对应 PyTorch 概念作用
Signature(签名)函数签名 / 类型注解声明模块的输入/输出行为
Module(模块)nn.Module封装提示技术,可组合成流水线
Teleprompter(提词器)Optimizer优化流水线以最大化指标

Signature:用自然语言声明接口

Signature 是一个声明式规范——告诉 DSPy 文本转换需要做什么,而不是具体如何提示。

简写语法:

# 简单 QA
qa_sig = "question -> answer"

# 带上下文的 QA
rag_sig = "context, question -> answer"

# 生成搜索查询
query_sig = "context, question -> search_query"

# 翻译
trans_sig = "english_document -> french_translation"

DSPy 会根据字段名推断角色,自动扩展为对 LM 有意义的指令。需要时也可以用类形式添加字段描述:

class AnswerQuestion(dspy.Signature):
    """根据上下文回答问题,如果上下文中没有答案则返回 'unknown'."""
    context = dspy.InputField(desc="可能包含答案的段落")
    question = dspy.InputField()
    answer = dspy.OutputField(desc="尽可能简短,优先直接提取")

Module:可组合的提示技术

DSPy 内置多种 Module,每个都把一种"提示技术"封装成可组合组件:

Module内部机制
dspy.Predict最基础——把 Signature 格式化为 prompt,调 LM,解析输出
dspy.ChainOfThought在输出前插入 rationale 字段强制 LLM 逐步思考
dspy.ProgramOfThought生成 Python 代码并交给解释器执行
dspy.ReActThought-Action-Observation 循环,支持工具调用
dspy.MultiChainComparison采样多条推理链并比较

关键点:只需要把上面"简单 QA"例子里的 dspy.Predict 换成 dspy.ChainOfThought,就能得到一个在给答案前先写推理的系统——签名和调用代码一个字都不用改

一个 3 行 RAG 示例

import dspy

class RAG(dspy.Module):
    def __init__(self, num_passages=3):
        self.retrieve = dspy.Retrieve(k=num_passages)
        self.generate_answer = dspy.ChainOfThought("context, question -> answer")

    def forward(self, question):
        context = self.retrieve(question).passages
        return self.generate_answer(context=context, question=question)

就这 8 行——包含检索、CoT 推理、答案生成的完整 RAG,每一步都是可优化的参数化组件

Teleprompter 与编译器

**编译(compile)**是 DSPy 最有魔力的部分。给定:

  • 一个 DSPy 程序 π\pi
  • 少量训练样本 Dtrain\mathcal{D}_{\text{train}}(可能只有输入,不需要中间标注)
  • 一个指标 m:(y^,y)Rm: (\hat{y}, y) \to \mathbb{R}

编译器返回一个优化后的程序 π\pi^*:

π=Compile(π,Dtrain,m)\pi^* = \text{Compile}(\pi, \mathcal{D}_{\text{train}}, m)

BootstrapFewShot:最简单的 Teleprompter

核心思路——用程序自己生成好示例,再用这些示例去提示程序:

用 zero-shot 版本跑训练集

对每个 xiDtrainx_i \in \mathcal{D}_{\text{train}},用当前未优化的程序生成轨迹 τi\tau_i(包含所有中间变量)。

指标过滤

用指标 mm 筛选出能通过评估的轨迹。例如对 GSM8K,保留最终答案正确的 traces。

反向注入为 Few-shot 示例

将成功的 traces 拆成每个 Predict 节点的 "(input, output)" 对,注入回 Signature 作为演示示例。

可选:迭代或集成

可以再跑一轮 bootstrap×2,或用集成策略把多个编译版本合并。

# 最小可运行编译示例
from dspy.teleprompt import BootstrapFewShot

teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
compiled_rag = teleprompter.compile(RAG(), trainset=qa_trainset)

# 之后像普通 PyTorch 模型那样使用
prediction = compiled_rag(question="法国的首都是什么?")

Khattab 2023 的关键实验结果:在 GPT-3.5 上,DSPy 编译后的程序超越标准 Few-shot 25–65%,甚至超过使用专家手写示例的版本 5–46%。在 GSM8K 上,从 vanilla 的 33% → 编译后 82%

MIPRO:多阶段联合优化

MIPRO(Multi-stage Instruction Proposal and Optimization,Opsahl-Ong et al., EMNLP 2024)进一步优化多阶段流水线,核心是用贝叶斯优化联合搜索每个模块的指令和示例:

maxθ1,,θM  E(x,y)Dval[m(πθ(x),y)]\max_{\theta_1, \dots, \theta_M} \; \mathbb{E}_{(x, y) \sim \mathcal{D}_{\text{val}}} \left[ m(\pi_\theta(x), y) \right]

其中 θi=(instructioni,demosi)\theta_i = (\text{instruction}_i, \text{demos}_i) 是第 ii 个 Predict 节点的指令与示例,MM 是节点数。MIPRO 的候选提议也由 LLM 生成,再用 TPE(Tree-structured Parzen Estimator)搜索,比纯随机搜索样本效率高一个数量级。

DSPy 在 GSM8K 上的完整对比

以下是论文中第 6 节的核心结果(GPT-3.5):

程序未编译编译(Bootstrap)编译(Bootstrap ×2)
vanilla (直接预测)33%65%67%
CoT (ChainOfThought)72%74%78%
CoT + reflection (多链比较)78%82%

两个启发:

  1. 结构 > prompt:从 vanilla 升级到 CoT 比手搓 prompt 更有价值
  2. 编译 > 手工:即便是 CoT,编译仍能再涨 6–10%

DSPy 的设计哲学

总结下来,DSPy 的方法论可以写成四条原则:

  1. 声明接口,而非描述行为——用 Signature 代替自由文本
  2. 模块化,而非单体 prompt——每个 Predict 节点独立演化
  3. 指标驱动,而非人类直觉——用 metric 替代"我觉得这 prompt 更好"
  4. 编译,而非手调——把优化留给 Teleprompter

这套哲学直接回应了第 3.1 节提出的"prompt 敏感性"问题:既然单一 prompt 不可靠,那就不要依赖单一 prompt——用指标来驱动程序自己找到稳健版本。

本节小结

概念要点
Signature自然语言类型声明,如 "question -> answer"
Module参数化的提示技术封装(Predict / CoT / ReAct)
Teleprompter优化器——BootstrapFewShot 最常用,MIPRO 用于复杂流水线
Compile用训练集 + 指标自动生成 Few-shot 示例或微调权重
核心收益把脆弱的手工 prompt 变成可迭代的工程化 pipeline
下一步TextGrad 进一步把"反馈"本身当梯度来优化(3.5 节)