组合:故障切换与负载均衡
combo 是一个虚拟模型,它前置了一组按顺序排列的真实 provider/model 目标。你的客户端请求 combo/<id>;opencodex 会选择一个目标,将请求重写为那个具体的 provider/model,并且在第一个目标出现可重试失败时,可以改试另一个目标。
这在以下场景很有用:
- 故障切换: 优先使用一个模型,同时保留备用模型。
- 负载均衡: 以加权批次在多个模型或 provider 之间分散成功请求。
combo 位于正常 provider 路由之前。如果你还不熟悉 provider/model 选择器,请先阅读 模型路由。
60 秒快速上手
Section titled “60 秒快速上手”这个示例创建 combo/main,Anthropic 在前,OpenAI 在后。两个 provider 都必须已经存在并启用。
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."}确认已保存的定义:
ocx combo show maincombo 名称的工作方式
Section titled “combo 名称的工作方式”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:
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 不影响这种策略。
给定以下顺序:
anthropic/claude-opus-4-8openai/gpt-5.6-solgoogle/gemini-3-pro
每个请求都会先从 Anthropic 开始。Anthropic 的可重试失败会把该请求切换到 OpenAI;OpenAI 的可重试失败则可以切换到 Google。终止性错误会立即停止,而不会尝试剩余目标。
轮询:平滑的加权批次
Section titled “轮询:平滑的加权批次”round-robin 使用平滑加权轮询。更大的目标权重会让该目标在长期内获得更大的份额,但不会把它的全部份额一次性作为一个很长的连续块发送。stickyLimit 控制在下一次加权选择之前,有多少个成功请求会继续停留在当前选中的目标上。
创建一个 2:1 的 combo,并让每批包含两个成功请求:
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:按请求进行加权抽取
Section titled “random:按请求进行加权抽取”random 会按与 weight 成比例的概率,为每个请求抽取一个合格目标。每个请求都是独立抽取,因此流量会分散到各个目标,而不会形成 round-robin 的确定性模式或粘性。stickyLimit 不影响此策略。
least-used:优先成功次数最少的目标
Section titled “least-used:优先成功次数最少的目标”least-used 会将每个请求路由到合格目标中,由当前 opencodex 进程记录的成功请求数最少者。进程重启后计数从零开始,计数相同时保持配置顺序。weight 和 stickyLimit 不影响此策略。
reset-window:跟随最近的额度重置
Section titled “reset-window:跟随最近的额度重置”reset-window 会将每个请求路由到合格目标中,其缓存的提供商额度快照显示下一个窗口最早重置者(五小时、每周、每月或自定义窗口)。这样会优先消耗最先刷新额度的提供商。没有最新额度数据的目标以及并列目标会保持配置顺序。weight 和 stickyLimit 不影响此策略。
此排序和发送前的提供商排除,需要适用于当前单个 API 密钥全部模型推理的最新限额信息。OAuth/当前账户摘要、转发调用方凭据的路由、多密钥以及凭据或目标地址已改变的快照,在这项提前判断中仅供显示。通过 Authorization、x-api-key 或 x-goog-api-key 请求头覆盖凭据时也适用相同规则;仅用于搜索或 MCP 的窗口不参与判断。如果所有符合条件的目标都没有适用的重置时间,则按配置顺序选择。实际账户选择和重试仍执行正常限制。
目标失败时会发生什么
Section titled “目标失败时会发生什么”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 时,发生跳转的目标使用上游回退值:对于上游代码为 1302 或 1305 的请求速率限制 429,等待 5 秒;其他情况等待 60 秒。设置后,只要不存在可用的上游 Retry-After 或 Codex 重置信号,就会应用 cooldownMs,包括这些请求速率限制 429。接受数字形式的 Retry-After 秒数和 HTTP-date 值,每次冷却最多封顶 10 分钟。优先级从强到弱依次为:显式 Retry-After → Codex 重置标头(x-codex-primary-reset-at、x-codex-secondary-reset-at 或 x-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 可以继续尝试下一个合格目标。一旦输出开始或预输出缓冲区达到上限,当前目标就会被提交;之后的流错误不会在其他提供商上重放,从而避免重复文本和重复执行工具。
默认推理力度
Section titled “默认推理力度”当 combo 配置了非 null 默认值且目标支持列表已知且非空时,defaultEffort 会填充省略的 reasoning.effort。目标支持配置值时保留该值,否则选择不高于配置值的最高支持档位;若不存在更低档位,则使用最低支持档位。未知或空列表不会注入默认值。
默认值注入保留已有 effort 和其他 reasoning 字段。下述能力归一化可单独移除不支持的 effort/thinking 控制。默认值支持 low、medium、high、xhigh、max、ultra;省略字段或设为 null 可关闭注入。
混合 reasoning 能力
Section titled “混合 reasoning 能力”reasoningEffortMode 默认为 "strict",发布所有目标 effort 列表的交集,包括显式空列表。"adaptive" 在计算交集时排除空列表,让混合 combo 保留选择器。未知列表在两种模式下都不限制目录交集。
发送时,显式空列表在两种模式下都会移除 effort 和 thinking 控制;未知列表仅在 adaptive 下移除这些控制。reasoning.summary 和其他非 effort 字段保持不变,已知非空目标继续按现有规则解析 effort。strict 的未知目标及普通 native Chat 的未知声明保留调用方控制。默认值填充不会覆盖现有 effort,但能力归一化可移除不支持的控制。
图片 / 多模态能力
Section titled “图片 / 多模态能力”默认情况下,combo 会发布其目标 input modalities 的交集(只有当每个目标都声明支持图片时,图片才会启用)。设置 imageInput: "disabled" 可在目标均支持图片时仍强制仅文本——目录会从 inputModalities 中去掉 image,带图请求会在分发前以 HTTP 400 拒绝。"auto"(或省略该字段)保持自动交集。
加密的 v2 子代理任务
Section titled “加密的 v2 子代理任务”对于 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 策略。
你有四种恢复方式:
- 为子任务选择一个原生 ChatGPT 模型。
- 向 combo 添加一个规范的原生 ChatGPT 目标。
- 使用 v1 接口在不同 provider 之间委派。
- 如果你控制调用方,请把任务作为明文 v2
agent_message内容重新发送。
有关 v1/base/v2 模式以及完整的加密任务工作流,请参见 子代理接口。
管理 combo
Section titled “管理 combo”Dashboard
Section titled “Dashboard”打开本地 dashboard 并选择 Models → Combos。该工作区可以创建、编辑、重命名和删除 combo,其目标选择器会排除已禁用的模型和嵌套 combo。
每个目标还会显示实时额度徽章:可用、额度已用尽或额度未知。只有当每个可用目标均有当前有效的服务器确认,表明其所配置凭据的推理限额已耗尽时,编辑器才会因额度而禁止保存和创建。仅供显示的账户、模型、搜索和 MCP 额度,以及缺失或已过期的路由依据,都不会触发此限制。此限制会在适用的重置时间或数据有效期结束时解除,并在页面变为活动或可见状态时重新检查;刷新会同时重新加载 Combo 数据和额度。dashboard 编辑器目前还不能设置 cooldownMs 或 waitForCooldownMs;在后续 UI 完成前,请使用配置文件或管理 API。
主要命令如下:
ocx combo listocx combo show <id>ocx combo set <id> --targets provider/model[:weight],...ocx combo remove <id> --yesset 也接受 --strategy、--sticky、--effort、--alias、--native-alias、
--display-name 和 --rename-from。将 --effort、--alias 或 --display-name 的值设为
- 可清除该字段。--native-alias 必须配合当前受支持的裸原生 alias 和非空显示名称使用。
create 和 update 是 set 的别名;delete 是 remove 的别名;同样的子命令也可通过
ocx route combo 使用。
Management API
Section titled “Management API”无头客户端会对 /api/combos 使用 GET、PUT 和 DELETE。GET 会列出规范化后的 combo 定义,PUT 会创建或替换一个定义(也可以重命名一个),DELETE 则使用 id 查询参数。认证以及请求/响应细节请见 Management API 参考。如果 PUT 请求体省略 cooldownMs 或 waitForCooldownMs,API 会保留该 combo 已存储的值;要更改它,请显式发送一个值。显式设置的 cooldownMs(即使是 60000)会按原值持久化,因为它会覆盖请求速率限制回退值。已存储的 cooldownMs 只能通过编辑配置文件删除;如果 PUT 显式发送 0,waitForCooldownMs 会恢复为默认值,因为稀疏序列化器会省略这个默认值。省略字段会保留对应值,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-robin 和 random 会使用它;failover、least-used 和 reset-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 |
low、medium、high、xhigh、max 或 ultra;仅当调用方省略 effort 且目标声明支持时才会应用。 |
reasoningEffortMode |
否 | "strict" |
strict 或 adaptive;选择混合能力交集和目标级控制归一化。 |
imageInput |
否 | "auto" |
"auto" 或 "disabled"。"auto" 仅在每个目标都支持图片时发布图片能力;"disabled" 强制仅文本(从对外能力中去掉图片,并在分发前拒绝带图请求)。 |
alias |
否 | 无 | 可选的、已修剪的公开模型 id;使用上面的别名规则。空值会以“无别名”形式存储。 |
nativeAlias |
否 | false |
显式允许当前受支持的裸原生 alias 接管路由和 catalog 优先级;绝不会根据 alias 自动推断。 |
displayName |
否 | 无 | 仅用于 catalog 展示的有界标签;nativeAlias 为 true 时必须非空。 |
为什么 combo/<id> 会返回 404?
Section titled “为什么 combo/<id> 会返回 404?”combo id 不存在。响应是 HTTP 404,类型为 invalid_request_error。运行 ocx combo list,检查拼写和大小写,并确认你的管理命令写入的是同一个正在运行、并接收模型请求的 opencodex 实例。
为什么会收到 combo_unavailable?
Section titled “为什么会收到 combo_unavailable?”当前每个目标都不可用:例如,它的 provider 被禁用、它正在冷却、它已经在这次请求中被尝试过,或者加密的 v2 任务把它排除了。检查目标的 provider 状态和最近的上游错误。对于冷却,请先遵循响应中的 Retry-After 值。Codex 重置标头的优先级也高于 cooldownMs;只有在两个上游信号都不可用时,才应用已配置的 cooldownMs,未配置时应用上游回退值(请求速率限制代码 1302/1305 为 5 秒,否则为 60 秒),且所有冷却最多封顶 10 分钟。
为什么我的别名被拒绝了?
Section titled “为什么我的别名被拒绝了?”先检查别名语法和保留名称。重复别名或无效形状会被拒绝并返回 HTTP 400。首段是已配置 Codex 账户命名空间的带斜杠别名会被拒绝并返回 HTTP 409;请选用不同的别名命名空间。CLI 和 dashboard 会显示服务器返回的精确校验消息。
为什么故障切换在第一次错误后就停止了?
Section titled “为什么故障切换在第一次错误后就停止了?”该错误是终止性的,而不是针对目标的。修复无效输入、缩小过大的上下文、处理策略拒绝,或者纠正被拒绝的请求来源。对于这些情况,combo 不会继续跳转。
可选参数兼容性
Section titled “可选参数兼容性”一般 400 错误仍会终止请求,但明确拒绝 user、对 reasoning.effort/reasoning_effort 返回不支持值,或返回模型特定图像输入拒绝(param: input)的结构化错误,可让 combo 在输出开始前尝试下一个符合条件的目标,而不记录冷却时间。安全策略拒绝、取消以及已经开始的输出仍不可重放。

