课程首页 › Module 1 · ReAct 循环与第一个 Agent › L2

L2 ReAct 循环:Agent 是怎么"思考—行动"的

课程目标:理解 ReAct 范式的核心机制,能用伪代码描述一个 ReAct 循环,并在脑中运行一遍完整的思考—行动—观察过程。


一、一句话回答#

如果 L1 你记住了一句话——"Agent = LLM + 工具 + 自主决策循环"——那么 L2 要记住的一句话是:

ReAct(Reasoning + Acting)是当前 Agent 实现"自主决策循环"的事实标准范式。

它的核心只有三步,反复循环:

  1. Thought(思考):现在情况怎样,下一步该做什么

  2. Action(行动):调用工具或决定给出最终答案

  3. Observation(观察):工具返回的结果是什么,把它纳入下一轮思考


二、从一个真实问题看 ReAct#

假设你问 Agent:

"《辛德勒名单》的导演还导演过哪些奥斯卡最佳影片?"

注意:这个问题不能靠 LLM 的记忆直接回答——LLM 训练数据截止后的事它不知道,而且"奥斯卡最佳影片"这种精确信息容易记错。Agent 必须主动查证。

我们跟着 Agent 跑一遍 ReAct 循环:


🔄 第 1 轮循环#

Thought 1:

用户问的是辛德勒名单的导演还导演过哪些奥斯卡最佳影片。我需要先确认导演是谁,再查他的其他作品。辛德勒名单的导演是斯皮尔伯格,但我不完全确定,需要先查一下。

Action 1: Search["辛德勒名单 导演"]

Observation 1:

搜索结果:辛德勒名单(1993)由史蒂文·斯皮尔伯格(Steven Spielberg)导演。


🔄 第 2 轮循环#

Thought 2:

好的,确认是斯皮尔伯格。现在我需要列出他导演过的所有奥斯卡最佳影片。但我不知道"奥斯卡最佳影片"具体指什么——是最佳影片(Best Picture)还是他获得的最佳导演奖?需要查清楚。

Action 2: Search["斯皮尔伯格 奥斯卡 获奖"]

Observation 2:

搜索结果:斯皮尔伯格 1994 年凭《辛德勒名单》获最佳导演奖;2013 年凭《林肯》提名;1994 年《辛德勒名单》获最佳影片;其他作品多数是提名而非获奖。


🔄 第 3 轮循环#

Thought 3:

嗯,根据我的知识,斯皮尔伯格作为导演拿过最佳导演奖和最佳影片的,主要是《辛德勒名单》。但我不确定《拯救大兵瑞恩》有没有拿最佳影片。让我再查一下。

Action 3: Search["拯救大兵瑞恩 奥斯卡 最佳影片"]

Observation 3:

搜索结果:《拯救大兵瑞恩》(1998)获 1999 年奥斯卡 5 项大奖,但最佳影片颁给了《莎翁情史》。斯皮尔伯格获最佳导演提名但未获奖。


🔄 第 4 轮循环(终止)#

Thought 4:

我现在有足够信息了。斯皮尔伯格作为导演,唯一一次让作品获得奥斯卡最佳影片是《辛德勒名单》(1993)。他的其他作品多数是提名。我应该诚实地告诉用户这一点,而不是编造。

Action 4: Finish[根据可查证的资料,史蒂文·斯皮尔伯格作为导演,唯一让作品获得奥斯卡最佳影片奖的是《辛德勒名单》(1993 年)。他的其他作品如《拯救大兵瑞恩》《林肯》等虽获多项提名,但未获最佳影片。]


三、为什么 ReAct 这么重要?#

对比两种范式:

❌ 范式 A:纯 Chain-of-Thought(CoT)#

问题 → LLM 直接思考 → LLM 直接回答

问题:LLM 可能"自信地编造"。比如它可能直接回答"斯皮尔伯格还导演过《E.T.》并获最佳影片"——但 E.T. 没有获最佳影片(只提名)。这就是幻觉(hallucination)。

✅ 范式 B:ReAct#

问题 → 思考 → 行动 → 观察 → 思考 → 行动 → 观察 → ... → 回答

优势:每一步都有外部证据支撑(Observation)。LLM 不能凭空捏造,因为下一步思考必须基于上一步观察到的真实结果。

这就是 ReAct 论文(Yao et al., 2022,arXiv:2210.03629)证明的核心结论:ReAct 比纯 CoT 更能减少幻觉,因为它把推理"锚定"在外部事实上。


四、用伪代码写出 ReAct 循环#

抛开框架,我们用 30 行 Python 伪代码把 ReAct 循环的骨架写清楚:

def react_agent(question: str, tools: dict, llm_call, max_steps: int = 10):
    """
    一个最简 ReAct Agent 的实现骨架
    
    参数:
        question: 用户的原始问题
        tools: 工具字典,如 {"Search": search_function, "Lookup": lookup_function}
        llm_call: 调用 LLM 的函数,接收 prompt 字符串,返回文本
        max_steps: 最多循环多少步,防止死循环
    """
    
    # 1. 准备初始 prompt:Few-shot 示例 + 用户问题
    prompt = build_react_prompt(question)  # 包含 Thought/Action/Observation 的格式说明和示例
    
    # 2. 进入循环
    for step in range(max_steps):
        # 2a. LLM 思考 + 决定行动
        llm_output = llm_call(prompt)
        # 输出形如:
        # Thought: 我需要先查导演是谁
        # Action: Search[辛德勒名单]
        
        # 2b. 解析 LLM 的输出,提取 Action
        thought, action_name, action_input = parse_react_output(llm_output)
        print(f"Step {step+1} | Thought: {thought}")
        
        # 2c. 判断是否要终止
        if action_name == "Finish":
            return action_input  # 这就是最终答案
        
        # 2d. 执行工具(行动)
        if action_name not in tools:
            observation = f"Error: 工具 {action_name} 不存在"
        else:
            try:
                observation = tools[action_name](action_input)
            except Exception as e:
                observation = f"Error: 工具执行失败: {e}"
        
        # 2e. 把观察结果追加到 prompt(这样下一轮 LLM 能看到)
        prompt += f"\nThought: {thought}\n"
        prompt += f"Action: {action_name}[{action_input}]\n"
        prompt += f"Observation: {observation}\n"
    
    # 3. 超过最大步数仍未 Finish,强制返回
    return "Agent 达到最大步数,未能完成回答。"

这段伪代码里最重要的三行#

prompt += f"\nObservation: {observation}\n"

这是 ReAct 的灵魂:每一步的观察都被追加回 prompt,作为下一步推理的事实基础。这就是为什么 ReAct 能减少幻觉——LLM 不是凭空思考,而是基于证据思考。


五、ReAct 在生产框架中的真实样貌#

1. smolagents 中的 ReAct(HuggingFace)#

src-002 §Unit 2 中,CodeAgent 内部就是 ReAct 循环,只是输出的是 Python 代码而不是 Thought/Action 文本:

from smolagents import CodeAgent, HfApiModel, tool

@tool
def search_wikipedia(query: str) -> str:
    """在 Wikipedia 上搜索关键词,返回第一段摘要。
    Args:
        query: 搜索关键词
    """
    # 真实实现会调用 Wikipedia API
    return f"这是关于 '{query}' 的搜索结果..."

agent = CodeAgent(tools=[search_wikipedia], model=HfApiModel())
result = agent.run("《辛德勒名单》的导演还导演过哪些奥斯卡最佳影片?")
print(result)
# 内部循环大致是:
# Thought → 生成 Python 代码调用 search_wikipedia → 执行 → 观察 → 再 Thought → ...

2. LangGraph 中的 ReAct(LangChain)#

src-002 §Unit 3 中,StateGraph 把 ReAct 显式建模为状态图:

from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition

graph = StateGraph(MessagesState)
graph.add_node("assistant", call_llm)         # 思考节点
graph.add_node("tools", ToolNode([search_wikipedia]))  # 行动节点

graph.add_edge(START, "assistant")            # 开始 → 思考
graph.add_conditional_edges("assistant", tools_condition)  # 思考后判断
graph.add_edge("tools", "assistant")          # 行动 → 再思考(循环)

agent = graph.compile()

注意 LangGraph 把 ReAct 拆成了显式的图结构——这让你能可视化地看到 Agent 在"思考"和"行动"之间循环多少次,这对调试非常有用。


六、ReAct 的三个常见陷阱#

陷阱 1:无限循环#

LLM 可能陷入"查—查—查—查"永远不 Finish。对策:设置 max_steps,并设计一个"兜底 Action"。

陷阱 2:Thought 编造 Observation#

LLM 可能在 Thought 里写"我观察到 X 是 Y",但实际上 Observation 里没有这条信息。对策:在 prompt 里强调"Thought 必须只引用前面 Observation 中的内容"。

陷阱 3:Action 格式错误#

LLM 可能输出格式不对的 Action(比如 Search(辛德勒名单) 而不是 Search[辛德勒名单])。对策:使用结构化输出(Function Calling / Tool Use),让 LLM 只能按预定义 schema 返回 Action。这正是 L4 课要深入讲的。


七、和 L1 的连接#

回到 L1 我们说的三件套:

要素在 ReAct 中对应什么
LLM产生 Thought 的引擎
ToolsAction 执行的实际工具
Planning LoopThought → Action → Observation 的循环本身

ReAct 就是把这三件套串联起来的标准范式。所有现代 Agent 框架(smolagents、LangGraph、AutoGen、Semantic Kernel)底层都在跑 ReAct 循环,区别只是循环的载体(代码生成 vs 状态图 vs 群聊)。


八、动手时间:模拟 ReAct#

假设 LLM 是你本人。请用 Thought/Action/Observation 格式回答:

"帮我查一下北京到上海明天的高铁票,选最便宜的一班,把车次告诉我。"

📋 参考回答(点击展开)
Thought 1: 我需要先查明天北京到上海的高铁有哪些班次和价格。
Action 1: Search[北京到上海高铁 明天]
Observation 1: 明天共有 28 班高铁,价格在 309-553 元之间。

Thought 2: 最便宜的是 309 元。我需要确认这班的具体车次和出发时间。
Action 2: Search[G1 车次 北京南 上海 309]
Observation 2: G1 次列车,08:00 北京南发车,12:28 到达上海虹桥,二等座 309 元。

Thought 3: 信息齐全了,可以回答。
Action 3: Finish[最便宜的车次是 G1,明天 08:00 从北京南出发,12:28 到达上海虹桥,二等座 309 元。]

注意几个要点:

  1. 每一步 Thought 都基于前一步 Observation,不是凭空想象

  2. 真实场景中"最便宜"可能需要查多个数据源,这里为了简洁只查两次

  3. 真实 Agent 还要考虑用户偏好(如不要凌晨的班次),这会改变搜索策略


九、小结#

知识点一句话回顾
ReAct 全称Reasoning + Acting(推理 + 行动)
三步循环Thought → Action → Observation → 回到 Thought
核心价值减少幻觉(推理锚定在外部证据上)
来源Yao et al., 2022, arXiv:2210.03629
现代实现所有主流框架底层都是 ReAct 循环

十、Read next#

下一节课 L3 用 smolagents 30 行代码构建第一个 Agent,我们会用真实的代码跑通一个 ReAct 循环,并亲眼看到 LLM 是怎么决定"该不该调工具"的。

主要参考:


十一、Sources#


💬 有疑问? 试试问我:"Thought 和 Action 必须分开吗?"、"为什么不用 ReAct 也能调工具?"、"smolagents 和 LangGraph 实现 ReAct 的区别是什么?"——我会用代码或比喻为你解答。

自测 · 检验是否真的掌握了

1. ReAct 循环中三个步骤的正确顺序是什么?

ReAct 就是 Reasoning + Acting,标准顺序是 Thought → Action → Observation,然后带着观察结果回到下一轮思考,直到模型选择 Finish 给出最终答案。先行动或先观察都破坏了因果链条:没有思考就无法确定该调哪个工具,没有观察就无法进入下一轮推理。

2. ReAct 相比纯 Chain-of-Thought 更能减少幻觉,根本原因是什么?

ReAct 的价值在于把推理锚定在外部证据上:下一步思考必须引用上一步 Observation 里的真实内容,因此不能凭空捏造。工具多少、步骤长短、模型强弱都不是关键——纯 CoT 即使用同样强的模型,依然可能自信地说出并不存在的获奖记录。

3. 在 ReAct 伪代码中,把 Observation 追加回 prompt 这一步为什么最关键?

追加 Observation 就是把工具的真实返回值接入下一轮的思考依据,这正是 ReAct 减少幻觉的机制来源。防死循环靠的是 max_steps 上限和兜底 Action,与追加观察无关;上下文变长反而是 ReAct 的代价之一,在步骤多时会造成 token 膨胀。