DSPy:声明式语言模型编程与自动优化
DSPy 声明式 LM 编程与自动优化完全指南
引言:当语言模型编程遇上声明式范式
你是否厌倦了一遍又一遍地手动调整提示词,只为让大语言模型(LLM)输出更符合预期的格式?是否在无数次的提示工程中感到筋疲力尽,却依然达不到理想的稳定性和可维护性?DSPy 正是为解决这些问题而生。它将语言模型调用抽象为声明式模块,并用自动优化替代手工调试,让你像构建普通软件一样构建可靠的 AI 流水线。本教程将带你从零开始,掌握 DSPy 的核心思想与实践技巧。
第1章:什么是 DSPy?
DSPy(Declarative Self-improving Python)是斯坦福大学推出的一个框架,它将语言模型编程从“编写提示词”提升为“定义任务签名并自动优化”。DSPy 的核心目标:让你专注于“程序要做什么”,而不是“如何对模型说话”。通过将提示、微调、增强、推理策略等抽象为可组合的模块,DSPy 能自动生成高质量提示,并用少量示例甚至零示例让整个流水线的性能达到最佳。
第2章:核心理念——声明式编程 vs 传统提示工程
要理解 DSPy 的威力,我们先对比两种开发方式:
- 传统提示工程:手写提示模板,反复试错,对格式、措辞进行手工优化。一旦模型、任务或数据分布发生变化,整个提示可能需要推倒重来。代码中充斥着拼接字符串和脆弱的后处理逻辑。
- DSPy 声明式编程:你只需声明输入/输出字段(即签名),并用高级模块组织流程。DSPy 会根据你提供的指标和少量示例,自动编译出最优的提示或微调策略。代码干净、模块化,且可以轻松迁移到不同 LM 上。
# 传统方式:手工提示 + 脆弱的解析
prompt = f"问题:{question}\n请用‘答案:’和‘依据:’两部分回答。"
# DSPy 方式:声明意图,让编译器处理提示细节
class QA(dspy.Signature):
"""用依据回答问题。"""
question = dspy.InputField()
answer = dspy.OutputField(desc="简短准确的回答")
rationale = dspy.OutputField(desc="逐步推理")
第3章:DSPy 基本组件
DSPy 程序由三个关键元素构成:签名、模块和优化器。
3.1 签名(Signature)
签名是任务的类型化接口,声明了输入域和输出域,并可用自然语言描述约束。
class Emotion(dspy.Signature):
"""将文本分类为高兴、悲伤或愤怒。"""
text = dspy.InputField()
label = dspy.OutputField(desc="情绪类别之一")
3.2 模块(Module)
模块是声明式程序的基本构建块,类似 PyTorch 中的 nn.Module。最常用的模块:
dspy.Predict:直接根据签名生成输出,适用于简单任务。dspy.ChainOfThought:让模型在输出前进行逐步推理,中间推理过程同样被记录。dspy.ProgramOfThought、dspy.ReAct等高级模块。
# 定义一个带思维链的问答模块
class CoTQA(dspy.Module):
def __init__(self):
super().__init__()
self.prog = dspy.ChainOfThought("question -> answer, rationale")
def forward(self, question):
return self.prog(question=question)
3.3 优化器(Optimizer / Compiler)
优化器接收你的程序、训练数据(少量示例即可)和一个评估指标,自动生成高质量的提示或微调适配器。典型优化器包括 BootstrapFewShot、BootstrapFewShotWithRandomSearch、MIPROv2 等。
第4章:动手构建你的第一个 DSPy 程序
下面我们构建一个“电影评论情感分析”程序,并体验自动优化。
环境准备:
pip install dspy-ai
配置语言模型:
import dspy
lm = dspy.LM('openai/gpt-4o-mini', api_key='YOUR_KEY')
dspy.configure(lm=lm)
4.1 定义任务签名
class Sentiment(dspy.Signature):
"""判断电影评论的情感倾向,积极或消极。"""
review = dspy.InputField()
sentiment = dspy.OutputField(desc="积极或消极")
4.2 创建模块
class SentimentClassifier(dspy.Module):
def __init__(self):
super().__init__()
self.classify = dspy.ChainOfThought(Sentiment)
def forward(self, review):
return self.classify(review=review)
4.3 准备训练数据(仅需少量示例)
trainset = [
dspy.Example(review="这部电影太棒了,强烈推荐!", sentiment="积极").with_inputs("review"),
dspy.Example(review="无聊到睡着了,浪费时间。", sentiment="消极").with_inputs("review"),
# 再添加 3~5 个示例即可
]
4.4 定义评估指标
def sentiment_metric(example, pred, trace=None):
return pred.sentiment == example.sentiment
4.5 编译并优化
from dspy.teleprompt import BootstrapFewShot
optimizer = BootstrapFewShot(metric=sentiment_metric)
compiled_classifier = optimizer.compile(SentimentClassifier(), trainset=trainset)
编译后的 compiled_classifier 内部已自动生成了最优的 few-shot 提示。现在直接调用:
result = compiled_classifier(review="演员演技精湛,但剧情有些拖沓。")
print(result.sentiment) # 自动输出 '消极' 或 '积极'
第5章:自动优化——告别手工调参
DSPy 的编译过程实际上在搜索最佳的提示结构。以 BootstrapFewShot 为例,其工作流程如下:
- 自举:从训练集中随机抽取示例,让模块尝试预测,将成功的预测(完整输入-输出对)收集起来作为示范。
- 组合:自动将这些示范嵌入到程序中,形成 few-shot 提示。
- 验证:在留出的验证集上评估效果,保留效果最好的提示配置。
更强大的优化器如 MIPROv2 还可以自动微调提示指令本身,并选择最佳的 few-shot 示例组合,只需定义好任务指标,一切由编译器完成。
第6章:实际案例——多跳问答系统
我们来设计一个较复杂的流水线:给定一个问题,先从已有文档库中检索相关内容,再结合检索结果生成答案。
6.1 定义模块
class RAG(dspy.Module):
def __init__(self, passages):
super().__init__()
self.retrieve = dspy.Retrieve(k=3) # 使用内置检索器
self.generate_answer = dspy.ChainOfThought("context, question -> answer, references")
def forward(self, question):
context = self.retrieve(question).passages
return self.generate_answer(context=context, question=question)
6.2 准备数据与指标
trainset = [ ... ] # 每个Example包含 question, passages, answer
def answer_match(example, pred, trace=None):
return pred.answer.lower() == example.answer.lower()
6.3 编译优化
from dspy.teleprompt import BootstrapFewShotWithRandomSearch
optimizer = BootstrapFewShotWithRandomSearch(metric=answer_match, num_candidate_programs=4)
compiled_rag = optimizer.compile(RAG(passages=passages), trainset=trainset)
编译后的 RAG 程序不仅拥有更好的检索查询表达方式,还能自动在答案生成时引用正确的文档片段。
第7章:最佳实践与常见陷阱
✅ 最佳实践
- 从简单模块开始:先用
dspy.Predict或ChainOfThought原型化,再逐步复杂化。 - 签名描述要清晰:签名中的自然语言描述就是优化器用来理解任务的关键,请仔细撰写。
- 训练集小而精:5~10 个高质量示例往往就能驱动显著的性能提升,不需要海量数据。
- 选择合适的优化器:预算有限时用
BootstrapFewShot;追求极致性能可尝试MIPROv2。 - 使用
dspy.Example的.with_inputs():明确指定哪些字段是输入,可避免数据泄漏。
❌ 常见陷阱
- 忘记定义评估指标:编译器必须知道何谓“好”的预测,否则无法优化。
- 在签名中写冗长的提示:签名只是声明,不要混入手工提示的细节,这些交给优化器。
- 在模块
forward中硬编码特殊逻辑:应保持模块流程清晰,让编译后的参数自动处理。 - 忽视模块的组合能力:DSPy 模块可以嵌套,构建多层流水线时复用已有编译模块。
结语
DSPy 将语言模型编程从一门“手艺”进化为一项“工程”。你不再需要同时成为语言学家、提示大师和调试专家;只需声明你要什么,框架会为你找到最好的实现方式。现在开始,用 DSPy 重新审视你的所有 NLP 流水线,体验自动优化带来的效率飞跃。
本教程基于 DSPy 最新版本编写,源码请参考 DSPy 官方文档。