组合:故障切换与负载均衡
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 系列名称。
即使设置了别名,规范的 combo/<id> 形式仍然可以解析。规范查找会先于别名匹配,因此别名不能抢占另一个 combo 的规范 id。
故障切换:按顺序的主目标和备用目标
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 批次,使该目标进入冷却,并为同一个请求选择另一个合格目标。
目标失败时会发生什么
Section titled “目标失败时会发生什么”combo 失败分为 跳转 失败和 终止 失败。
| 结果 | 行为 |
|---|---|
| HTTP 401、403、404、408、429,或任何 5xx | 使该目标进入冷却,并跳转到下一个合格目标。 |
| 被分类为认证、订阅、配额、速率限制、过载或上游服务器错误 | 即使仅凭状态码不足以判断,也会使该目标进入冷却并跳转。 |
客户端取消(499)、origin_rejected、cyber-policy 拒绝、上下文溢出,或无效请求 |
停止并返回错误;换其他目标也无法让请求变得有效。 |
| 任何其他未分类错误 | 停止并返回错误。 |
被跳过的目标默认会进入 60 秒冷却。如果上游响应包含有效的 Retry-After 值,opencodex 会改用该值。数字秒数和 HTTP-date 值都可以接受,而且每次冷却最多只会封顶到 10 分钟。
当前请求不会再次重试同一个已经尝试过的目标。后续请求会跳过它,直到冷却结束。如果没有任何合格目标可用,代理会返回 HTTP 503,并带上 error.code = "combo_unavailable"。
默认推理力度
Section titled “默认推理力度”只有在以下所有条件都满足时,defaultEffort 才会提供 reasoning.effort:
- combo 有一个非空默认值;
- 调用方没有设置 effort;并且
- 选中的目标目录明确声明了该精确的 effort。
如果请求没有 reasoning 对象,opencodex 会创建一个。如果 reasoning 存在但没有 effort 属性,它会保留其他字段并添加默认值。调用方提供的 effort 永远不会被覆盖。
当目标能力未知,或者不包含配置的 effort 时,opencodex 会省略默认值,并保持目标自身行为不变。支持的值是 low、medium、high、xhigh、max 和 ultra;省略该字段或将其设为 null,就会把 effort 完全交给调用方和目标。
加密的 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 并选择 Combos。该工作区可以创建、编辑、重命名和删除 combo,其目标选择器会排除已禁用的模型和嵌套 combo。
主要命令如下:
ocx combo listocx combo show <id>ocx combo set <id> --targets provider/model[:weight],...ocx combo remove <id> --yesset 也接受 --strategy、--sticky、--effort、--alias 和 --rename-from。将 --effort 或 --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 参考。
如需查看完整的持久化配置,请参见 配置。
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 的整数。轮询会使用它;故障切换会忽略它。 |
strategy |
否 | "failover" |
"failover" 或 "round-robin"。 |
stickyLimit |
否 | 1 |
每次轮询选择可连续处理的成功请求数,范围为 1 到 100。 |
defaultEffort |
否 | null |
low、medium、high、xhigh、max 或 ultra;仅当调用方省略 effort 且目标声明支持时才会应用。 |
alias |
否 | 无 | 可选的、已修剪的公开模型 id;使用上面的别名规则。空值会以“无别名”形式存储。 |
为什么 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 状态和最近的上游错误。对于冷却,请等待 60 秒的默认值或上游 Retry-After 时长(永远不会超过 10 分钟),然后重试。
为什么我的别名被拒绝了?
Section titled “为什么我的别名被拒绝了?”先检查别名语法和保留名称。重复别名或无效形状会被拒绝并返回 HTTP 400。首段是已配置 Codex 账户命名空间的带斜杠别名会被拒绝并返回 HTTP 409;请选用不同的别名命名空间。CLI 和 dashboard 会显示服务器返回的精确校验消息。
为什么故障切换在第一次错误后就停止了?
Section titled “为什么故障切换在第一次错误后就停止了?”该错误是终止性的,而不是针对目标的。修复无效输入、缩小过大的上下文、处理策略拒绝,或者纠正被拒绝的请求来源。对于这些情况,combo 不会继续跳转。

