DSPy:提示词工程师的“终章”?

作者:老余捞鱼

原创不易,转载请标明出处及原作者。

写在前面的话:
伴随 AI 应用的广泛推广与普及,人们逐渐意识到,提示词工程绝非万能的艺术抑或科学,它并非对所有问题的提示策略都行之有效,只有部分提示语在孤立情形下看似优良,然而在综合运用时却难担重任。并且,每当大模型平台的某个版本进行升级后,原先有效的提示词失效的情况屡见不鲜。正因如此,提示词工程师似乎愈发与数据标注员的工作性质趋同,需要进行大量的重复操作、反复试验以及手工制作,高价值的产出日益减少,陷入了尴尬之境,同时这也并非能够可持续构建出优质应用的有效方法。好在DSPy 的出现为化解此难题开辟了一条崭新的路径。

一、提示工程存在的问题

       我清晰地记得就在几个月之前,提示工程还是个热门话题,而且整个就业市场都遍布着招聘提示词工程师的身影。然而现今在硅谷,情况似乎并非如此。AI 从业公司已普遍意识到,提示词工程并非什么艺术或者科学,它仅仅是一种精妙的汉斯现象(是指在某些情况下,人们错误地将简单的关联或巧合视为具有某种深刻含义或规律的现象),提示词工程是人类为AI提供必要的上下文,使系统能以更优的方式给出回答。人们甚至还撰写了一些书籍/博客,诸如《人人都是提示工程师》之类的。

       随着大规模的应用已明确表明,提示词工程并不适用于所有问题的单一提示语或提示策略,只是有部分提示语在孤立的状态下看似更好,可在综合分析时却不堪重用。而当大模型平台的某个版本升级后,原来的提示词无效的例子比比皆是。所以就造成了提示词工程师看上去越来越和数据标注员这个工种的工作性质类似,需要大量重复、反复试验手工制作,高价值产出物在不断减少的尴尬境地,同时这也不是能可持续构建出一个好应用的方法。而DSPy的出现为破解这个问题探索出一条新的道路。

进一步理解提示工程存在的问题

        举例来说,当我说“Add 5-shot CoT with RAG, using hard negative examples”,这句话在概念上是不是非常清晰?但在具体到提示词实践中却很难实现,因为LLM 对提示非常敏感,所以在提示中加入这种结构在大多数情况下是行不通的。同时LLM 的行为对提示语的编写方式非常敏感,照成了在这种情况下很难引导它们。因此,当我们构建管道时,不仅仅是提示词工程师要说服 LLM 以某种方式提供输出,更多的输出应该受到限制,使其可以作为管道中其他模块的输入。

       为了解决这个问题,目前已经有很多研究在进行,但取得突破性进展的不多,大多数研究都致力于字符串模板,但这种模板既脆弱又不可扩展。随着时间的推移,语言模型会发生变化,提示也会随之中断。如果我们想将我们的模块插入不同的管道,它就会失效。我们想让它与更新的工具、新的数据库或检索器交互,它也无法工作。而这正是 DSPy 所要解决的问题,它将 LLM 视为一个模块,根据它与管道中其他组件的交互方式自动调整其行为。

二、DSPy 介绍

        DSPy(“Declarative Self-Improved Language Programs (in Python)”,发音为“dee-es-pie”)是一个由斯坦福大学 NLP 研究人员开发的“基于基础模型的编程”框架。提供开源供大家一起来研究使用, GitHub 页面(GitHub - stanfordnlp/dspy: DSPy: The framework for programming—not prompting—foundation models )。

        正如我前文描述所述,使用大型语言模型构建应用程序通常较为复杂且脆弱,因为其典型的流程常使用通过反复试验手工制作的提示语来实现,而语言模型对提示方式很敏感,当更改流程中的部分组件(如语言模型或数据)时,可能会影响性能,除非调整提示或进行微调步骤。DSPy 旨在解决基于语言模型的应用程序中的脆弱性问题,它强调编程而非提示,通过将程序的信息流与每个步骤的参数(如提示和语言模型权重)分离,为构建基于语言模型的应用程序提供了更系统化的方法。该框架引入了一系列概念,用签名抽象并替换手写的提示和微调,用模块抽象并替换更高级的提示技术,同时通过提示器和编译器将手动提示工程自动化。

        与其他一些框架相比,使用 LangChain 和 LlamaIndex 的典型流程通常使用提示模板实现,这使得整个流程对组件变化非常敏感。而 DSPy 中引入的编译器在改变基于语言模型应用程序中的组件(如语言模型或数据)时,消除了额外的提示工程或微调工作,开发人员只需重新编译程序,即可优化流程以适应新的变化,从而能以更少的工作量获得较好的流程性能。此外,对于有数据科学背景的人来说,DSPy 的语法与 PyTorch 有相似之处,例如在 PyTorch 中通用层可在任何模型架构中组合,在 DSPy 中通用模块可在任何基于语言模型的应用程序中组合,编译 DSPy 程序类似于在 PyTorch 中训练神经网络(其中使用优化器训练模型权重)。因此,DSPy 的目标是将重点从调整 LLM 转移到良好的总体系统设计上。

但如何做到这一点呢?
        从思维层面思考这个问题,我们可以将 LLMs 视为 设备:执行指令并通过类似 DNN 的抽象方法进行操作。例如,我们在 PyTorch 中定义了一个卷积层,它可以对来自其他层的一组输入进行操作。从概念上讲,我们可以堆叠这些层,并在原始输入上实现所需的抽象级别,我们不需要定义任何 CUDA 内核和许多其他指令。所有这些都已在卷积层的定义中进行了抽象。这就是我们希望通过 LLMs 实现的效果,LLMs 是抽象化的模块,通过不同的组合堆叠来实现某种行为,无论是 CoT、ReAct 还是其他。

三、如何用 DSPy 进行模型训练

        为了实现所需的行为,我们需要用 DSPy 进行模型训练。使用 DSPy 构建基于语言模型的工作流程如下:首先收集数据集,即收集程序输入和输出的一些示例(如问题和答案对),用于优化流程;然后编写 DSPy 程序,使用签名和模块定义程序逻辑以及组件之间的信息流来解决任务;接着定义验证逻辑,即使用验证度量和优化器(提示器)来优化程序;之后编译 DSPy 程序,编译器会将训练数据、程序、优化器和验证度量等都考虑在内,以优化程序(例如提示或微调);最后进行迭代,通过改进数据、程序或验证等,重复这个过程,直至对流程的性能满意为止。我下面用python一步步的演示给大家看:

1. 配置语言模型:使用 dspy.openai 函数配置所需的语言模型,例如指定模型名称(如性价比最高的gpt-4o-mini)和 API 密钥。

import dspy llm = dspy.openai(model='gpt-4o-mini-2024-07-18', api_key=your_openai_api_key) dspy.settings.configure(lm=llm)

2.定义任务签名:任务签名用于描述输入和输出的格式。可以创建一个自定义的签名类,明确输入字段和输出字段的描述。

class your_signature(dspy.signature): """任务的具体描述""" input_field = dspy.inputfield(desc="输入字段的描述") output_field = dspy.outputfield(desc="输出字段的描述")

3.创建模块:基于定义的签名创建相应的模块,模块将使用签名来执行具体的任务。

class your_module(dspy.module): def forward(self, *inputs): # 在此处实现模块的具体逻辑 # 使用语言模型进行处理并返回输出 return output

4.生成合成数据(可选):如果数据稀缺,可以使用其他方法(如结合 LangChain)生成合成数据,以便更好地进行提示优化。例如,使用 LangChain 根据预设的标准或结构生成具有真实数据特征的结构化输出。


5.进行提示优化(如果需要):利用 DSPy 的提示优化功能,根据合成数据或已有数据来优化提示。可以使用 bootstrap_fewshot 等方法进行优化。


6.定义训练集:如果需要训练模型,将合成数据或实际数据转换为适合训练的格式。例如,将数据与定义的签名进行关联,为模块提供输入。

train_set = (x.with_inputs('input_field_name') for x in few_shot_examples)

7.使用模块进行推理或训练:在训练或推理阶段,调用模块的方法并传入相应的输入数据,获取模型的输出。

module = your_module() response = module(input_data)

8.编译和优化(可选):使用 DSPy 编译器对整个流程进行编译和优化,编译器会考虑程序、训练数据、验证指标等,自动生成优化的调用策略和提示(或微调模型)。

from dspy.teleprompt import bootstrap_fewshot from dspy.evaluate import answer_exact_match # 进行编译和优化的代码示例 compiler_optimized_module = bootstrap_fewshot(module, train_set) response = compiler_optimized_module(input_data)

9.迭代和改进:根据模型的输出结果和性能评估,不断改进数据、程序或验证逻辑,重复上述步骤进行迭代,以提高模型的性能。

       这只是一个基本的概述,实际使用 DSPy 进行模型训练时,需要根据具体的任务和需求来详细设计签名、模块以及优化策略。同时,还需要根据数据的特点和问题的复杂性进行调整和扩展。

       请注意,确保已经正确安装了 DSPy 及其所需的依赖项。此外,不同的任务可能需要不同的签名、模块和优化方法,具体的实现会因问题而异。建议参考 DSPy 的文档、示例代码以及相关的教程,以便更好地理解和应用于特定的场景。

        以下是一个简单的示例代码,展示了使用 DSPy 进行谎言检测的基本步骤,包括定义签名、生成合成数据和进行提示优化:

import dspy 
from typing import List 
from langchain.prompts import prompt_template 
from langchain_core.output_parsers import json_output_parser 
from langchain_core.pydantic_v1 import BaseModel, Field 
from langchain_openai import ChatOpenAI 

# 配置语言模型 
llm = ChatOpenAI(temperature=1, api_key=your_openai_api_key) dspy.settings.configure(lm=llm) 

# 定义谎言检测的签名 
class Veracity(dspy.signature): 
"""评估一个陈述的真实性""" 
fact = dspy.inputfield(desc="一个陈述") 
answer = dspy.outputfield(desc="对陈述真实性的评估")

class LieDetector(dspy.module): 
def forward(self, fact):
# 这里可以添加具体的处理逻辑,使用语言模型进行判断等 
return {"answer": True} # 假设简单地返回 True 作为示例 

# 生成合成数据 
class Data(BaseModel): 
fact: str = Field(description="关于生活、科学或历史的一般事实") 
answer: str = Field(description="事实的真实性,布尔值 1 或 0") 

parser = json_output_parser(pydantic_object=Data) 
prompt = prompt_template( template="回答用户查询\n{format_instructions}\n{query}\n", 
input_variables=("query"), 
partial_variables={"format_instructions": parser.get_format_instructions()} ) 
chain = prompt | model | parser 

list_of_facts = (chain.invoke({"query": "生成数据"}) for _ in range(10)) # 生成 10 个事实-答案对 few_shot_examples = (dspy.example(fact) for fact in list_of_facts) 

# 合成提示优化 
from dspy.teleprompt import bootstrap_fewshot 

# 将事实定义为谎言检测器的输入 
train_set = (x.with_inputs('fact') for x in few_shot_examples) 

# 优化谎言检测器模块 
compiled_lie_detector = bootstrap_fewshot(LieDetector(), train_set) 

# 进行谎言检测 
text = "月亮是由奶酪组成的" 
response = compiled_lie_detector(fact=text) 
print(response.answer)

        在上述示例中,首先定义了一个用于谎言检测的签名 Veracity,其中包含输入字段 fact(待检测的陈述)和输出字段 answer(真实性评估)。然后创建了一个简单的 LieDetector 模块,其 forward 方法目前只是简单地返回 True 作为示例。

       通过生成合成数据,使用 LangChain 生成了一些带有事实和真实性答案的示例数据。接着,使用 bootstrap_fewshot 函数对 LieDetector 模块进行了提示优化,得到优化后的 compiled_lie_detector

       最后,使用优化后的模块对一个示例文本进行谎言检测,并打印出结果。

       请注意,这只是一个简单的示例,实际应用中可能需要更复杂的模块逻辑和更全面的优化策略,具体取决于你的任务需求和数据特点。同时,还需要根据实际情况调整签名、模块的定义以及生成合成数据的方式等。此外,确保将 your_openai_api_key 替换为你实际的 OpenAI API 密钥。

       上图是DSPy为了可视化优化过程中的进度,通过检查正在优化的提示、签名和演示、正在评估的示例以及 LM 调用和成本来帮助理解和调试优化器:DSPy Visualizer

       如果你想详细了解 DSPy,可以访问其 GitHub 页面获取更多信息。(GitHub - stanfordnlp/dspy: DSPy: The framework for programming—not prompting—foundation models )

四、结论

       很多现实结果都表明,在 DSPy 中结合多跳设置甚至可以超越人工反馈。甚至在 DSPy 设置中尝试使用 T5 等更小的模型也能与 GPT 相比。在 lang chain 发布后,DSPy 是我遇到过的最酷的系统之一,这表明我们有希望制造出更好的、系统化设计的系统,而不是在大型 LLM 管道中胡乱拼凑。最后,如果这篇文章对您有价值,请为我点赞。


本文内容仅限制用于技术探讨和学习,原创不易,转载请标明出处及原作者。

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老余捞鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值