DSPy 入门
可以组合多个模块,形成一个更大的程序网上给的示例是使用from dspy.datasets import HotPotQA加载数据,也可以用hf下载后放到本地,再从本地加载。但是我想用自己标注的数据,就需要处理为DSPy支持的格式。方法,可以将特定字段标记为输入。(其余的只是元数据或标签。可以使用(点)运算符访问值,train_set[0].answer带hint的数据集label=v,要访问或排
# 背景
学习下自动优化prompts方案,减少写提示词的工作量
# 用途
可以参考官网的例子,dspy可以用于:
Math
RAG
Classification
Information Extraction
Agents
Multi-Stage Pipelines
# 资源
# 启动
关注网页中以下几种不同调用方式的启动
OpenAI
Anthropic
Databricks
Gemini
Local LMs on your laptop
Local LMs on a GPU server
Other providers
官网提供的示例代码较简单,但实际会有更多入参,如下进行了一些介绍
import dspy
lm = dspy.LM("ollama_chat/llama3.2:1b", api_base="http://localhost:11434", api_key="",stream=True,complete_response=True,cache=True)
dspy.configure(lm=lm)
"""
model:ollama_chat/llama3.2:1b
api_base:本地接口
api_key:秘钥
stream:是否流式
complete_response:是否等待流式处理结束后返回所有内容
cache:是否基于缓存内容,如为True,且为之前一样的问题,会返回上次的回答。不再通过大模型即时生成
"""
# 模块:Predict、ChainofThought、ReAct
- predict
直接输出结果
- ChainofThought
CoT,返回思考过程,返回结果中会有reasoning
可以通过参数要求输出多个答案,如这里是5:
classify = dspy.ChainOfThought('question -> answer', n=5)
- ReAct
Reasoning+Action,调用工具(如查询某个网页的信息),进行推理及动作。官网上给了一个agents的示例,question是xxx除以某某的出生年份是多少。dspy根据question思考下一步act,并调用tools,再基于tools返回的结果继续reasoning和act,直到完成任务。
- ChainOfThoughtWithHint
带提示的CoT
适合分类、意图识别、多标签任务
# label 更新时从classes直接拍选择
signature = dspy.Signature("text -> label").with_updated_fields('label', type_=Literal[tuple(CLASSES)])
# 训练时使用提示hint
classify = dspy.ChainOfThoughtWithHint(signature)
ChainOfThoughtWithHint和ChainOfThought的区别:ChainOfThoughtWithHint用到了一些提示,引导模型思考过程,hint可以是类别、关键词、分类逻辑等。
hint在训练和推理时都能用,训练时可以在trainset中显式地带上hint(text、label、hint)。推理阶段也可以传入hint提升准确率,推理时可以result = optimized(text=xxx,hint=xxx)
- ProgramOfThought
教导语言模型输出代码,代码的执行结果将决定响应。
- MultiChainComparison
可以比较来自 ChainOfThought 的多个输出,以产生最终预测。
- majority
可以进行基本投票,从一组预测中返回最常见的响应。
- Module 自定义模块
可以组合多个模块,形成一个更大的程序
# signature 签名
用于定义输入dspy.InputField、输出dspy.OutputField、相关的数据类型(str、list、dict)、字段描述desc
可以用dspy.Signature搭建一个类,用于定义复杂的数据流,再控制,如下例
from typing import Literal
class Classify(dspy.Signature):
"""Classify sentiment of a given sentence."""
sentence: str = dspy.InputField()
sentiment: Literal['positive', 'negative', 'neutral'] = dspy.OutputField()
confidence: float = dspy.OutputField()
classify = dspy.Predict(Classify)
classify(sentence="This book was super fun to read, though not the last chapter.")
还可以指定数据类型,默认的是str:"question -> answer",这等同于 "question: str -> answer: str"
布尔型"sentence -> sentiment: bool"
复合型"question, choices: list[str] -> reasoning: str, selection: int"
可以结合指令instruct
toxicity = dspy.Predict(
dspy.Signature(
"comment -> toxic: bool",
instructions="Mark as 'toxic' if the comment includes insults, harassment, or sarcastic derogatory remarks.",
)
)
# Agent & tools & React 自我调用工具
可以定义一些tools,由DSPy搭建的智能体自己决定如何调用
def evaluate_math(expression: str):
return dspy.PythonInterpreter({}).execute(expression)
def search_wikipedia(query: str):
results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3)
return [x['text'] for x in results]
react = dspy.ReAct("question -> answer: float", tools=[evaluate_math, search_wikipedia])
pred = react(question="What is 9362158 divided by the year of birth of David Gregory of Kinnairdy castle?")
print(pred.answer)
# dspy.Module & 多阶段流水线 & desc 自我扩展提示
官网给了个例子,根据主题先写大纲然后每个模块润色,可以看到这个例子里,最后写出一篇1500文字的文章,可以看到这里先用signature定义了2个class,每个class是一个步骤,然后再用Module定义一个class即DraftArticle用来串联。
这里的desc可以理解为简单写了点提示词?然后让depy来进行扩展?
class Outline(dspy.Signature):
"""Outline a thorough overview of a topic."""
topic: str = dspy.InputField()
title: str = dspy.OutputField()
sections: list[str] = dspy.OutputField()
section_subheadings: dict[str, list[str]] = dspy.OutputField(desc="mapping from section headings to subheadings")
class DraftSection(dspy.Signature):
"""Draft a top-level section of an article."""
topic: str = dspy.InputField()
section_heading: str = dspy.InputField()
section_subheadings: list[str] = dspy.InputField()
content: str = dspy.OutputField(desc="markdown-formatted section")
class DraftArticle(dspy.Module):
def __init__(self):
self.build_outline = dspy.ChainOfThought(Outline)
self.draft_section = dspy.ChainOfThought(DraftSection)
def forward(self, topic):
outline = self.build_outline(topic=topic)
sections = []
for heading, subheadings in outline.section_subheadings.items():
section, subheadings = f"## {heading}", [f"### {subheading}" for subheading in subheadings]
section = self.draft_section(topic=outline.title, section_heading=section, section_subheadings=subheadings)
sections.append(section.content)
return dspy.Prediction(title=outline.title, sections=sections)
draft_article = DraftArticle()
article = draft_article(topic="World Cup 2002")
# 推理(非训练)
“将 AI 行为描述为代码,而非字符串。”,“DSPy 将您的签名扩展为提示并解析您的类型化输出”
例如上面多阶段流水线的例子,是简化了提示词,让depy来根据inputfield名称、outputfield名称、desc来自动扩展.
只需要告诉语言模型 需要做什么,而不是指定 如何 让语言模型去做。因此英文名称非常重要
# 优化器 & 编译
优化器可以参考pytorch中的概念
这里的编译指的是生成优化后的提示
优化器可以分为自动少样本学习、自动指令优化、自动微调、程序转换多种,具体看官网(https://dspy.org.cn/learn/optimization/optimizers/ https://dspy.org.cn/api/optimizers/MIPROv2/)吧,很复杂,官网还讲了些选择方法,下面介绍一些常见的。
- dspy.MIPROv2
“为每个提示提出并智能探索更好的自然语言指令”
import dspy
from dspy.datasets import HotPotQA
dspy.configure(lm=dspy.LM('openai/gpt-4o-mini'))
def search_wikipedia(query: str) -> list[str]:
results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3)
return [x['text'] for x in results]
trainset = [x.with_inputs('question') for x in HotPotQA(train_seed=2024, train_size=500).train]
react = dspy.ReAct("question -> answer", tools=[search_wikipedia])
tp = dspy.MIPROv2(metric=dspy.evaluate.answer_exact_match, auto="light", num_threads=24)
# 优化器.编译(任务,训练集)
optimized_react = tp.compile(react, trainset=trainset)
class RAG(dspy.Module):
def __init__(self, num_docs=5):
self.num_docs = num_docs
self.respond = dspy.ChainOfThought('context, question -> response')
def forward(self, question):
context = search(question, k=self.num_docs) # defined in tutorial linked below
return self.respond(context=context, question=question)
tp = dspy.MIPROv2(metric=dspy.evaluate.SemanticF1(decompositional=True), auto="medium", num_threads=24)
# max_bootstrapped_demos 提示词中模型自己编的示例数量
# max_labeled_demos 提示词中标注的示例数量
optimized_rag = tp.compile(RAG(), trainset=trainset, max_bootstrapped_demos=2, max_labeled_demos=2)
- dspy.BootstrapFinetune 自举微调
“为您的模块构建数据集并使用它们微调系统中语言模型的权重”
- dspy.BootstrapRS
“为每个模块合成良好的 Few-shot 示例”
# 优化(之前称为 teleprompters)
需要少量训练输入。数量可以非常少(例如,只有 5 或 10 个样本),并且可能不完整(只有程序的输入,没有标签)。也可以是很多数据
# 评估
可以是自定义的,也可以是dspy内置的方法
metric=(lambda x, y, trace=None: x.label == y.label)
metric=dspy.evaluate.answer_exact_match
metric=dspy.evaluate.SemanticF1(decompositional=True)
dspy.evaluate.metrics.answer_passage_match
除了内置方法,还可以写一些复杂函数作为度量
def metric(gold, pred, trace=None):
question, answer, tweet = gold.question, gold.answer, pred.output
engaging = "Does the assessed text make for a self-contained, engaging tweet?"
correct = f"The text should answer `{question}` with `{answer}`. Does the assessed text contain this answer?"
correct = dspy.Predict(Assess)(assessed_text=tweet, assessment_question=correct)
engaging = dspy.Predict(Assess)(assessed_text=tweet, assessment_question=engaging)
correct, engaging = [m.assessment_answer for m in [correct, engaging]]
score = (correct + engaging) if correct and (len(tweet) <= 280) else 0
if trace is not None: return score >= 2
return score / 2.0
# 使用自定义数据集
网上给的示例是使用from dspy.datasets import HotPotQA加载数据,也可以用hf下载后放到本地,再从本地加载。但是我想用自己标注的数据,就需要处理为DSPy支持的格式。
d = {"1":"a","2":"b","3":"c","4":"d"}
train_set = [dspy.Example(input_key=k,answer=v).with_inputs('input_key') for k,v in d.items()]
generate_answer = dspy.Predict("input_key -> answer")
generate_answer(input_key=xxx)
with_inputs() 方法,可以将特定字段标记为输入。(其余的只是元数据或标签。)
可以使用 .(点)运算符访问值,train_set[0].answer
带hint的数据集
train_set = [
dspy.Example(
input_key=k,
label=v,
hint = f"This question belongs to the category:{v}"
).with_inputs('input_key') for k,v in d.items()
]
要访问或排除某些键,请使用 inputs() 和 labels() 方法分别返回仅包含输入键或非输入键的新 Example 对象。
article_summary = dspy.Example(article= "This is an article.", summary= "This is a summary.").with_inputs("article")
input_key_only = article_summary.inputs()
non_input_key_only = article_summary.labels()
print("Example object with Input fields only:", input_key_only)
print("Example object with Non-Input fields only:", non_input_key_only)
# 保存与加载
官方提供了save()方法,对优化器编译后的模块保存为json文件
def AAA(dspy.Module):
def __init__(self):
...
def forward():
...
tp = dspy.MIPROv2(...) # 优化器
model = AAA() # 初始化模型
trainset = ... # 数据
res = tp.compile(model, trainset=trainset )
# 将模型结构、优化后的提示、配置保存到文件中
res.save('xxx.json')
# 实例化并加载权重
model2 = AAA()
model2.load('xxx.json')
# 官方教程
https://dspy.org.cn/tutorials/
结合自己的需要,选择对应的教程
更多推荐



所有评论(0)