签名
当我们向DSPy中的LMs分配任务时,我们将所需的行为指定为签名。
签名是DSPy模块输入/输出行为的声明式规范。 签名允许您告诉语言模型需要做什么,而不是指定如何要求语言模型去执行。
你可能熟悉函数签名,它们规定了输入和输出参数及其类型。DSPy签名类似,但有一些区别。虽然典型的函数签名只是描述事物,DSPy签名则声明并初始化模块的行为。此外,字段名称在DSPy签名中很重要。你可以用简单的英语表达语义角色:一个question不同于一个answer,一个sql_query不同于python_code。
为什么我应该使用DSPy签名?
为了实现模块化和简洁的代码,其中LM调用可以被优化为高质量的提示(或自动微调)。大多数人通过编写冗长且脆弱的提示来强制LM完成任务,或者通过收集/生成数据进行微调。编写签名比修改提示或微调更加模块化、自适应和可重现。DSPy编译器将根据您的签名、数据和流程,为您的LM构建高度优化的提示(或微调您的小型LM)。在许多情况下,我们发现编译生成的提示比人工编写的更好。这并不是因为DSPy优化器比人类更有创造力,而仅仅是因为它们可以尝试更多方法并直接调整指标。
内联 DSPy 签名
签名可以定义为一个简短的字符串,包含参数名称和可选类型,用于定义输入/输出的语义角色。
-
问答:
"question -> answer", 等同于"question: str -> answer: str"因为默认类型始终为str -
情感分类:
"sentence -> sentiment: bool", 例如True表示积极 -
摘要生成:
"document -> summary"
您的签名也可以拥有多个具有类型的输入/输出字段:
-
检索增强问答:
"context: list[str], question: str -> answer: str" -
多项选择题解答与推理:
"question, choices: list[str] -> reasoning: str, selection: int"
提示: 对于字段,任何有效的变量名都可以使用!字段名称应具有语义含义,但一开始保持简单,不要过早优化关键词!将这类优化工作留给DSPy编译器。例如,对于摘要任务,使用"document -> summary"、"text -> gist"或"long_context -> tldr"这样的命名通常就足够了。
你也可以在内联签名中添加指令,这些指令可以在运行时使用变量。使用instructions关键字参数向你的签名添加指令。
toxicity = dspy.Predict(
dspy.Signature(
"comment -> toxic: bool",
instructions="Mark as 'toxic' if the comment includes insults, harassment, or sarcastic derogatory remarks.",
)
)
comment = "you are beautiful."
toxicity(comment=comment).toxic
输出:
示例A:情感分类
sentence = "这是一段迷人且常常触动心灵的旅程。" # 来自SST-2数据集的示例
classify = dspy.Predict('sentence -> sentiment: bool') # 稍后我们将看到使用Literal[]的示例
classify(sentence=sentence).sentiment
示例B:摘要生成
# 来自XSum数据集的示例。
document = """这位21岁的球员为西汉姆联出场七次,并在上赛季对阵安道尔球队FC Lustrains的欧联杯资格赛比赛中打入了他的唯一进球。李上赛季在英甲联赛有过两次租借经历,先后效力于布莱克浦和科尔切斯特联。他为U's队打入两球,但未能挽救球队免于降级。李与升级的泰克斯队签订的合同期限尚未公布。请在我们的专属页面上查找所有最新的足球转会信息。"""
summarize = dspy.ChainOfThought('document -> summary')
response = summarize(document=document)
print(response.summary)
许多DSPy模块(除了dspy.Predict)会在底层通过扩展您的签名来返回辅助信息。
例如,dspy.ChainOfThought 还添加了一个 reasoning 字段,该字段包含LM在生成输出 summary 之前的推理过程。
基于类的DSPy签名
对于一些高级任务,您需要更详细的签名。这通常是为了:
-
明确任务性质的某些方面(以下表示为
docstring)。 -
提供关于输入字段性质的提示,通过
desc关键字参数在dspy.InputField中表达。 -
对输出字段的供应约束,表示为
dspy.OutputField的desc关键字参数。
示例C:分类
from typing import Literal
class Emotion(dspy.Signature):
"""分类情绪。"""
sentence: str = dspy.InputField()
sentiment: Literal['sadness', 'joy', 'love', 'anger', 'fear', 'surprise'] = dspy.OutputField()
sentence = "当巨大的聚光灯开始让我眼花时,我开始感到有点脆弱" # 来自 dair-ai/emotion
classify = dspy.Predict(Emotion)
classify(sentence=sentence)
提示: 向语言模型更清晰地指定你的请求没有任何问题。基于类的签名可以帮助你实现这一点。但是,不要过早手动调整签名的关键词。DSPy优化器可能会做得更好(并且在不同语言模型之间具有更好的迁移性)。
示例 D: 评估引用忠实度的指标
class CheckCitationFaithfulness(dspy.Signature):
"""验证文本是否基于提供的上下文。"""
context: str = dspy.InputField(desc="此处的事实被假定为真实")
text: str = dspy.InputField()
faithfulness: bool = dspy.OutputField()
evidence: dict[str, list[str]] = dspy.OutputField(desc="支持声明的证据")
context = "这位21岁的球员为西汉姆联出场7次,并在上赛季对阵安道尔球队FC Lustrains的欧联杯资格赛中打入唯一进球。李上赛季在英甲联赛有过两次租借经历,先后效力于布莱克浦和科尔切斯特联。他为U's队打入两球,但未能帮助球队避免降级。李与升班马泰克斯队的合同期限尚未公布。请在我们的专属页面查看所有最新足球转会信息。"
text = "李为科尔切斯特联打入3球。"
faithfulness = dspy.ChainOfThought(CheckCitationFaithfulness)
faithfulness(context=context, text=text)
Prediction(
reasoning="让我们根据上下文检查声明。文本称李为科尔切斯特联打入3球,但上下文明确指出'他为U's队打入两球'。这是直接矛盾。",
faithfulness=False,
evidence={'goal_count': ["为U's队打入两球"]}
)
示例E: 多模态图像分类
class DogPictureSignature(dspy.Signature):
"""Output the dog breed of the dog in the image."""
image_1: dspy.Image = dspy.InputField(desc="An image of a dog")
answer: str = dspy.OutputField(desc="The dog breed of the dog in the image")
image_url = "https://picsum.photos/id/237/200/300"
classify = dspy.Predict(DogPictureSignature)
classify(image_1=dspy.Image.from_url(image_url))
可能的输出:
签名中的类型解析
DSPy 签名支持多种注释类型:
- 基本类型 如
str,int,bool - Typing模块类型 如
list[str],dict[str, int],Optional[float].Union[str, int] - 自定义类型 在你的代码中定义
- 点符号 用于嵌套类型的正确配置
- 特殊数据类型 例如
dspy.Image, dspy.History
使用自定义类型
# Simple custom type
class QueryResult(pydantic.BaseModel):
text: str
score: float
signature = dspy.Signature("query: str -> result: QueryResult")
class MyContainer:
class Query(pydantic.BaseModel):
text: str
class Score(pydantic.BaseModel):
score: float
signature = dspy.Signature("query: MyContainer.Query -> score: MyContainer.Score")
使用签名构建模块并编译它们
虽然签名对于使用结构化输入/输出进行原型设计很方便,但这并不是使用它们的唯一原因!
你应该将多个签名组合成更大的DSPy modules,并将这些模块编译成优化的提示和微调。