图片来源:DALL-E 3
DSPy(发音为dee-es-pie)是斯坦福 NLP为编程语言模型开发的新框架,它是否不是一个理想的创新?它是否如其一些支持者所言,是对提示工程技术的替代?最后,用系统化、模块化、可组合的程序取代巧妙、巧妙但又繁琐、脆弱的快速构建,是否可以实现框架的目标?
在本文中,我将探讨 DSPy 的前景和困惑之处、不足之处和需要改进之处;以及它如何构建模块化管道以与 LLM 交互。通过几个端到端提示示例,我将把现有的提示技术转换为等效的 DSPy 模块化版本,并在此过程中评估其优点。您可以在 Google Colab 和 GitHub 上的 Python 应用上仔细阅读这些 IPython 笔记本。

图 1a. Python DSPy 笔记本展示如何使用 DSPy 模块
图 1b . Python DSPy 应用程序展示如何使用 DSPy 模块
DSPy 编程模型机器学习社区正在快速发展提示语言模型 (LM) 的技术,并将其集成到管道中以处理复杂任务。然而,当前的 LM 管道通常依赖于硬编码的“提示模板”,这些提示冗长、脆弱、易碎,并且是通过反复试验开发出来的手工提示 [1]。
斯坦福大学 NLP小组的研究人员 Omar Khatab 和 Arnav Singhvi 等人认为,这种方法虽然很常见,但可能很脆弱、不可靠且不可扩展,类似于手动调整分类器权重。此外,特定或复杂的字符串提示可能无法很好地推广到不同的管道、语言模型、数据域或输入 [2]。
因此,他们提出了一种更具声明性、系统性和程序性的方法来与语言模型交互——这是 PyTorch 和 Python 开发人员在开发机器学习 (ML) 程序和 ML 相关概念时习惯使用的方法。
DSPy 编程模型包含三个高级抽象:签名、模块和提词器(又称优化器)。签名抽象并规定模块的输入/输出行为;模块取代现有的手动提示技术,可以组合成任意管道;提词器通过编译优化管道中的所有模块以最大化指标 [3]。
让我们先从签名开始。
Signatures abstract away promptingDSPy 签名是一种自然语言类型的函数声明:简洁的规范,描述文本转换应该实现什么(例如,“使用问题并返回答案”),而不是详细说明应如何提示特定的 LM 执行该任务。
因此,它们比提示符有两个优势。首先,它们可以编译成自我改进、管道自适应的提示符,或者通过为每个签名引导有用的示例进行微调。其次,它们管理结构化的格式和解析逻辑,减少或理想情况下消除用户程序中脆弱的字符串操作 [4]。
“签名是 DSPy 模块输入/输出行为的声明性规范。签名允许您告诉 LM它需要做什么,而不是指定我们应该如何要求 LM 执行此操作,”文档中指出。[5]
例如,您可以使用简写字符串符号作为参数,以声明方式定义 DSPy Signature 对象。实际上,此 Signature 现在将任务声明为简明提示:给出一个问题,返回一个答案。简而言之,此简写符号是您对简单任务提示的声明性替换。以下是一些简写符号示例:
import dspysig_1 = dspy.Signature(“question -> answer”)sig_2 = dspy.Signature(“document -> summary”)sig_3 = dspy.Signature(“question, context -> answer”)
除了使用内联简写符号来声明任务之外,您还可以定义基于类的签名,从而更好地控制输入/输出字段格式、样式和描述性任务描述。任务描述是类定义中的 Python 文档字符串。输出的格式、样式或行为可以是 dspy.OutputField 的描述性和声明性参数,从而更容易对其进行调整,而不是将其作为较大提示的一部分。
class BasicQA(dspy.Signature): “””Answer questions with short factoid answers””” question = dspy.InputField() answer = dspy.OutputField(desc=”often between 1 and 5 words”, prefix=”Question’s Answer:”)
在内部,DSPy 将上述两种声明格式转换为底层 LLM 的提示,如图 2 所示。或者,使用 DSPy 提词器(优化器),可以编译这些提示以迭代生成优化的 LLM 提示(请参阅下面关于优化器的部分),类似于如何在 PyTorch 等 ML 框架中使用学习优化器(例如 SGD)优化 ML 模型。
图 2.基于类的声明式签名转换为 LLM 提示
使用上述基于类的签名简单且直观。
generate_response = dspy.Predict(BasicQA)pred = generate_resonse(question=”When was the last Solar Eclipse in the United States, and what states were covered in total darkness?”print(f”Answer: {pred.answer}”)
:使用任务描述作为 Python 类文档字符串或简写符号来生成 LLM 提示,而无需手动编写提示,这与 DSPy 框架的一个关键断言一致。感觉就像 Python 编程,而不是手动编写精细的提示。将上面的一些提示技术示例转换为图 1(a) 和 1(b) 让我产生了这种感觉。
虽然dspy.Signature 类是核心构建块,但 DSPy 还包括内置模块,可以有效地转化为提示技术,如思路链、ReAct、RAG、思路程序和复杂推理。
所有这些模块的核心都是dspy.Predict模块,包括 Signature 在内的所有模块都通过其forward()函数调用来调用该模块。在内部,Predict 存储 Signature 并使用它来构造提示。
接下来让我们探索这些模块。
模块构建复杂管道根据 DSPy 文档,DSPy 模块是构建使用语言模型的 DSPy 管道或程序的基本构建块。每个模块都抽象出一种提示技术,例如思路链或 ReAct,并被推广用于处理任何 DSPy 签名。
模块可以具有可学习的参数,包括提示组件和 LM 权重。作为可调用类,它们可以通过输入调用并返回输出。由于它们是构建块,因此可以将多个模块组合成更大的可组合程序作为管道。受 PyTorch 中的 NN 模块的启发,DSPy 模块专为 LLM 程序而设计 [6]。
作为可调用类,模块可以通过输入调用并返回输出。由于它们是构建块,因此可以将多个模块组合成更大的程序作为管道。受 PyTorch 中的 NN 模块的启发,DSPy 模块专为 LLM 程序而设计。
将模块视为简化复杂提示技术的智能捷径。它们就像预制块,您可以将它们拼合在一起以构建程序。我们鼓励您创建自己的模块,这是在 DSPy 中构建复杂数据管道程序的核心方法。这些模块可以独立运行,也可以组合成管道以完成更复杂的任务,并且可以用于各种应用程序 [7]。
例如,我可以使用简写签名符号定义一个独立的ChainOfThought模块。
class ChainOfThought(dspy.Module): def __init__( self, signature): super().__init__() self.predict = dspy.Signature(signature) # overwrite the forward function def forward(self, kwargs): return self.predict(kwargs)# create an instance of class with shorthand Signature notation# as argumentcot_generate = ChainOfThought(“context, question → answer”)# call the instance with input parameters specified in the# signatureresponse = cot_generate(“context=....”, “question=How to compute area of a triangle with height 5 feet and width 3 feet.”print(f”Area of triangle: {response.answer}”)
图 3.作为 LLM 提示生成的带有简写签名的模块 [8]
让我们更进一步地理解这个概念,并使用我们上面定义的构建块以及内置的dspy.Retriever模块构建一个可组合管道,以说明如何创建可组合管道作为 RAG DSPy 程序。
class RAGSignature(dspy.Signature): """ Given a context and question, answer the question. """ context = dspy.InputField() question = dspy.InputField() answer = dspy.OutputField() class RAG(dspy.Module) : def __init__ ( self , num_passages=3) : super().__init__() # Retrieve will use the user’s default retrieval settings # unless overridden . self.retrieve = dspy.Retrieve(k=num_passages) # ChainOfThought with signature that generates # answers given retrieval context & question . self.generate_answer = dspy.ChainOfThought(RAGSignature) def forward (self, question) : context = self.retrieve (question).passages return self.generate_answer(context=context, question=question)
图 4:带有 Retrieve 和 ChainOfThought 的可组合 DSPy RAG 程序模块管道
要查看带有 DSPy 模块的 Naive RAG 的完整实现,请仔细阅读图 1(a)和 1(b)中的链接。
: DSPy 模块是 Python 声明性代码,封装了您的任务逻辑(做什么而不是怎么做)、行为、输入/输出格式、样式和任何自定义代码。无需编写小说作为详尽的提示,也无需费力地反复试验提示。相反,将您的流程构建为可组合块的管道。我更喜欢编写 Python 代码而不是英语,尽管我确实喜欢写作。让 DSPy 完成生成提示和与语言模型交互的工作。
下表列出的内置模块可以很好地映射到常见的提示技术,并且可扩展和可定制。在撰写本文时,以下 DSPy 模块可用:
更棒的是,与 ML 模型一样,您可以通过编译使用 DSPy 优化器优化这些模块,以实现高效的提示生成和响应评估。接下来让我们看看如何实现这一点。
优化和编译模块就像 PyTorch 优化器(如 SGD)一样,DSPy 优化器 API 可让您提供训练示例和评估指标来衡量准确度,从而最大限度地减少损失并提高 ML 准确度。如果您熟悉 PyTorch,那么这个概念会引起您的共鸣。
优化器会接受一个训练集(引导一些选择性示例来学习如何生成提示)和一个指标(用于测量接近度或匹配正确响应);它们会生成一个优化程序的实例,可用于编译 DSPy 程序模块。目前,DSPy 支持许多内置优化器,每个优化器都具有一定程度的严谨性,可最大限度地提高您的指标。
最好用一些示例代码来说明。考虑一个小型训练示例集,您想要用它来训练 DSPy 模块进行情绪分析。在提示工程中,这类似于作为更大的上下文提示的一部分的少量学习技术。
与目标语言模型交互后,模块的响应可能是正面的、负面的或中性的。然后,您的指标可以检查返回的答案是否属于这些类别情绪类别之一,或者是虚假的。
让我们创建一个简短的训练集、指标和模块。请注意,指标可以简单到返回数字分数(如 0 或 1)、精确匹配 (EM) 或 F1,以及平衡和衡量预测中多个关注点的整个 DSPy 程序。
# Evaluate a metric for the right response categorydef evaluate_sentiment(example, pred, trace=None)->bool: return pred in [“positive”, “negative”, “neutral”]def get_examples() -> List[dspy.Example]: trainset = [dspy.Example(sentence=”””This movie is a true cinematic gem, blending an engaging plot with superb performances and stunning visuals. A masterpiece that leaves a lasting impression”””, sentiment=”positive”).with_inputs(“sentence”), dspy.Example(sentence=”””Regrettably, the film failed to live up to expectations, with a convoluted storyline, lackluster acting, and uninspiring cinematography. disappointment overall.””” sentiment=”negative”).with_inputs(“sentence”) dspy.Example(sentence=”””The movie had its moments, offering a decent storyline and average performances. While not groundbreaking, it provided an enjoyable viewing experience.”””, sentiment=”neutral”).with_inputs(“sentence”) ... ] return trainset# define our DSPy module that you want to optimize and compileclass ClassifyEmotion(dspy.Signature): “”” classify emotion based on the input sentence and provide the sentiment as output”"" sentence = dspy.InputField() sentiment = dspy.OutputField(desc=”generate sentiment as positive, negative or neutral”)
from dspy.teleprompt import BootstrapFewShot# Create an optimizeroptimizer = BootstrapFewShot(metric=evaluate_sentiment,trainset=get_examples())compiled_classifier = optimizer.compile(ClassifyEmotion(), trainset=get_examples()# Use our compiled classifier that has learned through bootstrapping # a few examples how to generate the responseresponse = compiled_classifier(sentence="I can't believe how beautiful the sunset was tonight! The colors were breathtaking and it really made my day"print(response.sentiment).
图 5:编译器生成优化提示的工件集[12]
从内部来看,以上所有操作实现了以下目标:
使用少量上下文提示来引导我们的训练集进行学习使用指标来评估输出是否预测了三种情绪类别之一编译 DSPy 模块生成提示使用编译后的分类器对我们的句子进行最佳提示的分类Omar Khattab 和 Arnav Singhvi 等人描述了上述优化和编译过程,该过程分为三个阶段 [13]:
候选生成:选择候选预测模块(如果有多个)。参数优化:选择提示中的候选人的说明或演示,然后使用不同的 LM 权重进行优化以获得最佳响应。高阶程序优化:将其视为语言编译器代码优化,其中代码被重新排列以便更好地执行。在 DSPy 中,复杂的管道被简化为集合并重新排列以改变控制流。要查看 DSPy 框架如何优化和调整您的提示,只需使用此命令打印所有生成的提示的历史记录,其中 n > 0。
your_model.inpspect_history(n= 3)
这将打印出为 LLM 生成的三个不同的优化提示。要查看带有优化器和编译的少样本示例的完整示例,请仔细阅读图 1(a) 和 1(b) 中 ReAct 任务的笔记本或 Python 应用程序。
Frederick Ros 的另一篇讨论探讨了 DSPy 模块的调整和优化 [14]。最后,Omar Khattab 等人提供了几个案例研究和实证数据,表明优化和编译的模块在复杂推理任务的量化测量方面比未优化的模块提供了切实的效率、性能和准确性 [15]。
:DSPy 框架中的优化器和编译器概念可能难以理解,看起来不直观,而且像黑匣子一样神秘。虽然它们实现了目标,但清晰度和简单性不足:为什么不将优化和编译合并为单个 API 调用,而不是两个独立的阶段。此外,文档很少,没有明确的例子或插图来照亮黑暗并使其更加清晰。
到目前为止,我研究过的所有资源(文档和已发布的博客)都无法清晰地解释这一强大的概念。因为这个概念是框架自我完善和优化方面的核心。由于缺乏明确的例子和用例,这个概念让我无法引起我的惊叹。
DSPy 端到端示例程序Omar Khattab 和 Arnav Singhvi 等人写道,编程框架可以从多个维度进行评估,包括计算效率、开发人员效率、代码和概念的直观性等。作者根据三个假设评估了 DSPy 编程框架 [15]:
H1:使用 DSPy,我们可以用简洁、定义明确的模块替换手工制作的提示字符串,而不会降低质量或表达能力。H2:参数化模块并将提示视为优化问题使得 DSPy 能够更好地适应不同的 LM,并且可能胜过专家编写的提示。H3:由此产生的模块化使得更彻底地探索具有有用性能特征或适合细微指标的复杂管道成为可能。我使用 DSPy 框架转换了之前发布的Prompt Engineering 博客中的所有示例,这些示例都使用了明确而详尽的提示技术。使用本地OLama 语言模型和 DSPy 的内置工具(如Retrievers ) ,我能够模块化并构建复杂管道以完成复杂的推理任务。
NLP 任务我使用 DSPy 声明式签名来表达通用 LLM 常见自然语言理解功能的操作方法代码示例,例如 ChatGPT、OLlama、Mistral 和 Llama 3 系列:
文本生成或完成文本摘要文本提取文本分类或情绪分析文本分类文本转换和翻译简单和复杂的推理为此,DSPy 模块可以胜任这项任务。代码是模块化的、声明性的,没有使用提示进行讲故事;无需CO-STAR 提示框架来手工制作复杂的提示。请参阅GenAI Cookbook GitHub 存储库中图 1(a) 和 1(b) 中的笔记本和 Python 应用程序。
思维程序任务对于法学硕士来说,思维程序提示,就像思维链一样,涉及在提示中提供一系列推理步骤,以引导模型找到解决方案。这种技术通过将复杂问题分解为中间步骤来帮助模型处理问题,就像人类一样。通过模仿人类推理,思维链提示提高了模型处理需要逻辑、推理和编程的任务的能力。
使用 DSPy 思维程序 (PoT) 模块dspy.ProgramOfThought,这些示例中的大多数都会生成 Python 代码来解决问题。几乎不需要指定详细的提示,只需提供简明的任务描述。
请参阅GenAI Cookbook GitHub Repository中图 1(a)和 1(b)中的笔记本和 Python 应用程序。
Naive RAGDSPy 模块非常简单且模块化,可以链接或堆叠以创建管道。在我们的例子中,构建 Naive RAG 包括使用dspy.Signature和dspy.ChainOfThought以及模块类 RAG(请参阅dspy_utils中的实现)。
开箱即用,DSPy 支持一组Retrievers 客户端。在这个例子中,我使用了支持的dspy.ColBERTv2工具。
请参阅GenAI Cookbook GitHub Repository中图 1(a)和 1(b)中的笔记本和 Python 应用程序。
ReAct 任务ReAct最早在Yao 等人于 2022 年发表的一篇论文中提出,是一种推理和行动范式,可指导 LLM 以结构化的方式响应复杂查询。推理和行动以思维链的方式交错递进,因此 LLM 可以使用上一个答案从一个结果进展到另一个结果。
结果表明,ReAct 在语言和决策任务中的表现优于其他领先方法,增强了人类对大型语言模型 (LLM) 的理解和信任。最好将其与思路链 (CoT) 步骤结合作为单独的任务,并将结果用于下一步,在推理过程中同时利用内部知识和外部信息。
这项任务是LLM ReAct 提示笔记本的 DSPy 转换。
请参阅GenAI Cookbook GitHub Repository中图 1(a)和 1(b)中的笔记本和 Python 应用程序。
:在上述所有提示工程任务中,DSPy 创建者的假设H1和H3似乎 都站得住脚,符合我的预期。然而,H2有点不清楚,不直观,我无法理解,就像我在上文优化和编译部分所哀叹的那样。在某种程度上,将 DSPy 模块映射到常见的提示任务是有效的,为H1和H2提供了支撑。
结论在本文中,我们介绍了 DSPy 框架和编程模型,这是一种创新、声明式、系统化和模块化的编程方式,用于编程和与语言模型交互,而不是使用显式和复杂的提示。通过我在上一篇博客中探讨的提示工程技术的详细示例,我将提示技术转换为其等效的 DSPy 程序。
在此过程中,我赞扬了 DSPy 的许多方面,这些方面吸引着我,也可能吸引习惯于声明式编程方法的 Python 开发人员。我指出了它在概念、文档示例和缺乏用例方面的一些缺点。原始论文中探讨的三个假设中有两个在我的编程努力中得到了很好的证实:转换复杂而明确的提示工程技术并构建模块化和声明式 DSPy 程序。
斯坦福 NLP为编程语言模型设计的新框架是否不是一个理想的创新?它会逐渐变得无关紧要吗?
我不这么认为。虽然它没有像 LangChain 和 LLamaIndex 等其他 LLM 框架那样迅速流行起来,但它拥有不断扩大的社区,在GitHub上的影响力不断增长(拥有 924 个分支、150 个贡献者和超过 12K 个星标),并在 Reddit和discord 论坛上引发了热烈的讨论,因此也不太可能变得无关紧要。
图6 DSPy与LlamaIndex、LangChain的搜索对比
正如一些支持者所言,它是否是促使工程技术发展的替代品?如果适用,它很可能是一种可选的偏好,有切实的用例可以证明其有效性和用途。如果更多的 GenAI、Data、AI 先锋公司(如Databricks)在其生态系统中展示其用途,或者 DSPy 背后出现资金雄厚的初创公司,我们很可能会看到更广泛的使用。
任何技术创新的全面替代都是夸张的说法;全面替代只能随着时间的推移而实现,而不是一夜之间。DSPy 不太可能立即取代或取代熟练和手工制作的提示和提示模板工程。
参考:https://medium.com/@2twitme/an-exploratory-tour-of-dspy-a-framework-for-programing-language-models-not-prompting-711bc4a56376