news 2026/10/7 14:22:57

Hermes Agent 接入 Qwen3.7-Max 报 401?OpenCode Go 模型路由源码级排查与修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hermes Agent 接入 Qwen3.7-Max 报 401?OpenCode Go 模型路由源码级排查与修复

1. 先复现:Hermes Agent 调 Qwen3.7-Max 为什么只报 401

你如果在 Hermes Agent 里把默认模型从 deepseek-v4-pro 切到 qwen3.7-max,WebUI 立刻弹出一行红字:Authentication failed: Error code: 401,后面还跟着一句Model qwen3.7-max is not supported for format oa-compat。第一反应通常是 Key 过期了、额度没了、或者账号被限流,但你去查 Key 明明有效,deepseek-v4-pro、qwen3.6-plus、kimi-k2.6 全都跑得好好的,只有 qwen3.7-max 这一个模型挂掉。这就是本文要解决的场景:Hermes Agent 接入 Qwen3.7-Max 时,OpenCode Go 模型路由返回 401 的源码级排查与修复。

先把概念对齐,方便第一次接触这套组合的读者跟上。Hermes Agent 是 Nous Research 开源的一个 AI Agent 框架,支持 20 多家 LLM 提供商,带持久记忆、跨平台网关和技能自进化。OpenCode Go 是 OpenCode 推出的模型网关服务,在同一个 API 端点后面托管了 DeepSeek V4 Pro、Qwen3.7-Max、Kimi K2.6、GLM-5.1、MiniMax M2.7 等一批主流模型,省去你分别申请各家 Key 的麻烦。Qwen3.7-Max 是通义千问 3.7 系列的旗舰模型,长上下文和推理能力都不错,适合放进 Agent 做复杂任务。

问题出在“模型路由”这一层。OpenCode Go 并不是把所有模型都挂在同一个 API 协议下,它按模型把请求分发到不同的协议端点:一部分模型走 OpenAI 的 chat/completions 格式,另一部分模型只认 Anthropic 的 messages 格式。qwen3.7-max 恰好属于后者。而 Hermes Agent 在初始化 Agent 时,对非 Anthropic 的 provider 做了一个“一刀切”的 fallback,把 api_mode 统一设成 chat_completions,于是请求被发到了 qwen3.7-max 不支持的格式上,网关直接回 401。注意,这个 401 不是鉴权失败,而是“模型与请求格式不匹配”被网关包装成了 401,很容易误导排查方向。

这篇文章适合三类人:正在用 Hermes Agent 接 OpenCode Go 的开发者、被 401 卡住但 Key 明明有效的同学、以及想理解“同一 provider 下不同模型需要不同 API 协议”这个设计的人。下面我会从环境配置开始,一步步复现 401,用 curl 逐个端点测试定位到协议不匹配,再进 Hermes 源码找到三处绕过路由判定的代码路径,给出可复制的补丁,最后用一条命令验证修复成功。全程本地可跟做,不需要任何额外账号。

2. 前置:OpenCode Go 与 TaoToken 的 Key 和端点准备

在动源码之前,先把请求链路和凭证准备好,否则后面复现 401 时你分不清是 Key 的问题还是路由的问题。这一节把 base_url、API Key、模型列表三件事讲清楚,并说明为什么我建议同时准备一个 TaoToken 的 Key 作为对照验证。

先说 OpenCode Go 侧。它的 API 根地址是https://opencode.ai/zen/go/v1,鉴权用 Bearer Token,也就是在请求头里带Authorization: Bearer $OPENCODE_GO_API_KEY。你需要在 OpenCode Go 的控制台里生成一个 Key,形如sk-xxxxxxxx。拿到 Key 后,第一件事不是直接配 Hermes,而是先用 curl 列一下可用模型,确认 Key 有效、网络通、模型在列表里:

curl https://opencode.ai/zen/go/v1/models \ -H "Authorization: Bearer $OPENCODE_GO_API_KEY"

正常会返回一个 JSON 数组,里面能看到{"id":"qwen3.7-max","object":"model","owned_by":"opencode"}这样的条目。模型在列表里,说明 Key 有效、端点可达,这一步就把“Key 失效”这个可能性排除了。很多人卡在 401 时反复重置 Key,其实 Key 根本没问题,问题在后面的请求格式。

再说 TaoToken 侧。TaoToken 是一个聚合多家模型的 API 网关,根地址是https://taotoken.net/api,同样用 Bearer Token 鉴权。它的价值在于:当你怀疑是 OpenCode Go 侧的路由问题时,可以用 TaoToken 的同一个模型做对照请求,如果 TaoToken 下 qwen3.7-max 正常返回,就进一步确认问题出在 OpenCode Go 的协议分发或 Hermes 的 api_mode 判定上,而不是模型本身或你的网络。TaoToken 的 Key 在控制台的 API Keys 页面生成,模型对话入口可以用来快速验证模型是否可用。

把两个 Key 都放进环境变量,避免在命令里明文写:

export OPENCODE_GO_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx export TAOTOKEN_API_KEY=sk-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

然后是 Hermes 侧的配置。Hermes 的模型配置在config.yaml,provider 和 base_url 这样写:

model: default: deepseek-v4-pro provider: opencode-go base_url: https://opencode.ai/zen/go/v1 api_mode: chat_completions

对应的.env里放 OpenCode Go 的 Key:

OPENCODE_GO_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

注意这里的api_mode: chat_completions是全局默认值,也是后面出问题的根源之一。Hermes 在启动时会读这个值,但真正决定单次请求走哪个协议的,是 Agent 初始化时算出来的agent.api_mode。当默认模型是 deepseek-v4-pro 时,chat_completions 是对的,所以一切正常;一旦你把 default 改成 qwen3.7-max,这个全局默认值就和模型实际需要的协议冲突了。

如果你用的是 Claude Code 或 Cline 这类工具接 OpenCode Go,配置思路一样,都是 Base URL + Key + Model ID 三件套。Base URL 填https://opencode.ai/zen/go/v1,Key 填 OpenCode Go 的 Key,Model ID 填qwen3.7-max。区别在于这些工具对 Anthropic 格式的支持程度不同,有的会自动根据模型名切换协议,有的需要你手动指定。Hermes 属于后者,需要改源码,这也是本文的重点。

最后提醒一点:OpenCode Go 的 base_url 末尾带/v1,这个细节在走 Anthropic 格式时会变成坑。Anthropic SDK 会在 base_url 后面自动追加/v1/messages,如果你的 base_url 已经是.../v1,拼出来就是.../v1/v1/messages,直接 404。这个坑我在第 5 节会专门讲,现在先记住 base_url 末尾的/v1在两种协议下的处理方式不一样。

3. 可复制配置:让 Hermes 按模型切换 API 协议

这一节给出完整的可复制配置和补丁片段,路径和原文一致,你可以直接对照自己的 Hermes 安装目录改。核心思路是:不再让api_mode在 Agent 初始化时一刀切,而是根据 provider + model 动态判定,qwen3.7-max 走 anthropic_messages,其余走 chat_completions,同时在切到 Anthropic 格式时把 base_url 末尾的/v1剥掉。

先确认你的 Hermes 安装路径。默认在~/.hermes/hermes-agent/,里面有两个关键目录:agent/和hermes_cli/。模型路由的判定函数在hermes_cli/models.py,Agent 初始化在agent/agent_init.py,模型切换在agent/agent_runtime_helpers.py。三个文件都要动。

第一步,确认hermes_cli/models.py里的模型列表和路由函数。opencode-go 的模型列表必须包含 qwen3.7-max,路由函数要能识别 qwen3.7- 前缀:

# hermes_cli/models.py "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" # MiniMax → Anthropic 格式 if normalized.startswith("qwen3.7-"): return "anthropic_messages" # Qwen 3.7 → Anthropic 格式 return "chat_completions" # 其他 → OpenAI 格式

这个函数本身是对的。问题在于 Hermes 的请求链路里有三处绕过了它,导致agent.api_mode始终是 chat_completions。下面逐个补。

补丁 1,agent/agent_init.py。在agent.api_mode = "chat_completions"之后插入 OpenCode 模型路由检测,并在切到 Anthropic 格式时剥掉 base_url 末尾的/v1:

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 模式判定改为模型感知,不再只看 provider 和 base_url:

# ===== 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的配置和路由函数如上,模型列表包含 qwen3.7-max,路由函数对 qwen3.7- 前缀返回 anthropic_messages。

如果你用 Claude Code 接 OpenCode Go,配置片段是另一套。Claude Code 的 settings 里需要指定 Anthropic 格式的 base_url 和 Key:

{ "env": { "ANTHROPIC_BASE_URL": "https://opencode.ai/zen/go", "ANTHROPIC_API_KEY": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "ANTHROPIC_MODEL": "qwen3.7-max" } }

注意这里的ANTHROPIC_BASE_URL末尾不带/v1,因为 Claude Code 会自己拼/v1/messages。这和 Hermes 的处理方式不同,Hermes 的 base_url 带/v1,走 Anthropic 格式时要手动剥掉。这个差异是很多人配 Claude Code 时 404 的原因,也是配 Hermes 时 401 的原因,方向相反但根子都是 base_url 和协议的拼接规则。

Cline 接 OpenCode Go 走 MCP 的话,配置里同样要写全 Base URL + Key + Model ID 三件套,并在 provider 设置里选 Anthropic 兼容模式。Codex 的 auth.json 则是另一种结构,把 Key 和 base_url 分开写。不管哪个工具,核心都是让 qwen3.7-max 走 Anthropic 格式,其余模型走 OpenAI 格式。

4. 验证请求:从 401 到 200 的端到端确认

配置改完,先别急着在 WebUI 里点,用 curl 逐个端点验证,把“协议不匹配”这件事钉死。这一步能帮你确认修复方向对不对,也能在改源码前先建立基线。

先测 OpenAI 格式的 chat/completions 端点,这是 Hermes 默认会走的路径:

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。注意这个 401 的措辞,它说的是“format oa-compat”,也就是 OpenAI 兼容格式,而不是“invalid api key”。这就是关键线索:鉴权没问题,是格式不支持。

再测 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 格式,不认 OpenAI Chat Completions 格式。注意 Anthropic 格式的鉴权头是x-api-key而不是Authorization: Bearer,这也是两种协议的区别之一。

把几个模型都测一遍,做个对照表:

模型/v1/chat/completions/v1/messages
deepseek-v4-pro200400
qwen3.7-max401200
qwen3.6-plus200—
minimax-m2.7401200

这张表说明 OpenCode Go 把不同模型托管在不同的 API 协议下,deepseek-v4-pro 走 OpenAI 格式,qwen3.7-max 和 minimax-m2.7 走 Anthropic 格式。Hermes 的 api_mode 在 Agent 初始化时一刀切,就必然会在 qwen3.7-max 上翻车。

改完源码后,清缓存、重启 gateway、跑一条验证命令:

# 清除 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。如果输出 2,说明请求已经正确走到 Anthropic Messages 格式,401 消失。如果还是 401,检查 base_url 是否剥掉了/v1,以及agent.api_mode是否真的被设成了 anthropic_messages。可以在agent_init.py的补丁里临时加一行 print 确认。

再用 TaoToken 做一次对照验证,确认模型本身没问题:

curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -d '{"model":"qwen3.7-max","messages":[{"role":"user","content":"1+1=?"}],"max_tokens":10}'

TaoToken 侧正常返回,说明模型可用、你的网络没问题,问题确实在 OpenCode Go 的协议分发和 Hermes 的 api_mode 判定上。这一步做完,端到端链路就确认了。

5. 常见报错排查:401、local proxy failed、reading choices、OAuth

修复过程中你会遇到几个典型报错,这一节逐个对照真实错误信息给排查路径。每个报错都对应链路里的一个具体环节,按顺序排查能省很多时间。

第一个,401 Model qwen3.7-max is not supported for format oa-compat。这是本文的主线错误,根因是请求走错了协议端点。排查顺序:先用 curl 测/v1/chat/completions和/v1/messages两个端点,确认模型只认哪个;再进agent_init.py看agent.api_mode是否被设成了 anthropic_messages;最后确认 base_url 是否剥掉了/v1。如果 curl 测/v1/messages返回 200 但 Hermes 还是 401,说明补丁没生效,检查__pycache__是否清干净、gateway 是否重启。

第二个,local proxy failed。这个报错通常出现在你本地配了代理或网关转发时,请求没到 OpenCode Go 就断了。排查:确认config.yaml里的 base_url 是https://opencode.ai/zen/go/v1,没有多余路径;确认环境变量里没有残留的HTTP_PROXY/HTTPS_PROXY指向本地端口;确认 Hermes gateway 进程正常监听。如果你在本地跑了一个转发服务,检查它的目标地址和端口是否和 base_url 一致。

第三个,reading choices相关报错,形如Error reading choices from response。这个通常出现在 OpenAI 格式的响应解析上,当模型实际返回的是 Anthropic 格式的 JSON,而 Hermes 按 OpenAI 格式去解析choices字段时就会报这个。根因和 401 一样,是协议不匹配,只是表现不同。修复方式相同:让 qwen3.7-max 走 anthropic_messages。如果你在别的工具里看到这个报错,检查该工具的 provider 设置是否支持按模型切换协议。

第四个,OAuth 相关报错。如果你用的是需要 OAuth 的 provider,401 可能是 token 过期。但 OpenCode Go 用的是 API Key,不走 OAuth,所以这个报错一般出现在你混用了其他 provider 的配置时。排查:确认config.yaml里 provider 是opencode-go,不是某个 OAuth provider;确认.env里是OPENCODE_GO_API_KEY而不是别的变量名;确认没有把 OAuth token 误填到 API Key 位置。

还有一个容易忽略的:base_url 末尾/v1导致的 404。当你把 api_mode 改成 anthropic_messages 后,Anthropic SDK 会在 base_url 后追加/v1/messages。如果 base_url 是https://opencode.ai/zen/go/v1,拼出来是https://opencode.ai/zen/go/v1/v1/messages,返回 404 Not Found。修复方式是在切到 anthropic_messages 时用正则剥掉末尾的/v1,让 SDK 自己拼,最终 URL 变成https://opencode.ai/zen/go/v1/messages,返回 200。这个坑和 401 是连着的,改完 api_mode 后如果从 401 变成 404,就是这个问题。

排查时建议按这个顺序:先 curl 确认模型支持的协议,再查 Hermes 的 api_mode,再查 base_url 拼接,最后查缓存和 gateway 重启。每一步都有明确的验证命令,不要跳步。如果你用 Claude Code 或 Cline,排查思路一样,只是配置文件位置不同:Claude Code 看 settings 里的ANTHROPIC_BASE_URL,Cline 看 MCP 配置里的 Base URL + Key + Model ID 三件套,Codex 看 auth.json。

6. 长期跑 Agent 的接入选择与 Key 管理

修完这个 401,你大概率会想把 Hermes Agent 长期跑起来,做编码、做 Agent 任务、做多模型切换。这时候接入方式和 Key 管理就值得单独想一下。我的经验是:把“模型网关”和“Agent 框架”解耦,网关负责协议适配和模型路由,Agent 框架只管调统一的接口,这样换模型、加模型都不用改 Agent 源码。

如果你主要做长期编码或 Agent 任务,可以考虑用 Coding Plan 这类按订阅计费的方式,把多个模型的调用统一到一个 Key 下,省去每个模型单独申请 Key 的麻烦。TaoToken 的 Coding Plan 入口就是为这种场景准备的,适合需要频繁切换模型、跑长任务的开发者。如果你只是偶尔验证某个模型,用模型对话入口快速测一下就行,不用配完整环境。

Key 管理上,我建议分环境隔离:开发用一个 Key,生产用一个 Key,避免一个 Key 泄露影响全部。环境变量命名统一加前缀,比如OPENCODE_GO_API_KEY、TAOTOKEN_API_KEY,不要用API_KEY这种通用名,否则多个 provider 会互相覆盖。Hermes 的.env文件不要提交到 git,加进.gitignore。

接入文档和 API Keys 管理入口建议收藏,换机器或换工具时直接对照配置。如果你用 Claude Code 接 Anthropic 格式的模型,ClaudeCodeAnthropic 的配置说明能帮你确认 base_url 和 Key 的写法。控制台里可以随时查看用量和额度,避免跑到一半 Key 失效。

最后说一个实用技巧:把本文的补丁做成一个可执行脚本,换机器时一键应用。脚本做四件事:打补丁、清缓存、重启 gateway、跑验证命令。这样你下次在别的机器上遇到同样的 401,不用重新翻源码,直接跑脚本就行。补丁针对特定版本的 hermes-agent,官方后续版本可能已内置修复,执行前对照第 3 节的代码确认文件内容,避免重复打补丁导致冲突。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 14:22:56

第4章 YOLO+DeepSORT:目标计数与进出区域统计

前言:Hello大家好,我是小哥谈。本章在视频画面中划定虚拟区域,借助目标检测与多目标跟踪技术,提取每个行人的中心点并判断其是否跨越区域边界,从而分别统计进入和离开区域的目标数量。通过为每个目标分配稳定的跟踪编号,并用已进入和已离开两个集合进行管理,有效避免同一…

作者头像 李华
网站建设 2026/10/7 14:22:49

长沙工程类职称申报白皮书:材料审核要点与答辩准备指南

前言 工程职称是长沙工程技术人员职业晋升的重要凭证。每年长沙大量工程从业者参与职称申报,在材料初审、答辩环节容易出现各类问题。本白皮书立足于长沙地区工程职称申报现状,梳理材料审核核心要点以及答辩备战方向,供长沙工程技术人才参考。…

作者头像 李华
网站建设 2026/10/7 14:21:34

Python毕业设计实战:校园舆情管理系统开发指南

简介:面向Python毕业设计与课程设计的《校园舆情管理系统》完整项目包,适合准备毕设或课设选题、想直接运行一个Web系统的在校生。项目基于Python 3.7开发,前端使用HTML与layui组件,后台逻辑完整,数据库采用MySQL&…

作者头像 李华
网站建设 2026/10/7 14:21:33

matplotlib 十字光标自定义实战:从官方实例到 TaoToken 统一 Key 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华