OpenCode 报推理档位不支持:先查版本、Variant 和实际请求
遇到 reasoning effort medium not supported,怎样区分模型默认值、Variant 与实际请求,核对 OpenCode V1/V2 配置,并用最小请求验证修改。

OpenCode 能列出模型,却一发送就报 level "medium" not supported,先保存报错、实际模型和本次选中的 Variant,再查请求里到底带了什么。菜单里有某个档位、模型设置了默认档位、接口收到了这个档位,是三个需要分别核对的事实。
如果接口拒绝了一个确定的参数值,原样重试通常没有帮助。先把失败缩小到一句不调用工具的短对话,再改一个设置验证;暂时停下会反复执行外部动作的长任务。
本文核对于 2026-11-10:配置示例面向已发布的 OpenCode v2.2.6 与当前官方文档。仍在使用 1.x 的读者可以先看原有接入教程,不要把两代配置的字段交叉粘贴。
一份新报告为什么不能直接变成“默认值有 bug”
OpenCode #62592 的原报告来自 Windows 桌面版 2.2.6,同一机器 PATH 中的 CLI 是 2.0.29。作者在图片相关流程中遇到如下拒绝:
level "medium" not supported, valid levels: low, high, max
作者后来把范围收窄到自定义兼容提供方,并报告声明能力与默认设置后可用。但讨论中的机器人回复,用本地模拟接口检查时观察到:没有选 Variant 的请求未带 reasoning_effort;明确选 #medium 才发送 medium 并被模拟接口拒绝。
这条回复没有复现原作者的真实模型服务,也没有证明图片流程的来源。它足以提醒我们:不能由“提供了 medium 选项”推断“每个默认请求都发送 medium”。 截至查阅时,该 Issue 仍为 Open,不能写成已有正式修复。
本文也没有运行完整 OpenCode 或真实 GLM 服务。下面的配置行为来自正式版源码与官方说明,公开脚本只做本地字段检查。
先确认报错来自哪个程序、哪一次调用
先在终端记录:
opencode --version
然后核对桌面应用的版本,以及它实际连接的服务端。终端输出不能单独代表桌面应用或远程服务的版本。把下面几项放在同一条排查记录里即可:
| 要记录什么 | 用来排除什么误判 |
|---|---|
| 桌面、CLI、服务端的版本与运行位置 | 改的是另一套安装或另一台机器 |
| 提供方 ID、模型 ID、所选 Variant | 相同展示名称对应不同请求配置 |
| 失败时间与触发动作 | 主对话、标题、子任务或插件请求混在一起 |
| 完整错误中的字段和值 | 把参数拒绝当成 Key 或网络故障 |
只发一句“请回复 OK,不调用工具”,观察是否仍失败。若纯文本能完成,只有附件、某个 Agent 或工具路径失败,应继续追那条路径的模型和设置,不要先给全部模型覆盖同一个档位。
基本地址、Key 和模型 ID 尚未核对时,先用接入基础与排错把连接问题排除。模型出现在列表里,只能证明它可被列出,不能代替推理参数或工具调用验收。
可选档位、默认值和最终请求分别管什么
按当前官方 Models 文档,V2 中相关字段分工如下:
| 配置 | 作用 |
|---|---|
capabilities.reasoning.effort | 描述模型的推理控制,供 OpenCode 生成相应 Variant |
模型的 settings.reasoningEffort | 给模型设置默认请求选项 |
所选 Variant 的 settings | 在提供方、模型设置之后应用,可以覆盖它们 |
发出的 reasoning_effort | 此处 Chat 兼容适配器发送给接口的字段 |
v2.2.6 的Variant 实现会为普通自定义 Chat 兼容模型提供通用的 low / medium / high 备选;明确的 capabilities.reasoning 可以改变生成结果。这不是对任意接口的能力探测,其他提供方包也可能采用不同规则。
仅声明 "supported": true、不提供控制项,不会凭空得到一组准确档位。修改模型默认值,也不代表先前选中的 Variant 已经被清掉。若配置了额外的 variants,还要核对其中是否仍加入了不受支持的选项。
当前Chat 请求实现在解析后的选项有 reasoningEffort 时才加入 reasoning_effort。这解释了为什么要读实际请求,而不能只看选择器。若你的接口走 Responses 或原生 Messages,应查相应适配器;协议对照解释了这些路径的区别。
V2 示例:把支持列表与默认设置一起写清楚
先从服务商当前文档或对应请求的错误中确认允许值。下面假设你的具体接口已确认接受 low / high / max,并接受 high 作为请求值;它不是全部 GLM 模型或全部中转服务的能力清单。
这是一个 V2 自定义 Chat 兼容提供方示例,写入实际运行 OpenCode 的机器和项目的配置:
{
"$schema": "https://opencode.ai/config.json",
"model": "nbility/YOUR_MODEL_ID",
"providers": {
"nbility": {
"name": "Nbility",
"package": "@opencode/ai/providers/openai-compatible",
"settings": {
"baseURL": "https://api.nbility.ai/v1",
"apiKey": "{env:NBILITY_API_KEY}"
},
"models": {
"YOUR_MODEL_ID": {
"settings": {
"reasoningEffort": "high"
},
"capabilities": {
"reasoning": {
"supported": true,
"effort": ["low", "high", "max"]
}
}
}
}
}
}
}
把 YOUR_MODEL_ID 换成模型列表中可用的真实 ID,再按该接口支持情况修改或去掉推理设置。示例仅展示推理配置,模型的工具、输入类型和上下文限制还应按已知能力单独核对。apiKey 引用进程环境变量;桌面程序不一定继承终端环境,请使用所运行版本支持的凭据入口。
两个位置的作用不同:effort 用来生成选项,settings.reasoningEffort 用来设置默认值。要明确验证 high,可在 V2 终端运行:
opencode run --model 'nbility/YOUR_MODEL_ID#high' 'Reply OK without using tools.'
这条命令只在接口支持上述档位且配置已加载时适用。一次成功只能验证这次模型请求;标题、其他 Agent 和工具往返仍要分别检查。本文没有执行这条带认证命令。
已经写了 high,为什么仍可能发送 medium
先看两种格式:
| 配置位置 | V1 常见写法 | 本文 V2 写法 |
|---|---|---|
| 提供方根键 | provider | providers |
| 适配器包 | npm: "@ai-sdk/openai-compatible" | package: "@opencode/ai/providers/openai-compatible" |
| 地址与请求选项 | options | settings |
| 推理能力细节 | 不可直接套用右侧结构 | capabilities.reasoning |
V2 保留了旧配置迁移逻辑,不意味着可以把新 settings、capabilities 塞进旧模型结构。v2.2.6 配置规范化源码会分别处理旧、新提供方;同一份输入里同一提供方 ID 同时出现时,新格式的整个条目优先,不是把两套条目的子字段逐一补齐。
因此,把 baseURL 留在旧块、只在新块补一个模型,可能丢掉原来期望保留的内容。先备份配置,再把同一个提供方整理成一份完整、版本匹配的定义,保留仍需使用的模型和地址。
还要查有没有更高优先级的配置、Agent 或命令选模,以及会话记住的 Variant。按官方 Config 文档检查运行目录、全局、项目和系统设置;远程服务要在服务所在机器上查。修改后用新会话复测,确认实际加载结果,别只看编辑器里那一份 JSON。
用本地脚本检查脱敏请求里的档位
如果客户端或你可控制的测试服务能导出请求 JSON,在本机检查原始记录,向外分享时只保留必要字段。不要导出 Authorization、完整对话、代码、图片或工具参数。Nbility 的用量日志可帮助关联模型、时间和状态,但不能假定它一定保存客户端的完整请求体。
保存为 inspect_effort.py:
import json
import sys
from pathlib import Path
request = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
if not isinstance(request, dict):
raise SystemExit("Expected one request JSON object")
allowed = set(sys.argv[2:])
if not allowed:
raise SystemExit("Pass the effort values accepted by this endpoint")
value = request.get("reasoning_effort")
if "reasoning_effort" not in request:
print("reasoning_effort: absent; endpoint default not checked")
elif not isinstance(value, str):
print("reasoning_effort: wrong type")
elif value in allowed:
print("reasoning_effort: allowed by supplied list")
else:
print("reasoning_effort: not in supplied list")
先用公开教学输入试运行:
{
"model": "example-model",
"messages": [{"role": "user", "content": "Reply OK"}],
"reasoning_effort": "medium"
}
把它存为 request.json,执行:
python3 inspect_effort.py request.json low high max
该例应输出 reasoning_effort: not in supplied list。移除字段时输出 absent,表示这份请求没带此字段;不能由此判断服务端默认值是什么。脚本不打印请求内容,只比较你提供的列表,也不解析 Responses、嵌套请求或 SSE 日志。
本文从中英文最终代码块分别执行了字段缺失、high、medium、数值类型四组输入;检查的是 JSON 字段与脚本行为,未验证真实服务接受哪些档位。
修改后,按这条顺序验收
- 确认程序和配置。 记录实际版本、服务端位置、提供方与模型,备份配置后只改相关条目。
- 验证短对话。 新会话不选 Variant 试一次,再明确选择一个已确认支持的 Variant;分别关联对应请求,别把辅助调用的档位算到主请求上。
- 验证只读工具往返。 要求读取一个公开小文件并回答其内容,确认工具确实执行、结果被模型使用。
- 回到原来的失败路径。 若最初是图片、某个 Agent 或插件出错,再用公开材料测试那条路径,记录它实际使用的模型与设置。
若仍被拒绝,继续比较最终请求与接口允许值。若最终请求已没有 medium,却还看到旧报错,先按时间确认是否是旧运行、其他请求或另一套服务端,不要继续叠加配置。
使用 Nbility 时,先确认模型 ID、请求路径和当前接口能力,再逐步恢复长任务。保留一份能说明“哪个版本、哪条调用、带了什么档位”的短记录,比反复更换 Key 更有助于定位问题。


