DSPy:声明式语言模型编程与自动优化

FreeGuideOnline 最新 2026-06-14

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.ProgramOfThoughtdspy.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)

优化器接收你的程序、训练数据(少量示例即可)和一个评估指标,自动生成高质量的提示或微调适配器。典型优化器包括 BootstrapFewShotBootstrapFewShotWithRandomSearchMIPROv2 等。

第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 为例,其工作流程如下:

  1. 自举:从训练集中随机抽取示例,让模块尝试预测,将成功的预测(完整输入-输出对)收集起来作为示范。
  2. 组合:自动将这些示范嵌入到程序中,形成 few-shot 提示。
  3. 验证:在留出的验证集上评估效果,保留效果最好的提示配置。

更强大的优化器如 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.PredictChainOfThought 原型化,再逐步复杂化。
  • 签名描述要清晰:签名中的自然语言描述就是优化器用来理解任务的关键,请仔细撰写。
  • 训练集小而精:5~10 个高质量示例往往就能驱动显著的性能提升,不需要海量数据。
  • 选择合适的优化器:预算有限时用 BootstrapFewShot;追求极致性能可尝试 MIPROv2
  • 使用 dspy.Example.with_inputs():明确指定哪些字段是输入,可避免数据泄漏。

❌ 常见陷阱

  • 忘记定义评估指标:编译器必须知道何谓“好”的预测,否则无法优化。
  • 在签名中写冗长的提示:签名只是声明,不要混入手工提示的细节,这些交给优化器。
  • 在模块 forward 中硬编码特殊逻辑:应保持模块流程清晰,让编译后的参数自动处理。
  • 忽视模块的组合能力:DSPy 模块可以嵌套,构建多层流水线时复用已有编译模块。

结语

DSPy 将语言模型编程从一门“手艺”进化为一项“工程”。你不再需要同时成为语言学家、提示大师和调试专家;只需声明你要什么,框架会为你找到最好的实现方式。现在开始,用 DSPy 重新审视你的所有 NLP 流水线,体验自动优化带来的效率飞跃。

本教程基于 DSPy 最新版本编写,源码请参考 DSPy 官方文档