Files
ailine/docs/superpowers/specs/2026-05-08-react-separation-design.md
2026-05-08 00:48:17 +08:00

200 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ReAct 循环分离设计
**日期**: 2026-05-08
**状态**: 已批准
**目标**: 将 "fat node" 中的 ReAct 循环拆分为独立的 agent/tools 图节点
---
## 1. 目标
将当前 `agent.py` 中的"厚节点"(包含完整 while 循环、工具执行、循环检测)重构为标准 LangGraph 图架构:
- **推理节点 (agent)**:只负责单步 LLM 调用
- **工具节点 (tools)**:只负责执行工具
- **条件边**:控制循环逻辑
- **finalize 节点**:轻量后处理
---
## 2. 新图结构
```
START
init_state ──→ memory_trigger ──→ agent ──┬──→ (条件边) ──→ tools ──→ agent (循环)
│ │ ▲
│ └──────────────┘
│ (无工具调用时)
finalize
END
```
### 节点职责
| 节点 | 职责 | 输入 | 输出 |
|------|------|------|------|
| `init_state` | 初始化状态,重置步数 | 无 | `{current_step: 0, max_steps: N}` |
| `memory_trigger` | 检测记忆指令,触发 Mem0 存储 | AgentState | 无修改 |
| `agent` | 单步 LLM 调用,输出 AIMessage | AgentState | `{messages: [AIMessage], ...}` |
| `tools` | 执行 tool_calls返回 ToolMessage | AgentState | `{messages: [ToolMessage], current_step: N+1, ...}` |
| `finalize` | 轻量后处理 | AgentState | `{final_reply: str, metadata: {...}}` |
---
## 3. 状态定义
`AgentState` 中新增字段:
```python
@dataclass
class AgentState:
# 现有字段保留...
# 新增字段
tool_call_history: List[dict] = field(default_factory=list) # [{"name": "...", "args": {...}}]
tool_result_history: List[str] = field(default_factory=list) # ["结果1", "结果2", ...]
stop: bool = False # 手动停止标志
stop_reason: str = "" # 停止原因
```
---
## 4. 节点实现
### 4.1 agent 节点
**职责**:单步 LLM 调用,不执行工具
**流程**
1. 步数检查(已达上限则用无工具模型)
2. 循环检测(检测到异常则设置 `stop=True`
3. 调用 LLM带工具绑定
4. 流式推送 token
**返回值**
```python
{
"messages": [AIMessage(content=..., tool_calls=[...])],
"stop": bool,
"stop_reason": str,
"llm_calls": int + 1
}
```
### 4.2 tools 节点
**职责**:执行 `AIMessage.tool_calls`,生成 `ToolMessage`
**流程**
1. 获取最后一条 `AIMessage`
2. 提取 `tool_calls` 列表
3. 遍历执行每个工具
4. 记录历史tool_call_history, tool_result_history
5. 更新步数 `current_step += 1`
6. 发送工具开始/结束事件(非 token 流)
**返回值**
```python
{
"messages": [ToolMessage(...), ...],
"current_step": current_step + 1,
"tool_call_history": [...],
"tool_result_history": [...],
"tools_used": [tool_names] # 新增:记录本轮使用的工具
}
```
### 4.3 finalize 节点
**职责**:轻量后处理
**流程**
1.`messages` 中提取最后一条 `AIMessage.content` 作为最终回复
2. 汇总元数据:步数、使用的工具、停止原因
3. 如果 `final_reply` 为空且有 `stop_reason`,生成说明文本
**返回值**
```python
{
"final_reply": str,
"metadata": {
"steps_taken": int,
"tools_used": List[str],
"stop_reason": str,
"llm_calls": int
}
}
```
### 4.4 条件边
```python
def should_continue(state: AgentState) -> Literal["tools", "finalize"]:
"""根据 agent 节点输出决定下一步"""
# 手动停止标志
if getattr(state, "stop", False):
return "finalize"
# 检查是否有工具调用
last_msg = state.messages[-1]
if isinstance(last_msg, AIMessage) and last_msg.tool_calls:
return "tools"
return "finalize"
```
### 4.5 循环回边
```
tools 节点执行完后 → 无条件回到 agent 节点
```
---
## 5. 流式事件
| 阶段 | 事件类型 | 内容 |
|------|----------|------|
| agent | `node_start` | `{"node": "agent"}` |
| agent | `llm_token` | `{"token": "...", "reasoning_token": "..."}` |
| agent | `node_end` | `{"node": "agent"}` |
| tools | `tool_start` | `{"tool": "name", "args": {...}, "id": "..."}` |
| tools | `tool_end` | `{"tool": "name", "id": "...", "result": "..."}` |
| finalize | 无 | - |
---
## 6. 循环检测
保留现有 `_should_stop_for_loop` 函数,放在 agent 节点中。
**检测逻辑**:连续 2 次调用相同工具 + 参数相似 + 结果相似 → 设置 `stop=True, stop_reason="loop_detected"`
---
## 7. 文件变更
### 新增文件
- `backend/app/main_graph/nodes/tools.py` — tools 节点实现
- `backend/app/main_graph/nodes/finalize.py` — finalize 节点实现
### 修改文件
- `backend/app/main_graph/state.py` — 新增状态字段
- `backend/app/main_graph/main_graph_builder.py` — 重构图构建逻辑
- `backend/app/main_graph/nodes/agent.py` — 移除 while 循环和工具执行逻辑
### 删除
-
---
## 8. 兼容性
- 对外接口(`process_message`, `process_message_stream`)保持不变
- 返回值格式调整:新增 `metadata` 字段
- checkpointer 兼容性:新增字段需设置默认值