从零构建 ReAct 智能体:让大模型学会”边想边做”
本文改编自《Hello Agents》第四章”智能体经典范式构建”中的 ReAct 部分。在保留核心技术细节的基础上,对内容进行了重新组织和润色,使其更适合作为一篇独立的技术博客来阅读。
为什么需要 ReAct?
大语言模型(LLM)已经很强大了,但它有两个绕不开的短板:知识有截止日期,且无法主动与外部世界交互。你问它”华为最新款手机是什么”,它要么承认不知道,要么凭记忆编一个——也就是我们常说的”幻觉”。
在 ReAct 诞生之前,业界尝试过两条路线:
- 纯思考型(如 Chain-of-Thought 思维链):让模型一步步推理,逻辑能力强,但”闭门造车”,无法获取外部信息,容易产生事实性幻觉。
- 纯行动型:模型直接输出要执行的动作,但没有规划,也没有纠错能力,像一个没有大脑的执行器。
2022 年,Shunyu Yao 提出了 ReAct (Reason + Act) 范式[1],核心思想非常朴素却有效:模仿人类解决问题的方式,把”思考”和”行动”显式地结合起来。
人类解决陌生问题时的典型过程是这样的:先想一下”我需要查什么资料” → 去查 → 看到结果后再想”这些信息够不够” → 不够就再查 → 够了就总结答案。ReAct 就是把这套流程固化成了智能体的运行机制。
ReAct 的工作流程
ReAct 通过提示工程引导 LLM,使其每一步输出遵循一个固定的轨迹:
| 阶段 |
含义 |
举例 |
| Thought (思考) |
智能体的”内心独白”,分析现状、拆解任务、规划下一步 |
“我需要搜索华为最新手机的信息” |
| Action (行动) |
决定采取的具体动作,通常是调用一个外部工具 |
Search['华为最新款手机'] |
| Observation (观察) |
执行 Action 后从工具返回的结果 |
搜索结果的摘要 |
智能体不断重复 Thought → Action → Observation 的循环,将每一步的观察结果追加到上下文中,形成一个不断增长的历史记录,直到它在 Thought 中判断已经找到了最终答案。
这个过程可以形式化地表达为:在每个时间步 $t$,模型 $\pi$ 根据问题 $q$ 和历史轨迹生成思考 $th_t$ 和行动 $a_t$:
$$
(th_t, a_t) = \pi(q, (a_1,o_1),\ldots,(a_{t-1},o_{t-1}))
$$
随后环境中的工具 $T$ 执行行动并返回观察:
$$
o_t = T(a_t)
$$
推理使得行动更具目的性,而行动则为推理提供了事实依据。 这就是 ReAct 的核心协同效应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| flowchart TD Q([用户问题 q]) --> T1
subgraph 循环 ["Thought → Action → Observation 循环"] T1["Thought t₁<br/>分析问题,决定搜索"] --> A1 A1["Action a₁<br/>Search[华为最新手机]"] --> O1 O1["Observation<br/>搜索结果返回"] --> T2 T2["Thought t₂<br/>分析结果,决定是否足够"] --> Check end
Check{"信息是否足够?"} Check -->|是| F(["Finish[答案]"]) Check -->|否| T1
style Q fill:#e1f5fe,stroke:#0288d1 style F fill:#e8f5e9,stroke:#388e3c style Check fill:#fff3e0,stroke:#f57c00
|
ReAct 特别适合以下场景:
- 需要外部知识的任务(实时信息、专业领域知识)
- 需要精确计算的任务(交给计算器工具,避免 LLM 算错)
- 需要与 API 交互的任务(操作数据库、调用服务)
接下来,我们亲手从零实现一个 ReAct 智能体,让它回答”华为最新的手机是哪一款?它的主要卖点是什么?”这个 LLM 自身知识库无法直接回答的实时性问题。
环境准备
安装依赖
1
| pip install openai python-dotenv google-search-results
|
配置 API 密钥
在项目根目录创建 .env 文件:
1 2 3 4 5
| LLM_API_KEY="YOUR-API-KEY" LLM_MODEL_ID="YOUR-MODEL" LLM_BASE_URL="YOUR-URL" SERPAPI_API_KEY="YOUR_SERPAPI_API_KEY"
|
SerpApi 提供 Google 搜索的结构化 API,可在 serpapi.com 免费注册获取密钥。
封装基础 LLM 客户端
为了让代码结构清晰且易于复用,先定义一个 LLM 客户端类,封装所有与模型服务交互的细节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import os from openai import OpenAI from dotenv import load_dotenv from typing import List, Dict
load_dotenv()
class HelloAgentsLLM: """ LLM 客户端,兼容任何 OpenAI 接口的服务,默认使用流式响应。 """ def __init__(self, model: str = None, apiKey: str = None, baseUrl: str = None, timeout: int = None): self.model = model or os.getenv("LLM_MODEL_ID") apiKey = apiKey or os.getenv("LLM_API_KEY") baseUrl = baseUrl or os.getenv("LLM_BASE_URL") timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))
if not all([self.model, apiKey, baseUrl]): raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(api_key=apiKey, base_url=baseUrl, timeout=timeout)
def think(self, messages: List[Dict[str, str]], temperature: float = 0) -> str: """调用大语言模型进行思考,并返回其完整响应。""" print(f"🧠 正在调用 {self.model} 模型...") try: response = self.client.chat.completions.create( model=self.model, messages=messages, temperature=temperature, stream=True, )
print("✅ 大语言模型响应成功:") collected_content = [] for chunk in response: if not chunk.choices: continue content = chunk.choices[0].delta.content or "" print(content, end="", flush=True) collected_content.append(content) print() return "".join(collected_content)
except Exception as e: print(f"❌ 调用LLM API时发生错误: {e}") return None
|
temperature 设为 0 是为了保证输出的确定性,这在需要严格格式解析的 ReAct 场景中尤为重要。
工具的定义与实现
如果说大语言模型是智能体的大脑,那么工具 (Tools) 就是其与外部世界交互的”手和脚”。
工具三要素
一个良好定义的工具应包含三个核心要素:
- 名称 (Name):简洁、唯一的标识符,供智能体在 Action 中调用,例如
Search
- 描述 (Description):清晰的自然语言描述。这是整个机制中最关键的部分——LLM 会依赖这段描述来判断何时、是否使用某个工具
- 执行逻辑 (Execution Logic):真正执行任务的函数
实现搜索工具
我们选用 SerpApi,它能返回 Google 搜索的”答案摘要框”和”知识图谱”等结构化信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| from serpapi import SerpApiClient
def search(query: str) -> str: """ 基于 SerpApi 的网页搜索引擎工具。 智能解析搜索结果,优先返回直接答案或知识图谱信息。 """ print(f"🔍 正在执行 [SerpApi] 网页搜索: {query}") try: api_key = os.getenv("SERPAPI_API_KEY") if not api_key: return "错误:SERPAPI_API_KEY 未在 .env 文件中配置。"
params = { "engine": "google", "q": query, "api_key": api_key, "gl": "cn", "hl": "zh-cn", }
client = SerpApiClient(params) results = client.get_dict()
if "answer_box_list" in results: return "\n".join(results["answer_box_list"]) if "answer_box" in results and "answer" in results["answer_box"]: return results["answer_box"]["answer"] if "knowledge_graph" in results and "description" in results["knowledge_graph"]: return results["knowledge_graph"]["description"] if "organic_results" in results and results["organic_results"]: snippets = [ f"[{i+1}] {res.get('title', '')}\n{res.get('snippet', '')}" for i, res in enumerate(results["organic_results"][:3]) ] return "\n\n".join(snippets)
return f"对不起,没有找到关于 '{query}' 的信息。"
except Exception as e: return f"搜索时发生错误: {e}"
|
解析策略的优先级是:answer_box_list → answer_box → knowledge_graph → organic_results(前 3 条)。这种”智能解析”能为 LLM 提供质量更高的信息输入。
构建通用工具执行器
当智能体需要使用多种工具时(搜索、计算、数据库查询等),我们需要一个统一的管理器来注册和调度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from typing import Dict, Any
class ToolExecutor: """工具执行器,负责管理和执行工具。""" def __init__(self): self.tools: Dict[str, Dict[str, Any]] = {}
def registerTool(self, name: str, description: str, func: callable): """向工具箱中注册一个新工具。""" if name in self.tools: print(f"警告:工具 '{name}' 已存在,将被覆盖。") self.tools[name] = {"description": description, "func": func} print(f"工具 '{name}' 已注册。")
def getTool(self, name: str) -> callable: """根据名称获取工具的执行函数。""" return self.tools.get(name, {}).get("func")
def getAvailableTools(self) -> str: """获取所有可用工具的格式化描述字符串。""" return "\n".join([ f"- {name}: {info['description']}" for name, info in self.tools.items() ])
|
验证工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| if __name__ == '__main__': toolExecutor = ToolExecutor()
search_description = "一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。" toolExecutor.registerTool("Search", search_description, search)
print("\n--- 可用的工具 ---") print(toolExecutor.getAvailableTools())
print("\n--- 执行 Action: Search['英伟达最新的GPU型号是什么'] ---") tool_function = toolExecutor.getTool("Search") observation = tool_function("英伟达最新的GPU型号是什么") print("--- 观察 (Observation) ---") print(observation)
|
运行结果:
1 2 3 4 5 6 7 8 9 10
| 工具 'Search' 已注册。
--- 可用的工具 --- - Search: 一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。
--- 执行 Action: Search['英伟达最新的GPU型号是什么'] --- 🔍 正在执行 [SerpApi] 网页搜索: 英伟达最新的GPU型号是什么 --- 观察 (Observation) --- [1] GeForce RTX 50 系列显卡 GeForce RTX™ 50 系列GPU 搭载NVIDIA Blackwell 架构,为游戏玩家和创作者带来全新玩法...
|
至此,智能体的”手和脚”已经就绪。
ReAct 智能体的编码实现
现在,我们将 LLM 客户端和工具执行器组装起来,构建一个完整的 ReActAgent 类。为了便于理解,将其拆分为几个关键部分逐一讲解。
第一步:系统提示词设计
提示词是整个 ReAct 机制的基石,它为 LLM 提供了行动的操作指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| # ReAct 提示词模板 REACT_PROMPT_TEMPLATE = """ 请注意,你是一个有能力调用外部工具的智能助手。
可用工具如下: {tools}
请严格按照以下格式进行回应:
Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。 Action: 你决定采取的行动,必须是以下格式之一: - `{{tool_name}}[{{tool_input}}]`:调用一个可用工具。 - `Finish[最终答案]`:当你认为已经获得最终答案时。 - 当你收集到足够的信息,能够回答用户的最终问题时,你必须在Action:字段后使用 Finish[最终答案] 来输出最终答案。
现在,请开始解决以下问题: Question: {question} History: {history} """
|
这个模板定义了智能体与 LLM 交互的规范:
- 角色定义:”你是一个有能力调用外部工具的智能助手”
- 工具清单
{tools}:告知 LLM 它有哪些可用的”手和脚”
- 格式规约 (
Thought/Action):最重要的部分,强制 LLM 输出具有结构性,使我们能通过代码精确解析其意图
- 动态上下文 (
{question}/{history}):注入用户的原始问题和不断累积的交互历史
第二步:核心循环
ReActAgent 的核心是一个 while 循环:不断”格式化提示词 → 调用 LLM → 执行动作 → 整合结果”,直到任务完成或达到最大步数限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| import re
class ReActAgent: def __init__(self, llm_client: HelloAgentsLLM, tool_executor: ToolExecutor, max_steps: int = 5): self.llm_client = llm_client self.tool_executor = tool_executor self.max_steps = max_steps self.history = []
def run(self, question: str): """运行 ReAct 智能体来回答一个问题。""" self.history = [] current_step = 0
while current_step < self.max_steps: current_step += 1 print(f"--- 第 {current_step} 步 ---")
tools_desc = self.tool_executor.getAvailableTools() history_str = "\n".join(self.history) prompt = REACT_PROMPT_TEMPLATE.format( tools=tools_desc, question=question, history=history_str )
messages = [{"role": "user", "content": prompt}] response_text = self.llm_client.think(messages=messages)
if not response_text: print("错误:LLM未能返回有效响应。") break
thought, action = self._parse_output(response_text)
if thought: print(f"🤔 思考: {thought}")
if not action: print("警告:未能解析出有效的Action,流程终止。") break
if action.startswith("Finish"): final_answer = re.match(r"Finish\[(.*)\]", action, re.DOTALL).group(1) print(f"🎉 最终答案: {final_answer}") return final_answer
tool_name, tool_input = self._parse_action(action) if not tool_name or not tool_input: print(f"警告:无法解析Action: {action}") continue
print(f"🎬 行动: {tool_name}[{tool_input}]")
tool_function = self.tool_executor.getTool(tool_name) if not tool_function: observation = f"错误:未找到名为 '{tool_name}' 的工具。" else: observation = tool_function(tool_input)
print(f"👀 观察: {observation}")
self.history.append(f"Action: {action}") self.history.append(f"Observation: {observation}")
print("已达到最大步数,流程终止。") return None
|
max_steps 参数是一个重要的安全阀,防止智能体陷入无限循环而耗尽资源。
第三步:输出解析器
LLM 返回的是纯文本,我们需要从中精确提取出 Thought 和 Action。这里使用正则表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def _parse_output(self, text: str): """解析 LLM 的输出,提取 Thought 和 Action。""" thought_match = re.search(r"Thought:\s*(.*?)(?=\nAction:|$)", text, re.DOTALL) action_match = re.search(r"Action:\s*(.*?)$", text, re.DOTALL) thought = thought_match.group(1).strip() if thought_match else None action = action_match.group(1).strip() if action_match else None return thought, action
def _parse_action(self, action_text: str): """解析 Action 字符串,提取工具名称和输入。""" match = re.match(r"(\w+)\[(.*)\]", action_text, re.DOTALL) if match: return match.group(1), match.group(2) return None, None
|
_parse_output:从完整响应中分离出 Thought 和 Action 两个部分
_parse_action:从 Search[华为最新手机] 中提取工具名 Search 和输入 华为最新手机
核心循环的完整流程
把上面的代码组合起来,ReAct 的运行逻辑可以总结为这张流程图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| flowchart TD Start([开始]) --> Format["格式化提示词<br/>注入 tools / question / history"] Format --> CallLLM["调用 LLM"] CallLLM --> Parse["解析 Thought + Action"] Parse --> CheckFinish{"Action 是 Finish?"}
CheckFinish -->|是| Answer([输出最终答案,结束]) CheckFinish -->|否| ParseTool["解析工具名和输入"] ParseTool --> ExecTool["执行工具得到 Observation"] ExecTool --> Append["将 Action + Observation 追加到 history"] Append --> StepCheck{"已达到 max_steps?"} StepCheck -->|否| Format StepCheck -->|是| Stop([流程终止])
style Start fill:#e1f5fe,stroke:#0288d1 style Answer fill:#e8f5e9,stroke:#388e3c style Stop fill:#ffebee,stroke:#d32f2f style CheckFinish fill:#fff3e0,stroke:#f57c00 style StepCheck fill:#fff3e0,stroke:#f57c00
|
将 Observation 追加到 self.history 是形成闭环的关键——智能体在下一轮生成提示词时,就能”看到”上一步行动的结果,并据此进行新一轮的思考和规划。
运行实例与分析
将所有组件组装起来,向智能体提问”华为最新的手机是哪一款?它的主要卖点是什么?”:
1 2 3 4 5 6 7 8 9 10 11 12
| if __name__ == '__main__': llm_client = HelloAgentsLLM()
tool_executor = ToolExecutor() search_description = "一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。" tool_executor.registerTool("Search", search_description, search)
agent = ReActAgent(llm_client, tool_executor, max_steps=5) agent.run("华为最新的手机是哪一款?它的主要卖点是什么?")
|
运行记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 工具 'Search' 已注册。
--- 第 1 步 --- 🧠 正在调用 xxxxxx 模型... ✅ 大语言模型响应成功: Thought: 要回答这个问题,我需要查找华为最新发布的手机型号及其主要特点。这些信息可能在我的现有知识库之外,因此需要使用搜索引擎来获取最新数据。 Action: Search[华为最新手机型号及主要卖点] 🤔 思考: 要回答这个问题,我需要查找华为最新发布的手机型号及其主要特点。这些信息可能在我的现有知识库之外,因此需要使用搜索引擎来获取最新数据。 🎬 行动: Search[华为最新手机型号及主要卖点] 🔍 正在执行 [SerpApi] 网页搜索: 华为最新手机型号及主要卖点 👀 观察: [1] 华为手机- 华为官网 智能手机 ; Mate 系列. 非凡旗舰 · HUAWEI Mate XTs. 非凡大师 ; Pura 系列. 先锋影像 · HUAWEI Pura 80 Pro+ ; Pocket 系列. 美学新篇. HUAWEI Pocket 2 ; nova 系列. 专业人像.
[2] 2025年华为手机哪一款性价比高?华为手机推荐与市场分析 ... 现在华为手机最大的卖点只剩下鸿蒙HarmonyOS系统...
[3] 2025年华为新款手机哪个性价比高?10款华为新款手机推荐 选华为主要还是要推荐高端手机,Mate 70和Pura 70系列是最新发布的旗舰机型...
--- 第 2 步 --- 🧠 正在调用 xxxxxx 模型... ✅ 大语言模型响应成功: Thought: 根据搜索结果,华为最新发布的旗舰机型包括Mate 70和Pura 80 Pro+。从提供的链接来看,Mate 70系列和Pura 80 Pro+都是近期发布的产品,但具体哪一个是"最新"还需要进一步确认。同时,我可以从这些信息中提取出它们的主要卖点。 Action: Finish[根据最新信息,华为的最新手机可能是HUAWEI Pura 80 Pro+或HUAWEI Mate 70。其中,HUAWEI Mate 70的主要卖点包括顶级的拍照配置,全焦段覆盖,适合专业摄影,做工出色,并且具有良好的户外抗摔性能。而HUAWEI Pura 80 Pro+则强调了先锋影像技术。] 🎉 最终答案: 根据最新信息,华为的最新手机可能是HUAWEI Pura 80 Pro+或HUAWEI Mate 70。其中,HUAWEI Mate 70的主要卖点包括顶级的拍照配置,全焦段覆盖,适合专业摄影,做工出色,并且具有良好的户外抗摔性能。而HUAWEI Pura 80 Pro+则强调了先锋影像技术。
|
从输出可以看到智能体清晰的思考链条:
- 第 1 步:意识到自身知识不足 → 调用
Search 工具
- 第 2 步:基于搜索结果进行推理和总结 → 判断信息足够 →
Finish 输出答案
仅用两步就完成了任务。这正是 ReAct 的魅力所在——推理指导行动,行动反馈推理。
由于模型知识和互联网信息会不断更新,你运行的结果可能与此不完全相同。
ReAct 的特点、局限性与调试技巧
亲手实现之后,我们来客观审视一下 ReAct 范式的优劣。
主要特点
1. 高可解释性
ReAct 最大的优点之一就是透明。通过 Thought 链,我们可以清晰地看到智能体每一步的”心路历程”——它为什么选择这个工具,下一步打算做什么。这对于理解、信任和调试智能体的行为至关重要。
2. 动态规划与纠错能力
与一次性生成完整计划的范式不同,ReAct 是”走一步,看一步”。它根据每一步从外部世界获得的 Observation 来动态调整后续的 Thought 和 Action。如果上一步的搜索结果不理想,它可以在下一步中修正搜索词,重新尝试。
3. 工具协同能力
ReAct 天然地将 LLM 的推理能力与外部工具的执行能力结合起来。LLM 负责运筹帷幄(规划和推理),工具负责解决具体问题(搜索、计算),二者协同工作,突破了单一 LLM 在知识时效性、计算准确性等方面的固有局限。
固有局限
1. 对 LLM 自身能力的强依赖
ReAct 流程的成功与否,高度依赖于底层 LLM 的综合能力。如果 LLM 的逻辑推理能力、指令遵循能力或格式化输出能力不足,就很容易在 Thought 环节产生错误的规划,或者在 Action 环节生成不符合格式的指令,导致整个流程中断。
2. 执行效率问题
由于循序渐进的特性,完成一个任务通常需要多次调用 LLM。每一次调用都伴随着网络延迟和计算成本。对于步骤较多的复杂任务,这种串行的”思考-行动”循环可能导致较高的总耗时和费用。
3. 提示词的脆弱性
整个机制的稳定运行建立在一个精心设计的提示词模板之上。模板中的任何微小变动,甚至是用词的差异,都可能影响 LLM 的行为。此外,并非所有模型都能持续稳定地遵循预设的格式,这增加了实际应用中的不确定性。
4. 可能陷入局部最优
步进式的决策模式意味着智能体缺乏全局的、长远的规划。它可能会因为眼前的 Observation 而选择一个看似正确但长远来看并非最优的路径,甚至在某些情况下陷入”原地打转”的循环中。
调试技巧
当你构建的 ReAct 智能体行为不符合预期时,可以从以下几个方面入手:
- 检查完整的提示词:在每次调用 LLM 之前,将最终格式化好的、包含所有历史记录的完整提示词打印出来。这是追溯 LLM 决策源头的最直接方式。
- 分析原始输出:当输出解析失败时(例如正则表达式没有匹配到 Action),务必将 LLM 返回的原始、未经处理的文本打印出来。这能帮助你判断是 LLM 没有遵循格式,还是你的解析逻辑有误。
- 验证工具的输入与输出:检查智能体生成的
tool_input 是否是工具函数所期望的格式,同时也要确保工具返回的 observation 格式是智能体可以理解和处理的。
- 调整提示词中的示例 (Few-shot Prompting):如果模型频繁出错,可以在提示词中加入一两个完整的”Thought-Action-Observation”成功案例,通过示例来引导模型更好地遵循指令。
- 尝试不同的模型或参数:更换一个能力更强的模型,或者调整
temperature 参数(通常设为 0 以保证输出的确定性),有时能直接解决问题。
总结
ReAct 的核心其实就四个字:边想边做。
它用一种非常符合直觉的方式,把大语言模型的推理能力和外部工具的执行能力串联起来:
- LLM 负责”想”——分析问题、规划动作、解读结果
- 工具负责”做”——搜索、计算、调用 API
- Observation 把两者粘合在一起,形成闭环
这个范式的代码实现并不复杂(核心就是一个 while 循环加正则解析),但它揭示了一个重要的设计思想:智能体的力量不在于 LLM 本身有多强,而在于它能否有效地与外部世界交互。
当然,ReAct 也有它的局限——串行执行效率低、依赖提示词稳定性、缺乏全局规划。这些正是后续范式(如 Plan-and-Solve 的”先规划后执行”、Reflection 的”自我反思”)试图解决的问题。理解了 ReAct 的长处和短板,你才能在实际场景中做出正确的技术选型。
参考文献
[1] Yao, S., Zhao, J., Yu, D., et al. “ReAct: Synergizing Reasoning and Acting in Language Models.” arXiv preprint arXiv:2210.03629, 2022.