Skip to content

Feature Request: Router 决策链路与错误来源可视化(自动路由的诊断痛点) #105

@gavin-luo

Description

@gavin-luo

问题描述:

当 PilotDeck 的 TokenSaver/自动路由模块选择了一个 provider/model 后,如果该模型调用失败(配额不足/上下文超限/认证错误等),前端 UI 或 QQ Bot 通道仅展示类似 错误:request (74815 tokens) exceeds the available context size (65536 tokens) 的消息,完全不体现是哪个 provider 和 model 报的错。

用户在前端无法直观判断:
当前错误是哪个供应商/模型产生的,比如欠费都不能立即知道应该向哪个供应商缴费、路由决策链路是怎样的(Judge 判断结果 → 选择的 tier → 最终 provider/model → fallback 链状态),出错的是主路由还是 TokenSaver Judge 模型。

当前仅有一条 console.log 路由决策日志(RouterRuntime.ts L263-265),但出错时不会与错误信息关联输出。
pilotdeck_router_execute_failed 事件(RouterRuntime.ts L569-578)已经包含了 provider 和 model,但未能传播到前端 GatewayEvent.error 事件中。

建议方案

方案一(最小侵入):

将 provider/model 注入 GatewayEvent.error 在 pilotdeck_router_execute_failed → RouterRuntime.ts L607 的 yield { type: "error", error: lastError } 前的某处,或在 execute() 方法尾部,将实际请求的 provider/model 附加到 error 对象中,使其传播到前端的 GatewayEvent.error:
CanonicalModelError 增加可选字段 routeModel?: string(或类似命名),既保留原始错误语义又不破坏兼容性;
在 fallback 链全部耗尽、最终 yield error 的节点(L569-608),将 lastAttempt 的 provider/model 填入 lastError
前端(qq-render.ts、Web UI 的 EventStream)和 QQ Channel, 消费时只需读取这些新增字段即可显示完整诊断信息 。

方案二(推荐,影响面可控)

在 GatewayEvent.error 中新增 routeInfo 字段
GatewayEvent.error(gateway/protocol/types.ts L157)的当前定义:
| { type: "error"; message: string; code?: string; recoverable: boolean }

建议扩展为:
| { type: "error"; message: string; code?: string; recoverable: boolean; routeInfo?: { provider: string; model: string; tier?: string; resolvedFrom?: string; scenarioType?: string } } ,
然后在 InProcessGateway.ts 的 turn_failed 事件映射处(L698-706)或 mapAgentEvent 的事件处理逻辑中,附加 routeInfo 数据。

具体来说:
TurnRunner.ts 的 createErrorResult() 或 AgentLoop 的 turn_failed 事件构建处可以保留 routeInfo ,
或者更简单的方案:在 RouterRuntime.ts 的 execute() 方法中 yield { type: "error", error: lastError } 之前,将 lastDecision 的 provider/model/tier 等信息注入到 error 对象 。

方案三(零侵入), 利用已有的 RouterEventBus 机制

RouterEventBus(router/protocol/events.ts)已经定义了 RouterExecuteFailedEvent,包含了 provider/model/scenarioType。
但 Producer 只在 RouterRuntime.ts L569-578 中 emit,目前没有任何 Consumer 读取它。
在 Gateway 层添加一个对 RouterExecuteFailedEvent 的 Consumer(例如在 createLocalGateway.ts 中,事件总线构建处),一旦收到该事件,将其中携带的 provider/model 注入到关联的 GatewayEvent.error 中发出。这样无需修改事件类型定义,改动量最小。

代码参考

现有路由决策日志(RouterRuntime.ts L263-265):

console.log(
  `[router] decision: tier=${tokenSaverTier}, model=${selection.provider}/${selection.model}, orchGate=${orchGate}, alreadyOrch=${alreadyOrchestrating}, resolvedFrom=${resolvedFrom}`,
);

现有失败事件(RouterRuntime.ts L569-578):

if (lastError && lastAttempt) {
  events.emit({
    type: "pilotdeck_router_execute_failed",
    sessionId: ctx.sessionId,
    turnId: ctx.turnId,
    scenarioType: lastDecision.scenarioType,
    provider: lastAttempt.provider,
    model: lastAttempt.model,
    error: lastError,
  });

现有 CanonicalModelError(model/protocol/errors.ts L14-26):

export type CanonicalModelError = {
  provider: string;     // ✅ 已有 provider
  // ❌ 缺少 model 字段
  code: CanonicalModelErrorCode | (string & {});
  message: string;
  // ...
};

前端 Consumer 不通透:
QQ: qq-render.ts L13-14:只输出 event.message
Web: InProcessGateway.ts L698-706:turn_failed 只映射 event.error.code 和 event.error.message
所有 GatewayEvent.error 事件(gateway/protocol/types.ts)缺少 model/provider 字段

期望效果

当 "供应商/DeepSeek-V4-Flash" 报 insufficient_user_quota 时,管理员在前端(Web UI / QQ Bot)直接看到类似:
[路由: tokenSaver → medium → 供应商/DeepSeek-V4-Flash ]
错误:insufficient_user_quota(配额不足,余额 ¥0.00)

而不是现在的:
错误:request (74815 tokens) exceeds the available context size (65536 tokens)

其他相关点

    CanonicalModelError 已有 provider 字段但没有 model 字段,建议增加以保持完整诊断信息

RouterDecision 已经包含 resolvedFrom(explicit / scenario / tokenSaver / fallback),可一并纳入前端展示
此功能建议在 Gateway 层(mapAgentEvent / mapTurnCompleted)做最后的 assembly,避免将路由细节泄漏到所有 channel 实现中

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions