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

5.2 KiB
Raw Blame History

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 中新增字段:

@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

返回值

{
    "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 流)

返回值

{
    "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,生成说明文本

返回值

{
    "final_reply": str,
    "metadata": {
        "steps_taken": int,
        "tools_used": List[str],
        "stop_reason": str,
        "llm_calls": int
    }
}

4.4 条件边

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 兼容性:新增字段需设置默认值