在大语言模型(LLM)技术飞速发展的今天,单纯的文本生成能力已无法满足复杂任务的需求。Agent 作为一种能够自主决策、规划和执行任务的智能体,正在成为 LLM 落地应用的关键载体。而 ReAct 模式作为 Agent 领域的重要范式,通过 “思考 - 行动 - 观察” 的循环机制,极大地提升了 Agent 的逻辑性和任务解决能力。本文将从基础概念出发,逐步深入 ReAct 模式 Agent 的实现原理,并通过网页端模拟和代码实践,带大家从零构建一个最简单的 ReAct 模式 Agent。

一. 什么是 Agent

在人工智能领域,Agent(智能体) 是指能够自主感知环境、做出决策并执行动作,以实现特定目标的实体。它并非单一的算法或模型,而是一个集成了 “感知 - 决策 - 执行” 能力的闭环系统。结合 LLM 技术的 Agent,核心特征可概括为以下三点:

  1. 自主性(Autonomy):无需人类持续干预,能根据任务目标自主规划步骤。例如,当用户提出 “整理近 3 个月的销售数据并生成可视化报告” 时,Agent 可自主决定 “获取数据→清洗数据→分析数据→生成图表” 的流程,而非依赖人类逐步指导。

  2. 交互性(Interactivity):既能与外部环境(如数据库、API、工具)交互,也能与用户交互。例如,当 Agent 无法获取某份数据时,会主动询问用户 “请提供销售数据的存储路径”,或调用数据接口直接拉取数据。

  3. 目标导向性(Goal-Oriented):所有行为均围绕 “完成用户目标” 展开,具备动态调整策略的能力。若某一步骤失败(如数据接口超时),Agent 会尝试替代方案(如切换备用接口、手动上传数据),而非停滞不前。

简单来说,LLM 是 Agent 的 “大脑”(负责思考和决策),而 Agent 则是 LLM 的 “手脚”(负责与外部环境交互并执行任务)。

二. 什么是 ReAct Agent

ReAct(Reasoning and Acting) 的概念来自论文 《ReAct: Synergizing Reasoning and Acting in Language Models》,这篇论文提出了一种新的方法,通过结合语言模型中的推理(reasoning)和行动(acting)来解决多样化的语言推理和决策任务。ReAct 提供了一种更易于人类理解、诊断和控制的决策和推理过程。该论文虽然是2022年发布,但目前可能仍然是目前使用最为广泛的Agent模式。

它的典型流程如下图所示,可以用一个有趣的循环来描述:思考(Thought)→ 行动(Action)→ 观察(Observation),简称TAO循环。

  • 思考(Thought):AI Agent通过推理拆解任务,明确目标与路径。 例:用户询问“2025年斯诺克世锦赛决赛冠军是哪里人”的问题,LLM可能会拆分如下任务:
    • 查询2025年斯诺克世锦赛冠军是谁
    • 查询该人家乡在哪
  • 行动(Action):调用工具执行具体操作(如计算、搜索API)。 技术细节:需预定义工具(如乘法计算工具)并通过Prompt模板引导LLM 调用。
  • 观察(Observation):验证结果有效性,若失败则重新规划。 例:若搜索航班超预算,Agent自动调整“直飞→转机”策略。

三. ReAct模式实现原理

ReAct 模式 Agent 的实现本质是 “prompt 引导 LLM 生成决策→执行行动→获取反馈→更新 prompt→循环直至完成任务” 的闭环流程。其核心原理可拆解为以下 5 个步骤:

3.1 步骤1:构建初始 prompt—— 定义 ReAct 的 “规则框架”

首先需要设计结构化的 prompt,明确告知 LLM“如何思考、如何生成行动指令”。一个标准的 ReAct prompt 包含以下部分:

  • 角色定义:明确 Agent 的身份(如 “数据查询助手”)和能力(如 “可调用搜索工具、表格生成工具”)。

  • 任务目标:用户的具体需求(需动态传入)。

  • 循环规则:告知 LLM 需交替生成 “Thought” 和 “Action”,直至任务完成(此时生成 “Final Answer”)。

  • 格式约束:规定 “Thought” 和 “Action” 的输出格式(避免 LLM 生成杂乱内容),例如:

下方给出一个示例提示词:

你需要解决一个任务。为此,你需要将任务分解为多个步骤。对于每个步骤,首先使用 <thought> 思考要做什么,然后使用 <action> 调用一个工具,工具的执行结果会通过 <observation> 返回给你。持续这个思考和行动的过程,直到你有足够的信息来提供 <final_answer>

所有步骤请严格使用以下 XML 标签格式输出:

  • <task>: 用户提出的任务
  • <thought>: 思考
  • <action>: 采取的工具操作
  • <observation>: 工具或环境返回的结果
  • <final_answer>: 最终答案

示例1:

<task>当前目录下创建一个名为"test.txt"的文件,并写入内容"Hello, World!"</task>
<thought>要完成创建文件并写入内容的任务,首先得用write_to_file工具,参数设置好文件名和内容。</thought>
<action>write_to_file("test.txt", "Hello, World!")</action>
<observation>写入成功</observation>
<thought>工具返回写入成功,说明文件创建和内容写入都完成了,现在可以给出最终答案。</thought>
<final_answer>已在当前目录下创建名为"test.txt"的文件,并成功写入内容"Hello, World!"。</final_answer>

示例2:

<task>帮我找一个简单的番茄炒蛋食谱,并看看家里的冰箱里有没有西红柿。</task>
<thought>这个任务分两步。第一步,找到番茄炒蛋的食谱。第二步,检查冰箱里是否有西红柿。我先用 find_recipe 工具。</thought>
<action>find_recipe(dish="番茄炒蛋")</action>
<observation>简单的番茄炒蛋食谱:将2个鸡蛋打散,2个番茄切块。热油,先炒鸡蛋,盛出。再热油,炒番茄至软烂,加入炒好的鸡蛋,加盐调味即可。</observation>
<thought>好的,我已经有食谱了。食谱需要西红柿。现在我需要用 check_fridge 工具看看冰箱里有没有西红柿。</thought>
<action>check_fridge(item="西红柿")</action>
<observation>冰箱检查结果:有3个西红柿。</observation>
<thought>我找到了食谱,并且确认了冰箱里有西红柿。可以回答问题了。</thought>
<final_answer>简单的番茄炒蛋食谱是:鸡蛋打散,番茄切块。先炒鸡蛋,再炒番茄,混合后加盐调味。冰箱里有3个西红柿。</final_answer>

可用工具

  • read_file(file_path):用于读取文件内容
  • write_to_file(filename, content):将指定内容写入指定文件。成功时返回 “写入成功”。
  • run_terminal_command(command):用于执行终端命令

注意事项

  • <task> 标签由用户提供,请不要擅自生成。
  • 你每次回答都必须包括两个标签,第一个是 <thought>,第二个是 <action><final_answer>
  • 输出 <action> 后立即停止生成,等待真实的 <observation>,擅自生成 <observation> 将导致错误
  • 如果 <action> 中的某个工具参数有多行的话,请使用 \n 来表示,如:
<action>write_to_file("test.txt", "a\nb\nc")</action>

环境信息

  • 操作系统:Windows 11
  • 当前目录:E:\software-engineer\llm
  • 目录下文件列表:空

3.2 步骤2:LLM 生成 “思考 - 行动”

将初始 prompt 传入 LLM(如 DeepSeek、QWen、GPT-5、Claude),LLM 会根据 prompt 的规则,先生成 “Thought”(分析任务),再生成符合格式的 “Action”(行动指令)。

我们以 DeepSeek 为例,向其提交 “帮我用HTML实现一个贪吃蛇小游戏” 任务,由于 DeepSeek 网页端没有区分系统提示词和用户提示词,我们就将原本需要放在系统提示词中的初始规范,和需要提交的任务<task>帮我用HTML、CSS、js实现一个贪吃蛇小游戏</task> 一起提交给DeepSeek:

可以看到 DeepSeek 已经完全按照提示词要求的规范,将思考过程使用 thought 标签包裹,将请求调用工具的动作使用 action 包裹。

3.3 步骤3:执行行动并获取观察结果

此时Agent解析LLM返回的内容中包含 action 标签,证明Agent需要调用外部工具完成LLM的处理请求,执行完 action 标签的方法后,会获取到执行结果,Agent会将执行结果返回给LLM,我们Mock文件已经成功写入:

<observation>写入成功</observation>

3.4 步骤4:更新 prompt 并进入下一轮循环

将上一轮的 “Thought、Action、Observation” 追加到初始 prompt 中,形成 “历史上下文”,再传入 LLM。此时 LLM 会基于历史信息,判断是否需要继续行动:

  • 若任务未完成,LLM 会生成新的 “Thought” 和 “Action”,例如:


3.5 步骤5:任务完成向用户输出结果

经过多轮的循环,任务最终完成,Agent检查到结果中存在 final_answer 标签后意识到任务已经完成,将标签内的内容输出给用户,最终结束该任务。

四. ReAct实践:使用 Python 实现简易版 Claude Code

4.1 核心代码

bigcoder84/claude-code-simple: 基于ReAct模式简易类似于Claude Code的Agent实现

import ast
import inspect
import os
import re
from string import Template
from typing import List, Callable, Tuple

import click
from dotenv import load_dotenv
from openai import OpenAI
import platform

from prompt_template import react_system_prompt_template


class ReActAgent:
    def __init__(self, tools: List[Callable], model: str, project_directory: str):
        self.tools = { func.__name__: func for func in tools }
        self.model = model
        self.project_directory = project_directory
        self.client = OpenAI(
            base_url="https://openrouter.ai/api/v1",
            api_key=ReActAgent.get_api_key(),
        )

    def run(self, user_input: str):
        messages = [
            {"role": "system", "content": self.render_system_prompt(react_system_prompt_template)},
            {"role": "user", "content": f"<question>{user_input}</question>"}
        ]

        while True:

            # 请求模型
            content = self.call_model(messages)

            # 检测 Thought
            thought_match = re.search(r"<thought>(.*?)</thought>", content, re.DOTALL)
            if thought_match:
                thought = thought_match.group(1)
                print(f"\n\n💭 Thought: {thought}")

            # 检测模型是否输出 Final Answer,如果是的话,直接返回
            if "<final_answer>" in content:
                final_answer = re.search(r"<final_answer>(.*?)</final_answer>", content, re.DOTALL)
                return final_answer.group(1)

            # 检测 Action
            action_match = re.search(r"<action>(.*?)</action>", content, re.DOTALL)
            if not action_match:
                raise RuntimeError("模型未输出 <action>")
            action = action_match.group(1)
            tool_name, args = self.parse_action(action)

            print(f"\n\n🔧 Action: {tool_name}({', '.join(args)})")
            # 只有终端命令才需要询问用户,其他的工具直接执行
            should_continue = input(f"\n\n是否继续?(Y/N)") if tool_name == "run_terminal_command" else "y"
            if should_continue.lower() != 'y':
                print("\n\n操作已取消。")
                return "操作被用户取消"

            try:
                observation = self.tools[tool_name](*args)
            except Exception as e:
                observation = f"工具执行错误:{str(e)}"
            print(f"\n\n🔍 Observation:{observation}")
            obs_msg = f"<observation>{observation}</observation>"
            messages.append({"role": "user", "content": obs_msg})


    def get_tool_list(self) -> str:
        """生成工具列表字符串,包含函数签名和简要说明"""
        tool_descriptions = []
        for func in self.tools.values():
            name = func.__name__
            signature = str(inspect.signature(func))
            doc = inspect.getdoc(func)
            tool_descriptions.append(f"- {name}{signature}: {doc}")
        return "\n".join(tool_descriptions)

    def render_system_prompt(self, system_prompt_template: str) -> str:
        """渲染系统提示模板,替换变量"""
        tool_list = self.get_tool_list()
        file_list = ", ".join(
            os.path.abspath(os.path.join(self.project_directory, f))
            for f in os.listdir(self.project_directory)
        )
        return Template(system_prompt_template).substitute(
            operating_system=self.get_operating_system_name(),
            tool_list=tool_list,
            file_list=file_list
        )

    @staticmethod
    def get_api_key() -> str:
        """Load the API key from an environment variable."""
        load_dotenv()
        api_key = os.getenv("OPENROUTER_API_KEY")
        if not api_key:
            raise ValueError("未找到 OPENROUTER_API_KEY 环境变量,请在 .env 文件中设置。")
        return api_key

    def call_model(self, messages):
        print("\n\n正在请求模型,请稍等...")
        response = self.client.chat.completions.create(
            model=self.model,
            messages=messages,
        )
        content = response.choices[0].message.content
        messages.append({"role": "assistant", "content": content})
        return content

    def parse_action(self, code_str: str) -> Tuple[str, List[str]]:
        match = re.match(r'(\w+)\((.*)\)', code_str, re.DOTALL)
        if not match:
            raise ValueError("Invalid function call syntax")

        func_name = match.group(1)
        args_str = match.group(2).strip()

        # 手动解析参数,特别处理包含多行内容的字符串
        args = []
        current_arg = ""
        in_string = False
        string_char = None
        i = 0
        paren_depth = 0
        
        while i < len(args_str):
            char = args_str[i]
            
            if not in_string:
                if char in ['"', "'"]:
                    in_string = True
                    string_char = char
                    current_arg += char
                elif char == '(':
                    paren_depth += 1
                    current_arg += char
                elif char == ')':
                    paren_depth -= 1
                    current_arg += char
                elif char == ',' and paren_depth == 0:
                    # 遇到顶层逗号,结束当前参数
                    args.append(self._parse_single_arg(current_arg.strip()))
                    current_arg = ""
                else:
                    current_arg += char
            else:
                current_arg += char
                if char == string_char and (i == 0 or args_str[i-1] != '\\'):
                    in_string = False
                    string_char = None
            
            i += 1
        
        # 添加最后一个参数
        if current_arg.strip():
            args.append(self._parse_single_arg(current_arg.strip()))
        
        return func_name, args
    
    def _parse_single_arg(self, arg_str: str):
        """解析单个参数"""
        arg_str = arg_str.strip()
        
        # 如果是字符串字面量
        if (arg_str.startswith('"') and arg_str.endswith('"')) or \
           (arg_str.startswith("'") and arg_str.endswith("'")):
            # 移除外层引号并处理转义字符
            inner_str = arg_str[1:-1]
            # 处理常见的转义字符
            inner_str = inner_str.replace('\\"', '"').replace("\\'", "'")
            inner_str = inner_str.replace('\\n', '\n').replace('\\t', '\t')
            inner_str = inner_str.replace('\\r', '\r').replace('\\\\', '\\')
            return inner_str
        
        # 尝试使用 ast.literal_eval 解析其他类型
        try:
            return ast.literal_eval(arg_str)
        except (SyntaxError, ValueError):
            # 如果解析失败,返回原始字符串
            return arg_str

    def get_operating_system_name(self):
        os_map = {
            "Darwin": "macOS",
            "Windows": "Windows",
            "Linux": "Linux"
        }

        return os_map.get(platform.system(), "Unknown")


def read_file(file_path):
    """用于读取文件内容"""
    with open(file_path, "r", encoding="utf-8") as f:
        return f.read()

def write_to_file(file_path, content):
    """将指定内容写入指定文件"""
    with open(file_path, "w", encoding="utf-8") as f:
        f.write(content.replace("\\n", "\n"))
    return "写入成功"

def run_terminal_command(command):
    """用于执行终端命令"""
    import subprocess
    run_result = subprocess.run(command, shell=True, capture_output=True, text=True)
    return "执行成功" if run_result.returncode == 0 else run_result.stderr

@click.command()
@click.argument('project_directory',
                type=click.Path(exists=True, file_okay=False, dir_okay=True))
def main(project_directory):
    project_dir = os.path.abspath(project_directory)

    tools = [read_file, write_to_file, run_terminal_command]
    agent = ReActAgent(tools=tools, model="deepseek/deepseek-chat-v3.1:free", project_directory=project_dir)

    task = input("请输入任务:")

    final_answer = agent.run(task)

    print(f"\n\n✅ Final Answer:{final_answer}")

if __name__ == "__main__":
    main()

4.2 提示词模板

react_system_prompt_template = """
你需要解决一个问题。为此,你需要将问题分解为多个步骤。对于每个步骤,首先使用 <thought> 思考要做什么,然后使用可用工具之一决定一个 <action>。接着,你将根据你的行动从环境/工具中收到一个 <observation>。持续这个思考和行动的过程,直到你有足够的信息来提供 <final_answer>。

所有步骤请严格使用以下 XML 标签格式输出:
- <question> 用户问题
- <thought> 思考
- <action> 采取的工具操作
- <observation> 工具或环境返回的结果
- <final_answer> 最终答案

⸻

例子 1:

<question>埃菲尔铁塔有多高?</question>
<thought>我需要找到埃菲尔铁塔的高度。可以使用搜索工具。</thought>
<action>get_height("埃菲尔铁塔")</action>
<observation>埃菲尔铁塔的高度约为330米(包含天线)。</observation>
<thought>搜索结果显示了高度。我已经得到答案了。</thought>
<final_answer>埃菲尔铁塔的高度约为330米。</final_answer>

⸻

例子 2:

<question>帮我找一个简单的番茄炒蛋食谱,并看看家里的冰箱里有没有西红柿。</question>
<thought>这个任务分两步。第一步,找到番茄炒蛋的食谱。第二步,检查冰箱里是否有西红柿。我先用 find_recipe 工具找食谱。</thought>
<action>find_recipe(dish="番茄炒蛋")</action>
<observation>简单的番茄炒蛋食谱:将2个鸡蛋打散,2个番茄切块。热油,先炒鸡蛋,盛出。再热油,炒番茄至软烂,加入鸡蛋,放盐调味即可。</observation>
<thought>好的,我已经有食谱了。食谱需要西红柿。现在我需要用 check_fridge 工具看看冰箱里有没有西红柿。</thought>
<action>check_fridge(item="西红柿")</action>
<observation>冰箱检查结果:有3个西红柿。</observation>
<thought>我找到了食谱,并且确认了冰箱里有西红柿。可以回答问题了。</thought>
<final_answer>简单的番茄炒蛋食谱是:鸡蛋打散,番茄切块。先炒鸡蛋,再炒番茄,混合后加盐调味。冰箱里有3个西红柿。</final_answer>

⸻

请严格遵守:
- 你每次回答都必须包括两个标签,第一个是 <thought>,第二个是 <action> 或 <final_answer>
- 输出 <action> 后立即停止生成,等待真实的 <observation>,擅自生成 <observation> 将导致错误
- 如果 <action> 中的某个工具参数有多行的话,请使用 \n 来表示,如:<action>write_to_file("/tmp/test.txt", "a\nb\nc")</action>
- 工具参数中的文件路径请使用绝对路径,不要只给出一个文件名。比如要写 write_to_file("/tmp/test.txt", "内容"),而不是 write_to_file("test.txt", "内容")

⸻

本次任务可用工具:
${tool_list}

⸻

环境信息:

操作系统:${operating_system}
当前目录下文件列表:${file_list}
"""

4.3 核心流程

用户 Agent主程序 模型 工具(函数) 写一个贪吃蛇 1 请求模型 2 Thought + Action 3 显示 Thought + Action 4 请求 Action 对应的工具 5 工具执行结果 6 显示工具执行结果 7 loop [重复 n 次] 请求模型 8 Thought + Final Answer 9 展示 Thought 和 Final Answer 10 用户 Agent主程序 模型 工具(函数)

4.4 测试

执行如下命令:

$ uv run agent.py snake 

然后输入任务描述后,agent就会按照ReAct模式不断请求LLM,直至完成任务:

五. LangChain Plan and Execute 模式

ReAct 通过 “思考 - 行动 - 观察” 的即时循环实现任务推进,每一步行动都依赖上一轮的观察结果动态调整,这种 “走一步看一步” 的逻辑在中等复杂度任务(如单文件生成、简单数据查询)中表现出色,但面对多步骤、长流程、强依赖的复杂任务(如 “搭建一个包含前端页面、后端接口、数据库的博客系统”“分析某行业近 5 年数据并生成多维度可视化报告”)时,会暴露两个核心问题:

  1. 缺乏全局规划:ReAct 的思考仅聚焦于 “当前 step 该做什么”,无法提前梳理任务的完整链路。例如,在开发博客系统时,ReAct 可能先调用工具创建前端 HTML 文件,后续才发现未规划后端接口格式,导致前端代码需要反复修改,效率低下。

  2. 步骤依赖管理薄弱:当任务包含 “先创建数据库表→再开发后端接口→最后对接前端” 这类强依赖步骤时,ReAct 无法提前识别依赖关系,可能出现 “先写接口再建表” 的逻辑错误,需要多次回滚调整。

为解决上述问题,LangChain 在 ReAct 模式的基础上,提出了Plan and Execute(规划 - 执行)模式—— 它将 “任务拆解” 与 “步骤执行” 拆分为两个独立模块,通过 “先全局规划、再分步执行、动态修正” 的逻辑,大幅提升了 Agent 在复杂任务中的稳定性与效率。该模式尤其适用于需要 “多工具协同、长流程管控、步骤依赖处理” 的场景,是当前 LangChain 生态中处理复杂任务的核心范式之一。

5.1 规划-执行模式的核心逻辑

LangChain 的 Plan and Execute 模式,本质是将 Agent 的能力拆解为 “规划器(Planner)”“执行器(Executor)” 两个核心模块,通过 “规划→执行→反馈→修正规划” 的闭环,实现复杂任务的系统化处理。其核心逻辑可概括为:

  1. 规划阶段(Plan):规划器接收用户的原始任务后,基于任务目标拆解出全局步骤清单,明确每个步骤的目标、所需工具、依赖关系(如 “步骤 3 需在步骤 1、2 完成后执行”),甚至会预估每个步骤的执行结果。

  2. 执行阶段(Execute):执行器按照规划器输出的步骤清单,逐一调用工具执行操作,并实时将执行结果反馈给规划器。

  3. 动态修正(Revise):规划器根据执行器返回的结果(成功 / 失败 / 异常),判断是否需要调整步骤清单 —— 例如,某步骤执行失败时,规划器会重新规划 “替代方案步骤”;某步骤结果超出预期时,规划器会简化后续步骤。

这种 “先规划后执行” 的逻辑,相当于为 Agent 增加了 “任务指挥官”(规划器)和 “操作执行者”(执行器),二者分工协作,既保证了任务的全局合理性,又确保了步骤的落地可行性。

用户 Agent主程序 Plan模型 Re_Plan模型 执行Agent 今年澳网男子冠军的家乡是哪里? 1 请给出执行计划(Plan) 2 执行计划如下: ... 3 请执行第一步(Execute) 4 执行完毕,结果如下...... 5 请给出一个新的执行计划(Replan) 6 新的执行计划如下: ...... 7 loop [重复n次] 返回最终结果 8 用户 Agent主程序 Plan模型 Re_Plan模型 执行Agent

5.2 Replan 阶段存在的意义

读到这里,你或许又有一个疑问,既然第一步已经制定好计划了,按照计划按部就班的执行就好了,为啥每一次调用工具后都会根据工具的输出结果进行 Replan(重新制定计划)。

在 “2025 年斯诺克世锦赛决赛冠军是哪里人” 的 Plan-and-Execute 执行流程中,Replan(重规划)阶段是衔接 “已执行结果” 与 “剩余计划” 的核心调节环节,其意义并非单纯重复 “规划” 动作,而是解决 “初始计划与实际执行场景不匹配” 的问题,确保整个流程能动态适配复杂、不确定的任务环境,具体可从以下 4 个关键维度展开:

5.2.1 修正初始计划的 “信息盲区”,避免无效执行

初始规划阶段依赖的是 “基于问题的预设逻辑”(如默认先查冠军姓名、再查国籍),但实际执行中可能因信息缺失或场景变化,导致原计划步骤失效或冗余 —— Replan 阶段的核心作用之一,就是用 “已执行步骤的真实结果” 填补初始盲区,调整计划方向。以本问题为例:

  • 若执行 “步骤 1:查 2025 斯诺克世锦赛结果” 时,搜索工具返回 “2025 年斯诺克世锦赛因赛事调整,延期至 2025 年 6 月举办,当前(执行时)决赛尚未进行”,此时初始计划中 “查冠军姓名→查国籍” 的步骤已无法推进;
  • Replan 阶段会基于这一结果,重新生成适配的计划:["记录2025斯诺克世锦赛延期后的举办时间(2025年6月)","待赛事决赛结束后,检索冠军姓名","根据冠军姓名查询国籍","整理最终答案"],避免继续执行 “查已结束赛事结果” 这类无效步骤。
5.2.2 补充任务中的 “隐性歧义”,确保答案准确性

用户问题中可能存在未明确的隐性需求(如 “斯诺克世锦赛” 可能包含男子 / 女子项目,“哪里人” 可能需区分 “国籍” 或 “出生地”),初始规划阶段可能因 “默认假设”(如默认男子项目)遗漏关键判断步骤 ——Replan 阶段会通过 “已执行结果反推需求”,补充必要的验证环节。以本问题为例:

  • 若执行 “步骤 1:查冠军姓名” 时,搜索结果同时出现 “男子单打冠军 XX” 和 “女子单打冠军 YY”(用户未明确性别),此时初始计划中 “直接查冠军国籍” 的步骤会导致答案歧义;
  • Replan 阶段会基于这一结果,插入补充步骤:["向用户确认需查询的是男子还是女子斯诺克世锦赛冠军","根据用户反馈,定位对应性别的冠军姓名","查询该冠军的国籍及出生地","整理答案"],避免因初始假设偏差导致最终答案错误。
5.2.3 应对执行中的 “工具局限性”,保障流程连续性

Plan-and-Execute 模式依赖外部工具(如搜索工具、数据库)完成执行,但工具可能存在 “数据不全、接口故障” 等问题,导致单个步骤执行结果不完整 —— Replan 阶段可基于 “不完整结果” 调整工具调用策略或步骤顺序,避免流程卡在 “工具失效” 环节。以本问题为例:

  • 执行 “步骤 2:查冠军国籍” 时,若调用的基础搜索工具仅返回 “冠军 XX 来自欧洲”,未明确具体国家(工具数据不全),此时原计划中 “整理‘姓名 + 国家’答案” 的步骤无法完成;
  • Replan 阶段会基于这一不完整结果,调整计划:["更换工具(如调用World Snooker Tour官网的选手档案库),查询XX的详细国籍信息","若官网无数据,补充搜索XX的采访报道或社交媒体个人资料","确认国籍后,整理最终答案"],通过切换工具或补充检索路径,确保流程能继续推进。
5.2.4 终止 “冗余步骤”,避免资源浪费

当已执行步骤的结果已足够生成最终答案,或任务目标因外部因素无需继续推进时,Replan 阶段会判断 “剩余计划是否必要”,及时终止冗余步骤,节省工具调用成本(如搜索 API 次数)和时间成本。以本问题为例:

  • 若执行 “步骤 1:查冠军姓名” 时,搜索结果直接显示 “2025 斯诺克世锦赛男子单打冠军为中国选手丁俊晖,出生地江苏宜兴”(一步获取姓名 + 国籍 + 出生地),此时初始计划中 “步骤 2:查国籍”“步骤 3:整理答案” 已部分冗余;
  • Replan 阶段会基于这一结果,直接调整计划:["基于已获取的‘丁俊晖+中国+江苏宜兴’信息,整理为‘2025年斯诺克世锦赛男子单打冠军是中国江苏人丁俊晖’的最终答案"],删除原计划中 “单独查国籍” 的冗余步骤,直接进入答案整理环节。

本文只是引出 Plan-and-Execute 模式,有兴趣的读者可以深入了解,可以参考 LangChain 官方博文:《Plan-and-Execute》

六. 总结

本文从概念、原理到实践,完整剖析了 ReAct 模式 Agent 的核心逻辑,并延伸对比了 LangChain Plan and Execute 模式。

ReAct 模式以 “思考 - 行动 - 观察” 的 TAO 循环为核心,通过结构化 Prompt 引导 LLM 决策,结合工具调用与上下文更新形成闭环,解决了单纯 LLM 无法对接外部环境、完成中等复杂度任务(如单文件生成、简单数据查询)的痛点。文中 Python 实现案例进一步验证了该模式的落地可行性 —— 无需复杂算法,仅通过 Prompt 工程与基础工具封装,即可让 Agent 具备自主拆解任务、执行操作的能力。

而 LangChain Plan and Execute 模式则是对 ReAct 的进阶优化,通过拆分 “规划器” 与 “执行器” 模块,先制定全局步骤清单、再分步执行并动态修正,弥补了 ReAct 在复杂任务中 “缺乏全局规划、步骤依赖管理薄弱” 的不足,更适配多工具协同、长流程管控的场景(如系统搭建、多维度数据分析)。

两种模式各有侧重:ReAct 灵活轻便,适合快速落地中低复杂度任务;Plan and Execute 严谨系统,更适合处理强依赖、长流程的复杂需求。二者共同构成了 LLM Agent 从 “文本生成” 向 “实用任务解决” 迈进的核心技术框架,为不同场景下的 Agent 开发提供了清晰的实践路径,也为后续更智能、更高效的 Agent 范式演进奠定了基础。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐