diff --git a/docs/superpowers/specs/2026-05-08-react-separation-design.md b/docs/superpowers/specs/2026-05-08-react-separation-design.md new file mode 100644 index 0000000..47146b3 --- /dev/null +++ b/docs/superpowers/specs/2026-05-08-react-separation-design.md @@ -0,0 +1,199 @@ +# 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 兼容性:新增字段需设置默认值