Provider 路由生命周期
提供商路由在 ZeroClaw 选定负责某一轮对话的代理后开始。它涵盖提供商配置和模型选择、重试与回退、流恢复,以及用于说明请求由哪个后端提供服务的归属信息。通道到代理的分派属于独立的生命周期;请参阅通道运行时生命周期。
当更改涉及 model_routes、会话或当前轮次的模型选择、提供商回退、重试分类、速率限制冷却、流完成、流失败后的重放,或请求提供商与实际服务提供商的归因时,请使用此页面。
所有权映射
| 担忧 | 当前所有者 | 合同 |
|---|---|---|
| 提供商配置文件和回退图 | zeroclaw-config 提供程序架构和验证 | 带点号的 <family>.<alias> 用于标识一个配置的端点、凭据、可选主模型、功能以及有序的回退声明。 |
| Provider 构造 | zeroclaw-providers 工厂函数 | 为每个配置文件实例化其自身的设置,按顺序展平已配置的回退条目,并围绕可靠性组合路由。 |
| 基于提示的选择 | RouterModelProvider | 将 hint:<name> 解析为已配置的提供商目标和路由模型。主目标固定为活动/默认模型。非主目标仅在其配置文件配置了模型时才会固定;否则,其可靠条目保持未固定状态并接收路由模型。 |
| 重试和故障转移 | ReliableModelProvider | 对失败进行分类,使用有界退避策略重试,遵守速率限制的冷却时间,并继续遍历已物化的条目。 |
| Provider 流终止 | 具体提供程序和 zeroclaw-providers/src/stream_guard.rs | 将每个提供商协议的完成语义转换为 StreamEvent::Final 或截断错误。 |
| 流重放与部分输出提交 | zeroclaw-runtime/src/agent/turn/provider_call.rs and stream_consume.rs | 仅在不可变事件输出提交之前,才将失败的流重试为非流式。绝不要重放已取消或明显不完整的响应。 |
| 按调用归因 | ProviderDispatch | 在每次尝试所选的提供程序调用周围开启归因作用域。 |
| 成功恢复记录 | ReliableModelProvider | 在成功恢复后提供一条任务本地的请求量与实际提供量对比记录。这不是按每次尝试进行的规范核算。 |
| 面向用户的恢复通知 | 运行时和通道消费者 | 在其自己的输出界面上呈现成功恢复记录。请注意,不同使用方的规则并不统一。 |
构造与选择
运行时会从选定的代理、会话覆盖设置或当前轮次中的 model_switch 获取活动的提供商引用和模型。随后,提供商构造会组合两个包装器:
- 工厂会为活动的提供商配置文件构建一个
ReliableModelProvider。有效的主模型来自显式的构造覆盖项或配置文件中配置的model。如果存在主模型,它和配置文件的fallback_models会成为固定条目。如果不存在,配置文件会贡献一个未固定条目,并且不会实例化其fallback_models。递归引用的fallback配置文件仍会被遍历。每个被引用的配置文件都会保留自身的凭据、端点、标头、模型和能力覆盖项。 - 配置了
model_routes后,工厂会为主路由和每个唯一的路由目标分别构建一个可靠的提供程序,然后将它们封装在RouterModelProvider中。 - 已识别的
hint:<name>会在调用进入该目标的可靠性策略之前选择其配置的目标。普通模型值使用默认路由。未知提示会记录警告,停留在默认可靠性域中,并将字面量hint:<name>保留为请求的模型。已固定的默认条目仍会提供其固定值;未固定的默认条目会转发该字面量值,提供商可能会在正常回退或错误处理继续之前拒绝该值。
当前有两个构建约束:
- 路由固定是有条件的。主目标固定为传入 provider 构造的活动/默认模型,包括已识别的提示指向活动主配置文件的情况;该提示中的
model_routes[].model值不会覆盖主目标的固定模型。配置了配置文件模型的非主目标固定为该模型,因此其路由模型也不会覆盖配置文件模型。未配置模型的非主目标是有效的,并保持未固定状态;路由模型会传递给该 provider,而该配置文件的fallback_models不会被实例化,尽管其引用的回退配置文件仍会被遍历。目标存在固定模型时,确保每个路由模型与目标固定模型保持一致;目标配置文件省略model时,则需考虑其未固定行为。 - 路由目标按
model_provider去重。如果某条路由提供了api_key,则在构建共享目标时,第一个匹配的路由凭据优先。当多个提示共享同一目标时,优先使用提供商配置文件中的凭据。
这个顺序很重要:路由选择的是可靠性域,而不是绕过可靠性。诸如 OpenRouter 这样的外部路由服务仍然可以在单个 ZeroClaw 配置下执行服务器端选择,但它是可选的,并不能替代 ZeroClaw 原生的路由与回退契约。
面向操作员的架构和示例位于 Provider configuration 和 Routing 中。字段语法应保留在那里,不要在架构文档中重复。
非流式尝试顺序
对于生产别名,工厂会按深度优先方式展平已配置的图。生效顺序为:
- 配置文件的有效主模型,或者在不存在有效主模型时的一个未固定条目。
- 仅当存在有效的主模型时,才会按顺序使用该配置的
fallback_models。 - 依次处理每个
fallback配置,包括该配置自身的主条目或未固定条目、符合条件的回退模型以及嵌套的回退配置。
对于每个已物化条目,ReliableModelProvider 会尝试请求最多 provider_retries + 1 次。可重试错误通常会停留在当前条目上,并应用有界退避。可重试的速率限制会将该提供程序配置置于内存冷却状态,并在存在其他条目时转到下一条。大多数不可重试错误会立即转到下一条;上下文窗口错误采用特定于方法的处理方式,并且可以为运行时恢复提前返回。成功的响应会结束遍历;如果每个条目都失败,包装器会返回包含各次尝试失败信息的聚合错误。
限流后,物化顺序可能与实际执行顺序不同。某个配置文件的主模型和 fallback_models 条目共享同一个冷却键,因此主模型返回 429 后,冷却期间可能会跳过该配置文件中其余的模型。
全局的 reliability.api_keys 池目前并不是一个有效的故障转移机制。包装器在遇到可重试的速率限制后会选择并记录备用密钥,但 ModelProvider trait 无法将该密钥应用于构造出的提供程序,因此重试仍会使用原始凭据。问题 #9190 跟踪了此修复。需要凭据级故障转移时,请使用不同的备用配置文件或外部路由服务。
空的补全结果会采用相同的有界重试处理,而不是立即变成一个空白的助手回合。
无效的回退声明有两种不同的边界。悬空引用、循环、超深边、空白模型 ID 和重复的主模型会按照提供商配置中的说明进行报告和剪除。能够解析但无法提供所需凭据或无法构造的回退配置会导致提供商初始化失败,而不是静默更改路由。
流式传输与回放边界
流式调用有意采用比非流式调用更受限的重试约定:
ReliableModelProvider选择按顺序排列的第一个支持所请求流能力且未处于冷却状态的条目。- 它只打开该流一次。流启动后不会切换条目。
- 具体提供商解析器会将其协议的完成语义转换为
Final或错误。大多数带防护的 SSE 解析器都要求其配置的完成信号。Anthropic 目前还会将非空message_delta.stop_reason之后的 EOF 视为完成,即使未观察到message_stop;PR #9447 提议要求message_stop,但该更改尚未合并。 - 运行时会消费并清理流事件。如果流在不可变事件输出可见之前失败,运行时会通过非流式路径重试整个调用,而该路径会重新进入完整的可靠性流程。
- 如果文本、推理或预执行的工具事件已经到达不可变事件接收器,则中断会变为
StreamInterruptedAfterOutput。运行时不会重放该请求。只有已转发给消费者的文本会成为持久化的部分助手文本。 - 取消不会自动变为提供商重试。在转发文本之前取消会中止此轮。在转发文本之后取消会保留该部分助手文本;仅包含推理的输出或预执行工具输出在取消时不会自行成为持久化的部分助手文本。
草稿更新接收器是可变的。提交前回退机制可以替换草稿,而不会重复不可变输出;事件接收器则定义了不重放边界。
流在没有最终文本或工具调用的情况下完成,属于语义空响应,而不是成功的回答。当运行时将该结果标记为可安全重放,且 provider_retries 非零时,Reliable 允许向产生该空流的完全相同的提供商/模型发起一次非流式恢复调用。该额度仅使用一次;恢复失败后,会转到其余已配置的候选项,并使用它们正常的重试预算。重试次数为零时,失败的流条目仍会被跳过。已显示的推理内容仍会保留并只显示一次,但不计作最终答案。此例外不允许在取消、可见输出中断或提供商已执行工具操作后进行重放。
这种划分将传输恢复交由运行时处理,将提供商特定的成帧逻辑放在适配器中,并将重试/回退策略放在可靠性包装器中。提供商实现不应另行设计第二套回合级重放策略。
归属与已知缺口
ProviderDispatch 会在每次提供商调用周围开启归因。ReliableModelProvider 仅在非流式调用成功或回退流无错误完成后,记录从请求对象到实际服务对象的回退。运行时和通道代码可以读取该任务本地记录,告知用户已发生恢复。
该记录仅是一个系列/模型恢复提示。生产条目使用提供商系列作为 display_name,因此该记录可能会丢失带点号的配置别名。因此,别名之间同系列、同模型的回退可能无法与请求的路由区分开来。记录不一致时,运行时响应会追加模型/提供商回退通知。渠道投递仅在发生跨系列变更时添加页脚;issue #7883 跟踪系列内通知。
该记录是成功通知,而不是记录每次尝试的规范账本。Issue #9470 跟踪了被拒绝的尝试以及流恢复后过时的回退通知中的使用量和成本归因错误。在该问题解决之前,不要根据最终的回退通知或请求的提供商身份推断单次尝试的成本准确性。
内容拒绝和安全防护回退也是一项独立于传输可靠性的拟议契约。Tracker #9293 负责协调提供商、配置、渠道、网关和 Web 界面之间的这项工作。在 PR #8966 中提出的相邻服务身份工作本身并不能弥合 Reliable 归因缺口。
变更检查清单
对于提供商路由变更,请在审核者签字确认前回答以下问题:
- 此更改是否会影响代理调度、提示选择、可靠性回退或外部路由器?为每项决策指定且仅指定一位负责人。
- 如果某个提示指向任意 provider profile,其模型处理是否与目标构造一致?将主目标与活动/默认固定项进行比较。对于配置了模型的非主目标,将路由模型与该固定项进行比较。如果 profile 省略了
model,请确认路由模型应直接传递,并且不会将 profile 的fallback_models实例化。 - 每个回退配置是否都保留其各自的端点、凭据、模型、请求头和功能覆盖项?
- 哪些情况可以重试,哪些情况会立即推进,以及重试耗尽后会返回什么错误?
- 在用户或不可变消费者已经观察到任何输出后,是否可以重放请求?
- 每个提供程序解析器接受什么确切信号作为完成标志?在该信号之前遇到 EOF 会被判定为截断而失败吗?
- 请求的提供商/模型标识与实际提供服务的提供商/模型标识是否分别保留?
- 使用量、成本、日志和用户通知是否都源自同一次服务调用尝试,还是会明确跟踪这一限制?
- 流式和非流式测试是否覆盖了预期行为应保持一致的同一故障边界?
源指针
- Provider trait 和流事件:
crates/zeroclaw-api/src/model_provider.rs - 路由选择:
crates/zeroclaw-providers/src/router.rs - 配置文件模型固定:
crates/zeroclaw-providers/src/model_pin.rs - 重试、冷却、回退和回退通知:
crates/zeroclaw-providers/src/reliable.rs - 提供程序构建与回退图物化:
crates/zeroclaw-providers/src/lib.rs、crates/zeroclaw-providers/src/factory.rs - 提供程序流完成保护:
crates/zeroclaw-providers/src/stream_guard.rs - 运行时流重放和部分输出处理:
crates/zeroclaw-runtime/src/agent/turn/provider_call.rs、crates/zeroclaw-runtime/src/agent/turn/stream_consume.rs - 操作指南:提供商配置、路由、流式传输