diff --git a/docs/design/v3.0-router-refactor.md b/docs/design/v3.0-router-refactor.md index 17f3866..c50537a 100644 --- a/docs/design/v3.0-router-refactor.md +++ b/docs/design/v3.0-router-refactor.md @@ -1,39 +1,116 @@ # v3.0 Router 重构方案:去掉独立 LLM,改用 Gateway spawn Agent **日期**: 2026-05-21 -**状态**: 方案待确认 +**状态**: 待司马懿评审 **影响文件**: `router.py`, `dispatcher.py`, `main.py`, `config/default.yaml` --- -## 问题 +## 1. 背景:Spawn 完整链路调查 -当前 Router(`LLMDriver`)用独立的 `OpenAI()` 客户端直接调 zhipu API 做路由决策。 -这违反设计文档 `architecture-v2.6.md` 的核心原则: +### 1.1 完整数据流 -> **系统只有两种 LLM 调用方式,都通过 Gateway**: -> 1. **L3 run agent** — `openclaw agent --agent `,spawn 完整 Agent -> 2. **L2 spawn sub** — `openclaw agent --agent --session-id `,轻量一次性 +``` +Ticker (30s一轮) + → _tick_project(project_id) + ├─ 1. 扫描状态摘要 + ├─ 2. 依赖推进(done 的解锁下游) + ├─ 3. 僵尸/超时检测(_check_timeouts) + ├─ 4. 调度 pending → _dispatch_pending() + ├─ 5. 调度 review → _dispatch_reviews() + ├─ 6. 聚合父 Task 状态 + ├─ 7. 写 daemon_tick 事件 + ├─ 8. 健康检查 + └─ 9. 经验蒸馏 -独立 `OpenAI()` 不属于任何一层,是设计之外的野路子。 +_dispatch_pending() + → queries.pending_dispatchable() # 找 pending 且 dependency 都满足的任务 + → dispatcher.dispatch(task) # 每轮最多 3 个 + ├─ Router.route(task) # ← 要改的:独立 LLM 调用 + │ → 返回 agent_id + ├─ Counter.can_acquire(agent_id) # 并发检查(全局上限5 + per-agent上限1-3) + ├─ Counter.acquire(agent_id) # 占用名额 + ├─ Spawner.spawn_full_agent() + │ ├─ cmd = ["openclaw", "agent", "--agent", agent_id, + │ │ "--session-id", uuid, "--message", prompt, "--json"] + │ ├─ asyncio.create_subprocess_exec(*cmd) # 异步非阻塞 + │ └─ asyncio.create_task(_monitor_process) # 后台监控 + └─ 黑板写 claimed + current_agent -### 具体问题 -1. **凭据管理**:需要单独维护 api_base/api_key,和 Gateway 配置重复 -2. **不走 Gateway**:无法利用 Gateway 的模型路由、fallback、计费 -3. **设计不一致**:设计文档三层模型(L1/L2/L3),Router 不在其中 -4. **可靠性差**:凭据为空时静默 fallback,不报错 +_monitor_process() # 异步等待,不阻塞 ticker + → await asyncio.wait_for(proc.wait(), timeout=600s) + → 超时 → proc.kill() + → _record_attempt(task_id, outcome) # 写 task_attempts 表 + → on_complete(agent_id, outcome) # → Counter.release() + +_dispatch_reviews() # review 状态的调度 + → 检查有没有 review 记录(防重复) + → 检查有没有产出(无产出直接标 failed) + → spawn 司马懿做审查 +``` + +### 1.2 涉及的模块 + +| 模块 | 文件 | 职责 | +|------|------|------| +| **Ticker** | `daemon/ticker.py` | 30s 轮询,驱动整个调度循环 | +| **Dispatcher** | `daemon/dispatcher.py` | 决策 + spawn 执行,协调 Router/Counter/Spawner | +| **Router** | `daemon/router.py` | 路由决策:返回 agent_id + mode | +| **Spawner** | `daemon/spawner.py` | 实际 spawn 进程 + 监控 + 记录 | +| **Counter** | `daemon/counter.py` | 并发控制:全局 Semaphore + per-agent Semaphore | + +### 1.3 设计文档中的三层执行模型(architecture-v2.6.md) + +| 层级 | 方式 | 命令 | 成本 | 适用场景 | +|------|------|------|------|---------| +| **L1 Daemon 直接操作** | SQLite/文件 | — | 几乎为零 | 状态更新、机械验证 | +| **L2 spawn sub** | 隔离 session | `openclaw agent --agent --session-id ` | 轻量 | scope guard、格式校验 | +| **L3 run agent** | 完整黑板参与者 | `openclaw agent --agent ` | 完整 | 编码、审查、策略 | + +> **核心原则:系统只有两种 LLM 调用方式,都通过 Gateway,没有第三种。** --- -## 方案:Router 改为"能力匹配 + spawn 庞统兜底" +## 2. 问题 -### 核心思路 +### 2.1 LLMDriver 是设计之外的野路子 + +当前 Router(`LLMDriver` 类)用独立的 `OpenAI()` 客户端直接调 zhipu API 做路由决策: +- 不属于 L1(调了 LLM)、不属于 L2(不是 openclaw agent)、不属于 L3(不是完整 Agent) +- 不走 Gateway(无模型路由、无 fallback、无计费) +- 需要单独维护 api_base/api_key(和 Gateway 配置重复) +- 凭据为空时静默 fallback 到庞统,不报错 + +### 2.2 实际故障 + +`general-20260521-0004` 任务反复调度失败: +``` +Router.route() → LLMDriver._get_client() → OpenAI(**kwargs) +→ OpenAIError: Missing credentials +→ fallback to pangtong-fujunshi (但 Router 不 spawn,只返回决策) +→ Dispatcher 拿到 agent_id="pangtong-fujunshi" → spawn 庞统 +→ 但庞统的 prompt 是"执行任务"而非"分配任务" +``` +而且 `config/default.yaml` 的 `routing.api_base/api_key` 都是空字符串,注释说"空=用 OpenClaw 默认"——但 OpenAI 库不会自动读 OpenClaw 配置。 + +### 2.3 设计违背 + +architecture-v2.6.md §1.1 核心设计原则: +> "Agent 决策,Daemon 执行" — 庞统做 plan、张飞领任务、关羽发现风险,都写在黑板上。Daemon 读黑板,执行 spawn/通知。 + +Router 用独立 LLM 做决策,违反了"Agent 决策"原则——决策应该由 Agent(通过 Gateway)来做,不是 Daemon 自己调 LLM。 + +--- + +## 3. 方案:Router 改为"能力匹配 + spawn 庞统兜底" + +### 3.1 核心思路 Router 有两种路由方式: - **确定性路由**(能力匹配、retry、handoff)→ 保留,纯 L1 逻辑,不调 LLM - **模糊路由**(首次分配、不确定场景)→ **不再调独立 LLM,改为 spawn 庞统让庞统决定** -### 路由决策流程(改后) +### 3.2 路由决策流程(改后) ``` 任务进入 Router.route() @@ -47,15 +124,35 @@ Router 有两种路由方式: └─ 模糊场景(以上都不匹配) │ → 返回 RouteDecision(agent_id="pangtong-fujunshi", mode="delegate") - 庞统被 spawn 后,读取黑板任务信息,自己决定分配给谁 - 庞统通过 API 回写 assignee → ticker 下一轮 spawn 实际执行者 + Dispatcher 拿到 mode="delegate" + → 构建 delegate prompt("请分配此任务") + → Spawner.spawn_full_agent(pangtong-fujunshi, delegate_prompt) + → 庞统读黑板任务信息,自己决定分配给谁 + → 庞统通过 API 回写 assignee → ticker 下一轮 spawn 实际执行者 ``` -### 改动清单 +### 3.3 关键区别:改前 vs 改后 -#### 1. 删除 `LLMDriver` 类(router.py) +| 阶段 | 改前(独立 LLM) | 改后(spawn 庞统) | +|------|-----------------|-------------------| +| **决策者** | `LLMDriver`(OpenAI 客户端) | 庞统 Agent(通过 Gateway) | +| **调用方式** | `OpenAI().chat.completions.create()` | `openclaw agent --agent pangtong-fujunshi` | +| **走的路径** | 直连 zhipu API | Gateway → 模型路由 | +| **凭据** | config/default.yaml 手动配 | Gateway 统一管理 | +| **上下文** | 300 token prompt(标题+描述+能力列表) | 完整黑板上下文(SOUL+AGENTS+任务详情) | +| **速度** | 1-2s | 30-60s | +| **准确性** | 低(信息少,模型弱) | 高(完整上下文,模型强) | +| **失败处理** | 静默 fallback,不报错 | 正常 Agent 失败流程(retry/escalate) | -整个 `LLMDriver` 类删除,约 120 行。Router 的 `route()` 方法末尾: +--- + +## 4. 改动清单 + +### 4.1 删除 `LLMDriver` 类(router.py) + +删除整个 `LLMDriver` 类(约 120 行),包括 `_get_client()`、`route()`、`_build_prompt()`。 + +Router 的 `route()` 方法末尾改为: ```python # 当前(Mode A: 独立 LLM 调用) @@ -72,103 +169,118 @@ return RouteDecision( ) ``` -#### 2. `AgentRouter.__init__` 去掉 `llm_driver` 参数 +### 4.2 `AgentRouter.__init__` 去掉 `llm_driver` 参数 ```python -def __init__(self, agent_profiles, counter=None): # 删 llm_driver +# 改前 +def __init__(self, agent_profiles, llm_driver=None, counter=None): + +# 改后 +def __init__(self, agent_profiles, counter=None): ``` -#### 3. Dispatcher 增加 `delegate` 模式处理 +### 4.3 Dispatcher 增加 `delegate` 模式 -当 `mode="delegate"` 时,spawn 庞统并传入"请分配此任务"的 prompt: - -```python -# dispatcher.py decide() 中 -if decision.mode == "delegate": - return { - "level": DispatchLevel.FULL_AGENT, - "agent_id": "pangtong-fujunshi", - "reason": decision.reason, - "mode": "delegate", # 标记,用于构建不同 prompt - } -``` - -`_build_spawn_message` 中为 `delegate` 模式生成专门的 prompt: +`dispatcher.py` 的 `_build_spawn_message()` 中,为 `delegate` 模式生成专门的 prompt: ```python if mode == "delegate": return f"""你是任务协调员。请分析以下任务,决定最合适的执行者。 ## 任务信息 -- ID: {task.id} -- 标题: {task.title} -- 描述: {task.description} -- 类型: {task.task_type} +- 项目: {project_id} +- 任务ID: {task_id} +- 标题: {title} +- 描述: {description} +- 类型: {task_type} + +## 团队 +- 张飞(zhangfei-dev): 编码、实现、脚本 +- 司马懿(simayi-challenger): 审查、质量检查、辩论 +- 关羽(guanyu-dev): 风控、合规 +- 赵云(zhaoyun-data): 数据获取、清洗、验证 +- 姜维(jiangwei-infra): 部署、基础设施、Docker、vnpy +- 庞统(pangtong-fujunshi): 规划、协调、策略 ## 操作 1. 分析任务需求 -2. 选择最合适的 Agent(从你已知的团队中) +2. 选择最合适的 Agent 3. 通过 API 回写分配结果: - curl -X POST {api}/tasks/{task.id}/status -d '{{"status":"claimed","assignee":""}}' -4. 如果你自己能做,直接认领执行 + curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/status \\ + -H 'Content-Type: application/json' \\ + -d '{{"status": "claimed", "agent": ""}}' +4. 如果你自己能做,直接认领并执行 """ ``` -#### 4. main.py 去掉 LLMDriver 初始化 +注意:其余 spawn 链路(Counter.acquire → Spawner.spawn_full_agent → _monitor_process → Counter.release)完全复用,不需要改。 + +### 4.4 main.py 去掉 LLMDriver 初始化 + +删除 `routing_config` / `llm_driver` 的初始化块(约 10 行)。Router 构造不再传 `llm_driver`: ```python -# 删掉 routing_config / llm_driver 的整个初始化块(~10行) -# Router 构造不再传 llm_driver router = AgentRouter( agent_profiles=agent_profiles, counter=counter, ) ``` -#### 5. config/default.yaml 去掉 routing 节 +### 4.5 config/default.yaml 去掉 routing 节 +删除 `daemon.routing` 配置节: ```yaml -# 删掉整个 routing: 节(model/api_base/api_key/timeout/...) -# 确定性路由的能力匹配不依赖配置 -# 模糊路由由庞统决策,不需要配置 +# 删掉: +routing: + model: "glm-4-flash" + api_base: "..." + api_key: "..." + confidence_threshold: 0.7 + timeout: 5.0 + max_tokens: 200 + temperature: 0.1 ``` ---- - -### 改动前后对比 - -| 场景 | 改前 | 改后 | -|------|------|------| -| retry | 原执行者(确定性)| 不变 | -| Agent handoff | 能力匹配(确定性)| 不变 | -| 生命周期 review | 能力匹配(确定性)| 不变 | -| 有 assignee | 直接分(确定性)| 不变 | -| **首次分配/模糊** | **独立 LLM 调用** | **spawn 庞统决策** | - -### 影响 - -- **删代码**:~130 行(LLMDriver + routing config) -- **改代码**:~30 行(Router.route 末尾 + Dispatcher._build_spawn_message) -- **config**:删 routing 节 -- **行为变化**:模糊场景从"1-2秒 LLM 返回"变成"spawn 庞统 → 庞统思考 → 回写",多 30-60 秒但更准确 -- **优点**:不再需要维护独立 LLM 凭据,所有 AI 调用统一走 Gateway - -### 风险 - -1. **庞统成为单点**:所有模糊路由都走庞统,如果庞统繁忙会被跳过(counter 限制) - - 缓解:庞统 max_concurrent=3,且 delegate 模式是轻量决策不是重活 -2. **速度变慢**:独立 LLM 1-2s vs spawn 庞统 30-60s - - 评估:首次分配本来就不用急,准确比快重要 -3. **确定性路由覆盖不到的场景**:如果能力匹配足够好,大部分场景不需要庞统 - - 评估:对。实际运行中大部分任务要么有 assignee 要么有 task_type 可匹配 +确定性路由的能力匹配不依赖配置,模糊路由由庞统决策不需要配置。 --- -## 实施步骤 +## 5. 改动前后场景对比 -1. 删 LLMDriver + router.py 清理 -2. main.py 去掉 llm_driver 初始化 -3. Dispatcher 增加 delegate 模式 prompt -4. config/default.yaml 删 routing 节 +| 场景 | 改前 | 改后 | 变化 | +|------|------|------|------| +| retry | 原执行者(确定性)| 原执行者(确定性)| 不变 | +| Agent handoff | 能力匹配(确定性)| 能力匹配(确定性)| 不变 | +| 生命周期 review | 能力匹配(确定性)| 能力匹配(确定性)| 不变 | +| 有 assignee | 直接分(确定性)| 直接分(确定性)| 不变 | +| **首次分配/模糊** | **独立 LLM 调用** | **spawn 庞统决策** | **改** | + +--- + +## 6. 代码量 + +- **删**:~130 行(`LLMDriver` 类 + routing config 初始化 + config.yaml routing 节) +- **改**:~30 行(`Router.route()` 末尾 + `Dispatcher._build_spawn_message()` 增加 delegate 分支) +- **净减**:~100 行 + +--- + +## 7. 风险与缓解 + +| # | 风险 | 评估 | 缓解 | +|---|------|------|------| +| 1 | 庞统成为单点 | 中 | 庞统 max_concurrent=3,delegate 是轻量决策(30s 内完成),不是重活 | +| 2 | 速度变慢(1-2s → 30-60s) | 低 | 首次分配不急,准确比快重要。且大部分场景走确定性路由,不触发 delegate | +| 3 | 庞统繁忙时 delegate 被跳过 | 中 | Counter 返回 skipped,下轮 ticker 重试。庞统 3 并发足够 | +| 4 | delegate prompt 不够精确 | 低 | 庞统有完整上下文(SOUL+AGENTS+团队信息),比 300 token 的 LLM prompt 强得多 | + +--- + +## 8. 实施步骤 + +1. `router.py`:删除 `LLMDriver` 类 + `AgentRouter` 去掉 `llm_driver` 参数 + `route()` 末尾改 delegate +2. `dispatcher.py`:`_build_spawn_message()` 增加 `delegate` 分支 +3. `main.py`:删除 `llm_driver` 初始化块 +4. `config/default.yaml`:删除 `routing` 节 5. 发司马懿评审 6. 评审通过后部署