AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


别再写脆弱的prompt讨好LLM啦!快用DSPy拯救你宝贵的prompt思维,偷偷甩掉99%的人
发布日期:2024-05-05 07:14:57 浏览次数: 2436


点击上方蓝字关注我


本文:8600字阅读22分钟 

亲爱的Prompt读者朋友们你们是否深感目前的提示语编写方式已无法满足日益复杂的人工智能系统需求正如我上一篇文章写的那样:重磅 | DSPy让你不写一句Prompt照样构建Agent,从此,你不再卑躬屈膝讨好LLM,面对AI我们人类始终应该是管理者,如果从开始你的占位就像个卑微的乞丐,只能(只会)跟LLM乞求、恭维、贿赂、PUA式沟通,即便是有一天有更高级的AI,你也终将直不起腰来!

写到这里,我陷入沉思,资本用生成式AI整合了人类自诞生以来的语言智慧成果,经过近些年的飞速发展,Prompt究竟放大了我们人性中的什么?是善亦或是恶?即便是发布这篇文章之后我还会偶尔会用手写PromptAI交流,但我相信:读过这篇文章的你会和我一样清醒!

图片来自DALL.E

让我们直视当前方法的不足之处,并一同探索一种全新的范式——DSP(Demonstrate-Search-Predict),它将彻底改变你对提示语编写的思路! DSPy最引人注目的方面之一是其与 PyTorch 在语法上的相似性,使熟悉神经网络训练的人能够轻松上手。由斯坦福大学的奥马尔·卡塔布(Omar Khattab)及其团队创建的 DSPy 为该框架增添了一层可信度和学术严谨性。在生成式人工智能社区内不断增长的兴趣表明,DSPy 可能会成为开发人员和研究人员的改变者,帮助他们充分发挥大型语言模型的潜力,不能说替代,只能说超越简单的提示工程,这样貌似更严谨一些。


传统做法:LLM知识密集型任务上捉襟见肘 


在知识密集型任务中,如开放域问答、多跳问答和对话问答等,传统的prompt方法常常力有不逮。让我们具体分析这些方法的典型缺陷:


1. 虚构事实:在上下文信息匮乏时,大型语言模型(LLM)如GPT-3容易凭空虚构事实并给出荒谬答复。例如,当被问及"大卫·格雷戈里继承了哪座城堡,这座城堡有几层楼?"时,GPT-3竟然虚构了一座并不存在的城堡以及楼层数。


2. 单一检索管道瓶颈:最近流行的"检索后阅读"管道虽然通过检索相关文档来辅助LLM,但在处理复杂问题时效果有限。以同一个城堡问题为例,经典方法检索到的文档无法直接回答原问题。


3. 自搪塌陷阱:另一种热门方法"Self-Ask"允许LLM自行生成子问题并检索相关信息,但常常陷入无关紧要的子问题,最终未能回答原问题。当被问及"大卫·格雷戈利继承的城堡有几层楼?"时,Self-Ask首先问"什么是埃德加多·莫塔拉绑架案?"完全跑偏了方向。自搪塌陷阱(self-handicapping)是一种人类的心理防御策略,人们通过这种策略为可能的失败制造借口,以此保护自己的自尊心和公众形象。这种行为通常发生在面临重要考验或评估的情况下。LLM竟也学会了这个,会不会是我们写Prompt时PUA的太狠了?


面对这些挑战,我们亟需一种全新的编程范式,能够灵活组合LLM和检索模型的功能,系统分解并解决复杂问题。而这正是DSP所擅长的!不要惧怕Python编程,这是用GPT或你能用到的任何一种AI学习Python并快速成长的最好时代!对,就是今天,就是你读到这篇文章的这一刻!再过三年,你一定会在心里记起我。因为你能读到这里,你和全球一流高手一样,面临的问题、困境和学习曲线都是一样的,或许你的独一无二,你的Prompt编写思维可能更是卓越超群,所以,不要被代码困住。


先看一下这个DSPy包里都有些什么?


对任何文章(包含论文)首先要保持批判态度,包括你读我的文章,你需要相信的是:你读完这篇文章能通过作者的方法亲手实现的结果,那才是你收获地知识;否则,你只是贡献了你的注意力而已。

很多新手、老手读不懂,学不会,认为这个DSPy学习曲线过于陡峭,其实是错误地。关键没有找到这个方法的根,这行命令很简单,也很复杂。作者团队迄今为止,关于DSPy这一主题应该有五篇论文。有时间找来看看吧,或者来我群里,一起探讨。


下面在为您介绍这些重要模块之前先介绍DSP:


DSP(Demonstrate-Search-Predict)提出了一种用自然语言编写程序的全新方式,将LLM和检索模型(RM)有机结合,完成复杂的知识密集型任务。其核心思想是通过自然语言文本在两个模型间相互传递信息,逐步分解和解决问题。DSP框架为开发者提供了一系列可组合的基本模块和数据类型,使其可以编写任务感知的程序流程。


DSP分为三个阶段:


图展示了一个DSP程序如何解决多跳问答任务的完整过程(图片来自上一篇文章论文的Refrences今天就不贴论文地址了)。让我们一步步来解析:

1. DEMONSTRATE阶段:对于输入的问题"大卫·格雷戈里继承的城堡有几层楼?"以及两个训练示例,DSP首先利用annotate函数自动为训练示例标注中间转换结果,生成高质量的示范数据。具体来说,对于每个训练示例,程序尝试通过SEARCH和PREDICT阶段回答该问题。如果回答正确,就将中间产生的查询语句(如"Edwin Hubble发现了Palomar 4?"、"Palomar 4的发现者Edwin Hubble出生于何时?")和检索文档一并保存在示范数据中。


这个阶段最大的亮点是,在无需任何中间标注的情况下,DSP可以自动为整个流程生成高质量的示范数据!这是通过annotate原语实现的:

def annotate(train:Examples, fn:Transformation) -> Examples:

# 对每个训练示例调用fn,将其中间预测结果作为新示范数据

...

def multihop_demonstrate(x:Example) -> Example:

demos = annotate(x.train,multihop_attempt)

return Example(x,demos=demos)


def multihop_attempt(d:Example) -> Example:

# 试图通过search和predict阶段回答示例问题

# 若答案正确,则返回填充了中间结果(如检索查询和文档)的示例

```

2. SEARCH阶段:基于输入问题和DEMONSTRATE生成的示范数据,DSP程序会分解问题并进行多跳检索。在该例中,第一跳检索查询是"大卫·格雷戈利继承了哪座城堡?",返回Kinnairdy城堡的信息。第二跳则是"Kinnairdy城堡有几层楼?"并成功检索到"五层"的答案。所有中间查询和检索结果都被记录到x.hops和x.context中。


这个阶段采取了多种创新策略,大幅提升了知识获取的效率和效果:


-多跳检索:将复杂问题分解为多个检索跳跃,每一跳专注于搜集部分所需信息。


-查询重写:基于之前的检索结果,重写查询以聚焦剩余需求信息。


-检索结果融合:对于同一查询生成多个检索结果,融合以提高召回率。

def multihop_search_v2(x,max_hops=3)

x.hops = []

for hop in range(max_hops):

summary, query = generate(hop_template)(x)

x.hops.append((summary, query))

if query == 'N/A'break

passages = retrieve(query, k=5)

x.context = [summary] + fused_retrieval(queries)

return x

```



3. PREDICT阶段:有了之前积累的示范数据和检索结果,DSP就可以高效地生成最终答复了。PREDICT阶段综合了多种策略,比如生成多个候选答复,根据上下文相关性排序,或者利用自我一致性等方法选择最可靠的答复。


在这一阶段,DSP提出了诸如多样化生成、自我评分和自我一致性选择等创新策略,确保答复的可靠性和高质量。

def multihop_predict(x)

candidates = generate(template_qa, n=20)(x)

topk = most_common(candidates.answers, k=4)

x.choices = [f"{x.question} {c}" for c in topk]

x.passages = fused_retrieval(x.choices)

x.answer = generate(TemplateMCQ)(x).answer

return x

```

通过将整个解决过程编程化,DSP赋予LLM和检索模型更高的表现力。每个阶段的功能模块可以相对独立地完成分内工作,并通过自然语言文本交换中间结果,最终实现错综复杂的多步分解和reasoning。


上面这张2022年的图片(没想到吧?)生动地呈现了DSP的智能和高效:通过将LLM和检索模型的能力自然编织,DSP可以系统地分解和解决知识密集型问题,并给出正确可靠的最终答复。这正是DSP相较于简单管道的独特优势所在,也是DSP在实证评测中展现出优异表现的关键所在。


通过将整个过程编程化,DSP赋予了LLM和RM更高的表现力。以下是一个DSP程序分解和解决最初"格雷戈利城堡"问题的示例:

def pipeline(x)

x.hops = []

summary, query = generate(hop_template)(x) # 第一跳:"格雷戈利继承了哪座城堡?"

x.hops.append((summary, query))

passages = retrieve(query, k=5) # 得知是Kinnairdy城堡

summary, query = generate(hop_template)(x) # 第二跳:"Kinnairdy城堡有几层楼?"

x.hops.append((summary, query))

passages += retrieve(query, k=5) # 得知有5层楼

x.context = [summary] + passages

x = multihop_predict(x)

return x

pipeline("格雷戈利继承的城堡有几层楼?")

# => "五层"

通过这个例子,我们可以看到DSP如何系统地将一个复杂的问题分解为多个可管理的步骤,让LLM和RM各司其职、相互协作,最终生成正确答复。


如何DSPy


继续上一篇文章中的三个核心模块:签名、模块和优化器,掌握这三个核心模块,你基本上已经掌握了这个复杂架构的主要方面。你也可以看一下前面的文章

2024,还纠结Prompt怎么写?不如用《实践论》和《矛盾论》把问题搞清楚



使用DSPy,您无需编写Prompt。您可以选择任务和优化器来配置最佳提示和其他可能的参数,以从LLM中获得最佳结果。以下是DSPy的八个步骤,但不是每一个应用都严格的需要这些步骤,具体根据你的项目。


  1. 定义您的管道


  2. 定义您的任务


  3. 探索几个例子


  4. 定义数据


  5. 定义您的指标


  6. 收集初步“Zero/Few shot”评价


  7. 使用DSPy优化器编译


  8. 迭代


签名:


别搞太复杂,签名定义了你将传递给模型的输入和你期望得到的输出,签名由三个要素组成:LM应该解决的子任务的最小描述,输入字段的说明,以及输出字段的说明。比如"question -> answer"我理解了,所以简化了很多,你可以找资料来看琢磨一下这句话的意思。


模块:


每一个模块都有独特的作用,我不想把这篇文章写成详细的指南或帮助文档,要不变成付费文章,你又嫌贵,问我为什么这么个定价,给你说一通,你还是悻悻离去,何必呢?


您可能熟悉一些不同的prompt语句,例如在提示的开头添加 "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签名适应任务。


这些个模块中最重要的是Assertions,这个断言模块提供了一系列工具,用于在数据处理流程中进行条件检查和验证。这些断言确保数据满足特定的质量标准或逻辑条件,帮助维护程序的稳定性和可靠性。有一篇论文专讲这个,后续有机会单独说吧。再就是下面这些模块(以下()内模块定义大小写在使用时以help内容为准):


1. Predict模块(dspy.Predict):


这是最基本的模块,类似于模板提示(template prompting)。你可以提供一个输入,模型会生成相应的输出。每次提示都可以修改输入,从而获得不同的输出。


2. Chain of Thought (CoT)模块(dspy.ChainofThought):


该模块旨在引导大型语言模型进行逐步思考和推理,以生成对提示的答复。模型会输出一个"rational"字段,解释它是如何一步步得出最终答案的。


3. Chain of Thought with Hint模块(dspy.ChainofThoughtWithHint):


类似于CoT模块,但允许用户额外提供一个"hint"(提示),被编码到模型的提示中。


4. Multi-Chain Comparison模块(dspy.MultiChainComparison):


该模块将创建多个"学生"模型(Predict对象),让它们对同一问题生成不同的输出。然后由一个"教师"模型评估这些输出,给出最终的reasoning和答案。


5. Program of Thought (PoT)模块(dspy.ProgramofThough): 


PoT模块不仅要求模型生成代码,还要执行生成的代码并输出结果。本质上,大型语言模型在此扮演了Python解释器的角色。


6. Retrieval模块(dspy.Retrieval):


该模块与向量存储引擎连接,从文本数据中检索相关的上下文段落,提供给语言模型辅助回答问题。


7. React模块(dspy.React):


React模块允许在提示中嵌入可执行的Python函数,语言模型可以在其中进行函数调用。还可以与Retrieval模块结合使用。


8. example模块(dspy.example)


示例模块用于创建和管理示例数据或案例,这些示例通常用于训练模型、测试系统或演示特定的功能和场景。


9. knn模块(dspy.knn)


k-最近邻 (k-NN) 模块实现了 k-最近邻算法,用于分类和回归任务。此模块可以快速检索与给定数据点最接近的 k 个邻居,常用于推荐系统和模式识别。


10. program模块(dspy.program)


定义了数据处理和分析任务的编程接口,允许用户编写和执行复杂的数据处理程序。


11. retry模块(dspy.retry)


重试模块提供了自动重试机制,用于处理网络请求、数据库操作或任何可能需要多次尝试成功的操作。


12.signature模块(dspy.signature)


签名模块定义了数据、函数和模块的接口签名。这有助于确保模块间的兼容性和数据的正确传递。

下面,我们就通过两个个简单的示例来感受一下DSPy的魔力。

from dspy import predict

#创建Predict模块

predictor = predict(signature="问题:{question}\n答案:{answer}")

#获取预测结果

result = predictor.predict(question="世界上最高的山峰是什么?")

print(result)

```

输出结果:

`
``

答案:世界上最高的山峰是珠穆朗玛峰,位于喜马拉雅山脉的中尼边境,海拔8848米。

```


我们只需导入predict模块并指定Prompt签名,就可以轻松地将问题发送给LLM并获取响应。这种简洁明了的方式大大减轻了Prompt工程师的负担,使他们能够专注于LLM应用程序的核心逻辑,而不必花费大量精力来编写和调试繁琐的Prompt。


DSPy内置了多种模块,每种模块都针对不同的任务场景进行了优化,通过灵活组合这些模块,我们可以构建出满足各种需求的复杂LLM应用程序,再比如:

from dspy import chain_of_thought

#创建ChainOfThought模块

cot = chain_of_thought(signature="问题:{question}\n解决方案:{result}")

#获取推理结果

result = cot.predict(question="如果一个正方体的体积是27立方米,那么它的边长是多少米?")

print(result)

```

输出结果:

`
``

解决方案:要找出正方体的边长,我们需要利用体积公式:

体积 = 边长^3

给定:

体积 = 27立方米

所以:

27 = 边长^3

边长 = 立方根(27)

边长 = 3

因此,如果一个正方体的体积是27立方米,那么它的边长是3米。

```



通过这个示例,您可以看到LLM不仅给出了最终答案,而且还展示了逐步的推理过程,这对于教育、解释性任务以及司法方面非常有帮助。


您可以将这些模块链接在从 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() 类中定义的模块之间创建了一个从question到prediction的信息流,中间经过Retrieve到Context再到ChainofThought,最后到Prediction。

优化器:Teleprompt


Teleprompte充当DSPy程序的优化器。他们采用一个度量标准,并与DSPy编译器一起学习引导和为DSPy程序的模块选择有效的提示。

from dspy.teleprompt import BootstrapFewShot

# Simple teleprompter example

teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)


DSPy实现了以下五种teleprompter:


dspy.LabeledFewShot :定义预测器要使用的 k 个样本。


dspy.BootstrapFewShot :Bootstrapping


dspy.BootstrapFewShotWithRandomSearch 继承自BootstrapFewShot teleprompter,并为随机搜索过程引入了额外的属性。


dspy.BootstrapFinetune :teleprompter定义为微调编译的 BootstrapFewShot 实例。


dspy.Ensemble :创建多个程序的集成版本,将不同程序的各种输出减少到单个输出中。


还有 SignatureOptimizerBayesianSignatureOptimizer ,它们改进了Zero/FewShot设置中模块中签名的输出前缀和指令。


不同的Teleprompte提供关于它们优化成本与质量等的程度的各种权衡。

上一篇文章举了个例子,配置了开发环境。篇幅原因,下面就不举例了,如果希望一键部署一个环境,自己摸索的朋友可以移步看下上一篇文章

重磅 | DSPy让你不写一句Prompt照样构建Agent,从此,你不再卑躬屈膝讨好LLM



53AI,企业落地应用大模型首选服务商

产品:大模型应用平台+智能体定制开发+落地咨询服务

承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询