Skip to content

3.5 自动 Prompt 优化:从手工到自动化

在上一节中,我们学习了 Prompt 模板化——用变量占位符和结构化格式将零散的提示词组织成可复用的模板。模板解决了"Prompt 怎么管理"的问题:输入字段定义清楚了,输出格式约定好了,代码调用也规范了。但一个新问题随之浮出水面:模板确定了,但措辞还能更好吗?

你也许有过这样的经历:写了一段 Prompt,效果还行,但总觉得某些句子的说法可以改进。于是你反复尝试——把"请分析"改成"请仔细分析",把"逐步推理"改成"请一步一步思考",每次改动都要重新跑一遍测试。这种"手工试错"的过程耗时、主观、且难以系统化。有没有办法让机器自己来寻找最优的 Prompt 措辞?

答案是肯定的。这就是本节要讨论的自动 Prompt 优化(Automatic Prompt Optimization)。我们将重点介绍 Stanford NLP 团队开发的 DSPy 框架,它将 Prompt 工程从"手写字符串"升级为"编程 + 编译",让 LLM 的提示词像代码一样可定义、可编译、可优化。


3.5.1 手工 Prompt 的困境

在深入 DSPy 之前,我们先来全面审视手工 Prompt 工程面临的痛点。理解这些问题,才能真正理解自动化的价值。

困境一:试错成本高昂

手工 Prompt 开发本质上是一个"猜测-验证"的循环:写一版 Prompt,拿测试数据跑一遍,看看效果,觉得不好就改几个词再跑一遍。每一轮迭代都需要人工思考措辞、手动修改、重新测试。一个稍微复杂的任务,可能需要几十轮迭代才能达到令人满意的效果。更糟糕的是,这个过程中充满了主观判断——"感觉这个说法更好"缺乏客观依据,改进方向往往靠直觉而非数据。

困境二:模型敏感性强

同一个 Prompt,在 GPT-4 上表现出色,换到 Claude 上可能效果大打折扣;在 Llama 3 上运行良好,切到 Qwen 可能完全变味。不同模型对指令的理解方式、对 Few-shot 示例的敏感度都不一样。这意味着每次切换模型,你之前精心调好的 Prompt 可能都要推倒重来。对于需要支持多模型的团队来说,这意味着成倍的工作量。

困境三:难以量化优化

"这个 Prompt 比之前的好"——好在哪里?好多少?如果没有客观的评估指标,优化就只能停留在主观感受层面。你无法知道改动一个词带来了 2% 的提升还是 5% 的下降。缺乏量化手段,团队就无法做数据驱动的决策,也无法向业务方证明优化效果。

困境四:无法规模化

一个实际的生产级 AI 应用,往往包含几十个甚至上百个 Prompt:意图识别的、信息抽取的、摘要生成的、安全过滤的……每个 Prompt 都需要调优、测试、维护。手工管理这些 Prompt 的版本、效果、模型适配,很快就会变成一场噩梦。团队需要一套系统化的方法来管理整个 Prompt 生命周期。

正是这些困境催生了自动 Prompt 优化技术的诞生。DSPy 的核心思路:将 Prompt Engineering 从"手写字符串"转变为"编程 + 编译"。开发者只需用 Python 代码声明任务的目标(输入什么、输出什么),选择推理策略,然后让编译器自动搜索最优的 Prompt 措辞和示例组合。这就像从"手写汇编"升级到了"使用编译器"——你关注逻辑,编译器负责优化。


3.5.2 从广告优化理解自动 Prompt 优化

在学习 DSPy 的技术细节之前,我们先用一个生活中的类比来建立直觉。

想象你是一家电商公司的广告文案优化师。你写了一段产品广告词,投放到线上后转化率是 3%。你觉得不够好,想提升到 5% 以上。你会怎么做?

最可能的做法是 A/B 测试:准备几个不同版本的广告文案,同时投放给不同用户群体,收集数据,看看哪个版本转化率最高。然后以优胜版本为基础,再做小幅变体,继续测试。一轮又一轮,逐步逼近最优文案。

自动 Prompt 优化的逻辑与此惊人地相似:

广告文案 A/B 测试自动 Prompt 优化
准备多个文案变体自动生成多个 Prompt 变体
投放给真实用户在测试数据集上运行
转化率作为衡量标准评估指标(准确率等)作为衡量标准
选出最优版本保留最优 Prompt
基于最优版本再做变体基于最优 Prompt 继续迭代

关键区别在于:广告文案的变体需要人脑构思,而 Prompt 的变体由算法自动生成和搜索。你只需要告诉系统"评估标准是什么"和"训练数据有哪些",编译器就能自动完成搜索过程。

还有一个值得注意的差异:广告文案的测试需要真实流量,成本高、周期长;而 Prompt 的测试只需要一批标注好的数据集和一次 API 调用,可以在几分钟内完成成百上千次迭代。这使得自动 Prompt 优化在实际效率上远超传统 A/B 测试。

理解了这个类比,我们再来看 DSPy 的技术架构就会顺畅很多。


3.5.3 DSPy 声明式编程范式

DSPy(Declarative Self-improving Python)是 Stanford NLP 团队开发的框架。它的名字本身就说明了核心理念:声明式(Declarative)——你声明"做什么",框架负责"怎么做";自我改进(Self-improving)——系统通过编译自动优化自身性能。

核心思想:用 Python 代码定义"做什么"(What),让编译器自动生成"怎么做"(How)——即 Prompt。

传统方式 vs DSPy 方式

传统 Prompt 开发的工作流是线性的、手工的:

开发者手写 Prompt 字符串

手动测试、凭感觉调整措辞

硬编码到代码中

换模型 → 重新手写 Prompt

DSPy 的工作流则是声明式 + 编译式的:

开发者定义 Signature(输入/输出规范)

选择 Module(推理策略模块)

提供训练数据和评估指标

DSPy 编译器自动搜索最优 Prompt

换模型 → 重新编译,自动适配

对比一下就能看出差异:传统方式中,Prompt 是人工编写的字符串,每次调整都依赖人的经验;DSPy 中,Prompt 是编译器的输出,由数据和指标驱动优化。这就好比——传统方式是手写机器码,DSPy 是用高级语言编程再让编译器优化。

DSPy 的四大核心概念

要使用 DSPy,需要理解四个核心概念。我们用一个通俗的类比来串联它们:

DSPy 概念通俗理解作用
Signature(签名)任务合同书声明输入什么、输出什么
Module(模块)执行策略决定怎么推理(直接回答/逐步思考/工具调用)
Optimizer(优化器)编译器自动搜索最优 Prompt 措辞和示例
Metric(指标)评分标准量化评估输出质量

Signature 就像一份任务合同书。你只需要写清楚:"给我一个问题,我返回一个答案",不需要写具体的 Prompt 措辞。例如 question -> answer,或者 document -> summary。合同书只规定"交付什么",不规定"怎么交付"——后者由模块和优化器决定。

Module 决定了执行策略。dspy.Predict 是直接预测,dspy.ChainOfThought 是思维链推理,dspy.ReAct 是工具调用推理。你可以根据任务复杂度选择不同的模块——简单问答用 Predict,复杂推理用 ChainOfThought,需要调用外部工具用 ReAct

Optimizer 是编译器的核心。给定训练数据和评估指标后,它会自动搜索:哪些 Few-shot 示例最有帮助?指令措辞怎么写更好?推理步骤怎么组织更有效?你不需要操心这些细节。

Metric 是评分标准。优化器需要知道"什么是好的"才能进行搜索。你可以用简单的准确率,也可以用 LLM-as-Judge 进行复杂评估。评分标准越准确,优化效果越好。

下面通过第一个 DSPy 程序来感受这些概念如何协同工作。

python
import dspy                                          # 导入 DSPy 框架

# 1. 配置 LLM —— 指定要使用的模型
lm = dspy.LM('openai/gpt-4o-mini')                  # 创建一个语言模型实例,使用 GPT-4o-mini
dspy.configure(lm=lm)                                # 将该模型设为全局默认,后续所有 DSPy 调用都会使用它

# 2. 定义 Signature —— 声明任务的输入和输出
class QuestionAnswer(dspy.Signature):                 # 继承 dspy.Signature,创建一个任务签名
    """回答用户的问题,如果不知道就诚实说不知道。"""        # 文档字符串:描述任务目标,DSPy 会将其纳入 Prompt
    question = dspy.InputField(desc="用户的问题")      # 输入字段:名为 question,附带描述
    answer = dspy.OutputField(desc="对问题的回答,不超过200字")  # 输出字段:名为 answer,附带约束

# 3. 创建 Module —— 选择推理策略
qa_module = dspy.ChainOfThought(QuestionAnswer)       # 使用思维链模块包装签名,模型会先推理再回答

# 4. 直接使用 —— 像调用函数一样使用模块
result = qa_module(question="什么是大语言模型的上下文窗口?")  # 传入问题,获得预测结果
print(result.answer)                                  # 打印最终答案
print(result.rationale)                               # 打印推理过程(ChainOfThought 自动生成)

这段代码的核心在于:我们全程没有手写任何 Prompt 字符串。没有"你是一个专业的AI助手,请回答以下问题……"这样的模板。我们只是定义了输入输出规范(Signature),选择了推理策略(ChainOfThought),DSPy 就自动生成了完整的 Prompt。

当你调用 qa_module(question=...) 时,DSPy 在后台做了这些事:将 Signature 的文档字符串作为任务指令,将字段描述转化为 Prompt 中的说明,加上思维链的推理格式要求,组装成一个完整的 Prompt 发送给 LLM,最后解析返回结果为结构化对象。

result.answerresult.rationale 就是 DSPy 自动解析出来的字段——你不需要自己写正则去提取答案,框架已经帮你做好了。

这就是"声明式编程"的力量:你声明"要什么",框架负责"怎么做"。但到目前为止,我们只是用了 DSPy 的基本功能——自动生成 Prompt。DSPy 真正的杀手锏是编译器,它能自动搜索最优 Prompt。接下来我们进入编译器的世界。


3.5.4 DSPy 编译器:自动 Prompt 搜索

上面的例子展示了 DSPy 的基本用法——用代码定义任务,自动生成 Prompt。但 DSPy 真正强大的地方在于它的编译器(Compiler):给定训练数据和评估指标,自动搜索最优的 Prompt 和 Few-shot 示例。

回到我们的广告文案类比:如果说基本用法是"写了一版广告词就投放",那编译器就是"系统自动生成几十个变体,逐一测试,选出转化率最高的那版"。

编译器的三要素

要让编译器工作,你需要提供三样东西:

  1. 训练数据:一批带有正确答案的示例。比如问答任务中,就是一堆 (问题, 答案) 对。这些数据用于搜索最优 Prompt。
  2. 评估指标(Metric):一个函数,接收模型的预测结果和正确答案,返回一个分数。比如准确率、F1 值,或者更复杂的 LLM-as-Judge 评分。
  3. 优化器(Optimizer):选择搜索策略。DSPy 提供了多种优化器,各有侧重。
优化器作用适用场景
BootstrapFewShot自动选择最优 Few-shot 示例有少量标注数据
BootstrapFewShotWithRandomSearch随机搜索 + Few-shot 选择想探索更多组合
MIPROv2同时优化指令和示例追求最佳效果
COPRO专注优化指令措辞只想改指令文本

下面是一个完整的编译示例:

python
import dspy                                          # 导入 DSPy 框架

# --- 前置准备:配置模型和定义模块 ---

lm = dspy.LM('openai/gpt-4o-mini')                  # 创建语言模型实例
dspy.configure(lm=lm)                                # 设为全局默认模型

# 定义 Signature —— 任务合同书
class QuestionAnswer(dspy.Signature):                 # 创建任务签名类
    """回答问题,给出简洁准确的答案。"""                   # 任务描述
    question = dspy.InputField()                      # 输入字段:问题
    answer = dspy.OutputField(desc="简洁准确的答案")    # 输出字段:答案

qa = dspy.ChainOfThought(QuestionAnswer)              # 用思维链模块包装

# --- 准备训练数据 ---

trainset = [                                          # 训练数据列表
    dspy.Example(question="法国首都?", answer="巴黎").with_inputs("question"),  # 第一条样本
    dspy.Example(question="水的化学式?", answer="H2O").with_inputs("question"),  # 第二条样本
    dspy.Example(question="太阳系最大行星?", answer="木星").with_inputs("question"),  # 第三条
    dspy.Example(question="光速约多少?", answer="约30万公里/秒").with_inputs("question"),  # 第四条
    dspy.Example(question="DNA全称?", answer="脱氧核糖核酸").with_inputs("question"),  # 第五条
]
# .with_inputs("question") 告诉 DSPy:question 是输入字段,answer 是标签

# --- 定义评估指标 ---

def exact_match(example, pred, trace=None):           # 定义评估函数:精确匹配
    """比较预测答案和标准答案是否一致。"""               # 函数说明
    return example.answer.lower().strip() == pred.answer.lower().strip()  # 返回 True/False

# --- 选择优化器并编译 ---

from dspy.teleprompt import BootstrapFewShot           # 导入 Few-shot 优化器

optimizer = BootstrapFewShot(                         # 创建优化器实例
    metric=exact_match,                               # 指定评估指标函数
    max_bootstrapped_demos=4,                         # 最多保留 4 个自动生成的示例
    max_labeled_demos=4,                              # 最多使用 4 个标注示例
)

compiled_qa = optimizer.compile(qa, trainset=trainset) # 编译!自动搜索最优 Prompt

# --- 使用编译后的模块 ---

result = compiled_qa(question="地球到太阳的平均距离?")     # 用优化后的 Prompt 回答
print(result.answer)                                  # 打印答案

编译过程中发生了什么?

当你调用 optimizer.compile() 时,DSPy 在后台执行了一个搜索过程:

  1. Bootstrap(自举):用原始模块在训练数据上运行,看看模型在哪些样本上表现好。表现好的样本被认为是"模型能正确处理的",可以作为 Few-shot 示例。
  2. 筛选示例:从成功处理的样本中选出最有代表性的几条,作为 Few-shot 示例加入 Prompt。
  3. 组合搜索:尝试不同的示例组合,用评估指标打分,保留得分最高的组合。
  4. 生成最终模块:把最优的 Prompt 配置固化到一个新的模块对象中,后续调用就直接使用这个优化后的配置。

回到广告类比的框架:Bootstrap 就像先让文案初稿在不同受众上试投,看哪些文案的转化效果好;然后从效果好的文案中提取共通元素,组合成一个"最优模板";最后把这个模板固化为正式投放版本。

编译前后的对比

python
# 编译前:DSPy 自动生成的基础 Prompt(无 Few-shot 示例)
result_before = qa(question="地球上最高的山?")
# 内部 Prompt 大致是:
# "回答问题,给出简洁准确的答案。
#  Question: 地球上最高的山?
#  Answer:"

# 编译后:DSPy 优化后的 Prompt(带最优 Few-shot 示例)
result_after = compiled_qa(question="地球上最高的山?")
# 内部 Prompt 大致是:
# "回答问题,给出简洁准确的答案。
#  Question: 太阳系最大行星?
#  Answer: 木星
#  Question: DNA全称?
#  Answer: 脱氧核糖核酸
#  Question: 地球上最高的山?
#  Answer:"

可以看到,编译后的模块自动加上了筛选过的 Few-shot 示例。这些示例不是随机选的,而是通过评估指标筛选出来的"最有帮助的示例"。这就是 DSPy 编译器的核心价值——用数据驱动的方式,自动找到最优的 Prompt 配置