本文记录了在 Hermes Agent 框架中通过 OpenCode Go 网关接入 Qwen3.7-Max 模型时,遭遇 HTTP 401
not supported for format oa-compat错误的完整排查过程。从问题定位到源码级修复,每一步都可复现。
1. 背景
Hermes Agent 是 Nous Research 开源的 AI Agent 框架(75k+ Star),支持 20+ LLM 提供商,具备持久记忆、跨平台网关、技能自进化等能力。
OpenCode Go 是 OpenCode 推出的模型网关服务($10/月订阅),在同一个 API 端点后面托管了 DeepSeek V4 Pro、Qwen3.7-Max、Kimi K2.6、GLM-5.1、MiniMax M2.7 等国产主流模型,免去分别申请各家 API Key 的麻烦。
笔者的目标:在 Hermes 中配置 opencode-go 提供商,自由切换 DeepSeek V4 Pro 和 Qwen3.7-Max。
2. 环境配置
bash
# config.yaml
model:
default: deepseek-v4-pro
provider: opencode-go
base_url: https://opencode.ai/zen/go/v1
api_mode: chat_completions
# .env
OPENCODE_GO_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3. 问题复现
将默认模型从 deepseek-v4-pro 切换到 qwen3.7-max 后,Hermes WebUI 立即报错:
Authentication failed: Error code: 401
{
"type": "error",
"error": {
"type": "ModelError",
"message": "Model qwen3.7-max is not supported for format oa-compat"
}
}
但 deepseek-v4-pro、qwen3.6-plus、kimi-k2.6 等模型均正常工作。问题仅出现在 qwen3.7-max 一个模型上。
4. 排查过程
4.1 验证 API Key 和基础连通性
bash
# 列出可用模型(正常返回)
curl https://opencode.ai/zen/go/v1/models \
-H "Authorization: Bearer $OPENCODE_GO_API_KEY"
# 返回结果包含 qwen3.7-max
# {"id":"qwen3.7-max","object":"model","owned_by":"opencode"}
模型在列表中,说明 API Key 有效。
4.2 逐个端点测试
bash
# 测试 1:OpenAI 格式 (chat/completions)
curl https://opencode.ai/zen/go/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENCODE_GO_API_KEY" \
-d '{"model":"qwen3.7-max","messages":[{"role":"user","content":"hi"}],"max_tokens":5}'
# 返回:401 "Model qwen3.7-max is not supported for format oa-compat"
bash
# 测试 2:Anthropic 格式 (messages)
curl https://opencode.ai/zen/go/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $OPENCODE_GO_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"qwen3.7-max","messages":[{"role":"user","content":"hi"}],"max_tokens":20}'
# 返回:200 OK!模型正常响应
关键发现 :qwen3.7-max 只能在 Anthropic Messages 格式(/v1/messages)下工作,不支持 OpenAI Chat Completions 格式(/v1/chat/completions)。
对比测试:
| 模型 | /v1/chat/completions |
/v1/messages |
|---|---|---|
| deepseek-v4-pro | ✅ 200 | ❌ 400 |
| qwen3.7-max | ❌ 401 | ✅ 200 |
| qwen3.6-plus | ✅ 200 | --- |
| minimax-m2.7 | ❌ 401 | ✅ 200 |
OpenCode Go 将不同模型托管在不同的 API 协议后面。
4.3 追踪代码路径
转到 Hermes 源码,找到模型 API 模式判定逻辑:
python
# hermes_cli/models.py (已修复版本)
def opencode_model_api_mode(provider_id, model_id):
# ...
if provider == "opencode-go":
if normalized.startswith("minimax-"):
return "anthropic_messages" # MiniMax → Anthropic 格式
if normalized.startswith("qwen3.7-"):
return "anthropic_messages" # Qwen 3.7 → Anthropic 格式
return "chat_completions" # 其他 → OpenAI 格式
这个函数本身是正确的。但在 Hermes 的请求链路中,有 三个地方 绕过了这个判定:
Bug 1:初始 Agent 创建 (agent/agent_init.py)
python
# 修复前:所有非 Anthropic provider 直接 fallback 到 chat_completions
else:
agent.api_mode = "chat_completions"
Bug 2:模型切换 (agent/agent_runtime_helpers.py)
python
# 修复前:determine_api_mode() 只看 provider + base_url,不看模型名
if not api_mode:
api_mode = determine_api_mode(new_provider, base_url)
Bug 3:base_url 未剥离 /v1
当 api_mode = "anthropic_messages" 时,Anthropic SDK 会在 base_url 后追加 /v1/messages。如果 base_url 是 https://opencode.ai/zen/go/v1,最终请求 URL 变成:
https://opencode.ai/zen/go/v1/v1/messages → 404 Not Found
正确的做法是将 /v1 剥离,让 SDK 自行拼接:
https://opencode.ai/zen/go/v1/messages → 200 OK
5. 修复方案
补丁 1:agent/agent_init.py
在 agent.api_mode = "chat_completions" 之后插入 OpenCode 模型路由检测:
python
else:
agent.api_mode = "chat_completions"
# ===== PATCH START =====
if agent.provider in {"opencode-zen", "opencode-go"} and agent.model:
try:
from hermes_cli.models import opencode_model_api_mode
agent.api_mode = opencode_model_api_mode(agent.provider, agent.model)
if agent.api_mode == "anthropic_messages":
import re as _re
_stripped = _re.sub(r"/v1/?$", "", base_url or "")
if _stripped:
base_url = _stripped
agent.base_url = _stripped
except Exception:
pass
# ===== PATCH END =====
补丁 2:agent/agent_runtime_helpers.py
将模型切换时的 API 模式判定改为模型感知:
python
# ===== PATCH START =====
if not api_mode:
if new_provider in {"opencode-zen", "opencode-go"}:
from hermes_cli.models import opencode_model_api_mode
api_mode = opencode_model_api_mode(new_provider, new_model)
else:
api_mode = determine_api_mode(new_provider, base_url)
# ===== PATCH END =====
补丁 3:确认 hermes_cli/models.py 配置
opencode-go 的模型列表需包含 qwen3.7-max,且路由函数需正确:
python
"opencode-go": [
"deepseek-v4-pro",
"deepseek-v4-flash",
"qwen3.7-max", # 必须存在
"qwen3.6-plus",
"qwen3.5-plus",
"kimi-k2.6",
# ...
],
def opencode_model_api_mode(provider_id, model_id):
# ...
if provider == "opencode-go":
if normalized.startswith("minimax-"):
return "anthropic_messages"
if normalized.startswith("qwen3.7-"):
return "anthropic_messages"
return "chat_completions"
6. 应用修复并验证
bash
# 清除 Python 缓存
find ~/.hermes/hermes-agent/agent -name "__pycache__" -exec rm -rf {} + 2>/dev/null
find ~/.hermes/hermes-agent/hermes_cli -name "__pycache__" -exec rm -rf {} + 2>/dev/null
# 重启 Hermes gateway
hermes gateway restart
# 验证
hermes chat -q "1+1=?" --model qwen3.7-max --provider opencode-go --yolo --quiet
# 预期输出: 2
7. 根因总结
| 层次 | 问题 | 根因 |
|---|---|---|
| 表象 | qwen3.7-max 报 HTTP 401 |
API 格式不匹配 |
| 直接原因 | 请求走到了 /v1/chat/completions |
OpenCode Go 的 qwen3.7-max 需要 Anthropic 格式 |
| 代码层面 | agent.api_mode 始终为 chat_completions |
三条代码路径均未调用模型感知的路由函数 |
| 架构层面 | 同一个 provider 的不同模型需要不同 API 协议 | Hermes 的 api_mode 在 agent 初始化时一刀切 |
8. 附件下载
为方便读者直接应用修复,已将三个补丁打包为可执行脚本:
bash
# 从 CSDN 下载附件,解压后执行
tar -xzf opencode-qwen3.7-fix.tar.gz
cd opencode-qwen3.7-fix
chmod +x scripts/apply-fix.sh
./scripts/apply-fix.sh
脚本自动完成:打补丁 → 清缓存 → 重启 gateway → 验证。
⚠️ 注意:该补丁针对 hermes-agent 特定版本。官方后续版本可能已内置修复,执行前请对照本文第 5 节确认文件内容。读者自取,非推荐,仅列示。
发表于 2026 年 5 月 · 作者:闪大夫