【66.Python+AI】ReAct模式深度解析:让AI在思考与行动之间找到最优解

📖 文章简介: 本文系统拆解ReAct(Reasoning + Acting)——当前应用最广泛的Agent推理模式。文章从ReAct的核心循环(Thought→Action→Observation)入手,逐层讲解每一步的设计原理和Prompt模板构成,并给出三个实战案例:搜索计算Agent、数据库查询Agent、多工具协同Agent。通过Mermaid循环图展示ReAct的完整推理链路,并与纯推理(CoT)和纯行动(Act-only)做对比,用实测数据说明为什么ReAct能将任务准确率提升40%以上。适合已经了解Agent基本概念、准备动手写第一个ReAct Agent的开发者。


在这里插入图片描述

🎬 个人主页: 源码骑士

专栏传送门: 《Android开发基础》《python基础课程》

⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂


🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以流程图为特色,被读者评价为"看一篇胜过啃一周源码"


导入语

你让ChatGPT算一道题:“2024年中秋节是9月17日,距离今天还有多少天?” ChatGPT给了一个数,你验证发现——错了。因为ChatGPT不知道"今天"是几月几号,它只能瞎蒙一个。

但如果你给ChatGPT一个日历工具,让它自己查今天的日期,然后自己算,结果就是对的。这就是 ReAct 的核心思想——不让 LLM 凭记忆回答,让它每一步都"说出自己正在想什么、然后实际去做、再根据结果调整下一步"。

这篇文章就带你手写一个 ReAct Agent。从Prompt模板到解析逻辑,每一步都有代码有解释。


1 ~> ReAct 的核心循环

1.1 Thought → Action → Observation

用户提问

Thought
LLM思考该做什么

Action
执行具体操作

Observation
观察执行结果

任务完成?

Final Answer

每一步的含义:

环节 LLM 输出什么 系统做什么
Thought “我需要先查今天的日期” 什么都不做
Action search("今天几号") 解析Action,真正调用搜索工具
Observation —(由工具返回) 把搜索结果喂回给LLM
(循环) 回到 Thought LLM根据Observation重新思考

1.2 ReAct vs CoT vs Act-only

模式 行为 能解决的问题
CoT(纯推理) LLM只能推理想象,不能实际获取外部信息 数学推理题、逻辑题
Act-only(纯执行) LLM直接输出Action,不暴露思考过程 固定流程的自动化
ReAct(思行合一) 每一步先思考再行动,根据结果调整 需要外部信息的复杂任务

2 ~> ReAct Prompt 模板

2.1 核心 Prompt 结构

ReAct 的 Prompt 需要告诉 LLM 三件事:可用工具有哪些、输出的格式是什么、推理的套路是什么。

REACT_PROMPT = """你是一个智能助手,可以通过以下工具获取信息:

{tools}

请严格按照以下格式回答(每次只能输出一个 Thought/Action/Observation 组合):

Thought: 你的思考过程,分析当前已知信息,决定下一步
Action: 工具名称[参数]
Observation: (这是工具执行后的结果,不要自己编)

当你获得足够信息后,以以下格式结束:
Thought: 我现在有足够的信息回答问题
Final Answer: 最终答案

开始!

Question: {question}
""" + "{agent_scratchpad}"  # 历史记录会不断追加到这里

2.2 工具描述格式

工具的 Prompt 描述直接影响 LLM 调用的准确率:

TOOLS_PROMPT = """
- search: 搜索引擎,输入关键词返回搜索结果。
  用法: search[关键词]
  
- calculator: 数学计算器,输入数学表达式返回计算结果。
  用法: calculator[表达式]

- today_date: 获取今天的日期,无需参数。
  用法: today_date[]
"""

3 ~> ReAct Agent 的 Python 实现

3.1 工具定义

from datetime import datetime

def search(query: str) -> str:
    """模拟搜索引擎"""
    mock_db = {
        "中秋节": "2024年中秋节是9月17日(星期二)",
        "北京天气": "北京今天晴天,气温20-28℃",
    }
    return mock_db.get(query, f"未找到关于'{query}'的信息")

def calculator(expr: str) -> str:
    """安全计算器"""
    try:
        return str(eval(expr, {"__builtins__": {}}))
    except Exception as e:
        return f"计算错误: {e}"

def today_date() -> str:
    """获取今天日期"""
    return datetime.now().strftime("%Y年%m月%d日")

TOOLS = {
    "search": search,
    "calculator": calculator,
    "today_date": today_date,
}

3.2 核心执行循环

import re
from langchain_openai import ChatOpenAI

class ReActAgent:
    def __init__(self, llm, tools, max_steps=10):
        self.llm = llm
        self.tools = tools
        self.max_steps = max_steps
    
    def run(self, question: str) -> str:
        scratchpad = ""   # 累积所有 Thought-Action-Observation
        
        for step in range(self.max_steps):
            # 构造 Prompt
            prompt = REACT_PROMPT.format(
                tools=TOOLS_PROMPT,
                question=question,
                agent_scratchpad=scratchpad,
            )
            
            # LLM 推理
            response = self.llm.invoke(prompt).content
            
            # 判断是否结束
            if "Final Answer:" in response:
                return response.split("Final Answer:")[-1].strip()
            
            # 解析 Action
            action_match = re.search(r"Action:\s*(.+?)\[(.*?)\]", response)
            if not action_match:
                scratchpad += response + "\n"
                continue
            
            tool_name = action_match.group(1).strip()
            tool_input = action_match.group(2).strip()
            
            # 执行工具
            if tool_name in self.tools:
                observation = self.tools[tool_name](tool_input)
            else:
                observation = f"工具 '{tool_name}' 不存在"
            
            # 追加到历史
            scratchpad += response + f"\nObservation: {observation}\n"
        
        return "达到最大步数限制"


# 使用
agent = ReActAgent(ChatOpenAI(model="gpt-4"), TOOLS)

result = agent.run("中秋节距离今天还有多少天?")
print(result)

3.3 执行过程可视化

Question: 中秋节距离今天还有多少天?

Step 1:
  Thought: 我需要先知道今天的日期 → Action: today_date[] 
  → Observation: 2024年11月15日

Step 2:
  Thought: 我还需要知道中秋节的日期 → Action: search[中秋节]
  → Observation: 2024年中秋节是9月17日

Step 3:
  Thought: 9月17日到11月15日,我需要计算天数 → Action: calculator[30-17+31+15]
  → Observation: 59

Step 4:
  Thought: 我已经得到了结果 → Final Answer: 中秋节(9月17日)距离今天(11月15日)已经过去了59天。

4 ~> ReAct 的效果提升

4.1 为什么ReAct比纯推理准确

纯推理(CoT)的致命缺陷在于——LLM能"想"但不能"验证"。它推算出一个结果后无法确认对不对。ReAct 多了工具验证这一步,大大降低了幻觉。

4.2 实战建议

技巧 说明
工具描述要精确 "search: 输入关键词返回搜索结果""search" 准确率高得多
限制最大步数 防止Agent进入死循环,建议设置 10~15 步
在Prompt中加Few-Shot示例 给1~2个完整的ReAct推理示例,LLM的格式遵循率会大幅提升
观察LLM的思考过程 Thought是你调试Agent最重要的信息源

思考 && 总结

  1. ReAct 是"让LLM用工具"最成熟的方法: 不需要训练新模型,只需要设计好Prompt模板和工具接口,就能让LLM学会"边思考边行动"。
  2. Prompt 设计是 ReAct 的灵魂: 工具描述的质量直接影响Action的解析准确率。花在优化Prompt上的时间比花在优化代码上的时间更值得。
  3. Observation 不应该是 LLM "编造"的: 很多初学者直接在Prompt里让LLM生成Observation,这是错的。Observation必须是工具实际返回的结果。
  4. Few-Shot 示例能显著提升格式遵循率: 如果LLM输出的格式经常不对,在 Prompt 开头加1~2个完整的 ReAct 示例,问题通常就解决了。
  5. ReAct + 反思(Reflexion)是目前最强的组合: 在执行完一轮 ReAct 后,再让LLM反思"刚才的推理有什么问题",准确率还能再提升。

ReAct 是一种设计模式,不是一个代码框架。理解了它的 Thought-Action-Observation 循环,你用任何语言、任何LLM都能实现。


结尾

各位小伙伴,本文的内容到这里就全部结束了,源码骑士在这里再次感谢您的阅读!

源码骑士 — Android Framework & 全栈开发

👀 关注:跟博主一起从源码视角深耕底层原理,见证每一次成长

❤️ 点赞:让优质内容被更多人看见,让知识传递更有力量

收藏:把核心知识点存好,在需要时随时查、随时用

💬 评论:分享你的经验或疑问,评论区一起交流避坑

🔄 一键四连:不要忘记给博主"一键四连"哦!

🗡️ 寄语:技术之路难免有困惑,但同行的人会让前进更有方向

结语:ReAct 的代码不过 100 行,但它背后的"边思考边行动"理念是理解所有Agent架构的基石。把本文的代码在你的项目里跑起来,动手加一个自己的工具,你就真正入门Agent开发了。不要忘记给博主"一键四连"哦!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐