5.2 KiB
5.2 KiB
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 调用,不执行工具
流程:
- 步数检查(已达上限则用无工具模型)
- 循环检测(检测到异常则设置
stop=True) - 调用 LLM(带工具绑定)
- 流式推送 token
返回值:
{
"messages": [AIMessage(content=..., tool_calls=[...])],
"stop": bool,
"stop_reason": str,
"llm_calls": int + 1
}
4.2 tools 节点
职责:执行 AIMessage.tool_calls,生成 ToolMessage
流程:
- 获取最后一条
AIMessage - 提取
tool_calls列表 - 遍历执行每个工具
- 记录历史(tool_call_history, tool_result_history)
- 更新步数
current_step += 1 - 发送工具开始/结束事件(非 token 流)
返回值:
{
"messages": [ToolMessage(...), ...],
"current_step": current_step + 1,
"tool_call_history": [...],
"tool_result_history": [...],
"tools_used": [tool_names] # 新增:记录本轮使用的工具
}
4.3 finalize 节点
职责:轻量后处理
流程:
- 从
messages中提取最后一条AIMessage.content作为最终回复 - 汇总元数据:步数、使用的工具、停止原因
- 如果
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 兼容性:新增字段需设置默认值