Chapter 02

Signature:用签名声明任务

Signature 是 DSPy 里最小的抽象。它说清楚"给 LLM 什么输入,期望什么输出"——剩下全由框架处理。

两种写法:字符串 vs 类

字符串签名(快速原型)

import dspy

qa = dspy.Predict("question -> answer")
print(qa(question="法国的首都是?").answer)   # 巴黎

更复杂:

summarize = dspy.Predict("document -> title, summary, keywords")
out = summarize(document=long_text)
print(out.title, out.keywords)

-> 左边是输入字段,右边是输出字段,逗号分隔。适合 30 秒内写完一个 demo。

类签名(生产首选)

from typing import Literal

class SentimentAnalysis(dspy.Signature):
    """判断客服对话里用户的情绪是否激动。"""

    dialogue: str = dspy.InputField(desc="用户与客服的多轮对话,每行一条")
    emotion: Literal["calm", "upset", "angry"] = dspy.OutputField(
        desc="calm=平和, upset=有抱怨, angry=显著愤怒"
    )
    reason: str = dspy.OutputField(desc="一句话解释判断依据")

类签名有三项字符串签名做不到的事:

DSPy 底层做了什么

当你写 dspy.Predict(SentimentAnalysis),DSPy 会生成类似这样的 prompt(简化版):

判断客服对话里用户的情绪是否激动。

---

Follow the following format.

Dialogue: 用户与客服的多轮对话,每行一条
Emotion: calm=平和, upset=有抱怨, angry=显著愤怒
Reason: 一句话解释判断依据

---

Dialogue: {{对话文本}}
Emotion:

你不需要自己写这些——DSPy 根据 Signature 拼出来。换个 adapter(ChatAdapter/JSONAdapter)还能换成 JSON 结构化输出。

InputField 和 OutputField 详解

常用参数

desc: str
对这个字段的描述。会被写进 prompt,是 LLM 理解字段含义的唯一依据——desc 越清晰,输出越稳。
prefix: str
字段在 prompt 里的标题名,默认就是字段名首字母大写。一般不用改。
format: Callable
自定义字段渲染函数。比如把 list 转成带序号的字符串。

类型注解的威力

from pydantic import BaseModel

class Address(BaseModel):
    province: str
    city: str
    street: str

class ExtractAddress(dspy.Signature):
    """从文本中提取结构化地址"""
    text: str = dspy.InputField()
    address: Address = dspy.OutputField()   # DSPy 自动生成 JSON schema

上面这个例子,DSPy 会:

  1. 在 prompt 里把 Pydantic schema 转成 JSON 描述
  2. 调用 LLM 时要求按 JSON 格式输出
  3. 收到输出后用 Pydantic parse,parse 失败自动重试

字符串签名的进阶语法

可以加字段类型注解(2.5+):

cls = dspy.Predict("text -> label: Literal['pos','neg']")
extract = dspy.Predict("text -> entities: list[str], relations: list[tuple]")

多输入多输出

class ResolveTicket(dspy.Signature):
    """根据工单内容、用户历史、知识库片段给出处理建议"""
    ticket: str = dspy.InputField()
    user_history: list[str] = dspy.InputField(desc="用户过去 30 天的 5 条工单摘要")
    kb_snippets: list[str] = dspy.InputField(desc="检索到的 top-3 知识库片段")

    category: Literal["bug", "feature", "billing", "other"] = dspy.OutputField()
    priority: Literal["p0", "p1", "p2"] = dspy.OutputField()
    suggested_reply: str = dspy.OutputField(desc="给客服看的回复草稿,500 字以内")

DSPy 会保证三个输出字段都有,缺任何一个会触发重试或报错(可配)。

desc 怎么写才高效

给 desc 写"约束"而不是"任务"
desc="这是文章摘要"(LLM 知道是摘要,废话)
desc="不超过 80 字,不用问号和感叹号,包含核心数字"(给了边界条件)

好的 desc 通常包含:

继承 Signature:复用字段定义

class QAInputs(dspy.Signature):
    context: str = dspy.InputField()
    question: str = dspy.InputField()

class SimpleQA(QAInputs):
    """只给答案"""
    answer: str = dspy.OutputField()

class QAWithCitations(QAInputs):
    """给答案 + 引用"""
    answer: str = dspy.OutputField()
    citations: list[str] = dspy.OutputField(desc="引用的 context 片段原文")

动态生成签名

运行时拼字段名:

def make_extractor(fields: list[str]):
    sig = "text -> " + ", ".join(fields)
    return dspy.Predict(sig)

extractor = make_extractor(["name", "email", "phone"])

常见坑

症状解法
字段名用中文部分模型报错字段名保持英文 snake_case,desc 写中文
忘写 descLLM 输出格式飘即便是 30 秒原型,desc 也要写关键约束
输出类型太宽(str)Optimizer 难用能 Literal 就 Literal,能结构化就用 Pydantic
docstring 过长token 浪费docstring 写 1-2 句"总目标",细节放到各字段 desc

本章小结