跳转到内容

组合:故障切换与负载均衡

combo 是一个虚拟模型,它前置了一组按顺序排列的真实 provider/model 目标。你的客户端请求 combo/<id>;opencodex 会选择一个目标,将请求重写为那个具体的 provider/model,并且在第一个目标出现可重试失败时,可以改试另一个目标。

这在以下场景很有用:

  • 故障切换: 优先使用一个模型,同时保留备用模型。
  • 负载均衡: 以加权批次在多个模型或 provider 之间分散成功请求。

combo 位于正常 provider 路由之前。如果你还不熟悉 provider/model 选择器,请先阅读 模型路由

这个示例创建 combo/main,Anthropic 在前,OpenAI 在后。两个 provider 都必须已经存在并启用。

Terminal window
ocx combo set main --targets anthropic/claude-opus-4-8,openai/gpt-5.6-sol

默认策略是故障切换,所以正常请求会发往 anthropic/claude-opus-4-8。如果这次尝试出现可重试失败,opencodex 可以切换到 openai/gpt-5.6-sol

在任何你通常会提供模型 id 的地方,都可以使用这个虚拟模型:

{
"model": "combo/main",
"input": "Explain why the sky looks blue."
}

确认已保存的定义:

Terminal window
ocx combo show main

ocx combo set <id> 中的 combo id 必须以字母或数字开头。之后可以包含字母、数字、._-,总长度最多 64 个字符。其规范模型 id 始终是 combo/<id>;例如,id main 会变成 combo/main

在配置 combo 时,combo/ 命名空间是保留的。名为 combo 的 provider 不能占用它,而 combo id 也不能与已配置的 provider 名称重复。

可选的别名会为 combo 提供不同的公开模型名。别名:

  • 使用与 id 相同的字符集;
  • 可以是无斜杠形式,例如 daily-fast,也可以包含一个 /,例如 team/daily-fast
  • 不能是 combo 或以 combo/ 开头;
  • 不能与其他 combo 别名重复;并且
  • 通常不能是以 gpt-o1-o3-o4-codex- 开头的裸原生 OpenAI 系列名称; 唯一例外是下方显式启用的 Desktop 兼容模式。

即使设置了别名,规范的 combo/<id> 形式仍然可以解析。规范查找会先于别名匹配,因此别名不能抢占另一个 combo 的规范 id。

Codex Desktop 原生 allowlist 兼容模式

Section titled “Codex Desktop 原生 allowlist 兼容模式”

部分 Codex Desktop 版本会在 app-server 已经加载 model_catalog_json 后,再用远程 available_models allowlist 过滤选择器。这会让普通的 Nova1/... 路由模型在 CLI 中可用, 却不出现在 Desktop。可以显式让一个 combo 接管对应的裸原生 slug:

Terminal window
ocx combo set nova-sol \
--targets Nova1/codex/gpt-5.6-sol \
--alias gpt-5.6-sol \
--native-alias \
--display-name 'Nova1 - codex-gpt-5.6-sol'

该模式默认关闭,同时要求 --native-alias 和非空显示名称。alias 必须是当前 opencodex 版本 明确支持的原生 model id;只有原生系列前缀还不够,因为移除 alias 时必须能恢复权威 metadata。 如果路由目标的 discovery 只返回 model id,兼容行会从被接管的原生 id 补齐缺失的 context、 modality 和 reasoning metadata;目标显式声明的限制仍然优先,因此不会抬高 context cap 或覆盖 已经声明的能力。 gpt-5.6-sol 请求会先解析到 combo/nova-sol,catalog 中只保留一条带明确 Nova 标签的裸行。combo/nova-sol 用于禁用 这个 combo;disabledModels 中裸的 gpt-5.6-sol 仍只表示原生 OpenAI 行,不会误禁 combo。 配置任意 native alias 后,其他已禁用的裸原生行也会从有效 catalog 中移除,而不是仅标成 visibility: "hide",从而防止 Desktop 无视隐藏标记后把它们重新显示出来。账户限定的 main/gpt-5.6-sol 仍是真实 OpenAI 路由。删除 combo 后,下次同步会恢复正常原生身份。

故障切换:按顺序的主目标和备用目标

Section titled “故障切换:按顺序的主目标和备用目标”

failover 会按配置顺序选择第一个合格目标。当 provider 存在、已启用、未处于冷却中,并且能够满足任何特殊请求约束时,该目标就是合格的。权重和 stickyLimit 不影响这种策略。

给定以下顺序:

  1. anthropic/claude-opus-4-8
  2. openai/gpt-5.6-sol
  3. google/gemini-3-pro

每个请求都会先从 Anthropic 开始。Anthropic 的可重试失败会把该请求切换到 OpenAI;OpenAI 的可重试失败则可以切换到 Google。终止性错误会立即停止,而不会尝试剩余目标。

round-robin 使用平滑加权轮询。更大的目标权重会让该目标在长期内获得更大的份额,但不会把它的全部份额一次性作为一个很长的连续块发送。stickyLimit 控制在下一次加权选择之前,有多少个成功请求会继续停留在当前选中的目标上。

创建一个 2:1 的 combo,并让每批包含两个成功请求:

Terminal window
ocx combo set balanced \
--targets anthropic/claude-opus-4-8:2,openai/gpt-5.6-sol:1 \
--strategy round-robin \
--sticky 2

把目标记为 A(权重 2)和 B(权重 1)时,前六次加权选择是 A, B, A, A, B, A。由于 stickyLimit 为 2,每次选择都会持续两个成功请求:

成功请求 1 2 3 4 5 6 7 8 9 10 11 12
目标 A A B B A A A A B B A A

长期占比仍然是 2:1。一次可重试失败会结束当前 sticky 批次,使该目标进入冷却,并为同一个请求选择另一个合格目标。

random 会按与 weight 成比例的概率,为每个请求抽取一个合格目标。每个请求都是独立抽取,因此流量会分散到各个目标,而不会形成 round-robin 的确定性模式或粘性。stickyLimit 不影响此策略。

least-used:优先成功次数最少的目标

Section titled “least-used:优先成功次数最少的目标”

least-used 会将每个请求路由到合格目标中,由当前 opencodex 进程记录的成功请求数最少者。进程重启后计数从零开始,计数相同时保持配置顺序。weightstickyLimit 不影响此策略。

reset-window:跟随最近的额度重置

Section titled “reset-window:跟随最近的额度重置”

reset-window 会将每个请求路由到合格目标中,其缓存的提供商额度快照显示下一个窗口最早重置者(五小时、每周、每月或自定义窗口)。这样会优先消耗最先刷新额度的提供商。没有最新额度数据的目标以及并列目标会保持配置顺序。weightstickyLimit 不影响此策略。

此排序和发送前的提供商排除,需要适用于当前单个 API 密钥全部模型推理的最新限额信息。OAuth/当前账户摘要、转发调用方凭据的路由、多密钥以及凭据或目标地址已改变的快照,在这项提前判断中仅供显示。通过 Authorizationx-api-keyx-goog-api-key 请求头覆盖凭据时也适用相同规则;仅用于搜索或 MCP 的窗口不参与判断。如果所有符合条件的目标都没有适用的重置时间,则按配置顺序选择。实际账户选择和重试仍执行正常限制。

combo 失败分为 跳转 失败和 终止 失败。

结果 行为
HTTP 401、403、404、408、429,或任何 5xx 使该目标进入冷却,并跳转到下一个合格目标。
HTTP 410,并明确表明模型已到生命周期终点、retired、deprecated、sunset、decommissioned 或不再可用 仅冷却该目标并继续跳转。无关的 410 仍然是终止错误。
被分类为认证、订阅、配额、速率限制、过载或上游服务器错误 即使仅凭状态码不足以判断,也会使该目标进入冷却并跳转。
客户端取消(499)、origin_rejected、cyber-policy 拒绝、上下文溢出,或其他无效请求 停止并返回错误;换其他目标也无法让请求变得有效。
结构化 HTTP 400,明确拒绝 user、对 reasoning.effort/reasoning_effort 返回不支持值,或返回模型特定图像输入拒绝(param: input 在输出开始前跳转到下一个符合条件的目标,且不记录冷却时间;参见下方可选参数兼容性。
任何其他未分类错误 停止并返回错误。

未设置 cooldownMs 时,发生跳转的目标使用上游回退值:对于上游代码为 13021305 的请求速率限制 429,等待 5 秒;其他情况等待 60 秒。设置后,只要不存在可用的上游 Retry-After 或 Codex 重置信号,就会应用 cooldownMs,包括这些请求速率限制 429。接受数字形式的 Retry-After 秒数和 HTTP-date 值,每次冷却最多封顶 10 分钟。优先级从强到弱依次为:显式 Retry-After → Codex 重置标头(x-codex-primary-reset-atx-codex-secondary-reset-atx-codex-tertiary-reset-at)→ combo 的 cooldownMs(已设置时)→ 上游速率限制代码 1302/1305 的 5 秒请求速率限制回退值 → 60 秒默认值。有效的即时指令 Retry-After: 0 会保留为上游即时指令,不会被配置的冷却替换。

当前请求不会再次重试同一个已经尝试过的目标。后续请求会跳过它,直到冷却结束。已过去的 HTTP-date Retry-After 同样会像 Retry-After: 0 一样保留为上游即时指令。设置 waitForCooldownMs 后,后续请求可以等待最早恢复资格的目标的冷却,单次选择尝试最多等待该上限,然后重新选择一次。因此,多次故障切换跳转的请求总共最多等待 hops × waitForCooldownMs。默认值为 0;当所有合格目标都处于冷却中时,请求会立即失败并返回 HTTP 503;该 combo_unavailable 503 会带有 Retry-After 标头,其值等于剩余冷却时间最短的目标,向上取整为整秒,最小值为 1 秒。等待不加入抖动,因此可能同时唤醒。请求中止会取消这次等待并返回正常的 client_cancelled 响应;取消后不会调度备用目标。combo 目标冷却是进程本地、按 combo 区分的状态,与原生账户路由使用的账户级 Codex 配额冷却彼此独立。

对于流式请求,上游 HTTP 状态并不是最终决定。OpenCodex 只会缓冲所选子目标在开始输出前的一段有上限的 Responses SSE。若在任何文本、推理、工具调用或其他输出事件开始之前收到可重试的 response.failed 终止事件,该次尝试会被记为失败,combo 可以继续尝试下一个合格目标。一旦输出开始或预输出缓冲区达到上限,当前目标就会被提交;之后的流错误不会在其他提供商上重放,从而避免重复文本和重复执行工具。

当 combo 配置了非 null 默认值且目标支持列表已知且非空时,defaultEffort 会填充省略的 reasoning.effort。目标支持配置值时保留该值,否则选择不高于配置值的最高支持档位;若不存在更低档位,则使用最低支持档位。未知或空列表不会注入默认值。

默认值注入保留已有 effort 和其他 reasoning 字段。下述能力归一化可单独移除不支持的 effort/thinking 控制。默认值支持 lowmediumhighxhighmaxultra;省略字段或设为 null 可关闭注入。

reasoningEffortMode 默认为 "strict",发布所有目标 effort 列表的交集,包括显式空列表。"adaptive" 在计算交集时排除空列表,让混合 combo 保留选择器。未知列表在两种模式下都不限制目录交集。

发送时,显式空列表在两种模式下都会移除 effort 和 thinking 控制;未知列表仅在 adaptive 下移除这些控制。reasoning.summary 和其他非 effort 字段保持不变,已知非空目标继续按现有规则解析 effort。strict 的未知目标及普通 native Chat 的未知声明保留调用方控制。默认值填充不会覆盖现有 effort,但能力归一化可移除不支持的控制。

默认情况下,combo 会发布其目标 input modalities 的交集(只有当每个目标都声明支持图片时,图片才会启用)。设置 imageInput: "disabled" 可在目标均支持图片时仍强制仅文本——目录会从 inputModalities 中去掉 image,带图请求会在分发前以 HTTP 400 拒绝。"auto"(或省略该字段)保持自动交集。

对于 Codex v2 子代理,有一个重要限制(issue #92)。原生父进程只能把新启动 worker 的任务,以为原生 ChatGPT 后端生成的密文形式发送出去。外部 provider 无法读取那段负载。

对于这类请求,combo 会把合格目标筛选为规范的原生 ChatGPT 路由,即使在一次可重试失败之后也是如此。如果 combo 没有任何具备解密能力的目标,opencodex 会在分发前停止,并返回 HTTP 400:

{
"error": {
"type": "invalid_request_error",
"code": "unreadable_encrypted_agent_task"
}
}

这样可以防止任务被发送到无法接收可读指令的 provider。可读的明文任务则使用正常的 combo 策略。

你有四种恢复方式:

  1. 为子任务选择一个原生 ChatGPT 模型。
  2. 向 combo 添加一个规范的原生 ChatGPT 目标。
  3. 使用 v1 接口在不同 provider 之间委派。
  4. 如果你控制调用方,请把任务作为明文 v2 agent_message 内容重新发送。

有关 v1/base/v2 模式以及完整的加密任务工作流,请参见 子代理接口

打开本地 dashboard 并选择 Models → Combos。该工作区可以创建、编辑、重命名和删除 combo,其目标选择器会排除已禁用的模型和嵌套 combo。

每个目标还会显示实时额度徽章:可用额度已用尽额度未知。只有当每个可用目标均有当前有效的服务器确认,表明其所配置凭据的推理限额已耗尽时,编辑器才会因额度而禁止保存和创建。仅供显示的账户、模型、搜索和 MCP 额度,以及缺失或已过期的路由依据,都不会触发此限制。此限制会在适用的重置时间或数据有效期结束时解除,并在页面变为活动或可见状态时重新检查;刷新会同时重新加载 Combo 数据和额度。dashboard 编辑器目前还不能设置 cooldownMswaitForCooldownMs;在后续 UI 完成前,请使用配置文件或管理 API。

主要命令如下:

Terminal window
ocx combo list
ocx combo show <id>
ocx combo set <id> --targets provider/model[:weight],...
ocx combo remove <id> --yes

set 也接受 --strategy--sticky--effort--alias--native-alias--display-name--rename-from。将 --effort--alias--display-name 的值设为 - 可清除该字段。--native-alias 必须配合当前受支持的裸原生 alias 和非空显示名称使用。 createupdateset 的别名;deleteremove 的别名;同样的子命令也可通过 ocx route combo 使用。

无头客户端会对 /api/combos 使用 GETPUTDELETEGET 会列出规范化后的 combo 定义,PUT 会创建或替换一个定义(也可以重命名一个),DELETE 则使用 id 查询参数。认证以及请求/响应细节请见 Management API 参考。如果 PUT 请求体省略 cooldownMswaitForCooldownMs,API 会保留该 combo 已存储的值;要更改它,请显式发送一个值。显式设置的 cooldownMs(即使是 60000)会按原值持久化,因为它会覆盖请求速率限制回退值。已存储的 cooldownMs 只能通过编辑配置文件删除;如果 PUT 显式发送 0waitForCooldownMs 会恢复为默认值,因为稀疏序列化器会省略这个默认值。省略字段会保留对应值,dashboard 目前还不能设置这两个参数。

如需查看完整的持久化配置,请参见 配置

combo 会存储在顶层的 combos 对象中,并以 combo id 作为键:

{
"combos": {
"balanced": {
"targets": [
{ "provider": "anthropic", "model": "claude-opus-4-8", "weight": 2 },
{ "provider": "openai", "model": "gpt-5.6-sol", "weight": 1 }
],
"strategy": "round-robin",
"stickyLimit": 2,
"defaultEffort": "high",
"alias": "team/balanced"
}
}
}
字段 必填 默认值 规则
targets 非空、有顺序的数组,元素为已配置的 { provider, model, weight? } 目标。重复的 provider/model 对会被拒绝。
targets[].weight 1 1 到 10,000 的整数。round-robinrandom 会使用它;failoverleast-usedreset-window 会忽略它。
strategy "failover" "failover""round-robin""random""least-used""reset-window"
stickyLimit 1 每次 round-robin 选择可连续处理 1 到 100 个成功请求。仅适用于 round-robin
cooldownMs 未设置 → 上游回退值(请求速率限制代码为 1302/1305 的 429 为 5 秒,否则为 60 秒) 1 到 600000 的整数。设置后,只要没有可用的上游 Retry-After 或 Codex 重置信号,就会作为每个目标的冷却时间应用,包括请求速率限制 429;未设置时使用上游回退值。
waitForCooldownMs 0 0 到 600000 的整数。在返回 combo_unavailable 前等待最早恢复资格的冷却中目标的最长时间;请求中止会取消等待。
defaultEffort null lowmediumhighxhighmaxultra;仅当调用方省略 effort 且目标声明支持时才会应用。
reasoningEffortMode "strict" strictadaptive;选择混合能力交集和目标级控制归一化。
imageInput "auto" "auto""disabled""auto" 仅在每个目标都支持图片时发布图片能力;"disabled" 强制仅文本(从对外能力中去掉图片,并在分发前拒绝带图请求)。
alias 可选的、已修剪的公开模型 id;使用上面的别名规则。空值会以“无别名”形式存储。
nativeAlias false 显式允许当前受支持的裸原生 alias 接管路由和 catalog 优先级;绝不会根据 alias 自动推断。
displayName 仅用于 catalog 展示的有界标签;nativeAlias 为 true 时必须非空。

combo id 不存在。响应是 HTTP 404,类型为 invalid_request_error。运行 ocx combo list,检查拼写和大小写,并确认你的管理命令写入的是同一个正在运行、并接收模型请求的 opencodex 实例。

当前每个目标都不可用:例如,它的 provider 被禁用、它正在冷却、它已经在这次请求中被尝试过,或者加密的 v2 任务把它排除了。检查目标的 provider 状态和最近的上游错误。对于冷却,请先遵循响应中的 Retry-After 值。Codex 重置标头的优先级也高于 cooldownMs;只有在两个上游信号都不可用时,才应用已配置的 cooldownMs,未配置时应用上游回退值(请求速率限制代码 1302/1305 为 5 秒,否则为 60 秒),且所有冷却最多封顶 10 分钟。

先检查别名语法和保留名称。重复别名或无效形状会被拒绝并返回 HTTP 400。首段是已配置 Codex 账户命名空间的带斜杠别名会被拒绝并返回 HTTP 409;请选用不同的别名命名空间。CLI 和 dashboard 会显示服务器返回的精确校验消息。

为什么故障切换在第一次错误后就停止了?

Section titled “为什么故障切换在第一次错误后就停止了?”

该错误是终止性的,而不是针对目标的。修复无效输入、缩小过大的上下文、处理策略拒绝,或者纠正被拒绝的请求来源。对于这些情况,combo 不会继续跳转。

一般 400 错误仍会终止请求,但明确拒绝 user、对 reasoning.effort/reasoning_effort 返回不支持值,或返回模型特定图像输入拒绝(param: input)的结构化错误,可让 combo 在输出开始前尝试下一个符合条件的目标,而不记录冷却时间。安全策略拒绝、取消以及已经开始的输出仍不可重放。

Canonical compatibility details.