OpenCode Go 模型漂移记录

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(彻底移除凭据)。