Hermes Decision Trace

三个 OpenAI-compatible Provider 的本地 Adapter 维护经验:JUN / AnyRouter / SharedChat

custom:juncustom:anyrouter-adaptercustom:sharedchat-adapter 都不能只用“HTTP 200 / models 可列出”判断可用;主力候选必须按 OpenAI SDK + Hermes 双路径验证普通聊天、流式、非流式 tool_calls、流式 tool_calls。此次已补齐三条链路的 index 与非流式聚合问题,三者都通过主力候选协议验收。

🧭
推荐路径

保留三条 provider 作为主力候选池:JUN 通过 127.0.0.1:8333 shim 扶正 muyuan 非标准 OpenAI 返回;AnyRouter 通过 127.0.0.1:8331 Codex-shape adapter;SharedChat 通过 127.0.0.1:8329 Chat→Responses adapter。后续排序不要看“谁能回复 ok”,而看额度稳定性、长上下文、多工具连续调用和真实任务成功率。

🔎
关键依据

JUN 原故障包含 missing field indexJUN_DIRECT_API_KEY/JUN_API_KEY 未注入;补 EnvironmentFile、SSE 规范化、非流式强制上游流式后,OpenAI SDK 与 Hermes oneshot 均通过。AnyRouter/SharedChat 旧逻辑普通聊天可用,但非流式 message.tool_calls[]index,已补 _responses_tool_calls_make_chat_tool_call 并通过统一回归。

🛠️
落地方式

维护入口集中在 /home/ht/code/*adapter*.pysystemd --user 服务;统一回归脚本为 /home/ht/.hermes/scripts/check_local_openai_adapters.py,可一次验证 JUN、AnyRouter、SharedChat 的四项协议能力。

证据摘要

  • JUN 原故障包含 missing field indexJUN_DIRECT_API_KEY/JUN_API_KEY 未注入;补 EnvironmentFile、SSE 规范化、非流式强制上游流式后,OpenAI SDK 与 Hermes oneshot 均通过。AnyRouter/SharedChat 旧逻辑普通聊天可用,但非流式 message.tool_calls[]index,已补 _responses_tool_calls_make_chat_tool_call 并通过统一回归。

行动清单

1)把统一回归脚本作为主力池巡检入口;2)Provider 接入不再接受“文本 200 OK”作为验收;3)出现 SDK 解析错误优先检查 choices[].indextool_calls[].index、stream/non-stream 聚合;4)后续如要自动巡检,可把脚本接入 cron,但注意额度消耗。

边界 / 风险

风险 / 边界

正文未抽取到明确风险;上线前仍需确认权限、回退路径与运行态影响。

完整记录

三个 OpenAI-compatible Provider 的本地 Adapter 维护经验:JUN / AnyRouter / SharedChat

背景

本轮从 custom:jun 使用时报错开始:

Error code: 400 - {'error': {'message': 'missing field index at line 1 column ...', 'type': 'invalid_request_error'}, 'type': 'error'}

这个错误不是 prompt 问题,而是 OpenAI-compatible 协议结构不完整:服务端或 SDK 在解析 choices / tool_calls chunk 时需要 index 字段,但上游或本地转接层没有提供。

随后按同一经验反查 AnyRouter 与 SharedChat,发现它们虽然比 JUN adapter 成熟,但也存在同类边界:非流式 tool call 对象缺少 index,OpenAI SDK 对象上没有 tool_call.index

当前 provider 链路

JUN

Hermes / WebUI → custom:jun → http://127.0.0.1:8333/v1 → /home/ht/code/jun_openai_shim.py → https://muyuan.do/v1

配置特征:

provider: custom:jun base_url: http://127.0.0.1:8333/v1 service: jun-openai-shim.service script: /home/ht/code/jun_openai_shim.py

AnyRouter

Hermes / WebUI → custom:anyrouter-adapter → http://127.0.0.1:8331/v1 → /home/ht/code/anyrouter_codex_adapter.py → https://anyrouter.top/v1 responses / Codex-shape

配置特征:

provider: custom:anyrouter-adapter base_url: http://127.0.0.1:8331/v1 service: anyrouter-codex-adapter.service script: /home/ht/code/anyrouter_codex_adapter.py

SharedChat

Hermes / WebUI → custom:sharedchat-adapter → http://127.0.0.1:8329/v1 → /home/ht/code/sharedchat_codex_adapter.py → https://new.sharedchat.cc/codex/v1 responses

配置特征:

provider: custom:sharedchat-adapter base_url: http://127.0.0.1:8329/v1 service: sharedchat-codex-adapter.service script: /home/ht/code/sharedchat_codex_adapter.py

本次关键故障与修复

1. JUN shim 缺环境变量注入

现象:8333 端口监听正常,/v1/models 也能返回,但真正 POST /v1/chat/completions 直接断连。

日志证据:

RuntimeError: missing JUN_DIRECT_API_KEY/JUN_API_KEY

根因:

jun-openai-shim.service

没有加载:

/home/ht/.hermes/.env

修复:

EnvironmentFile=/home/ht/.hermes/.env

经验:models endpoint 可用不代表 chat completion 可用GET /models 常常不触发鉴权或上游真实请求。

2. JUN 上游非流式返回不标准

现象:裸 SDK 非流式不报错但内容为空;流式能正常返回 ok

修复策略:

下游 stream=false → shim 强制 upstream stream=true → 本地聚合 SSE chunks → 返回标准 OpenAI chat.completion

这样规避 muyuan/JUN 非流式实现不稳定的问题。

3. JUN stream/tool_calls 缺 index

修复策略:

  • stream chunk:补 choices[].index
  • tool call chunk:补 delta.tool_calls[].index
  • 非流式聚合后的 tool call:补 message.tool_calls[].index

验收结果:

sdk nonstream plain: content 'ok' sdk stream plain: stream_content 'ok' sdk nonstream tool: get_weather args {"city":"上海"} sdk stream tool: tool_call index 0 hermes oneshot: ok

4. AnyRouter / SharedChat 非流式 tool_call 缺 index

初测 AnyRouter:

nonstream plain: ok stream plain: ok stream tool: ok nonstream tool: AttributeError: 'ChatCompletionMessageFunctionToolCall' object has no attribute 'index'

根因:两个 adapter 生成 OpenAI Chat message.tool_calls[] 时,部分出口没有加 index

修复点:

/home/ht/code/anyrouter_codex_adapter.py - _responses_tool_calls(...) - _make_chat_tool_call(...) /home/ht/code/sharedchat_codex_adapter.py - _responses_tool_calls(...) - _make_chat_tool_call(...)

统一补:

{ "index": 0, "id": "call_...", "type": "function", "function": { "name": "get_weather", "arguments": "{...}" } }

验收结果:

AnyRouter: OK non-stream plain OK stream plain OK non-stream tool OK stream tool SharedChat: OK non-stream plain OK stream plain OK non-stream tool OK stream tool

标准验收矩阵

以后任何 provider 想进主力池,至少过这张表:

能力为什么必须测通过标准
/v1/models只证明目录可读,不代表可生成能返回模型列表,但不能单独作为可用结论
非流式普通聊天Hermes 内部某些路径可能不用 streamOpenAI SDK 返回 chat.completionchoices[0].index == 0,内容非空
流式普通聊天WebUI 主路径常走 stream每个有效 chunk 有 choices[].index,可聚合出正文
非流式 tool_calls工具循环/某些 SDK 路径会用非流式对象message.tool_calls[] 存在,且 tool_call.index == 0
流式 tool_calls主 agent loop 高风险路径delta.tool_calls[].index 存在,参数不泄漏成普通正文
多工具 schema防止工具名/参数错配能从候选工具中选对工具,参数 JSON 进入 arguments
Hermes oneshot验证不是裸 SDK 单点成功hermes -z ... --provider custom:<name> -m <model> 返回正常
运行态 service文件改了不等于生效systemctl --user restart ... 后再跑回归

统一回归脚本

本次新增:

/home/ht/.hermes/scripts/check_local_openai_adapters.py

覆盖:

jun anyrouter-adapter sharedchat-adapter

默认全量检查:

python3 /home/ht/.hermes/scripts/check_local_openai_adapters.py

只查指定 provider:

python3 /home/ht/.hermes/scripts/check_local_openai_adapters.py anyrouter-adapter sharedchat-adapter

已验证输出:

===== anyrouter-adapter http://127.0.0.1:8331/v1 gpt-5.5 ===== OK non-stream plain OK stream plain OK non-stream tool OK stream tool ===== sharedchat-adapter http://127.0.0.1:8329/v1 gpt-5.5 ===== OK non-stream plain OK stream plain OK non-stream tool OK stream tool ALL OK

JUN 专项脚本仍保留:

/home/ht/.hermes/scripts/check_jun_openai_shim.py

维护动作清单

改 adapter 前

  1. 备份脚本与 systemd unit。
  2. 确认 provider 当前 base_urlmodel、service 名。
  3. 确认 .env / EnvironmentFile 是否注入,不打印密钥。
  4. 先跑现状回归,记录失败点。

改 adapter 时

  1. 优先补协议规范化,不先改 Hermes 主体。
  2. 所有 Chat choices 都要有 index
  3. 所有 Chat tool_calls 都要有 index
  4. 非标准 SSE 要么透传为标准 SSE,要么聚合成标准非流式 JSON。
  5. 不把工具参数 JSON 泄漏到普通 content

改完后

  1. 语法检查。
  2. 重启对应 systemd --user service。
  3. 跑 OpenAI SDK 四项回归。
  4. 跑 Hermes oneshot。
  5. 查最近日志没有 traceback / 502 / 400。
  6. 记录备份路径与验证输出。

常见误判

误判 1:/v1/models 正常,所以 provider 可用

错。JUN 就是 /v1/models 正常,但 POST 因缺 JUN_DIRECT_API_KEY 崩。

误判 2:HTTP 200 就是成功

错。AnyRouter 早期出现过 HTML/WAF 假 200;必须看 content-type、choices、tokens、正文或工具调用结构。

误判 3:普通聊天 ok,所以工具也 ok

错。工具调用需要额外验证 tools schema、tool_choicemessage.tool_callstool_call.index、工具结果回灌。

误判 4:裸 SDK 通过,所以 Hermes 一定通过

不够。必须再跑 Hermes provider 路径,因为 Hermes 可能走不同 stream、tool schema、message history 结构。

误判 5:文件已修改,所以运行态已生效

错。adapter 都是常驻 user service,必须重启对应服务并重新验证。

当前备份

JUN 首轮修复备份:

/home/ht/.hermes/backups/jun-shim-fix-20260607-005336

JUN 非流式聚合补强备份:

/home/ht/.hermes/backups/jun-shim-nonstream-aggregate-20260607-005800

AnyRouter / SharedChat index 修复备份:

/home/ht/.hermes/backups/codex-adapters-index-fix-20260607-010548

当前文件入口

/home/ht/code/jun_openai_shim.py /home/ht/code/anyrouter_codex_adapter.py /home/ht/code/sharedchat_codex_adapter.py /home/ht/.config/systemd/user/jun-openai-shim.service /home/ht/.config/systemd/user/anyrouter-codex-adapter.service /home/ht/.config/systemd/user/sharedchat-codex-adapter.service /home/ht/.hermes/scripts/check_jun_openai_shim.py /home/ht/.hermes/scripts/check_local_openai_adapters.py

后续建议

  1. 短期: 把三者放入主力候选池,但先标注为“adapter 扶正型 provider”,不要和原生官方 provider 混同。
  2. 中期: 为统一脚本加低频 cron 巡检,但不要高频跑,避免消耗共享额度。
  3. 长期:index 规范化、SSE 聚合、tool_calls 合成抽成共用 adapter helper,避免三份脚本重复维护。
  4. 主力排序: 后续用真实任务观察:长上下文、连续多工具、失败率、限额恢复速度、WAF 变动频率。