数据截至 (上游 commit 822f393194b8)
DSPy — 架构与原理
30 秒导读: DSPy 把「写 prompt」变成「写程序」。你只声明每一步的输入输出(叫 签名 Signature),用可组合的模块 Module 把它们拼成管线,然后交给一个优化器 (teleprompter)自动去调那些原本要你手搓的东西——few-shot 样例、指令文字。核心口号: Programming, not prompting(编程,而非提示)。
1. 这是什么(零基础也能懂)
一句话定义: DSPy 是一个让你用 Python 声明式地搭 LLM 管线、再用算法自动优化 prompt 和权重的框架(名字 = Declarative Self-improving Python)。
它解决谁的什么问题。 假设你在搭一个问答系统:先检索资料、再让模型推理、最后给答案。 传统做法是手写一大段 prompt,反复试参数、调措辞、塞 few-shot 例子——脆、难维护、换个模型就得重调。
DSPy 让你换一种写法:
- 你只说每一步要什么输入、产出什么输出(
question, context -> answer),不写措辞; - 措辞、格式、few-shot 例子由框架的 Adapter 和优化器负责;
- 想让它更准?跑一个优化器,喂训练集和一个打分函数,它自动把 prompt 调好。
它能做什么。
- 用一行字符串或一个类声明一个 LLM 任务(签名)。
- 把多个任务组合成程序(RAG、agent、分类器、多跳推理……)。
- 自动给每个步骤的 prompt 注入 few-shot 样例、改写指令。
- 在任意 LiteLLM 支持的模型上跑同一份程序(OpenAI / Anthropic / 本地)。
- 用
Evaluate在数据集上并行打分。
用起来什么样。 一个最小的真实例子:
import dspy
# 1) 配一个语言模型(全局默认)
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
# 2) 声明一个任务:输入 question,输出 answer——不写任何 prompt 措辞
qa = dspy.Predict("question -> answer")
# 3) 直接调用,像调一个函数
pred = qa(question="法国的首都是哪里?")
print(pred.answer) # -> "巴黎"
你从没写过 "You are a helpful assistant..." 这种 prompt。"question -> answer" 这串字符串
就是签名,DSPy 在 背后把它渲染成一个带格式约定的 prompt,调用模型,再把模型的文字解析回
pred.answer 这个字段。
一句话直觉/类比。 把 DSPy 想成 PyTorch + 编译器,但作用对象是 prompt:
Signature≈ 函数签名 / 类型声明(这一步吃什么、吐什么)。Module≈nn.Module(可组合、可嵌套、有「参数」)。Predict的demos(few-shot 样例)和签名的instructions(指令文字)≈ 可学习的参数。teleprompter(优化器)≈ 优化器/编译器:拿训练集和一个 metric,把那些「参数」调好。
本节不碰底层。记住一句话就够:你声明意图,DSPy 负责把它编译成能跑、且被调优过的 prompt。
2. 顶层全景(它大概怎么转)
2.1 五个核心部件
DSPy 的代码量很大,但价值集中在五类部件上:
| 部件 | 一句话职责 | 在哪个文件 |
|---|---|---|
| Signature | 声明一步任务的输入/输出字段 + 指令(类型声明) | dspy/signatures/signature.py |
| Predict / Module | 把签名变成一 次可调用的 LM 步骤;模块可组合成程序 | dspy/predict/predict.py、dspy/primitives/module.py |
| Adapter | 把「签名+输入」渲染成 prompt 消息,再把 LM 文本解析回字段 | dspy/adapters/base.py、dspy/adapters/chat_adapter.py |
| LM (client) | 实际打模型 API(默认走 LiteLLM),返回文本/工具调用 | dspy/clients/base_lm.py、dspy/clients/lm.py |
| Teleprompter | 优化器:拿训练集+metric,自动调 demos / instructions | dspy/teleprompt/*.py |
2.2 一次调用的数据流(推理时)
怎么读这张图:从上往下是一次 qa(question=...) 的生命周期,左侧是「你写的东西」,
右侧是「DSPy 在背后做的事」。
你的代码 DSPy 内部
───────── ─────────
qa = dspy.Predict("q -> a") ── ► 解析签名字符串 → Signature 类(字段+类型+指令)
qa(question="...") ──► Module.__call__ → Predict.forward
│
▼
选 LM + Adapter (默认 ChatAdapter)
│
▼ format()
把签名+demos+输入渲染成 chat messages
(system: 字段说明+格式约定; user: 你的输入)
│
▼ 调 LM (LiteLLM)
模型返回纯文本
│
▼ parse()
按 [[ ## field ## ]] 标记切出每个输出字段
│
▼
pred.answer ◄────────────────── Prediction(answer=...) 回传
关键点:你只碰最上和最下两行(声明 + 拿结果)。中间「渲染 prompt → 调模型 → 解析」 全由 Adapter 封装,所以你换模型、换格式(Chat / JSON / XML)都不用改业务代码。
2.3 优化时多了一圈(编译)
推理只是「跑一遍」。DSPy 的招牌是优化(compile)——把上面那个程序当成有「参数」的对象去调:
trainset + metric 优化器 (teleprompter)
──────────────── ────────────────────
┌──► 在训练集上跑学生程序,收集成功的执行轨迹
optimizer.compile( │ (BootstrapFewShot: 把成功轨迹变成 few-shot demos)
student=program, │ │
trainset=..., ─────┤ ▼ 或
metric=...) │ 搜索更好的指令文字 (MIPROv2 / GEPA)
│ │
└──◄ 返回一个「参数被调好」的新程序
同一个 program,编译前后接口完全一样,只是内部每个 Predict 的 demos 和签名
instructions 被换成了更好的。这就是「Self-improving」。
2.4 主线走一遍(不进代码)
- 你写
dspy.Predict("question -> answer")或一个dspy.Module子类。 dspy.configure(lm=...)设好模型。- 调用程序 → Adapter 渲染 prompt → LM 出文本 → Adapter 解析 → 得到
Prediction。 4.(可选)拿一个优化器 + 训练集 + metric,compile出一个更准的程序。 program.save(...)存下被优化的状态,下次load回来直接用。
3. 阅读地图(建议顺序)
按由浅入深读:
- 01-signatures-and-predict.md — 从最小原子开始:签名怎么
声明任务、
"a, b -> c"字符串怎么被解析成 pydantic 模型、Predict如何把签名变成一次 LM 调用。读完你能写出并调用任意单步任务。 - 02-adapters-format-parse.md — DSPy 工程含量最高的一层:
Adapter 如何把签名渲染成 prompt(
[[ ## field ## ]]协议),如何把 LM 的纯文本切回 结构化字段,以及 ChatAdapter 失败时回退 JSONAdapter 的设计。 - 03-modules-and-agents.md — Module/Parameter 体系怎么递归发现 子模块和「参数」、ChainOfThought 怎么只是「在签名前插一个 reasoning 字段」、ReAct 智能体 怎么用签名多态搭出一个工具调用循环。
- 04-optimizers-teleprompt.md — 招牌功能:BootstrapFewShot 如何用「教师跑成功轨迹」自举 few-shot 样例、MIPROv2 如何联合搜索指令+样例、GEPA 如何用 反思式 prompt 进化。
如果你只想懂「DSPy 凭什么不一样」,读 §1 + §2 + 第 4 章开头即可。
4. 代码地图(导航索引)
| 主题 | 文件 | 关键符号 |
|---|---|---|
| 签名元类 / 字段顺序 | dspy/signatures/signature.py | SignatureMeta、make_signature、_parse_signature |
| 输入/输出字段 | dspy/signatures/field.py | InputField、OutputField、move_kwargs |
| 单步预测 | dspy/predict/predict.py | Predict、Predict.forward、_forward_preprocess |
| 模块基类 | dspy/primitives/module.py | Module、ProgramMeta、named_predictors |
| 参数遍历 | dspy/primitives/base_module.py | named_parameters、deepcopy、save、load |
| Adapter 主流程 | dspy/adapters/base.py | Adapter.__call__、format、_call_postprocess |
| Chat 协议 | dspy/adapters/chat_adapter.py | ChatAdapter、parse、field_header_pattern |
| JSON 回退 | dspy/adapters/json_adapter.py | JSONAdapter、JSONAdapter.parse |
| LM 客户端 | dspy/clients/base_lm.py | BaseLM、BaseLM.__call__、forward |
| CoT / ReAct | dspy/predict/chain_of_thought.py、dspy/predict/react.py | ChainOfThought、ReAct、ReAct.forward |
| 数据容器 | dspy/primitives/example.py、dspy/primitives/prediction.py | Example、Prediction、with_inputs |
| 优化器基类 | dspy/teleprompt/teleprompt.py | Teleprompter、compile |
| 自举样例 | dspy/teleprompt/bootstrap.py | BootstrapFewShot、_bootstrap_one_example |
| 指令+样例联合搜索 | dspy/teleprompt/mipro_optimizer_v2.py | MIPROv2、AUTO_RUN_SETTINGS、_propose_instructions |
| 反思式进化 | dspy/teleprompt/gepa/gepa.py | GEPA、GEPA.compile |
| 评估 | dspy/evaluate/evaluate.py | Evaluate、EvaluationResult |