LLM Agent 零微调范式:ReAct 与 Self Ask
LLM Agent 零微调范式主要介绍 ReAct 与 Self Ask 两种方案。通过 LangChain 实现无需微调的工具调用。Self Ask 侧重问题拆解解决组合性问题,ReAct 解耦推理与行动支持多工具。两者均存在规划能力受限、串行延迟高等局限。文章详细对比了两种范式的原理、LangChain 实现代码及适用场景,并给出了方案选择建议。

LLM Agent 零微调范式主要介绍 ReAct 与 Self Ask 两种方案。通过 LangChain 实现无需微调的工具调用。Self Ask 侧重问题拆解解决组合性问题,ReAct 解耦推理与行动支持多工具。两者均存在规划能力受限、串行延迟高等局限。文章详细对比了两种范式的原理、LangChain 实现代码及适用场景,并给出了方案选择建议。

在之前的章节中,我们分别介绍了思维链(Chain of Thought)的使用、原理及其在小模型上的应用。本章正式进入应用层面,探讨如何将思维链与工具使用结合,构建人工智能代理(AI Agent)。
AI Agent 可以解决哪些问题?我们可以从两个视角来理解:
OpenAI 应用研究主管 Lilian Weng 将人工智能代理(AI Agent)分成了以下三个部分:规划模块、工具调用模块和记忆模块。
本文将结合 LangChain 介绍无需微调,使用 few-shot 或 zero-shot prompt 来生成推理和工具调用模板的两个方案:ReAct 和 Self Ask。
Self Ask 提出了一种把问题拆解成子问题的 Prompt 范式。每一步模型都会自我提问是否可以把问题改写/拆解成一个简单的子问题,并进行回答。回答时可以调用搜索工具来获得答案,然后根据工具返回结果,继续进行自我提问,直到获得最终答案。其实自我提问的推理形式并不是核心,核心是引导模型来进行问题拆解,也就是开头提到的规划能力。
论文提出之所以需要把原始的思维链改造成一步步自我提问的形式,是因为发现模型在回答复杂问题的时候,虽然可以正确回答其中的子问题,但是却无法回答由子问题组合起来的复杂问题。作者称之为 Compositionality Gap(组合性差距)。
举个栗子:模型可以正确回答贾斯汀比伯是哪年出生的?以及谁是 94 年大师赛的冠军?但是模型无法回答谁是贾斯汀比伯出生那一年的大师赛的冠军?而通过引入问题拆解的推理方式,可以很好解决这个问题。
我们来看下 LangChain 的 Self Ask 实现。这里我们把中间步骤拆解开,使用了 Google 搜索工具和 GPT3.5。
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
# 定义大模型和搜索工具
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 类型,其中 SELF_ask_with_search 是 Self Ask 的实现。不同 Agent 的差异,主要是以下 few-shot prompt 和对应的 parser 不同。
Self Ask 的 few-shot prompt 如下,few-shot 除了提供解码的格式之外,还提示了模型要对问题进行拆解:
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
...
Question: {input} Are followup questions needed here:{agent_scratchpad}
构建完 chain 我们来跑一个问题看下模型的中间返回结果。直接调用 callable 可以返回中间过程。
以下是带中间结果的返回值,可以发现 few-shot-prompt 引导模型把问题拆分成了多个子问题,并通过谷歌搜索得到结果后,继续提问得到最终结果。
Self Ask 结果不好的两个主要原因有:
Self Ask 是一类最简单的工具调用模板,只支持单一搜索工具的使用,不支持工具选择。
ReAct: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS
ReAct 文如其名,模型推理分成了两个部分,Reason 和 Action。Reason 生成分析步骤,Action 生成工具调用请求,二者交替进行直到得到最终的结果。和 Self Ask 对比,ReAct 进一步把推理和工具调用进行了解耦。在 Self Ask 中,自我提问既是推理步骤也是搜索工具的请求 query,而在 ReAct 中工具调用的请求在推理步骤之后,这样可以更好的支持搜索以外的其他工具。
ReAct 在文档问答上给出的 few-shot-cot 推理模板如下:
同样是 AGENT_TO_CLASS,ReActDocstoreAgent 和 ZeroShotAgent 是基于 ReAct 开发的。为了保持一致性,我们用和以上 Self Ask 相同的方式来初始化以下两个 Agent。
需要提供可以使用的工具列表,以及每种工具的描述,LLM 完全基于上下文,根据工具的描述进行工具选择,适用于没有固定推理套路的场景。为了和 SelfAsk 对比,这里我们还是使用谷歌搜索,再额外加入 Wolfram Alpha 工具。
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["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 股市场涨幅最高的板块成交量多少")
加入谷歌搜索和 Wolfram 工具后,zero-shot prompt 包含工具的描述和 Action 部分可以调用的工具列表。因为没有 few-shot 拆解问题的指引,只有以上 zero-shot 去描述工具选择,因此模型并没有正确拆解问题,不过正确选择了搜索工具。
当我们提问 Wolfram 可以解决的问题领域,例如求解几何面积时,大模型会选择调用 Wolfram 来解决数学问题。
适用于文档问答的固定推理模板 + 固定工具使用,论文定义了两种工具 Search 检索,和 Lookup 在文档中查找关键词所在的句子。DocStore 因为推理模板固定,可用的场景比较有限。
React 虽然本身是可以不经过模型指令微调直接使用的,但论文中也提出指令微调后效果会有提升。
看完了 Self Ask 和 React 的实现,不难发现二者存在一些局限性,同时也明确了各自的适用场景。
方案选择建议:
针对更复杂多样的工具调用,和更有针对性/复杂的模型规划能力,下一章我们将介绍基于指令微调的工具调用方案。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online