Files
sanguo_moziplus_v2/docs/design/v3.0-router-refactor.md
T
2026-05-21 11:12:09 +08:00

11 KiB
Raw Blame History

v3.0 Router 重构方案:去掉独立 LLM,改用 Gateway spawn Agent

日期: 2026-05-21 状态: 待司马懿评审 影响文件: router.py, dispatcher.py, main.py, config/default.yaml


1. 背景:Spawn 完整链路调查

1.1 完整数据流

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. 经验蒸馏

_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

_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 <id> --session-id <uuid> 轻量 scope guard、格式校验
L3 run agent 完整黑板参与者 openclaw agent --agent <id> 完整 编码、审查、策略

核心原则:系统只有两种 LLM 调用方式,都通过 Gateway,没有第三种。


2. 问题

2.1 LLMDriver 是设计之外的野路子

当前 RouterLLMDriver 类)用独立的 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.yamlrouting.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()
  │
  ├─ 快速路径1: 本地 action → daemon
  ├─ 快速路径2: retry → 原执行者
  ├─ Mode B: Agent handoff (next_capability) → 能力匹配
  ├─ 快速路径3: 生命周期流转 → 能力匹配
  ├─ 快速路径4: 有 assignee → 直接分
  │
  └─ 模糊场景(以上都不匹配)
      │
      → 返回 RouteDecision(agent_id="pangtong-fujunshi", mode="delegate")
        Dispatcher 拿到 mode="delegate"
        → 构建 delegate prompt"请分配此任务"
        → Spawner.spawn_full_agent(pangtong-fujunshi, delegate_prompt)
        → 庞统读黑板任务信息,自己决定分配给谁
        → 庞统通过 API 回写 assignee → ticker 下一轮 spawn 实际执行者

3.3 关键区别:改前 vs 改后

阶段 改前(独立 LLM 改后(spawn 庞统)
决策者 LLMDriverOpenAI 客户端) 庞统 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

4. 改动清单

4.1 删除 LLMDriver 类(router.py

删除整个 LLMDriver 类(约 120 行),包括 _get_client()route()_build_prompt()

Router 的 route() 方法末尾改为:

# 当前(Mode A: 独立 LLM 调用)
if self.llm_driver:
    decision = self.llm_driver.route(...)
    ...

# 改后(委托庞统)
return RouteDecision(
    agent_id=self.FALLBACK_AGENT,  # "pangtong-fujunshi"
    reason="Uncertain routing, delegate to coordinator",
    mode="delegate",
    confidence=0.0,
)

4.2 AgentRouter.__init__ 去掉 llm_driver 参数

# 改前
def __init__(self, agent_profiles, llm_driver=None, counter=None):

# 改后
def __init__(self, agent_profiles, counter=None):

4.3 Dispatcher 增加 delegate 模式

dispatcher.py_build_spawn_message() 中,为 delegate 模式生成专门的 prompt

if mode == "delegate":
    return f"""你是任务协调员。请分析以下任务,决定最合适的执行者。

## 任务信息
- 项目: {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
3. 通过 API 回写分配结果:
   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": "<agent_id>"}}'
4. 如果你自己能做,直接认领并执行
"""

注意:其余 spawn 链路(Counter.acquire → Spawner.spawn_full_agent → _monitor_process → Counter.release)完全复用,不需要改。

4.4 main.py 去掉 LLMDriver 初始化

删除 routing_config / llm_driver 的初始化块(约 10 行)。Router 构造不再传 llm_driver

router = AgentRouter(
    agent_profiles=agent_profiles,
    counter=counter,
)

4.5 config/default.yaml 去掉 routing 节

删除 daemon.routing 配置节:

# 删掉:
routing:
    model: "glm-4-flash"
    api_base: "..."
    api_key: "..."
    confidence_threshold: 0.7
    timeout: 5.0
    max_tokens: 200
    temperature: 0.1

确定性路由的能力匹配不依赖配置,模糊路由由庞统决策不需要配置。


5. 改动前后场景对比

场景 改前 改后 变化
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=3delegate 是轻量决策(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. 评审通过后部署