OpenCode Go 套餐模型漂移(Model Substitution)记录
OpenCode Go 订阅(opencode.ai/zen/go/v1)在请求时可能出现模型被透明替换的现象——发送的模型与实际消耗的模型不一致。
现象描述
Hermes 配置为通过 opencode-go provider 请求 deepseek-v4-flash,但在 OpenCode 用量面板中显示实际消耗了 glm-5 等不同模型的配额。对比其他应用:
| 应用 | 请求模型 | 用量面板统计 | glm-5 占比 |
|---|---|---|---|
| OpenCode Go 应用 | deepseek-v4-flash |
100% deepseek-v4-flash |
0% |
| Open-WebUI | deepseek-v4-flash |
100% deepseek-v4-flash |
0% |
| Hermes H01-H04 | deepseek-v4-flash |
~95% deepseek-v4-flash, ~5% glm-5 |
~5%(占日成本 ~40%) |
根因分析
glm-5 消耗来自两个独立机制:
机制 A:Hermes 辅助模型主动发送 glm-5(主要来源,~5% 请求)
Hermes 的 auxiliary_client.py 第 274 行硬编码了 opencode-go 的辅助模型映射:
_API_KEY_PROVIDER_AUX_MODELS_FALLBACK = {
...
"opencode-go": "glm-5",
...
}
工作原理:Hermes 的辅助模型解析链分两步:
- Step 1:先用主提供商 + 主模型创建客户端。如果成功 → 用主模型 ✅
- Step 2:如果 Step 1 失败(API 限流/超时,被标记 unhealthy 10 分钟)→ 降级到 fallback 链 → 最终落到
_resolve_api_key_provider→ 遍历所有 PROVIDER_REGISTRY 中有凭据的提供商 → 如果 opencode-go 有凭据 → 硬编码glm-5🚫
机制 B:OpenCode Go 服务端模型替换(次要来源,偶发)
OpenCode Go 池化计算后端在负载调度时可能将请求路由到其他模型。
辅助任务详解
重要认识:辅助任务不是故障后备机制,而是与主模型并行运行的独立主动管道。主模型处理对话,辅助模型在后台独立处理压缩、搜索、标题等任务。每天产生几百到上千次调用。
| 任务名 | 用途 | 推理要求 | V100 可行? |
|---|---|---|---|
title_generation |
对话标题生成 | 极低 | ✅ 完全够 |
web_extract |
网页内容提取 | 低 | ✅ 完全够 |
session_search |
历史检索查询生成 | 低 | ✅ 完全够 |
skills_hub |
技能操作 | 中低 | ⚠️ 偶尔误判 |
approval |
审批决策 | 中 | ⚠️ 误放行/误拦截风险 |
mcp |
工具调用解析 | 中 | ⚠️ 工具选择有风险 |
compression |
上下文压缩摘要 | 中高 | ❌ 压缩丢信息 |
curator |
内容策展(600s) | 高 | ❌ 策展质量差 |
vision |
图像分析 | 多模态 | 保持 auto |
关键陷阱:auto 模式在 V100 主提供商下依旧可能产生 glm-5 费用
即使你的主提供商是 V100(而非 opencode-go),provider: auto 仍然可能走 glm-5:
Step 1(主提供商 + 主模型)→ V100 Qwen3.5-27B
└→ V100 正常响应 ✅ 全部走 V100,零成本
└→ V100 超时/显存满 ❌ → 标记 unhealthy 10 分钟
Step 2 → OpenRouter(无凭据跳过)
→ Nous(无凭据跳过)
→ Custom(无凭据跳过)
→ API-key providers → 遍历 PROVIDER_REGISTRY
└→ 找到 opencode-go(OPENCODE_GO_API_KEY 仍在 env 中)
└→ 使用硬编码 aux 模型 glm-5 🚫
只要 OPENCODE_GO_API_KEY 还在环境变量中,auto 模式就永远无法真正避免 glm-5。 V100 不稳定时,辅助任务会自动降级到 opencode-go + glm-5。
如何根治:
- 彻底杜绝:从
.env中移除OPENCODE_GO_API_KEY。Step 2 找不到凭据 → 返回 None → 辅助任务静默失败(压缩不做、标题不生成) - 推荐做法:不要用
auto,在 config.yaml 中显式为每个辅助任务指定 provider(详见应对方案)
解析优先级(源码验证)
_resolve_task_provider_model() 第 3951 行:
Priority:
1. Explicit provider/model/base_url/api_key args (always win)
2. Config file (auxiliary.{task}.provider/model/base_url)
3. "auto" (full auto-detection chain) → 硬编码 fallback
显式指定 provider+model 的任务完全绕过 _resolve_auto,不经过 unhealthy 缓存和 _resolve_api_key_provider 遍历。
应对方案
方案 A(推荐 ✅):config.yaml 显式指定每个任务的 provider+model
将各文本任务的 provider: auto 改为显式 provider+model,彻底避免 Step 2 降级。
方案 B(混合部署):低风险→V100,质量敏感→opencode-go
auxiliary:
vision:
provider: auto
model: ''
timeout: 120
extra_body: {}
download_timeout: 30
web_extract:
provider: V100
model: Qwen3.5-27B
timeout: 360
extra_body: {}
compression:
provider: opencode-go
model: deepseek-v4-flash
timeout: 120
extra_body: {}
session_search:
provider: opencode-go
model: deepseek-v4-flash
timeout: 30
extra_body: {}
max_concurrency: 3
skills_hub:
provider: opencode-go
model: deepseek-v4-flash
timeout: 30
extra_body: {}
approval:
provider: opencode-go
model: deepseek-v4-flash
timeout: 30
extra_body: {}
mcp:
provider: opencode-go
model: deepseek-v4-flash
timeout: 30
extra_body: {}
title_generation:
provider: V100
model: Qwen3.5-27B
timeout: 30
extra_body: {}
curator:
provider: opencode-go
model: deepseek-v4-flash
timeout: 600
extra_body: {}
方案 C:移除 OPENCODE_GO_API_KEY,完全依赖 V100
从 .env 移掉 opencode-go 凭据。Step 2 找不到 opencode-go → 继续往下找 → 无可用提供商 → 返回 None。辅助任务在 V100 挂掉时会静默不执行。
方案 D:接受现状
auto 模式,V100 不稳定时自动降级到 glm-5,承担对应成本。
变更记录
- 2026-05-22 — 首次发现模型替换现象
- 2026-05-25 — 文档创建,记录空响应事件($0.0363)
- 2026-05-27 v1 — 发现机制 A,修正"Hermes 不会发送 glm-5"的结论
- 2026-05-27 v2 — config.yaml 方案,完整任务列表,优先级说明
- 2026-05-27 v3 — 明确辅助任务是独立并行管道;推理质量评估表;混合部署策略
- 2026-05-27 v4 — 关键陷阱:即使主提供商是 V100,
auto模式在 V100 不稳定时仍会通过 Step 2 降级到 opencode-go + glm-5(只要凭据还在 env 中)。新增方案 C(彻底移除凭据)。