当AI开始“边想边干”:深入聊聊ReAct,以及如何用Python手搓一个简单Agent

前言

你有没有试过让大模型帮你做一件稍微复杂点的事?比如:"帮我查一下今天上海的天气,再根据天气推荐穿什么,顺便算算如果去外滩玩大概要花多少钱。"

大多数时候,模型会直接给你一长串看似合理的回答。但仔细一看,它可能把气温记错了,或者把"外滩门票"这种根本不存在的东西编出来了。更要命的是,它不会中途停下来核实信息,也不会在发现自己算错了之后回头修正。

这就是传统"一次性生成"模式的局限------模型像一个只会写报告的书呆子,把所有思考都憋在脑子里,最后一次性吐出来。一旦中间哪一步出错,整篇报告就跟着歪了。

直到2022年,一群研究者提出了一个叫 ReAct 的框架,事情开始变得有趣起来。

ReAct的全称是 Reasoning + Acting (推理 + 行动)。它的核心思想非常简单,却异常有效:让AI像人一样,边想边干,边干边观察,再根据观察结果继续想。

听起来是不是有点像我们去一个从未去过的地方?先计划怎么走,然后向着目标地点走去。途中不免要看看路牌,然后发现自己走错了,再问路人该怎么走,然后纠正自己的行动路线,走到正确的路上,然后看看路牌......如此循环往复

今天我们就来好好聊聊这个"边想边干"的哲学,以及如何用不到一百行Python代码,亲手搓一个能真正"干活"的简单Agent。


一、ReAct到底解决了什么问题?

在ReAct出现之前,AI Agent大致走两条路:

  1. 书呆子------纯推理路线(比如Chain-of-Thought):让模型把思考过程全部写出来,再给出最终答案。好处是逻辑清晰,坏处是它无法获取外部真实信息,只能在自己的知识边界里兜圈子。
  2. 莽夫------纯行动路线(比如早期的工具调用):模型直接决定调用某个工具,拿到结果后就结束。好处是能碰触真实世界,坏处是缺少中间的思考过程,容易做出盲目行动。

ReAct把这两条路巧妙地拧在一起。它强制模型在每一步都明确输出三种东西:

  • Thought(思考):我现在在想什么?接下来要做什么?为什么?
  • Action(行动):具体调用哪个工具,参数是什么?
  • Observation(观察):工具返回了什么结果?

然后把这个Observation再塞回模型,让它继续Thought......如此循环,直到模型认为任务完成,输出最终答案。

这种"思考-行动-观察"的闭环,有四个巨大的好处:

第一,可解释性大幅提升。你能清楚地看到模型每一步在想什么,出错时也更容易定位问题。

第二,错误纠正能力变强。如果某一步工具返回的结果和预期不符,模型可以在下一步的Thought里意识到"刚才可能查错了",然后换个方式再试。

第三,减少幻觉。出错后立刻调整,边想边干,才不容易在回答时"瞎说"。

第四,处理实时信息。能够随时获取最新数据,完美解决模型知识库更新不及时的问题。

这就像一个靠谱的助手:他不会闷头写完一堆东西再交给你,而是边做边跟你汇报进度,发现问题立刻调整。


二、ReAct的工作流程,拆开来看

我们用一个具体例子把流程走一遍。

假设任务是:"查询当前比特币价格,并告诉我如果我现在有1万元人民币,大概能买多少个比特币。"

一个典型的ReAct循环可能是这样的:

yaml 复制代码
Thought: 用户想知道比特币价格,以及1万元能买多少。我需要先获取最新价格。
Action: Search[比特币 当前价格]
Observation: 比特币当前价格约为 650,000 人民币/个

Thought: 已经知道价格了,现在可以计算了。1万元除以650000,大约是0.015个。
Action: Calculator[10000 / 650000]
Observation: 0.015384615384615385

Thought: 计算完成,可以给最终答案了。
Final Answer: 当前比特币价格约65万元人民币一个,1万元大约可以买0.0154个比特币。

注意几个关键点:

  1. Thought必须先于Action。模型不能直接跳到行动,必须先说明自己的意图。这强制它"想清楚再动手"。
  2. Action的格式要严格 。通常用 工具名[参数] 的形式,方便程序解析。
  3. Observation是真实世界的反馈。这一步完全由外部工具返回,模型无法"编造"观察结果。
  4. 循环可以继续。如果价格查询失败,或者计算结果异常,模型可以在下一个Thought里决定重新查询或换个工具。

整个过程就像一个状态机,状态在"思考中→行动中→观察中"之间不断切换,直到遇到"Final Answer"。


三、为什么ReAct特别适合Agent?

你可能会问:现在很多框架(LangChain、LlamaIndex、AutoGPT)都支持工具调用了,还需要专门强调ReAct吗?

答案是:ReAct不只是一种工具调用方式,它更是一种"思考范式"。

很多现代Agent框架底层其实都在用ReAct的变体。它们只不过把"Thought / Action / Observation"的文本格式,包装成了更优雅的函数调用接口(Function Calling)。但核心循环并没有变------模型依然需要决定"下一步做什么",执行后拿到结果,再决定下一步。

ReAct真正的价值在于它把"推理过程"显式化了。这带来了几个实际好处:

  • 调试友好:你可以打印出每一步的Thought,快速定位模型哪里想歪了。
  • 可干预:你甚至可以在中间插入人工Observation,强制模型沿着正确方向走。
  • 少幻觉:因为关键事实来自工具返回的Observation,而不是模型自己"回忆",幻觉明显减少。

当然,ReAct也不是万能的。它的主要缺点是:

  • Token消耗大:每一步都要把历史Thought和Observation塞进上下文,长任务很容易把上下文撑爆。
  • 对模型能力要求高:如果模型本身推理能力弱,它可能会陷入"反复调用同一个无效工具"的死循环。
  • 格式脆弱:如果模型输出的Action格式稍微乱一点,解析就会失败。

不过这些问题都有对应的工程解法(压缩历史、加反思机制、用结构化输出等),并不影响ReAct作为Agent基础范式的地位。


四、动手时间:用Python手写一个简单的ReAct Agent

理论说得再多,不如亲手写一个。下面我们用最朴素的方式,实现一个能真正调用工具的ReAct Agent。

为了不依赖外部付费API,我们这里用一个"模拟大模型"的方式演示核心逻辑。你完全可以把它替换成真正的OpenAI、Claude或本地模型调用。

1. 定义工具

先准备两个最基础的工具:一个搜索(这里用假数据模拟),一个计算器。

python 复制代码
import re
from typing import Dict, Callable

# 工具字典
tools: Dict[str, Callable] = {}

def search(query: str) -> str:
    """模拟搜索工具"""
    fake_db = {
        "比特币价格": "当前比特币价格约为 650000 人民币/个",
        "上海天气": "上海今天多云,气温 22°C,湿度 65%",
        "北京天气": "北京今天晴,气温 18°C,空气质量良好",
    }
    for key, value in fake_db.items():
        if key in query:
            return value
    return f"没有找到关于「{query}」的相关信息。"

def calculator(expression: str) -> str:
    """简单计算器,只支持基本运算"""
    try:
        # 安全起见,只允许数字和基本运算符
        if not re.match(r'^[\d\s\+\-\*\/\.\(\)]+$', expression):
            return "表达式包含非法字符"
        result = eval(expression)
        return str(result)
    except Exception as e:
        return f"计算错误: {e}"

tools["Search"] = search
tools["Calculator"] = calculator

2. 写一个超级简陋的"大模型"

真正项目中你会调用OpenAI的API。这里我们用一个规则引擎来模拟,方便大家看清整个流程。

python 复制代码
def mock_llm(prompt: str) -> str:
    """
    一个极度简化的模拟LLM。
    真实场景请替换成 openai.ChatCompletion.create(...) 之类的调用。
    """
    # 简单根据prompt内容决定输出
    if "比特币" in prompt and "价格" in prompt and "Observation" not in prompt[-200:]:
        return """Thought: 用户想知道比特币价格以及1万元能买多少,我需要先查询当前价格。
Action: Search[比特币价格]"""
    
    if "650000" in prompt and "Calculator" not in prompt:
        return """Thought: 已经获取到比特币价格为650000元,现在可以计算1万元能买多少个。
Action: Calculator[10000 / 650000]"""
    
    if "0.015" in prompt or "0.015384" in prompt:
        return """Thought: 计算完成,可以给出最终答案了。
Final Answer: 当前比特币价格约为65万元人民币一个。如果你有1万元人民币,大约可以买 0.0154 个比特币。"""
    
    # 默认兜底
    return """Thought: 我需要更多信息才能回答。
Action: Search[未知查询]"""

3. 核心ReAct循环

这是整个Agent的心脏。

python 复制代码
def run_react_agent(question: str, max_steps: int = 5) -> str:
    """
    运行一个简单的ReAct Agent
    """
    # 初始prompt
    history = f"""你是一个智能助手,可以使用以下工具:

Search[查询内容] - 搜索信息
Calculator[数学表达式] - 计算数学表达式

请严格按照以下格式回答:
Thought: 你的思考过程
Action: 工具名[参数]
或者
Thought: 你的思考过程
Final Answer: 最终答案

用户问题:{question}
"""
    
    print("=" * 50)
    print(f"用户问题:{question}")
    print("=" * 50)
    
    for step in range(max_steps):
        print(f"\n--- 第 {step + 1} 步 ---")
        
        # 1. 让模型思考并决定行动
        response = mock_llm(history)
        print(response)
        
        # 2. 解析是否是Final Answer
        if "Final Answer:" in response:
            final = response.split("Final Answer:")[-1].strip()
            print("\n" + "=" * 50)
            print("任务完成!")
            return final
        
        # 3. 解析Action
        action_match = re.search(r"Action:\s*(\w+)\[(.*?)\]", response, re.DOTALL)
        if not action_match:
            print("无法解析Action,结束。")
            break
        
        tool_name = action_match.group(1).strip()
        tool_input = action_match.group(2).strip()
        
        # 4. 执行工具
        if tool_name not in tools:
            observation = f"未知工具: {tool_name}"
        else:
            observation = tools[tool_name](tool_input)
        
        print(f"Observation: {observation}")
        
        # 5. 把Observation加入历史,继续循环
        history += f"\n{response}\nObservation: {observation}\n"
    
    return "达到最大步数,未能完成任务。"

4. 跑起来看看

python 复制代码
if __name__ == "__main__":
    result = run_react_agent("查询当前比特币价格,并告诉我1万元人民币能买多少个比特币")
    print("\n最终结果:", result)

运行后你会看到类似这样的输出:

yaml 复制代码
==================================================
用户问题:查询当前比特币价格,并告诉我1万元人民币能买多少个比特币
==================================================

--- 第 1 步 ---
Thought: 用户想知道比特币价格以及1万元能买多少,我需要先查询当前价格。
Action: Search[比特币价格]
Observation: 当前比特币价格约为 650000 人民币/个

--- 第 2 步 ---
Thought: 已经获取到比特币价格为650000元,现在可以计算1万元能买多少个。
Action: Calculator[10000 / 650000]
Observation: 0.015384615384615385

--- 第 3 步 ---
Thought: 计算完成,可以给出最终答案了。
Final Answer: 当前比特币价格约为65万元人民币一个。如果你有1万元人民币,大约可以买 0.0154 个比特币。

==================================================
任务完成!

恭喜,我们刚刚亲手实现了一个最简版的ReAct Agent。

虽然它很粗糙(模拟LLM、工具也极其有限),但核心骨架已经完整了 :Thought → Action → Observation → 再Thought......这个循环,就是ReAct精髓所在


结语

ReAct最打动我的地方,其实不是它的技术有多高深,而是它重新定义了"智能"的呈现方式。

以前我们期待AI给出一个完美的最终答案。现在我们更希望它能像一个靠谱的同事:遇到问题先思考,不确定就去查,查完了再算,算错了就回头改。整个过程透明、可干预、可解释。

这种"边想边干"的模式,正在悄悄改变我们和AI协作的方式。

下次当你再让大模型帮你做复杂任务时,不妨试试明确要求它用ReAct的方式输出。你会发现,那个原本只会"一本正经胡说八道"的模型,突然开始变得靠谱起来。

而这一切的起点,不过是让它学会说三句话:

我在想什么。

我准备做什么。

我看到了什么。

然后,循环往复,直到事情被真正解决。

这大概就是ReAct最迷人的地方------它没有发明什么全新的魔法,只是把人类解决问题时最朴素的节奏,教给了机器。

相关推荐
qinglongcn1 小时前
为什么 Agent 循环是 for{} 而不是 DAG——架构选择的深层原因
agent·ai编程
小七-七牛开发者1 小时前
周一上线 | DeepSeek 开始卷“许愿式编程”,Agent 手机来了,Character.AI 申请破产
ai·大模型·agent·claude·codex·deepseek·ai coding
全栈弄潮儿2 小时前
Python实战第8期:函数
后端·python·编程语言
PingCAP2 小时前
从一句需求到一套可追溯机制:我们这样推进电子病历改造
人工智能·agent·工作流引擎
PingCAP2 小时前
平凯星辰刘松:稳定交付严肃任务正成为企业级智能体的重要考验
人工智能·agent·工作流引擎
米小虾2 小时前
模型即 Agent?那 Harness 还有用吗
人工智能·agent
米小虾3 小时前
Harness 五要素:从"能跑"到"可靠"
人工智能·agent
GoGeekBaird3 小时前
云沙箱活跃时,文件怎么进出云沙箱
agent
卷无止境4 小时前
暗流与破局:AI时代的代码安全审查工具全景指南
后端·python