Chapter 03

内置 Module:Predict / ChainOfThought / ReAct

Signature 只说清楚"做什么",Module 决定"怎么做"。DSPy 内置的几个 Module 覆盖了 80% 日常场景。

四大内置 Module 速查

Module做什么适用成本
Predict直接输出答案简单映射、分类最低
ChainOfThought先 reasoning 再输出需要多步推理中(多 token)
ReAct思考-调工具-观察循环需要外部工具高(多轮调用)
ProgramOfThought生成并执行 Python数学/精确计算高(沙箱)

Predict:最直接的调用

class Categorize(dspy.Signature):
    """把邮件归类为三个类别之一"""
    email: str = dspy.InputField()
    category: Literal["spam", "work", "personal"] = dspy.OutputField()

cat = dspy.Predict(Categorize)
cat(email="您的订单已发货...").category    # → "work"

Predict 是最纯粹的 Signature 执行器:拼 prompt → 调 LLM → 解析输出。适合不需要思考过程的快速任务。

ChainOfThought:加一个 reasoning 字段

cot = dspy.ChainOfThought("question -> answer")
result = cot(question="小明有 5 个苹果,分给 3 个朋友,每人至少 1 个,最多 2 个,有多少种分法?")
print(result.reasoning)   # 步骤推理
print(result.answer)      # 答案

底层等价于一个新的 Signature:

# ChainOfThought 其实做的事
class QAWithReasoning(dspy.Signature):
    """..."""
    question: str = dspy.InputField()
    reasoning: str = dspy.OutputField(desc="分步思考")
    answer: str = dspy.OutputField()
什么时候用 CoT
✓ 多步推理、数学、因果判断、需要解释的场景
✓ Optimizer 效果显著(有 reasoning 后示例更有信息量)
✗ 简单分类、抽取——只会浪费 token

和 reasoning 字段相关的技巧

# 自定义 rationale 字段名和描述
cot = dspy.ChainOfThought(
    QA,
    rationale_type=dspy.OutputField(
        prefix="分析过程",
        desc="从给定 context 中找证据并逐步推导",
    ),
)

ReAct:工具调用循环

Reason-Act-Observe 的经典模式。DSPy 2.5 里的 ReAct 已经很接近生产可用:

def search_web(query: str) -> str:
    """搜索引擎,返回前 3 条摘要。"""
    return tavily.search(query)

def calc(expr: str) -> str:
    """计算数学表达式,如 '2+2*3'。"""
    return str(eval(expr))   # 生产请用 sympy

agent = dspy.ReAct(
    "question -> answer",
    tools=[search_web, calc],
    max_iters=5,
)

out = agent(question="马斯克现年多少岁?再加上圆周率前三位之和。")

DSPy 会自动:

  1. 从工具的 docstring 和类型提示生成工具签名,塞进 prompt
  2. 解析 LLM 输出的 Thought: ... / Action: tool(args) / Observation:
  3. 执行工具,把返回拼进下一轮 context
  4. 发现 "Finish" 或达到 max_iters 就停

查看 trajectory

out = agent(question="...")
print(out.trajectory)    # 完整的 Thought/Action/Observation 序列

ProgramOfThought:让 LLM 写代码算

pot = dspy.ProgramOfThought("question -> answer")
pot(question="2024 年有几个周三?").answer

LLM 生成一段 Python,DSPy 在沙箱里跑,把结果塞回去。对于:

ProgramOfThought 比 CoT 靠谱得多——因为 Python 不会"幻觉"。

沙箱很重要
默认 PoT 会直接 exec() LLM 生成的代码,生产一定要:
① 用 Docker/Firejail 隔离; ② 过滤危险 import(os/subprocess/socket); ③ 资源/时间限制。

Module 组合优先于继承

上面四个都是"基础元件",复杂逻辑通过把它们塞到 dspy.Moduleforward 里组合:

class EmailAssistant(dspy.Module):
    def __init__(self):
        self.classify = dspy.Predict("email -> category: Literal['spam','work','personal']")
        self.summarize = dspy.ChainOfThought("email -> summary, action_items: list[str]")

    def forward(self, email):
        cat = self.classify(email=email).category
        if cat == "spam":
            return dspy.Prediction(category=cat, summary="(已标记垃圾)", action_items=[])
        out = self.summarize(email=email)
        return dspy.Prediction(category=cat, summary=out.summary, action_items=out.action_items)

选择哪个 Module 的决策树

任务需要思考吗? │ ├── 否 ──▶ Predict │ └── 是 ──▶ 需要调外部工具吗? │ ├── 否 ──▶ 需要精确计算吗? │ ├── 否 ──▶ ChainOfThought │ └── 是 ──▶ ProgramOfThought │ └── 是 ──▶ ReAct

控制解码参数

# 单次配置
out = cot(question=q, config={"temperature": 0.2, "max_tokens": 512})

# 全局默认
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini", temperature=0, max_tokens=1000))

N-best 采样

outs = cot.forward(question=q, n=5)    # 返回 5 个候选
# 配合自定义投票/评分挑最佳

常见误区

误区真相
"CoT 总比 Predict 好"简单任务上 CoT 浪费 token,有时反而过度思考错得离谱
"ReAct 一定比手写 Agent 灵活"ReAct 循环太长就卡;复杂流程用 LangGraph + DSPy 模块更好
"ProgramOfThought 不安全,别用"生产场景用沙箱是基本操作,别因噎废食
"Module 是轻量对象,随便建"Module 会被 Optimizer 存优化状态,要当成持久对象管理

本章小结