You're reading for free via Leonie Monigatti's Friend Link. Become a member to access the best of Medium.
DSPy简介:告别提示,你好编程!
DSPy 框架如何通过用编程和编译替换提示来解决基于 的应用程序中的脆弱性问题

DSPy(作者手绘的图像)
目前,使用大型语言模型构建应用程序(LLMs)可能不仅复杂,而且脆弱。典型的管道通常使用提示实现,这些提示是通过试错手工制作的,因为LLMs对提示方式非常敏感。因此,当你改变管道中的某个部分,例如LLM或你的数据时,你可能会削弱其性能,除非你调整提示(或微调步骤)。
当你更改管道中的某个部分,例如LLM或你的数据时,可能会降低其性能..
DSPy [1] 是一个框架,旨在通过优先考虑编程而不是提示来解决基于语言模型(LM)的应用程序中的脆弱性问题。它允许您重新编译整个管道,以优化它以适应您的特定任务——而不是重复手动提示工程——每当您更改组件时。
虽然该框架的论文[1]已经在2023年10月发表,但我最近才了解到它。在观看了一个视频(“DSPy Explained!” by
[
),我已经理解了为什么开发人员社区对DSPy如此兴奋了!
本文简要介绍了DSPy框架,涵盖了以下主题:
什么是DSPy(包括讨论DSPy与LangChain、LlamaIndex和DSPy与PyTorch的比较)?
DSPy编程模型:签名、模块和提词器- DSPy编译器
DSPy示例:天真的RAG管道
DSPy是什么?
DSPy(“Declarative Self-improving Language Programs(在Python中)”,发音为“dee-es-pie”)[1]是由斯坦福NLP研究人员开发的“基于基础模型的编程框架”。它强调编程而不是提示,将基于LM的管道构建从操纵提示中移开,更接近于编程。因此,它旨在解决构建基于LM的应用程序中的脆弱性问题。
DSPy还提供了一种更系统的方法来构建基于LM的应用程序,通过将程序的信息流与每个步骤的参数(提示和LM权重)分开。然后,DSPy将自动优化如何提示(或微调)LM以适应您的特定任务。
为此,DSPy引入了一套以下概念:
手写提示和微调被抽象和替换为签名
提示技术,如思维链或ReAct,被抽象并替换为模块
手动提示工程使用优化器(提词器)和DSPy编译器进行自动化
使用DSPy构建基于LM的应用程序的工作流程,如在DSPy介绍笔记本中讨论的,如下所示。它将提醒您训练神经网络的工作流程:

使用DSPy构建基于LLM的应用程序的工作流程
收集数据集:收集一些你的程序的输入和输出示例(例如,问题和答案对),这些示例将用于优化你的管道。
编写DSPy程序:使用签名和模块定义您的程序逻辑,以及组件之间的信息流,以解决您的任务。
定义验证逻辑:定义一个逻辑,以使用验证指标和优化器(提词器)来优化您的程序。
编译DSPy程序:DSPy编译器考虑训练数据、程序、优化器和验证指标,以优化您的程序(例如提示或微调)。
迭代:通过改进数据、程序或验证过程来重复该过程,直到你对管道的性能感到满意为止。
以下是与DSPy相关的所有重要链接的简短列表:
DSPy 论文:DSPy:将声明性语言模型调用编译为自我改进的管道 [1]
DSPy GitHub:https://github.com/stanfordnlp/dspy
通过关注Omar Khattab来了解DSPy的最新动态
DSPy与LangChain或LlamaIndex有什么不同之处?
LangChain、LlamaIndex和DSPy都是帮助开发人员轻松构建基于LM的应用程序的框架。使用LangChain和LlamaIndex的典型管道通常使用提示模板实现,这使得整个管道对组件更改敏感。相比之下,DSPy将构建基于LM的管道从操纵提示中移开,更接近于编程。
DSPy中引入的新编译器消除了在基于LM的应用程序中更改部分(如LM或数据)时进行任何额外的提示工程或微调工作。相反,开发人员可以简单地重新编译程序,以优化管道以适应新添加的更改。因此,DSPy帮助开发人员以比LangChain或LlamaIndex更少的努力获得其管道的性能。
虽然LangChain和LlamaIndex已经在开发者社区中广泛采用,但DSPy已经在同一社区中引起了相当大的兴趣,作为新的替代方案。
DSPy与PyTorch有什么关系?
如果你有数据科学背景,当你开始使用DSPy时,你会很快注意到与PyTorch的语法相似之处。DSPy论文的作者[1]明确表示PyTorch是灵感的来源。
与PyTorch类似,通用层可以在任何模型架构中组合,在DSPy中,通用模块可以在任何基于LM的应用中组合。此外,编译DSPy程序,其中DSPy模块中的参数会自动优化,类似于在PyTorch中训练神经网络,其中使用优化器训练模型权重。
以下表格总结了PyTorch和DSPy之间的类比关系:

比较:PyTorch vs. DSPy
DSPy编程模型
本节讨论了DSPy编程模型引入的以下三个核心概念:
签名:抽象提示和微调
在DSPy程序中,对LM的每次调用都必须有一个自然语言签名,它取代了传统的手写提示。签名是一个短函数,指定变换的作用而不是如何提示LM执行它(例如,“消费问题和上下文并返回答案”)。

DSPy签名替换手写提示。
签名是其最小形式中的输入和输出字段元组。

DSPy 签名最小结构
下面,您可以看到一些速记签名示例。
"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")
与手写提示不同,签名可以通过为每个签名提供示例来编译成自我改进和管道自适应提示或微调,从而实现自我改进和管道自适应。
模块:抽象提示技术
你可能熟悉几种不同的提示技术,例如在提示的开头添加像 "Your task is to ..."
或 "You are a ..."
这样的句子,思维链( "Let's think step by step"
),或者在提示的结尾添加像 "Don't make anything up"
或 "Only use the provided context"
这样的句子。
DSPy中的模块是模板化和参数化的,以抽象这些提示技术。这意味着它们用于通过应用提示、微调、增强和推理技术来适应DSPy签名以适应任务。
下面,你可以看到如何将签名传递给 ChainOfThought
模块,然后使用输入字段 context
和 question
的值进行调用。
# Option 1: Pass minimal signature to ChainOfThought module
generate_answer = dspy.ChainOfThought("context, question -> answer")
# Option 2: Or pass full notation signature to ChainOfThought module
generate_answer = dspy.ChainOfThought(GenerateAnswer)
# Call the module on a particular input.
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?")
下面,你可以看到 ChainOfThought
模块如何初始实现签名 "context, question -> answer"
。如果你想自己试试,可以使用 lm.inspect_history(n=1)
打印最后一个提示。

使用ChainOfThought模块实现“上下文,问题->答案”签名的初始实现
在编写时,DSPy实现了以下六个模块:
dspy.Predict
:处理输入和输出字段,生成指令,并为指定的signature
创建模板。
dspy.ChainOfThought
:继承自Predict
模块,并添加了“思维链”处理功能。
dspy.ChainOfThoughtWithHint
:从Predict
模块继承并增强了ChainOfThought
模块,提供了提供推理提示的选项。
dspy.MultiChainComparison
:从Predict
模块继承并添加了多链比较的功能。
dspy.Retrieve
:从检索器模块检索段落。
dspy.ReAct
:设计用于组成思维、行动和观察的交错步骤。
您可以将这些模块组合在一起,创建一个从 dspy.Module
继承的类,并包含两个方法。您可能已经注意到与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()
类中定义的模块之间创建了以下信息流:

示例代码,用于简单的RAG管道,以及模块之间信息流的输出结果。
提词器:自动化任意管道的提示
提词器作为DSPy程序的优化器。它们接受一个指标,并与DSPy编译器一起学习如何启动和选择有效的提示,以优化DSPy程序的模块。
from dspy.teleprompt import BootstrapFewShot
# Simple teleprompter example
teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
在编写时,DSPy实现了以下五个提词器:
dspy.LabeledFewShot
:定义k
预测器要使用的样本数量。
dspy.BootstrapFewShot
:引导程序
dspy.BootstrapFewShotWithRandomSearch
:继承自BootstrapFewShot
提词器,并引入了随机搜索过程的附加属性。
dspy.BootstrapFinetune
:t将提词器定义为微调编译的BootstrapFewShot
实例。
dspy.Ensemble
:创建多个程序的集成版本,将不同程序的各种输出减少为单个输出。
不同的提词器在优化成本和质量等方面提供了不同的权衡。
DSPy编译器
DSPy编译器将内部跟踪您的程序,然后使用优化器(提词器)来优化它,以最大化给定的指标(例如提高质量或成本)以完成您的任务。优化取决于您正在使用的LM类型:
对于:构建高质量的少样本提示
对于较小的语言模型:训练自动微调
这意味着DSPy编译器自动将模块映射到高质量的提示、微调、推理和增强的组合中。[1] 内部,编译器在输入上模拟程序的各个版本,并为每个模块的自改进启动示例跟踪,以优化管道以适应您的任务。这个过程类似于神经网络的训练过程。
例如,虽然最初的提示,即之前创建的 ChainOfThought
模块,可能是任何LM理解任务的良好起点,但它可能不是最佳提示。正如您在下面的图片中可以看到的那样,DSPy编译器优化了初始提示,因此消除了手动提示调整的需求。

DSPy编译器如何优化初始提示(灵感来自 's帖子)
编译器接受以下输入,如以下代码和图像所示:
- 该程序,
包括定义的验证指标在内的提词器- 一些训练样本。
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)。如果您一直关注我的工作,您会看到我使用LangChain(见教程)和LlamaIndex(见教程)构建了简单和高级的RAG管道。因此,从简单的RAG管道开始学习DSPy是很有意义的。
对于以Jupyter Notebook形式呈现的端到端管道,我建议查看DSPy GitHub存储库中的Intro Notebook或由DSPy提供的Getting Started with RAG in DSPy Notebook
先决条件:安装DSPy
要安装 dspy-ai
Python 包,您只需 pip
安装它即可。
pip install dspy-ai
步骤1:设置
首先,您需要设置LM和检索模型(RM)
LM:我们将使用OpenAI的gpt-3.5-turbo
,您需要一个OpenAI API密钥才能使用。要获取一个,您需要一个OpenAI账户,然后在API密钥下选择“创建新密钥”。
RM:我们将使用Weaviate,一个开源向量数据库,我们将用其他数据填充它。
让我们从使用LlamaIndex GitHub存储库(MIT许可证)中的示例数据填充外部数据库开始。你可以用你自己的数据替换这部分。
!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,它免费且无需注册API密钥。请注意,在使用Weaviate摄入数据时,使用一个名为 "content"
的属性非常重要。
import weaviate
from weaviate.embedded import EmbeddedOptions
import re
# Connect to Weaviate client in embedded mode
client = weaviate.Client(embedded_options=EmbeddedOptions(),
additional_headers={
"X-OpenAI-Api-Key": "sk-<YOUR-OPENAI-API-KEY>",
}
)
# Create Weaviate schema
# DSPy assumes the collection has a text key 'content'
schema = {
"classes": [
{
"class": "MyExampleIndex",
"vectorizer": "text2vec-openai",
"moduleConfig": {"text2vec-openai": {}},
"properties": [{"name": "content", "dataType": ["text"]}]
}
]
}
client.schema.create(schema)
# Split document into single sentences
chunks = []
with open("./data/paul_graham_essay.txt", 'r', encoding='utf-8') as file:
text = file.read()
sentences = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s', text)
sentences = [sentence.strip() for sentence in sentences if sentence.strip()]
chunks.extend(sentences)
# Populate vector database in batches
client.batch.configure(batch_size=100) # Configure batch
with client.batch as batch: # Initialize a batch process
for i, d in enumerate(chunks): # Batch import data
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-<YOUR-OPENAI-API-KEY>"
# 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)
第二步:收集数据
接下来,我们将收集一些训练示例(在这种情况下,是手工标注的)。与训练神经网络不同,您只需要几个示例。
# 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'),
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系统。首先,你需要定义一个签名 context, question -> answer
,如签名所示,称为 GenerateAnswer
:
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")
定义签名后,你需要编写一个自定义的RAG类,该类继承自 dspy.Module
。在 __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程序
最后,您可以定义提词器并编译DSPy程序。这将更新 ChainOfThought
模块中使用的提示。对于这个例子,我们将使用一个简单的 BootstrapFewShot
提词器。
from dspy.teleprompt import BootstrapFewShot
# Set up a basic teleprompter, which will compile our RAG program.
teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
# Compile!
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)
现在你可以调用你的RAG管道,如下所示:
pred = compiled_rag(question = "What programming language did the author learn in college?")
从这里开始,您可以评估结果并迭代过程,直到对管道的性能感到满意。对于评估的详细说明,我建议查看DSPy GitHub存储库中的Intro Notebook或由DSPy提供的Getting Started with RAG in DSPy Notebook。
摘要
本文简要介绍了DSPy框架[1],这是生成人工智能社区目前非常关注的话题。DSPy框架引入了一套概念,将基于LM的应用程序的构建从手动提示工程转向编程。
在DSPy中,传统的提示工程概念被替换为:
签名替换手写提示
模块取代了特定的提示工程技术,并
提词器和DSPy编译器取代了提示工程的手动迭代。
在介绍DSPy概念之后,本文将引导您完成一个使用OpenAI语言模型和Weaviate向量数据库作为检索模型的naive RAG管道示例。
喜欢这个故事吗?
免费订阅,以便在发布新故事时收到通知。
在 LinkedIn、Twitter 和 Kaggle 上找到我!
免责声明
我是一名开发者倡导者,在撰写本文时在Weaviate工作。
参考文献
文学
[1] Khattab, O., Singhvi, A., Maheshwari, P., Zhang, Z., Santhanam, K., Vardhamanan, S., ... and Potts, C. (2023). Dspy: 将声明性语言模型调用编译为自我改进的管道。arXiv预印本arXiv:2310.03714。
附加学习资源
DSPy 介绍笔记本- 由[人名]的视频系列 : DSPy 解释!
在DSPy中使用RAG入门!
带有相关Jupyter笔记本
图片
除非另有说明,所有图片均由作者创作。