ReAct 状态与编排
一句话本质
ReAct 不是"思考一下 → 调个工具 → 结束",而是 让模型在"状态"的支撑下,循环决定下一步去哪里(分支)、做什么(节点)、要不要再来一轮(回环)。
1. 什么是 ReAct
ReAct = Reason(思考)+ Act(行动)。
核心思想:让大语言模型像人一样解决问题——不是一次性给答案,而是先思考、再行动、再根据行动结果继续思考,形成闭环循环。
1.1 基本流程
text
用户输入 ↓ 存入对话状态(state) ↓ 模型思考 → 判断是否需要调用工具 ↓ ┌─ 不需要工具 ─→ 直接输出答案 → 结束 │ └─ 需要工具 ─→ 生成工具调用参数 → 执行工具 ↓ 工具结果写入 state ↓ 模型继续思考 → 决定是否继续调用工具 ↓ 直到不需要工具 → 结束
1.2 为什么 ReAct 有效
- 更符合人类解决问题的方式:先想再做
- 能把复杂任务拆解成多个小步骤
- 中间过程可解释,可以看到模型为什么这么做
- 依赖"思维链"能力,模型不断拆解"下一步该做什么"
2. 状态管理:ReAct 的记忆基础
2.1 为什么"思考+行动"不够
"思考+行动"只是表面行为,状态才是让这个行为能连续进行下去的记忆基础。
2.2 状态里需要记录什么
一本"工作记录本",至少包含:
| 内容 | 说明 |
|---|---|
| 用户最初的问题 | 任务起点 |
| 历史对话 / 消息 | 每轮思考和工具结果必须保存,否则下一轮模型无法接上 |
| 工具调用结果 | weather_tool、calculator 等结果,供下一轮推理使用 |
| 当前执行到第几步 | 配合 max_steps 防止死循环 |
| 是否结束 | 终止条件判断 |
2.3 最简单的状态结构
json
{ "messages": [ { "role": "user", "content": "183+192-90,顺便判断难度" }, { "role": "assistant","content": "我先算加法" }, { "role": "tool", "name": "add", "content": "375" }, { "role": "assistant","content": "再做减法" }, { "role": "tool", "name": "sub", "content": "285" } ], "step_count": 2, "finished": false, "final_answer": null }
3. 图编排:ReAct 的流程骨架
3.1 什么叫"图编排"
ReAct 的执行过程不是一条死板的直线,而是一个会分支、会循环的结构。只要有分支和回环,本质上就是"图"。
图编排的核心是解决三个问题:
| 问题 | 回答 |
|---|---|
| 下一步去哪里 | 分支(Branch) |
| 这一步做什么 | 节点(Node) |
| 要不要再来一轮 | 回环(Loop) |
3.2 图编排不是可视化画图
最小版 ReAct 图:
text
开始 ↓ 模型节点(模型思考 + 生成调用参数) ↓ 分支节点:要不要调用工具? ↓ ┌─ 是 ─→ 工具节点(执行工具) │ ↓ │ 工具结果写入 state │ ↓ └──────────────→ 回到模型节点 ↑(回环) ↓ 否 ─→ 结束节点(输出最终答案)
3.3 分支、节点、回环
分支(Branch)
"路怎么走"的问题:
- 是否使用工具
- 用哪个工具
- 工具执行完后是继续推理还是直接结束
节点(Node)
"每走到一个位置,要做什么"的问题。可插入的节点类型:
| 节点类型 | 作用 |
|---|---|
start | 入口 |
pre handler | prompt 预处理、安全检查 |
chat model | 模型思考节点 |
tool node | 工具执行节点 |
post handler | 结果校验、清洗、重试 |
branch | 条件分支判断 |
end | 结束 |
回环(Loop)
ReAct 和普通线性流程最大的区别:
- 不是走一遍就结束
- 而是 模型 → 工具 → 模型 → 工具 → 模型
- 可以反复循环多轮,直到满足结束条件
4. ReAct vs n8n:核心区别
| n8n | ReAct | |
|---|---|---|
| 性质 | 业务流程自动化 | 给模型一个可循环决策框架 |
| 流程 | 人提前把流程基本写死 | 人只搭骨架,模型在骨架里动态决定怎么走 |
| 分支决定 | 人预设好 | 模型动态决定 |
| 典型场景 | 固定链路:收邮件→解析→写入→通知 | 灵活任务:先查天气→再判断→再给建议 |
5. 为什么需要图编排
简单场景下 while 循环就够了,但任务复杂后图编排就很有价值:
5.1 更容易加分支
text
需要搜索 → search_tool 需要计算 → calc_tool 需要查数据库 → db_tool 需要人工确认 → human_node
5.2 更容易加中间节点
在模型前加:
- prompt 预处理
- 安全检查
- 上下文裁剪
- 记忆检索
在工具后加:
- 工具结果校验
- 结果清洗
- 错误重试
- 总结归纳
5.3 更容易观察和调试
你能清楚看到:
- 卡在哪个节点
- 是模型判断错了
- 还是工具调用失败了
- 还是状态没更新好
6. 最小实现示例
6.1 伪代码
python
state = init_state(user_input) while state.step_count < max_steps: model_output = call_model(state) if model_output.requires_tool: tool_result = call_tool( model_output.tool_name, model_output.args ) state.messages.append(tool_result) state.step_count += 1 else: state.final_answer = model_output.answer state.finished = True break
这段代码没有画图,但它已经是一个完整的 ReAct 图:
- 节点1:模型
- 节点2:工具
- 节点3:结束
- 边1:需要工具就去工具节点
- 边2:工具完成后回模型节点
- 边3:不需要工具就结束
6.2 Mermaid 流程图
graph TD
A[开始] --> B[初始化状态]
B --> C[模型思考]
C --> D{需要调用工具?}
D -->|是| E[执行工具]
E --> F[工具结果写入状态]
F --> G[步数 +1]
G --> C
D -->|否| H[输出最终答案]
H --> I[结束]
C -->|步数超限| IRendering diagram...
7. 核心配置项
| 配置项 | 作用 |
|---|---|
tools / model | 指定使用哪些工具和模型 |
message modifier | 在消息送给模型前做预处理 |
max_steps | 限制最多循环多少步,避免死循环 |
tool return directly | 某些工具调用后可直接返回 |
stream tool checker | 流式输出时检查工具调用是否完整 |
execute concurrently | 控制工具是并行还是串行执行 |
8. 开发者视角的最小理解
把 ReAct 拆解成三件事:
- 一个循环 — 模型反复决定要不要继续调用工具
- 一个状态对象 — 把消息、工具结果、步数、最终答案都存起来
- 一个分支逻辑 — 要工具 → 去工具节点;不要工具 → 结束