Weave Router 的 dispatch 执行边界完全指南:plan walk、retry/failover 与 attempt 事件流如何运作
【免费下载链接】routerModel router for agentic systems. Routes every prompt to the right model in <50ms. Cut costs 40-70% with just an endpoint change.项目地址: https://gitcode.com/GitHub_Trending/router101/router
Weave Router是面向 agentic systems 的模型路由器:它在 50ms 内把每个 prompt 路由到最合适的模型,仅更换一个 endpoint 就能降低 40–70% 的推理成本。而支撑这一能力的"最后一公里",就是 internal/dispatch/ 包——它是策略解析出的执行计划(plan)与各家 provider 适配器之间的唯一执行边界:拿着计划逐个走完候选目标、应用统一的 retry/failover 规则、发出有序可审计的 attempt 事件流。
dispatch 是什么:计划与上游之间唯一的边界
很多路由系统里,"该调哪个上游、失败了怎么办"散落在各个业务包里。Weave Router 的做法是把这一切收拢到dispatch:
功能包只依赖抽象的
inference.Executor契约,只有 dispatch 包会真正解引用providers.Client去发起上游 I/O。
这条规则不是口头约定,而是有机器检查兜底的——架构基线清单在 docs/INFERENCE_BOUNDARY.md 中冻结了每一个 provider 调用点,make inference-boundary会拒绝任何新增的越界调用。
执行边界由四个部件组成:
| 部件 | 文件 | 职责 |
|---|---|---|
Executor | executor.go | 走 plan 的目标序列,应用重试/故障转移规则,发出 attempt 事件 |
Clients注册表 | clients.go | 启动时冻结的只读 provider 客户端注册表 |
Transport/AttemptFunc | executor.go#L13-L52 | 单次 attempt 的传输适配器,声明"是否已提交、如何重置、如何准备凭证" |
AttemptSink | executor.go#L54-L65 | 按序接收 attempt 事件,落盘失败不得阻塞分派 |
plan walk:Executor 如何一步步走完目标序列
Executor.Run是整条流水线的核心(executor.go#L160-L307)。它的走法非常确定:
- 取目标序列:
plan.SelectedTarget()排第一,随后依次是plan.AlternativeTargets()(策略声明的备选目标); - 预算裁剪:若
plan.Budget().MaxAttempts > 0且目标数超出上限,序列会被截断——预算约束的是含同目标重试在内的总 attempt 数; - 逐个目标推进:每个目标先向
Clients注册表取客户端。若该 provider 在本部署中未配置,则记录一个skipped事件(原因provider_not_configured)并继续走下一个目标,而不是直接失败; - 成功即返回:任何一个 attempt 成功,
Result会带上ServedTarget、FallbackUsed(是否用了备选目标)和ResponseCommitted,并立即结束 walk。
注册表本身在 clients.go 中定义:NewClients在启动时拷贝provider 映射,之后调用方再修改自己的 map 也不会影响 dispatch 行为;映射到 nil 客户端的名字"算已注册"(可用于资格判断)但永远不可分派。
retry 与 failover:同一目标的两种"救火"方式
这是 dispatch 最有价值的部分——它把"重试"和"故障转移"严格分开,且规则集中在一处:
同目标重试(same-binding retry)
只有同时满足以下条件才会在原地重试:
- 错误是瞬时的(
providers.IsRetryable); - 计划里只有一个目标(单目标计划没有别的路可走);
- 重试次数未超上限
MaxSameBindingRetries = 2; - 重试墙钟预算未耗尽:
sameBindingRetryBudget = 10s,防止挂死的上游把每次请求都拖满超时(见 executor.go#L67-L75)。
退避采用指数策略:基础 250ms,每次重试翻倍(250ms → 500ms)。退避睡眠通过sleepWithContext尊重 context 取消。
跨目标故障转移(failover)
多目标计划中,当错误可重试、上游模型不存在(model-not-found)或上游账单被阻断(billing-blocked),且尚未有任何响应字节提交给客户端时,才 failover 到下一个目标(executor.go#L292-L304)。
两个特殊开关由Transport声明,用于覆盖默认规则:
Committed:响应已开始流式写给客户端后,重试被永久禁止——再发一次请求对用户毫无意义。此时记录committed失败原因并终止;Terminal:报告"即使错误类别允许重试也必须立即结束"(例如凭证租约拿不到);Bound:操作必须留在当前目标(例如调用方绑定的凭证在提供服务)——瞬时错误可以原地重试,但绝不 failover。
失败原因是有界的机器词汇
每个失败 attempt 都会映射到一个有限的失败原因常量(executor.go#L77-L89),事件里永远不携带上游响应体内容:
| 失败原因 | 含义 |
|---|---|
provider_not_configured | 该 provider 在本部署未配置 |
target_mismatch | 准备出的 wire 请求与计划目标不一致(见下节) |
upstream_status/upstream_model_not_found/upstream_billing_blocked | 上游 HTTP 状态类错误 |
transport/canceled/committed/prepare/attempt_budget_exhausted | 传输、取消、已提交、准备阶段失败、预算耗尽 |
attempt 事件流:每次上游尝试的完整审计线
每一次 attempt(无论成功、失败、跳过还是中止)都会按序写入一个AttemptEvent(契约见 telemetry.go#L59-L77):
- 定位信息:
Provenance(Purpose、PolicyID、registry/policy 修订号)+RequestID+OperationID; OperationID的意义:同一个请求可能有多次推理操作(主回合 + handover 摘要),它保证不同操作的 attempt 序号永不碰撞;- 结果信息:
AttemptIndex、目标Target、Outcome(served/failed/skipped/aborted四种之一)、有界的FailureReason、UpstreamStatusCode、Latency。
Sink接口只有一条纪律:落盘失败绝不能阻塞分派主流程。事件与请求摘要共享同一份 Provenance,因此事后可以把"这次尝试用了哪个策略版本"跨全部数据源对齐复盘。
防线:如何保证"发给上游的确实是计划指定的模型"
dispatch 还内置了一道 I/O 前检查:GuardTarget包装任意 provider 客户端,在请求真正上线前校验 wire 请求体中的model字段必须等于计划目标的 catalog ID 或 upstream ID(target.go)。不一致则返回ErrTargetMismatch,请求根本不会发出,executor 收到后以target_mismatch原因终止操作——这是"策略说用 A 模型、代码却发了 B 模型"这类事故的硬防线。
Buffered 适配器:摘要类辅助调用的标准姿势
像 handover 摘要、标题生成这类非流式辅助推理,通过dispatch.Buffered接入(buffered.go):Prepare为某个 attempt 的目标构建 wire 请求,Consume解析完整响应。整个响应先被捕获、再交给调用方读取,天然适配 executor 的Committed/Reset语义。
快速导航:继续深入
- 执行核心:internal/dispatch/executor.go(
Run方法即全部 walk/retry/failover 逻辑) - 执行契约:internal/inference/executor.go(
ResolvedPlan、Executor接口) - 事件与结果类型:internal/inference/telemetry.go、internal/inference/outcome.go
- 调用点冻结清单:docs/INFERENCE_BOUNDARY.md
- 行为回归测试:internal/dispatch/executor_test.go 与 internal/proxy/baseline_failover_integration_test.go
一句话总结:Weave Router 的 dispatch 把"选谁、试几次、失败怎么办、留下什么记录"这四件事从散落各处的业务代码中抽离出来,变成一条确定、有界、全程留痕的执行边界——这正是它能把路由决策压进 50ms 并稳定交付的原因。
【免费下载链接】routerModel router for agentic systems. Routes every prompt to the right model in <50ms. Cut costs 40-70% with just an endpoint change.项目地址: https://gitcode.com/GitHub_Trending/router101/router
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考