← 返回 AI大模型从0到1——理论与实操
C3-04 零框架理解 Agent:ReAct 手撕
🎯 本章目标:不依赖任何框架,用不到 150 行 Python 代码,从零实现一个能思考、能行动、能观察的 Agent。理解 Agent 的"灵魂"——Thought → Action → Observation 循环。
ReAct 是什么?
ReAct = Reasoning(推理) + Acting(行动)
这是 2022 年 Google 提出的 Agent 范式。核心思想:让 LLM 在思考和行动之间循环,直到完成任务。
flowchart LR
Q[用户问题] --> T1[Thought
思考]
T1 --> A[Action
调用工具]
A --> O[Observation
观察结果]
O --> T2{任务完成?}
T2 -->|否| T1
T2 -->|是| F[Final Answer
最终答案]
style T1 fill:#FFE4B5
style A fill:#87CEEB
style O fill:#90EE90
style F fill:#FFB6C1
关键角色:
- Thought:LLM 推理"我下一步该做什么"
- Action:执行工具调用(如计算、搜索)
- Observation:获取工具返回结果
- Final Answer:推理出最终答案,结束循环
准备工作:定义工具
Agent 的"手脚"就是工具。每个工具都是一个普通 Python 函数,需要注册到工具字典中供 LLM 调用。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
💡 类比:工具就像 Agent 的"瑞士军刀"。LLM 不会算数,但有了 calculator 工具,它就"会"了。工具的本质是 把外部能力封装成函数。
ReAct Prompt 模板
Prompt 是 Agent 的"灵魂",它告诉 LLM:按什么格式思考、按什么格式行动、什么时候停止。
Plain Text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
💡 Prompt 设计的三个关键点:
- 明确格式:Thought/Action/Observation/Final Answer 四种前缀必须对齐
- 停止词:调用 LLM 时设置
stop=["Observation:"],让 LLM 在需要观察结果时暂停
- 工具描述:
{tools_desc}必须清晰准确,LLM 靠它决定调哪个工具
完整可运行代码
这是整个课程最核心的代码。请逐行阅读并亲手运行。
Plain Text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | |
运行结果
运行 python 01_react_agent.py 后,你会看到类似这样的输出:
Plain Text
1 2 3 4 5 6 7 | |
✅ 看到了吗?这就是 Agent 在"思考"和"行动"之间的循环。LLM 不是一步给出答案,而是 先思考 → 再行动 → 观察结果 → 再思考。这种循环让 LLM 能处理需要多步推理的复杂任务。
关键设计要点
| 设计点 | 为什么这样做 |
|---|---|
| 正则解析 Action | LLM 输出不一定是标准 JSON,正则容错性更强 |
| stop=["Observation:"] | 让 LLM 在需要观察结果时停下来,避免它"自说自话"生成 Observation |
| 字符串拼接对话 | ReAct 需要完整历史,LSTM 风格的 messages 列表反而更复杂 |
| 最大步数限制 | 防止死循环(LLM 没完没了地调工具),默认 5 步 |
| 错误回填 | 工具调用失败时,把错误作为 Observation 喂回,让 LLM 自我纠正 |
手撕版的局限性
手撕 ReAct Agent 帮我们理解原理,但生产环境远远不够:
- ❌ 字符串拼接易错,Prompt 一改就崩
- ❌ 无状态管理,长任务容易"失忆"
- ❌ 难以处理复杂工作流(分支、循环、并行)
- ❌ 无持久化,程序重启就丢失上下文
- ❌ 工具调用格式不标准,不同模型行为不一致
👉 这就是为什么需要 Function Calling 和 LangGraph等开发框架。