Agent开发(一) - ReACT框架详解
背景
一切的起点要从一篇论文开始:arxiv.org/abs/2210.03…。该论文提出了一种名为 ReAct 的框架,其中 LLMs 以交错的方式生成推理轨迹和特定任务的行动。
ReAct 框架能够生成推理轨迹,使模型能够滚动跟踪并逐步逼近终点以完成目标,甚至可以处理异常。
Action 阶段允许与外部来源(如知识库或环境)交互,或通过 MCP 协议收集外界信息,从而产生更可靠和事实性的响应。
并且,ReAct 通过与外部知识库(如 Wikipedia API)交互,可以克服仅推理方法(如 CoT)的“幻觉”问题,并超越仅行动方法(Act),避免陷入“仅操作不反思”和“仅推理不操作”两大陷阱。
核心在于旨在通过提示(prompting)大型语言模型(LLMs),使其在解决各种任务时能够协同进行推理(Reasoning)和行动(Acting)。
1. 核心思想与动机
-
人类智能的启发:人类在执行任务(如在厨房做饭)时,会自然地将具体行动(如切菜、开冰箱)与口头推理(如“现在该烧水了”、“没有盐,用酱油代替吧”、“我需要上网查一下面团怎么做”)结合起来。这种“行动”与“推理”的紧密结合,使得人类能够快速学习新任务,并在面对未知情况或信息不确定时做出稳健的决策。
-
现有方法的局限:
- 仅推理(如
Chain-of-Thought, CoT):模型仅在内部生成思维链,缺乏与外部世界的交互,容易产生事实性幻觉(hallucination)和错误累积。 - 仅行动(如
WebGPT):模型主要根据语言先验生成动作,缺乏高层次的抽象推理来制定、维护和调整计划,或利用工作记忆来支持行动,导致在复杂任务中表现不佳。
- 仅推理(如
-
ReAct 的解决方案:ReAct 通过提示 LLMs 以交错(interleaved)的方式生成 推理轨迹(Thoughts) 和 任务相关动作(Actions),从而实现两者的协同:
- 推理指导行动(Reason to Act):推理帮助模型分解目标、制定计划、跟踪进度、处理异常并决定下一步行动。
- 行动辅助推理(Act to Reason):通过与外部环境(如 Wikipedia API、模拟游戏、购物网站)交互获取新信息,使推理过程更接地气、更准确。
2. ReAct 的工作原理
- 增强的动作空间:将代理(Agent)的动作空间 扩展为 ,其中 是语言空间。在 中的动作被称为 Thought,它不改变外部环境,但会更新上下文,为后续的推理或行动提供支持。
- 灵活的提示设计:
- 对于推理密集型任务(如问答),采用密集思考模式,即 “Thought → Action → Observation” 的循环。
- 对于决策密集型任务(如文本游戏),采用稀疏思考模式,仅在关键时刻(如分解目标、跟踪进度、处理异常)插入 “Thought”,由模型自行决定何时思考。
- 实现方式:主要使用少样本提示(few-shot prompting)。提供少量人工编写的、包含 Thought、Action 和 Observation 的成功任务解决轨迹作为示例,引导模型在新任务中模仿这种协同模式。
- 人机协作:推理轨迹使得“人在回路”(Human-in-the-loop)的干预成为可能。人类可以通过编辑模型的 “Thought” 来轻松纠正其行为,引导其走向成功,这比直接修改动作或模型参数要直观得多。
它是如何工作的?
ReAct 的核心思想是通过提示(prompting)大型语言模型(LLMs),使其在解决任务时能够交错地(interleaved)生成“推理轨迹(Thoughts)”和“任务相关动作(Actions)”,从而实现推理与行动的协同增效。
其工作原理可以分解为以下几个关键部分:
1. 扩展的动作空间 (Augmented Action Space)
ReAct 对传统智能体的动作空间进行了根本性的扩展。
- 传统框架:智能体在时间步 接收环境观察 ,然后从动作空间 中选择一个动作 来执行。动作直接影响环境,环境会返回下一个观察 。
- ReAct 框架:ReAct 将动作空间扩展为 。其中:
- 是原有的、能改变外部环境的动作空间(例如,在问答任务中是
Search[entity]、Lookup[string]、Finish[answer];在文本游戏中是go to cabinet 1、take apple 1)。 - 是语言空间。在这个空间中生成的动作被称为 Thought(思考/推理轨迹)。Thought 不会改变外部环境,因此不会触发环境的观察反馈。它的作用是对当前上下文 进行推理,生成有用的信息,并将这些信息更新到上下文中,以支持未来的推理或行动。
- 是原有的、能改变外部环境的动作空间(例如,在问答任务中是
2. 交错生成推理与动作 (Interleaved Generation of Thoughts and Actions)
这是 ReAct 的核心机制。模型被提示以特定的格式生成输出,这个格式强制或鼓励模型在生成动作之前或之后插入思考步骤。
-
推理指导行动 (Reason to Act):Thoughts 用于:
- 分解目标:将复杂任务拆解成子目标(如:“1. 我需要先找到胡椒瓶 2. 然后把它放进抽屉。”)。
- 制定和调整计划:根据当前状态决定
下一步该做什么(如:“现在胡椒瓶找到了,下一步是去抽屉。”)。 - 处理异常:当行动失败或信息不符时,
调整策略(如:“搜索‘iPhone17ProMax’ wiki 没找到,我应该转变思路通过 Google 搜索关键词”)。 - 提取和总结信息:从环境观察中
提炼关键事实(如:“之前有提到,当前最新的 iPhone 是 16”)。 - 注入常识或进行计算:利用模型的内部知识辅助决策(如:“应该告知用户想要的东西并不存在”)。
-
行动辅助推理 (Act to Reason):Actions 用于:
- 与外部环境交互:通过执行动作(如搜索、点击、移动)从外部世界(如 Wikipedia API、模拟游戏环境、购物网站)获取新的、模型内部知识库可能没有或已过时的信息。
- 验证或修正推理:获取到的新信息可以用来验证之前的假设,或修正错误的推理路径。
这种交错模式创造了一个动态的、闭环的决策过程:
Thought → Action → Observation → Thought → Action → ...
ReAct 提示词工程
ReAct 提示词是一种精心设计的输入模板,通过少样本学习教会大型语言模型遵循 Thought → Action → Observation 的循环模式。其核心由三个关键组件构成,共同构建了一个完整的推理-行动框架。
-
前缀 (PREFIX):定义任务目标和可用工具集
-
功能:清晰说明任务性质和模型可调用的工具
-
典型内容:
Answer the following questions as best you can. You have access to the following tools: List <tool_name>:
-
-
循环参考 (CONTEXT):定义以什么方式进行运行
-
功能:明确循环的开头和边界
-
典型内容:
Use the following format: Question: <Question> Thought: ... Action: <tool_name> Action Input: <tool_input> Observation: <result>
-
-
后缀 (SUFFIX):整合当前问题与历史交互
-
功能:让 LLM 能够沿着设定好的路线进行
Next Token Prediction -
典型内容:
Begin! Question: {input} + {agent_scratchpad} Thought: -
关键作用:
{agent_scratchpad}动态包含之前的 Thought/Action/Action Input/Observation 历史,使模型能在完整上下文中进行决策。
-
-
组合起来就是:强制执行思考-行动循环
-
典型内容:
Answer the following questions as best you can. You have access to the following tools: Search: a tool for searching the web Calculator: a tool for performing calculations Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [Search, Calculator] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: What is the capital of France? Thought: -
关键作用:通过严格的格式要求,确保模型输出可被系统解析,并强制模型在行动前进行思考。
-
动态交互机制 (Dynamic Interaction Mechanism)
ReAct 提示词的精髓在于其动态构建机制,使模型能够通过多轮交互解决复杂任务。
- agent_scratchpad 的核心作用:
- 初始状态:首次调用时为空,仅包含
Question: {input}\\nThought: - 迭代更新:每次交互后,系统将模型生成的 Thought/Action/Action Input 与真实 Observation 拼接到
agent_scratchpad - 关键价值:维持完整的决策历史,使后续思考基于先前的交互结果
- 初始状态:首次调用时为空,仅包含
- 停止序列 (Stop Sequences) 的精细控制:
- 典型设置:
stop=["\\nObservation"] - 功能:在模型生成 "Action Input" 后停止,防止模型伪造观察结果
- 关键作用:确保 Observation 部分由系统填充真实环境反馈,保持推理的“接地性”
- 典型设置:
这种设计使 ReAct 提示词成为连接模型内部推理能力与外部环境交互的桥梁,通过结构化但灵活的框架,实现了边想边做的人类式问题解决过程,同时保持了高度的可解释性和可控性。
LangChain ReAct 使用方法
以下是 ReAct 提示方法在实际应用中的高层级示例。我们将使用 LangChain 和 langchain-community,因为它已经内置了利用 ReAct 框架构建能够通过结合 LLM 的力量和不同工具来执行任务的代理的功能。
获取免费模型
登陆 modelscope.cn/ 并注册成功后,在该页面 modelscope.cn/my/myaccess…
获取 API 密钥后,配置环境即可使用任意兼容 OpenAI /v1 接口的模型。
1. 环境准备
首先,确保系统已安装 Python ≥ 3.10。推荐使用虚拟环境以隔离依赖:
python -m venv react-env
source react-env/bin/activate # Linux/macOS
# 或 react-env\Scripts\activate # Windows
随后安装必要依赖:
pip install --upgrade langchain langchain-community langchain-openai ddgs python-dotenv playwright pypdf requests
python -m playwright install chromium
说明:Playwright 用于无头浏览器渲染,确保能获取动态网页内容;
ddgs提供 DuckDuckGo 搜索能力;pypdf支持 PDF 文本提取。
2. 配置模型接入(.env)
在项目根目录创建 .env 文件:
OPENAI_API_KEY=your_api_key_here
OPENAI_API_BASE=https://your-llm-endpoint/v1
MODEL_ID=your-model-name
这里推荐
Qwen/Qwen3-Next-80B-A3B-Instruct模型
3. 构建 ReAct 代理(react_agent.py)
创建 react_agent.py,逐步构建代理逻辑:
(1) 基础脚手架与环境加载
from __future__ import annotations
import argparse, os
from dotenv import load_dotenv
load_dotenv()
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--question", required=True)
args = ap.parse_args()
print("Question:", args.question)
# 后续在此处插入 LLM 与代理逻辑
(2) 初始化 OpenAI 兼容 LLM
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model=os.getenv("MODEL_ID"),
base_url=os.getenv("OPENAI_API_BASE"),
api_key=os.getenv("OPENAI_API_KEY"),
temperature=0,
streaming=True,
)
(3) 定义外部工具集(Actions)
ReAct 的"行动"能力**依赖于工具**。我们定义四类工具,覆盖信息获取的主要路径:
# Web 搜索(DuckDuckGo)
from ddgs import DDGS
def ddgs_search(q: str) -> str:
items = []
with DDGS() as s:
for it in s.text(q.strip(), max_results=6):
items.append(f"{it['title']} :: {it['href']} :: {it['body']}")
return "\n".join(items)[:2000] or "no results"
# 浏览器渲染(获取真实页面内容)
from playwright.sync_api import sync_playwright
def browser_render(url: str) -> str:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context()
page = context.new_page()
page.goto(url.strip(), wait_until="domcontentloaded", timeout=30000)
html = page.content()
context.close(); browser.close()
return html[:4000]
# HTTP 端点获取
import requests
def http_fetch(url: str) -> str:
r = requests.get(url.strip(), timeout=20)
r.raise_for_status()
return r.text[:2000]
# PDF 文本提取(可选)
import tempfile
from pathlib import Path
def download_pdf_text(url: str) -> str:
r = requests.get(url.strip(), timeout=30)
r.raise_for_status()
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
tmp.write(r.content)
path = Path(tmp.name)
try:
from langchain_community.document_loaders.pdf import PyPDFLoader
pages = PyPDFLoader(str(path)).load()
return "\n".join(p.page_content for p in pages)[:4000]
finally:
path.unlink(missing_ok=True)
(4) 封装为 LangChain 工具
from langchain_core.tools import StructuredTool
tools = [
StructuredTool.from_function(ddgs_search, name="ddgs_search", description="Perform web search via DuckDuckGo."),
StructuredTool.from_function(browser_render, name="browser_render", description="Render a webpage using headless Chromium and return HTML content."),
StructuredTool.from_function(http_fetch, name="http_fetch", description="Fetch plain text or JSON from HTTP endpoints."),
StructuredTool.from_function(download_pdf_text, name="download_pdf_text", description="Download a PDF file and extract its textual content."),
]
(5) 初始化 ReAct 代理
from langchain.agents import AgentType, initialize_agent
agent = initialize_agent(
tools=tools,
llm=llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
max_iterations=30,
agent_kwargs={
"system_message": (
"You are a ReAct analyst. Solve the task by interleaving Thought, Action, and Observation. "
"Do not treat search engine result pages (SERPs) as evidence. Always open and summarize at least one "
"content page, PDF, or API response before concluding. If evidence is insufficient, state 'Insufficient Evidence' "
"instead of hallucinating."
)
},
)
result = agent.invoke({"input": args.question})
print("\n[Final Answer]", result.get("output"))
4. 运行与验证
执行以下命令测试代理:
python react_agent.py --question "凹凸实验室 详情"
你将观察到完整的 ReAct 轨迹,例如:
Thought: 我需要先搜索"凹凸实验室"以获取基本信息。
Action: ddgs_search
Action Input: 凹凸实验室
Observation: [搜索结果摘要]
Thought: 搜索结果中包含官网链接,我应渲染该页面以获取详细内容。
Action: browser_render
Action Input: https://aotu.io/
Observation: [HTML 内容片段]
Thought: 页面显示凹凸实验室是京东的前端技术团队...
Final Answer: 凹凸实验室是京东集团旗下的前端技术团队...
若信息不足,代理将主动返回 "Insufficient Evidence",而非生成幻觉内容——这正是 ReAct 通过行动辅助推理克服 CoT 幻觉问题的体现。
结语:迈向更稳健、可解释、可协作的智能体
ReAct 不仅是一种提示工程技巧,更代表了一种认知架构的范式转变——将语言模型从“被动回答者”转变为“主动探索者”。通过强制模型在推理与行动之间建立反馈闭环,ReAct 有效弥合了纯内省式推理(如 CoT)与盲目试错式行动(如早期 WebAgent)之间的鸿沟。
正如人类在真实世界中解决问题时,既会思考“下一步该做什么”,也会动手“查资料、点链接、试操作”,ReAct 使 LLM 具备了类似的边想边做、以行促思的能力。这种能力带来了三重核心价值:
- 事实可靠性提升:通过与外部知识源交互,显著减少幻觉,尤其在开放域问答与事实核查任务中表现突出;
- 任务鲁棒性增强:面对信息缺失、环境异常或目标模糊时,模型可通过动态调整计划实现容错;
- 人机协同成为可能:清晰的推理轨迹(Thoughts)为人类干预提供了天然接口,使“人在回路”的调试、引导与监督变得直观可行。
未来,随着工具生态的丰富(如数据库查询、代码执行、多模态感知)与代理架构的演进(如记忆机制、多智能体协作),ReAct 所奠定的“推理-行动”协同框架,将持续作为构建可信、可控、可扩展人工智能系统的重要基石。
最终启示:真正的智能,不在于无所不知,而在于知道何时思考、何时行动,以及如何在未知中稳健前行。ReAct,正是朝这一方向迈出的关键一步。