微信扫码
与创始人交个朋友
我要投稿
目前,使用大型语言模型(LLMs)构建应用程序不仅可能复杂,而且可能脆弱。典型的流水线通常使用提示来实现,这些提示是通过反复试验手工制作的,因为LLMs对其提示方式敏感。因此,当您更改流水线中的一个部分,例如LLM或您的数据时,您可能会削弱其性能 —— 除非您调整提示(或微调步骤)。
当您更改流水线中的一个部分,例如LLM或您的数据时,您可能会削弱其性能...
DSPy是什么?
DSPy是一个旨在通过优先考虑编程而不是提示词来解决基于语言模型(LM)的应用程序脆弱性问题的框架。它允许您重新编译整个流水线,以优化到您特定的任务 —— 而不是重复手工调整提示的过程 —— 每当您更改一个组件时。
DSPy是由斯坦福NLP的研究人员开发的“使用基础模型进行编程”的框架。它强调编程而不是提示词,并将基于LM的流水线构建从操作提示转移到编程。因此,它旨在解决构建基于LM的应用程序中的脆弱性问题。
DSPy还通过将程序的信息流与每个步骤的参数(提示和LM权重)分离,提供了更系统化的构建基于LM的应用程序的方法。DSPy然后将接管您的程序,并自动优化如何提示(或微调)模型以适应您的特定任务。
为此,DSPy引入了以下一组概念:
- 手工编写提示以及微调被抽象为签名
- 提示技术(例如“思维链”或“ReAct”)被抽象为模块
- 手动提示工程通过优化器(teleprompters)和DSPy编译器被自动化
使用DSPy构建基于语言模型的应用程序的工作流程,如下图所示。它会让您想起训练神经网络的工作流程:
1.收集数据集:收集您程序的输入和输出的一些示例(例如,问题和答案对),这些示例将用于优化您的流水线。
2.编写DSPy程序:使用签名和模块定义您程序的逻辑以及组件之间的信息流,以解决您的任务。
3.定义验证逻辑:定义一个逻辑来使用验证指标和优化器(自动提示器)优化您的程序。
4.编译DSPy程序:DSPy编译器考虑训练数据、程序、优化器和验证指标来优化您的程序(例如,提示或微调)。
5.迭代:通过改进数据、程序或验证来重复该过程,直到您对流水线的性能满意为止。
DSPy与LangChain/LlamaIndex有何不同?
LangChain、LlamaIndex和DSPy都是帮助开发人员轻松构建基于LM的应用程序的框架。使用LangChain和LlamaIndex的典型流水线通常使用提示模板来实现,这使得整个流水线对组件变化非常敏感。相比之下,DSPy将基于LM的流水线构建从操作提示转移到了更接近编程的方向。
在DSPy中引入的新编译器消除了在更改LM或数据时所需的任何额外的提示工程或微调工作。相反,开发人员只需重新编译程序,即可优化流水线以适应新添加的更改。因此,DSPy帮助开发人员以比LangChain或LlamaIndex更少的工作量获得其流水线性能的提升。
尽管LangChain和LlamaIndex已经在开发者社区中被广泛采用,但DSPy已经作为一种新的替代方案在社区中引起了相当大的兴趣。
DSPy与PyTorch有何关系?
如果您具有数据科学背景,当您开始使用DSPy时,您会很快注意到其与PyTorch语法的相似之处。DSPy论文的作者明确将PyTorch列为灵感来源之一。
与PyTorch类似,通用层可以在任何模型架构中组合,在DSPy中,通用模块可以在任何基于LM的应用程序中组合。此外,编译DSPy程序,其中DSPy模块中的参数会自动优化,类似于在PyTorch中训练神经网络,其中模型权重是使用优化器训练的。
以下表格总结了PyTorch和DSPy之间的类比:
DSPy程序中对LM的每次调用都必须具有自然语言签名,这取代了传统的手写提示。签名是一个简短的函数,它指定了如何转换,而不是如何提示LM去做它(例如,“接受问题和上下文并返回答案”),如下图所示,左边是传统的手写提示:"基于下面的上下文回答问题",右边是签名:"上下文,问题--->答案"。
签名是以其最简形式表示的输入和输出字段的元组。
下面是几个简写的语法签名示例
"question -> answer"
"long-document -> summary"
"context, question -> answer"
在很多情况下,这些简写语法签名就足够了。然而,在需要更多控制的情况下,您还可以使用以下表示法定义签名。在这种情况下,一个签名由三个元素组成:
LM应该解决的子任务的最小描述,
输入字段的描述,
输出字段的描述。
下面,您可以看到签名的完整表示法 context, question -> answer
:
class GenerateAnswer(dspy.Signature):"""Answer questions with short factoid answers."""context = dspy.InputField(desc="may contain relevant facts")question = dspy.InputField()answer = dspy.OutputField(desc="often between 1 and 5 words")
与手写提示相比,签名可以被编译成自我改进和流水线自适应的提示或微调。
模块:抽象提示技巧
您可能熟悉一些不同的提示技巧,比如在提示开头添加句子“您的任务是…”或“您是…”,思维链(“让我们逐步思考”),或在提示结尾添加句子“不要凭空想象”或“仅使用提供的上下文”。
DSPy中的模块是模板化和参数化的,它抽象了这些提示技巧。这意味着它们用于通过应用提示、微调、增强和推理技术来使DSPy签名适应不同任务。
下面,您可以看到如何将签名传递给一个ChainOfThought模块,然后使用输入字段context
和question的值调用该模块。
# 选项 1,只传递必须的参数
generate_answer = dspy.ChainOfThought("context, question -> answer")
# 选项 2,将一个定义完整的签名类传递进去
generate_answer = dspy.ChainOfThought(GenerateAnswer)
# 调用模块.
pred = generate_answer(context = "Which meant learning Lisp, since in those days Lisp was regarded as the language of AI.",
question = "What programming language did the author learn in college?")
目前DSPy 实现了以下六个模块:
dspy.Predict:处理输入和输出字段,生成指令,并为指定的签名创建模板。
dspy.ChainOfThought:继承自 Predict 模块,并添加了“思维链”处理功能。
dspy.ChainOfThoughtWithHint:继承自 Predict 模块,并通过提供提示来增强 ChainOfThought 模块的功能。
dspy.MultiChainComparison:继承自 Predict 模块,并添加了多链比较的功能。
dspy.Retrieve:从检索器模块中检索段落。
dspy.ReAct:旨在组成思维、行动和观察的交错步骤。
您可以将这些模块链接在一起。您可能已经注意到与 PyTorch 的语法相似性:
__init__(): 声明所使用的子模块。
forward(): 描述了定义的子模块之间的控制流。
class RAG(dspy.Module):
def __init__(self, num_passages=3):
super().__init__()
self.retrieve = dspy.Retrieve(k=num_passages)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
def forward(self, question):
context = self.retrieve(question).passages
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(context=context, answer=prediction.answer)
以上代码片段在 RAG() 类中创建了如下所示的模块之间的信息流:
Teleprompters:自动化任意流水线的提示
Teleprompters充当 DSPy 程序的优化器。它们接收一个度量指标,并与 DSPy 编译器一起学习引导和选择 DSPy 程序模块的有效提示。
from dspy.teleprompt import BootstrapFewShotteleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
DSPy 实现了以下5种teleprompters:
dspy.LabeledFewShot: 定义要由预测器使用的 k 个样本数量。
dspy.BootstrapFewShot: 引导启动
dspy.BootstrapFewShotWithRandomSearch: 继承自 BootstrapFewShot 提示器,并为随机搜索过程引入了额外的属性。
dspy.BootstrapFinetune: t 将提示器定义为 BootstrapFewShot 实例,用于微调编译。
dspy.Ensemble: 创建多个程序的集成版本,将来自不同程序的各种输出合并为单个输出。
还有 SignatureOptimizer 和 BayesianSignatureOptimizer,它们在零/少样本设置的模块中改进输出前缀和签名指令。
不同的teleprompters 在优化成本与质量等方面提供了各种选择。
DSPy 编译器
DSPy 编译器会在内部跟踪您的程序,然后使用优化器(teleprompter)对其进行优化,以最大化给定指标(例如,改善质量或成本)以完成您的任务。优化取决于您所使用的语言模型的类型:
对于大型语言模型(LLMs):构建高质量的少样本提示
对于较小的语言模型:训练自动微调
这意味着 DSPy 编译器会自动将模块映射到高质量的提示、微调、推理和增强组合。在内部,编译器会在输入上模拟程序的各种版本,并为自我改进引导每个模块的示例跟踪,以优化流水线以适应您的任务。该过程类似于神经网络的训练过程。
例如,虽然之前创建的初始提示链思维模块可能是任何语言模型理解任务的良好起点,但它可能并不是最佳提示。如下图所示,DSPy 编译器优化了初始提示,从而消除了手动提示调整的需要。
编译器接受以下输入:
程序,
teleprompter,包括定义的验证指标,
一些训练样本。
from dspy.teleprompt import BootstrapFewShot
# Small training set with question and answer pairs
trainset = [dspy.Example(question="What were the two main things the author worked on before college?",
answer="Writing and programming").with_inputs('question'),
dspy.Example(question="What kind of writing did the author do before college?",
answer="Short stories").with_inputs('question'),
...
]
# The teleprompter will bootstrap missing labels: reasoning chains and retrieval contexts
teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)
DSPy示例:简单的RAG流水线
现在您已经熟悉了DSPy中的所有基本概念,让我们将它们整合到您的第一个DSPy流水线中。
检索增强生成(RAG)目前在生成式人工智能领域非常流行。
对于以Jupyter笔记本形式的端到端管道,我建议您查看DSPy GitHub官方教程:https://github.com/stanfordnlp/dspy/blob/main/intro.ipynb
先决条件:安装DSPy
要安装 dspy-ai Python包,您只需使用pip install命令即可。
pip install dspy-ai
第一步:设置
首先,您需要设置语言模型(LM)和检索模型(RM):
LM:我们将使用OpenAI的gpt-3.5-turbo模型,您需要一个OpenAI API密钥。要获取API密钥,您需要一个OpenAI账户,然后在API密钥选项中选择“创建新的秘密密钥”。
RM:我们将使用Weaviate,一个开源的向量数据库。
我们从LlamaIndex GitHub上下载一些示例数据。您可以用您自己的数据替换此部分。
!mkdir -p 'data'!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham_essay.txt'
接下来,我们将把文档拆分成单个句子,并将其导入数据库。为简单起见,我们将在本文中使用Weaviate 的Embedded模式,这样我们就本地使用而不用访问服务器,请注意,在使用Weaviate时,使用一个名为“content”的属性导入您的数据非常重要。
import weaviate
from weaviate.embedded import EmbeddedOptions
import re
# 连接到 Weaviate 客户端的embedded模式
client = weaviate.Client(embedded_options=EmbeddedOptions(),
additional_headers={
"X-OpenAI-Api-Key": "sk-",
}
)
# 创建Weaviate schema
# DSPy 假定有一个键:'content'
schema = {
"classes": [
{
"class": "MyExampleIndex",
"vectorizer": "text2vec-openai",
"moduleConfig": {"text2vec-openai": {}},
"properties": [{"name": "content", "dataType": ["text"]}]
}
]
}
client.schema.create(schema)
# 将文档分割成单个句子
chunks = []
with open("./data/paul_graham_essay.txt", 'r', encoding='utf-8') as file:
text = file.read()
sentences = re.split(r'(?sentences = [sentence.strip() for sentence in sentences if sentence.strip()]
chunks.extend(sentences)
# 使用batch模式处理向量数据集
client.batch.configure(batch_size=100)
with client.batch as batch: # 初始化一个batch过程
for i, d in enumerate(chunks): # 导入数据
properties = {
"content": d,
}
batch.add_data_object(
data_object=properties,
class_name="MyExampleIndex"
)
现在你可以在全局设置中配置你的LM和RM
import dspy
import openai
from dspy.retrieve.weaviate_rm import WeaviateRM
# Set OpenAI API key
openai.api_key = "sk-"
# Configure LLM
lm = dspy.OpenAI(model="gpt-3.5-turbo")
# Configure Retriever
rm = WeaviateRM("MyExampleIndex",
weaviate_client = client)
# Configure DSPy to use the following language model and retrieval model by default
dspy.settings.configure(lm = lm,
rm = rm)
第二步:收集数据
接下来,我们将收集一些训练示例(在本例中为手动注释的)。与训练神经网络相比,您只需要几个示例。
#只需非常少的训练用的问题-答案对trainset = [dspy.Example(question="What were the two main things the author worked on before college?", answer="Writing and programming").with_inputs('question'),dspy.Example(question="What kind of writing did the author do before college?", answer="Short stories").with_inputs('question'),dspy.Example(question="What was the first computer language the author learned?", answer="Fortran").with_inputs('question'),dspy.Example(question="What kind of computer did the author's father buy?", answer="TRS-80").with_inputs('question'),dspy.Example(question="What was the author's original plan for college?", answer="Study philosophy").with_inputs('question'),]
第三步:编写DSPy程序
现在,您已经准备好编写您的第一个DSPy程序。它将是一个RAG系统。首先,您需要定义一个名为GenerateAnswer的签名context, question -> answer
class GenerateAnswer(dspy.Signature):
"""Answer questions with short factoid answers."""
context = dspy.InputField(desc="may contain relevant facts")
question = dspy.InputField()
answer = dspy.OutputField(desc="often between 1 and 5 words")
在您定义了签名之后,您需要设置一个继承自dspy.Module的自定义RAG类。在__init__()方法中,您声明相关的模块,在forward()方法中,您描述了模块之间的信息流。
class RAG(dspy.Module):
def __init__(self, num_passages=3):
super().__init__()
self.retrieve = dspy.Retrieve(k=num_passages)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
def forward(self, question):
context = self.retrieve(question).passages
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(context=context, answer=prediction.answer)
第四步:编译DSPy程序
最后,您可以定义teleprompter并编译DSPy程序。这将更新在ChainOfThought
模块中使用的提示。在此示例中,我们将使用一个简单的BootstrapFewShot
的teleprompter。
from dspy.teleprompt import BootstrapFewShot
# 定义
teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
# 编译
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)
现在,你可以调用你的RAG流水线了
pred = compiled_rag(question = "What programming language did the author learn in college?")
从这里开始,您可以评估您的结果,并在整个过程中进行迭代,直到您对流水线的性能满意为止。关于评估的详细说明,我建议您查看DSPy GitHub存储库中的教程:https://github.com/stanfordnlp/dspy/blob/main/intro.ipynb
总结
本文简要介绍了当前生成式人工智能社区的振奋人心的DSPy框架。DSPy框架引入了一组概念,将基于语言模型的应用程序的构建从手动提示工程转移到编程中。
在DSPy中,传统的提示工程概念被替换为:
- 签名取代手写提示
- 模块取代特定的提示工程技术
- Teleprompters 和DSPy编译器取代手动提示工程的迭代
在介绍了DSPy概念之后,本文通过一个示例向您展示了如何使用OpenAI语言模型和Weaviate向量数据库作为检索器模型的简单RAG流水线。
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-05-28
2024-04-26
2024-08-21
2024-04-11
2024-08-13
2024-07-09
2024-07-18
2024-10-25
2024-07-01
2024-06-17