跳到主要内容

别再手写提示词了:利用 DSPy 和 MIPRO 实现自动化优化

· 阅读需 11 分钟
Tian Pan
Software Engineer

你会花一个下午的时间来调整提示词(prompt)。你会移动一个句子的位置,把“classify”(分类)换成“categorize”(归类),添加一条关于边缘情况的注释,并针对笔记本中记录的少量示例进行抽查。到这一天结束时,提示词有了略微的改善——你觉得是这样。但你无法证明这一点。你没有一个可重复的基准。一周后,一位同事改动了几个词,整个系统就退化了。

这就是目前大多数团队提示词工程的现状。DSPy 是斯坦福大学给出的答案。与其手动编写指令文本,你只需声明你的 LLM 程序应该做什么,定义一个指标,然后让优化器为你编译实际的提示词。MIPRO——多提示词指令提案优化器(Multi-prompt Instruction PRoposal Optimizer)——是一种让这种方法能与人工编写的替代方案竞争(且通常优于人工编写方案)的算法。

核心抽象:签名(Signatures)与模块(Modules)

DSPy 引入了两个构建块。**签名(Signatures)**描述了流入和流出 LLM 调用的内容。**模块(Modules)**是围绕 LLM 调用的可组合包装器,用于实现特定的推理模式。

在 DSPy 的 Python API 中,支持工单分类器的签名可能如下所示:

class ClassifyTicket(dspy.Signature):
"""Classify a customer support ticket into a category."""
ticket: str = dspy.InputField()
category: Literal["Billing", "Technical", "Shipping", "Account"] = dspy.OutputField()

这就是全部的规范。没有指令段落。没有示例。没有“你是一个得力的助手”。你声明了输入类型、输出类型以及对任务的一行描述。

模块将该签名包装在推理模式中:

class TicketClassifier(dspy.Module):
def __init__(self):
self.classify = dspy.ChainOfThought(ClassifyTicket)

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

ChainOfThought 告诉系统在输出中包含逐步推理。ReAct 会添加工具使用。ProgramOfThought 会生成可执行代码。这些模块定义了 LLM 如何 推理,而不是它 说什么

实际的提示词文本——指令表述、示例、格式提示——是由优化生成的,而不是由你生成的。

MIPRO 究竟在做什么

DSPy 包含几个具有不同权衡的优化器。BootstrapFewShot 仅侧重于选择好的少样本(few-shot)示例。COPRO 仅通过坐标上升(爬山算法)优化指令措辞。MIPRO 则同时优化这两者,并且它使用贝叶斯搜索(Bayesian search)而不是贪婪爬山算法。

MIPRO 的三个阶段过程:

第 1 阶段:引导(Bootstrap)示例。 系统在训练集上运行你的 LLM 程序并收集输入/输出轨迹。它根据这些轨迹在你的指标上是否获得高分来过滤它们。这些高分轨迹就成为了候选的少样本示例。

第 2 阶段:提出指令候选。 MIPRO 使用三种策略生成多个指令变体:总结训练数据中的模式、分析模块之间如何连接,以及应用学习哪些指令策略倾向于在类似任务中奏效的元优化。

第 3 阶段:对组合进行贝叶斯搜索。 MIPRO 并没有穷举尝试每一个(指令,示例集)组合,而是使用树状 Parzen 估计器(Tree-structured Parzen Estimator)——这也是 Optuna 等超参数优化框架中使用的算法——来构建搜索空间的代理模型。在评估了几十个配置后,模型会识别出空间中哪些区域最有前景,将评估重点放在关键位置。

结果是一个在管道中的每个模块都拥有优化指令文本和精选少样本示例的程序,在 40–100 次评估中就能收敛,而不是成千上万次。

MIPROv2 增加了预设配置——“light”、“medium”和“heavy”——它们根据你可用的计算预算来设置试验次数和候选数量。对于大多数团队来说,从“medium”开始并在转向“heavy”之前检查成本是合理的。

性能数据

MIPRO 的原始论文(EMNLP 2024)在多种多阶段 LM 程序上针对 Llama-3-8B 进行了测试,报告称相比竞争优化器基准,准确率提高了高达 13%。针对特定应用任务的从业者报告了更大的增益:一个情感分类器在优化后准确率从 62% 提高到 82%(相对提高了 32%),一个基于维基百科的问答代理在 HotPotQA 上从 24% 提高到 51%,以及一个 RAG 系统在语义评估指标上获得了 10% 的相对质量提升。

这种范围并不令人意外。任务越复杂,管道中的阶段越多,优化就越有空间找到手动调整会错过的信号。具有清晰指令的简单单次调用分类器没有太大的提升空间。而模块输出会输入到下游模块的多步骤代理则为 MIPRO 提供了实质上更多的操作空间——它可以优化每个连接点,而不仅仅是最终的提示词。

值得注意的一个细微差别是:在旧模型或较小模型上的收益比在前沿模型(frontier models)上更一致。GPT-4 级别的模型在遵循模糊指令方面已经足够熟练,以至于优化带来的增益会缩小。而在 Llama-3-8B 或更小的 GPT 变体等模型上,MIPRO 编译的提示词往往能明显胜出。

当你使用 DSPy 时,你实际上在构建什么

技能转变是这种方法中常被低估的部分。你不再是一个提示词撰稿人,而是重新成为了软件工程师。

你编写评估函数。 优化的效果取决于你提供的信号。如果你正在构建一个支持工单分类器,你需要编写一个函数来根据标注数据对模块输出进行评分。如果你正在构建一个 RAG 流水线,你可能会使用 SemanticF1 作为你的评分函数。确保评估指标能真正捕捉到生产环境中的核心问题,而不仅仅是那些易于衡量的指标,这成为了关键的工程判断。

你编写测试数据。 MIPRO 需要训练示例来引导示例 (bootstrap demonstrations),并需要一个验证集来进行贝叶斯搜索。这意味着你需要筛选出具有代表性的输入输出对,这迫使你明确思考系统将处理的真实查询分布。这是大多数提示词工程流程中以前会被跳过的工作。

你编写模块组合。 对于多阶段流水线,有趣的设计决策在于如何分解任务——每个模块应该接收什么输入、产生什么输出,在哪里使用思维链 (chain-of-thought) 还是直接预测,以及在哪里添加检索步骤。这看起来更像是软件架构,而不是内容创作。

输出结果是可版本化、可差异对比且可重现的。 MIPRO 会生成一个优化的程序状态,你可以将其序列化、提交到 git 并精确重现。当你想要改进时,可以使用更多数据或新模型再次运行优化,并且可以客观地将新配置与旧配置在预留测试集上进行比较。这比“我觉得这个版本更好”是更直接的升级。

什么时候不该使用它

MIPRO 的开销是客观存在的。在投入该框架之前,有必要了解成本与收益的平衡点在哪里。

简单的单步任务不需要它。 如果你的应用只涉及一次 LLM 调用,且任务可以通过性能强大的模型可靠处理,那么一个写得好的静态提示词就足够了。编译机制增加了复杂性,但带来的价值不足以抵消其成本。

你需要标注数据。 BootstrapFewShot 可以从大约 10 个示例开始。而 MIPRO 在验证集有 200 个或更多示例时效果最好。如果你正在构建一个尚不存在标注数据的新领域,你需要在优化生效前先收集数据。

评估指标必须是可衡量的。 MIPRO 无法优化它无法衡量的东西。对于那些质量取决于人类判断的开放式生成任务(如创意写作、细微的解释),并没有自然的程序化评估指标。没有可靠的指标,优化器就失去了信号。

预留 API 成本预算。 每一个 MIPRO 试验都会通过 LLM 处理你的整个验证批次。如果使用价格适中的模型,进行 50 次试验、每次 200 个示例,你可能需要支付数十到数百美元的 API 成本。这些成本发生在离线阶段,而不是推理阶段,但它们并不是免费的。

固化的提示词与之不兼容。 一些受监管的行业要求对生产中使用的确切提示词进行审查、批准和锁定。MIPRO 会自动重写提示词。如果你的合规性要求必须对每一次指令更改进行人工审查,那么自动化优化就会带来审计问题。

分布偏移会削弱收益。 当生产流量偏离验证分布时,优化后的提示词可能会损失 2–4 个百分点的准确度。MIPRO 优化的程序应该像训练好的模型一样对待:随着新数据的积累定期重新训练,而不是设置后就一劳永逸。

一种有效的生产模式

大多数成功在生产中部署 DSPy 的团队都遵循相同的模式:优化是一个离线的批处理过程,而运行时系统使用固化的、导出的提示词。

具体来说:MIPRO 每晚(或每周)针对一个验证集运行,该验证集会根据生产流量中新标注的示例不断更新。当优化后的程序在预留测试集上的得分高于当前部署的版本时,它就会被部署。部署的程序是一个具有固定指令文本和固定少样本示例的静态产物——在推理时没有动态重新编译。

这种分离至关重要。它保持了推理延迟的可预测性,使回滚变得非常简单(重新部署之前的产物),并为你提供了一个在性能退化触及用户之前捕获它的明确机制。

技能的现状

DSPy 并没有消除对提示词工程的判断——它只是改变了这种判断的应用对象。你不再纠结 “analyze” 还是 “examine” 是更好的动词。你是在决定哪些任务值得投入数据收集和优化的开销,编写能真正捕捉生产质量的评估指标,并设计模块分解,为优化器提供有意义的结构。

从这种方法中获益最多的团队是那些已经认真对待提示词工程的团队——他们拥有标注好的评估集,跟踪随时间变化的性能,并感受到了大规模手动调优的摩擦。对他们来说,MIPRO 是对现有流程的升级。对于没有这些基础的团队来说,它暴露了缺失的部分:如果没有评估指标和数据,你就无法有效使用 MIPRO,而构建这些才是真正的工作。

案例研究中出现的 20–40% 的性能提升并不是免费的——它们是构建一个可测试、可衡量的 LLM 应用(而不是提示词破解式的流程)所带来的投资回报。一旦基础存在,MIPRO 就能加速迭代。但它不能替代这些基础。

References:Let's stay in touch and Follow me for more thoughts and updates