理解DSPy适配器
什么是适配器?
适配器是dspy.Predict与实际语言模型(LM)之间的桥梁。当你调用DSPy模块时,适配器会获取你的签名、用户输入以及其他属性如demos(少样本示例),并将它们转换为发送给LM的多轮消息。
适配器系统负责:
- 将DSPy签名转换为系统消息,用于定义任务和请求/响应结构。
- 根据DSPy签名中概述的请求结构格式化输入数据。
- 将语言模型的响应解析回结构化的DSPy输出,例如
dspy.Prediction实例。 - 管理对话历史和函数调用。
- 将预构建的DSPy类型转换为LM提示消息,例如:
dspy.Tool,dspy.Image等。
配置适配器
你可以使用 dspy.configure(adapter=...) 为整个Python进程选择适配器,或者使用 with dspy.context(adapter=...): 仅影响某个命名空间。
如果在DSPy工作流中没有指定适配器,每次dspy.Predict.__call__默认使用dspy.ChatAdapter。因此,以下两个代码片段是等效的:
import dspy
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
predict = dspy.Predict("question -> answer")
result = predict(question="What is the capital of France?")
import dspy
dspy.configure(
lm=dspy.LM("openai/gpt-4o-mini"),
adapter=dspy.ChatAdapter(), # This is the default value
)
predict = dspy.Predict("question -> answer")
result = predict(question="What is the capital of France?")
适配器在系统中的位置
流程工作方式如下:
- 用户调用他们的DSPy智能体,通常是一个带有输入的
dspy.Module。 - 内部的
dspy.Predict被调用来获取语言模型的响应。 dspy.Predict调用 Adapter.format(),将其签名、输入和演示转换为发送到dspy.LM的多轮消息。dspy.LM是litellm的一个轻量封装器,负责与语言模型端点通信。- LM接收消息并生成响应。
- Adapter.parse() 将语言模型的响应转换为结构化的DSPy输出,如签名中所指定。
dspy.Predict的调用者会接收到解析后的输出。
你可以显式调用Adapter.format()来查看发送给语言模型的消息。
# Simplified flow example
signature = dspy.Signature("question -> answer")
inputs = {"question": "What is 2+2?"}
demos = [{"question": "What is 1+1?", "answer": "2"}]
adapter = dspy.ChatAdapter()
print(adapter.format(signature, demos, inputs))
输出应类似于:
{'role': 'system', 'content': 'Your input fields are:\n1. `question` (str):\nYour output fields are:\n1. `answer` (str):\nAll interactions will be structured in the following way, with the appropriate values filled in.\n\n[[ ## question ## ]]\n{question}\n\n[[ ## answer ## ]]\n{answer}\n\n[[ ## completed ## ]]\nIn adhering to this structure, your objective is: \n Given the fields `question`, produce the fields `answer`.'}
{'role': 'user', 'content': '[[ ## question ## ]]\nWhat is 1+1?'}
{'role': 'assistant', 'content': '[[ ## answer ## ]]\n2\n\n[[ ## completed ## ]]\n'}
{'role': 'user', 'content': '[[ ## question ## ]]\nWhat is 2+2?\n\nRespond with the corresponding output fields, starting with the field `[[ ## answer ## ]]`, and then ending with the marker for `[[ ## completed ## ]]`.'}
适配器类型
DSPy 提供多种适配器类型,每种都针对特定用例量身定制:
ChatAdapter
ChatAdapter 是默认适配器,可与所有语言模型配合使用。它采用带有特殊标记的基于字段的格式。
格式结构
ChatAdapter使用[[ ## field_name ## ]]标记来划分字段。对于非基本Python类型的字段,它包含该类型的JSON模式。下面,我们使用dspy.inspect_history()来清晰地显示由dspy.ChatAdapter格式化的消息。
import dspy
import pydantic
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"), adapter=dspy.ChatAdapter())
class ScienceNews(pydantic.BaseModel):
text: str
scientists_involved: list[str]
class NewsQA(dspy.Signature):
"""Get news about the given science field"""
science_field: str = dspy.InputField()
year: int = dspy.InputField()
num_of_outputs: int = dspy.InputField()
news: list[ScienceNews] = dspy.OutputField(desc="science news")
predict = dspy.Predict(NewsQA)
predict(science_field="Computer Theory", year=2022, num_of_outputs=1)
dspy.inspect_history()
输出内容如下:
[2025-08-15T22:24:29.378666]
System message:
Your input fields are:
1. `science_field` (str):
2. `year` (int):
3. `num_of_outputs` (int):
Your output fields are:
1. `news` (list[ScienceNews]): science news
All interactions will be structured in the following way, with the appropriate values filled in.
[[ ## science_field ## ]]
{science_field}
[[ ## year ## ]]
{year}
[[ ## num_of_outputs ## ]]
{num_of_outputs}
[[ ## news ## ]]
{news} # note: the value you produce must adhere to the JSON schema: {"type": "array", "$defs": {"ScienceNews": {"type": "object", "properties": {"scientists_involved": {"type": "array", "items": {"type": "string"}, "title": "Scientists Involved"}, "text": {"type": "string", "title": "Text"}}, "required": ["text", "scientists_involved"], "title": "ScienceNews"}}, "items": {"$ref": "#/$defs/ScienceNews"}}
[[ ## completed ## ]]
In adhering to this structure, your objective is:
Get news about the given science field
User message:
[[ ## science_field ## ]]
Computer Theory
[[ ## year ## ]]
2022
[[ ## num_of_outputs ## ]]
1
Respond with the corresponding output fields, starting with the field `[[ ## news ## ]]` (must be formatted as a valid Python list[ScienceNews]), and then ending with the marker for `[[ ## completed ## ]]`.
Response:
[[ ## news ## ]]
[
{
"scientists_involved": ["John Doe", "Jane Smith"],
"text": "In 2022, researchers made significant advancements in quantum computing algorithms, demonstrating their potential to solve complex problems faster than classical computers. This breakthrough could revolutionize fields such as cryptography and optimization."
}
]
[[ ## completed ## ]]
练习:在打印的LM历史中定位签名信息
尝试调整签名,并观察这些变化如何反映在打印的LM消息中。
每个字段前都有一个标记 [[ ## field_name ## ]]。如果输出字段具有非基本类型,指令会包含该类型的 JSON 模式,并且输出会相应地格式化。由于输出字段按照 ChatAdapter 定义的结构化方式组织,它可以自动解析为结构化数据。
何时使用ChatAdapter
ChatAdapter 提供以下优势:
- 通用兼容性: 适用于所有语言模型,但较小的模型可能会生成不符合所需格式的响应。
- 回退保护: 如果
ChatAdapter失败,它会自动使用JSONAdapter重试。
一般来说,如果没有特定要求,ChatAdapter是一个可靠的选择。
何时不使用聊天适配器
如果你正在使用以下情况,请避免使用 ChatAdapter:
- 延迟敏感:
ChatAdapter相比其他适配器包含更多样板输出标记,因此如果您正在构建对延迟敏感的系统,请考虑使用其他适配器。
JSONAdapter
JSONAdapter 提示语言模型返回包含签名中指定的所有输出字段的JSON数据。对于通过response_format参数支持结构化输出的模型非常有效,利用原生JSON生成能力实现更可靠的解析。
格式结构
由JSONAdapter格式化的提示输入部分与ChatAdapter类似,但输出部分有所不同,如下所示:
import dspy
import pydantic
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"), adapter=dspy.JSONAdapter())
class ScienceNews(pydantic.BaseModel):
text: str
scientists_involved: list[str]
class NewsQA(dspy.Signature):
"""Get news about the given science field"""
science_field: str = dspy.InputField()
year: int = dspy.InputField()
num_of_outputs: int = dspy.InputField()
news: list[ScienceNews] = dspy.OutputField(desc="science news")
predict = dspy.Predict(NewsQA)
predict(science_field="Computer Theory", year=2022, num_of_outputs=1)
dspy.inspect_history()
System message:
Your input fields are:
1. `science_field` (str):
2. `year` (int):
3. `num_of_outputs` (int):
Your output fields are:
1. `news` (list[ScienceNews]): science news
All interactions will be structured in the following way, with the appropriate values filled in.
Inputs will have the following structure:
[[ ## science_field ## ]]
{science_field}
[[ ## year ## ]]
{year}
[[ ## num_of_outputs ## ]]
{num_of_outputs}
Outputs will be a JSON object with the following fields.
{
"news": "{news} # note: the value you produce must adhere to the JSON schema: {\"type\": \"array\", \"$defs\": {\"ScienceNews\": {\"type\": \"object\", \"properties\": {\"scientists_involved\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}, \"title\": \"Scientists Involved\"}, \"text\": {\"type\": \"string\", \"title\": \"Text\"}}, \"required\": [\"text\", \"scientists_involved\"], \"title\": \"ScienceNews\"}}, \"items\": {\"$ref\": \"#/$defs/ScienceNews\"}}"
}
In adhering to this structure, your objective is:
Get news about the given science field
User message:
[[ ## science_field ## ]]
Computer Theory
[[ ## year ## ]]
2022
[[ ## num_of_outputs ## ]]
1
Respond with a JSON object in the following order of fields: `news` (must be formatted as a valid Python list[ScienceNews]).
Response:
{
"news": [
{
"text": "In 2022, researchers made significant advancements in quantum computing algorithms, demonstrating that quantum systems can outperform classical computers in specific tasks. This breakthrough could revolutionize fields such as cryptography and complex system simulations.",
"scientists_involved": [
"Dr. Alice Smith",
"Dr. Bob Johnson",
"Dr. Carol Lee"
]
}
]
}
何时使用 JSONAdapter
JSONAdapter 擅长:
- 结构化输出支持: 当模型支持
response_format参数时。 - 低延迟: 语言模型响应中的最小样板代码带来更快的响应速度。
何时不使用 JSONAdapter
如果你符合以下情况,请避免使用JSONAdapter:
- 使用一个本身不支持结构化输出的模型,例如在Ollama上托管的小型开源模型。
总结
适配器是DSPy的关键组件,它弥合了结构化DSPy签名与语言模型API之间的差距。 了解何时以及如何使用不同的适配器将帮助您构建更可靠和高效的DSPy程序。