解密Prompt系列12. LLM Agent零微调范式 ReAct & Self Ask

前三章我们分别介绍了思维链的使用,原理和在小模型上的使用。这一章我们正式进入应用层面,聊聊如何把思维链和工具使用结合得到人工智能代理。

要回答我们为什么需要AI代理?代理可以解决哪些问题?可以有以下两个视角

首先是我们赋能模型,如果说LLM是大脑,那Agent提供了手脚和感官

  1. 感官:获取真实世界的信息,包括实时信息像天气情况,金融市场,交通状况;包括私有信息例如用户个人数据;包括多模态信息像声音和图像
  2. 手脚:获得和真实世界交互的能力,例如运行python脚本,调用搜索引擎,预定机票酒店。

其次是模型赋能我们,Agent加持的大模型,作为更优的数据和任务中介/代理,赋予了我们和任意数据类型交互的能力,大模型正在重构数据和信息的处理方式。从之前的结构化数据为主向更多的非结构化数据转变。

OpenAI应用研究主管LilianWeng写的LLM Powered Autonomous Agents把人工智能代理(AI Agent)分成了以下3个部分:规划模块,工具调用模块和记忆模块。

之后几章我们会聊到AI代理方案的主要差异也在这三个方向

  1. 规划:如何对问题进行拆解得到解决路径,既模型推理步骤
  2. 工具:支持哪些工具使用,如何进行工具选择,并生成调用工具的请求
  3. 记忆:短期记忆包括工具的返回值,已经完成的推理路径,长期记忆包括可访问的外部长期存储例如知识库

第一篇我们结合langchain介绍无需微调,使用few-shot,zero-shot prompt来生成推理和工具调用模板的两个方案ReAct和SelfASk。个人对langchain是又爱又恨,爱的是它集成了很多前沿的大模型应用方案,恨是感觉它有些过度封装,有点简单问题复杂设计的感觉。因此推荐使用langchain来理解每种方案的实现原理,然后脱离langchain自己写,或者只使用langchain的基础组件来实现,不要去使用它的高级API。

Self Ask

原理

Self Ask提出了一种把问题拆解成子问题的Prompt范式,每一步模型都会自我提问是否可以把问题改写/拆解成一个简单的子问题,并进行回答,回答时可以调佣搜索工具来获得答案,然后根据工具返回结果,继续进行自我提问,直到获得最终答案。其实自我提问的推理形式并不是核心,核心是引导模型来进行问题拆解,也就是开头提到的规划能力。

论文提出之所以需要把原始的思维链改造成一步步自我提问的形式,是因为发现模型在回答复杂问题的时候,模型虽然可以正确回答其中的子问题,但是却无法回答由子问题组合起来的复杂问题,作者称之为Compositionality Gap。举个栗子:模型可以正确回答贾斯汀比伯是哪年出生的? 以及谁是94年大师赛的冠军? 但是模型无法回答谁是贾斯汀比伯出生那一年的大师赛的冠军?而通过引入问题拆解的推理方式,可以很好解决这个问题

应用

我们来看下langchain的Self Ask实现,官网Demo是直接用initialize_agent来初始化代理,这里我们把中间步骤拆解开。以下使用了SerpAPI的google搜索工具和GPT3.5,都需要先去官网申请Key

python 复制代码
import os
from langchain.agents.loading import AGENT_TO_CLASS
from langchain.agents.agent import AgentExecutor
from langchain.agents import AgentType, Tool
from langchain import OpenAI, SerpAPIWrapper
## 需要科学上个网
os.environ["http_proxy"] = "http://127.0.0.1:7890"
os.environ["https_proxy"] = "http://127.0.0.1:7890"
## 定义大模型和搜索工具
llm = OpenAI(temperature=0, openai_api_key=$你的Key)
search = SerpAPIWrapper(params={
    "engine": "google",
    "gl": "us",
    "hl": "zh-cn",
}, serpapi_api_key=$你的Key)

"""
以下的工具初始化方式对齐了Self Ask 的Prompt模板
"""

tools = [
    Tool(
        name="Intermediate Answer",
        func=search.run,
        description="useful for when you need to ask with search"
    )
]
## 组装:初始化agent和Chain
agent_cls = AGENT_TO_CLASS[AgentType.SELF_ASK_WITH_SEARCH]
agent = agent_cls.from_llm_and_tools(llm, tools)
chain = AgentExecutor.from_agent_and_tools(agent, tools, return_intermediate_steps=True)

AGENT_TO_CLASS里面定义了所有的Agent类型,其中SelfAskWithSearchAgent是Self Ask的实现,但其实不同Agent的差异,主要是以下few-shot prompt和对应的parser不同。

python 复制代码
from langchain.agents.self_ask_with_search.output_parser import SelfAskOutputParser
from langchain.agents.self_ask_with_search.prompt import PROMPT

其中SelfAsk的few-shot prompt 如下,few-shot除了提供解码的格式之外,还提示了模型要对问题进行拆解

less 复制代码
_DEFAULT_TEMPLATE = """Question: Who lived longer, Muhammad Ali or Alan Turing?
Are follow up questions needed here: Yes.
Follow up: How old was Muhammad Ali when he died?
Intermediate answer: Muhammad Ali was 74 years old when he died.
Follow up: How old was Alan Turing when he died?
Intermediate answer: Alan Turing was 41 years old when he died.
So the final answer is: Muhammad Ali

Question: When was the founder of craigslist born?
Are follow up questions needed here: Yes.
Follow up: Who was the founder of craigslist?
Intermediate answer: Craigslist was founded by Craig Newmark.
Follow up: When was Craig Newmark born?
Intermediate answer: Craig Newmark was born on December 6, 1952.
So the final answer is: December 6, 1952

省略2个shot

Question: {input}
Are followup questions needed here:{agent_scratchpad}"""

构建完chain我们来跑一个问题看下模型的中间返回结果

python 复制代码
# chain.run是用于直接返回最终结果,直接调用callable可以返回中间过程
output =  chain("昨日A股市场涨幅最高的板块成交量如何")

以下是带中间结果的返回值,可以发现few-shot-prompt引导模型把问题"昨日A股市场涨幅最高的板块成交量如何"拆分成了,"昨日A股市场涨幅最高的板块?",并通过谷歌搜索得到是券商板块后,继续提问"券商板块昨日成交量"得到最终结果

这里只展示了一个goodcase,因为badcase太多啦哈哈~SelfAsk结果不好的两个主要原因有

  • 搜索没有返回有效结果:当前搜索引擎的返回结果并非为大模型回答设计,而还是为传统搜索引擎设计,返回结果不可用可能是抽取的文章摘要(snippet)不合理,或者排序逻辑返回的Top1答案不合适,再或者回答的时效性错误等等,这里存在很大的优化空间
  • 模型拆解问题有误:SelfAsk当前主要针对组合类问题,如果你的问题拆解方式不同,需要对以上few-shot-prompt进行调整,或者进一步通过COT finetune来注入问题拆解的方式

Self Ask是一类最简单的工具调用模板,只支持单一搜索工具的使用,不支持工具选择。下面我们看下支持多种工具调用的ReAct范式~

ReAct

原理

ReAct文如其名,模型推理分成了两个部分,Reason和Action。Reason生成分析步骤,Action生成工具调用请求,二者交替进行直到得到最终的结果。和SelfAsk对比,ReAct进一步把推理和工具调用进行了解耦, 在Self Ask中,自我提问既是推理步骤也是搜索工具的请求query,而在ReAct中工具调用的请求在推理步骤之后,这样可以更好的支持搜索以外的其他工具。

ReAct在文档问答上给出的few-shot-cot推理模板如下

应用

同样是AGENT_TO_CLASS,ReActDocstoreAgent和ZeroShotAgent是基于ReAct开发的。为了保持一致性,我们用和以上Self Ask相同的方式来初始化以下两个Agent,更简洁的初始化代码详见官网ReAct, ReAct Document Store

  • ZeroShotAgent

需要提供可以使用的工具列表,以及每种工具的描述,LLM完全基于上下文,根据工具的描述进行工具选择,适用于没有固定推理套路的场景。为了和SelfAsk对比,这里我们还是使用谷歌搜索,再额外加入Wolfram Alpha工具,代码部分只用替换工具定义的部分和agent class,其余部分完全一样。

python 复制代码
"""
需要提供工具的描述description:用于工具选择和工具请求的生成
同时tool.name从selfAsk中统一的Intermediate Answer,调整为工具本身的名称用于生成工具调用的前缀
"""
import os
from langchain.agents.loading import AGENT_TO_CLASS
from langchain.agents.agent import AgentExecutor
from langchain.agents import AgentType, Tool
from langchain import OpenAI, SerpAPIWrapper
from langchain.utilities.wolfram_alpha import WolframAlphaAPIWrapper
## 需要科学上个网
os.environ["http_proxy"] = "http://127.0.0.1:7890"
os.environ["https_proxy"] = "http://127.0.0.1:7890"
os.environ["WOLFRAM_ALPHA_APPID"] = "你的key"
## 定义大模型和搜索工具
llm = OpenAI(temperature=0, openai_api_key=$你的key)
search = SerpAPIWrapper(params={
    "engine": "google",
    "gl": "us",
    "hl": "zh-cn",
}, serpapi_api_key=$你的key)
wolfram = WolframAlphaAPIWrapper()
tools = [
    Tool(
        name="搜索",
        description="搜索引擎,当你需要回答当前问题的时候调用,输入是检索query",
        func=search.run
    ),
    Tool(
        name="Wolfram",
        description="Wolfram Alpha,当你需要回答和数学,科学,科技,文化,社会,日常生活相关的问题时调用,输入是检索query",
        func=wolfram.run
    ),
]
agent_cls = AGENT_TO_CLASS[AgentType.ZERO_SHOT_REACT_DESCRIPTION ]
agent = agent_cls.from_llm_and_tools(llm, tools)
chain = AgentExecutor.from_agent_and_tools(agent, tools, return_intermediate_steps=True)
output = chain("昨日A股市场涨幅最高的板块成交量多少") #chain.run不能返回中间结果,直接调用可以返回中间过程

加入谷歌搜索和Wolfram工具后,zero-shot prompt如下,包含工具的描述和Action部分可以调用的工具列表。

继续问:昨日A股市场涨幅最高的板块成交量如何?因为没有few-shot拆解问题的指引,只有以上zero-shot去描述工具选择,因此模型并没有正确拆解问题,不过正确选择了搜索工具。

当我们提问wolfram可以解决的问题领域,例如求解几何面积时,大模型会选择调用Wolfram来解决数学问题。

  • ReActDocstoreAgent

适用于文档问答的固定推理模板+固定工具使用,论文定义了两种工具Search检索,和Lookup在文档中查找关键词所在的句子。DocStore因为推理模板固定,可用的场景比较有限,我们就做不测试了,大家可以直接去看官网给出的Demo。

React虽然本身是可以不经过模型指令微调直接使用的,但论文中也提出指令微调后效果会有提升,不过微调的方案我们会单独放一章来说。

总结

看完了SelfAsk和React的实现,不难发现二者存在一些局限性

  • 更适合简单的工具调用:这里的简单是指工具的输入和上文的文本语义比较符合,工具输入比较"自然语言"风格例如搜索。高度结构化和符号化的工具输入,使用Prompt实现,准确率比较有限。
  • 更适合少量的工具组合:受限于Prompt上文的长度,不能把几十个工具prompt全塞进去,因此更适合少量的工具组合一般是3~5个以内
  • 规划能力:在问题拆解上few-shot的效果会比zero-shot要好,不过要支持特定的问题拆解逻辑需要定制化领域few-shot。如果逻辑过于复杂或者多样性较高,只依赖固定prompt的效果也会比较一般。
  • 串行计算延时高:SelfAsk和React都是串行推理逻辑,每一步推理和工具调用都依赖上一步的推理结果,导致整体计算耗时太长。针对这个问题可以看下ReWOO[4]提出的并行推理+槽位填充的方案~

针对更复杂多样的工具调用,和更有针对性/复杂的模型规划能力,我们下一章介绍基于指令微调的工具调用方案。

想看更全的大模型相关论文梳理·微调及预训练数据和框架·AIGC应用,移步Github >> DecryPrompt


Reference

  1. towardsdatascience.com/all-you-nee...
  2. lilianweng.github.io/posts/2023-...
  3. MRKL Systems. A modular, neuro-symbolic architecture that combines large language, models, external knowledge sources and discrete reasoning
  4. ReWOO: Decoupling Reasoning from Observations for Efficient Augmented Language Models
  5. 拾象投研机构对LLM的调研报告(文中有两次PPT的申请链接)
相关推荐
天涯海风2 小时前
检索增强生成(RAG) 缓存增强生成(CAG) 生成中检索(RICHES) 知识库增强语言模型(KBLAM)
人工智能·缓存·语言模型
lxmyzzs3 小时前
基于深度学习CenterPoint的3D目标检测部署实战
人工智能·深度学习·目标检测·自动驾驶·ros·激光雷达·3d目标检测
跟着珅聪学java4 小时前
Apache OpenNLP简介
人工智能·知识图谱
AwhiteV4 小时前
利用图数据库高效解决 Text2sql 任务中表结构复杂时占用过多大模型上下文的问题
数据库·人工智能·自然语言处理·oracle·大模型·text2sql
Black_Rock_br5 小时前
AI on Mac, Your Way!全本地化智能代理,隐私与性能兼得
人工智能·macos
☺����5 小时前
实现自己的AI视频监控系统-第一章-视频拉流与解码2
开发语言·人工智能·python·音视频
fsnine5 小时前
机器学习——数据清洗
人工智能·机器学习
小猿姐6 小时前
KubeBlocks AI:AI时代的云原生数据库运维探索
数据库·人工智能·云原生·kubeblocks
算法_小学生6 小时前
循环神经网络(RNN, Recurrent Neural Network)
人工智能·rnn·深度学习
吱吱企业安全通讯软件7 小时前
吱吱企业通讯软件保证内部通讯安全,搭建数字安全体系
大数据·网络·人工智能·安全·信息与通信·吱吱办公通讯