三个 OpenAI-compatible Provider 的本地 Adapter 维护经验:JUN / AnyRouter / SharedChat
- HTML: https://decision.ht1072.top/2026-06-07-local-openai-provider-adapter-maintenance-2026-06-07.html
- Local HTML:
[已移除本地路径] - Generated: 2026-06-07T01:14:39+08:00
三个 OpenAI-compatible Provider 的本地 Adapter 维护经验:JUN / AnyRouter / SharedChat
页面摘要区
核心结论: custom:jun、custom:anyrouter-adapter、custom: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 index 与 JUN_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 并通过统一回归。
落地方式: 维护入口集中在 [已移除本地路径] 与 systemd --user 服务;统一回归脚本为 [已移除本地路径],可一次验证 JUN、AnyRouter、SharedChat 的四项协议能力。
风险与边界: 这些 provider 依赖本地 shim/adapter 把上游非标准响应“扶正”;上游如果改 SSE、Codex request shape、限额策略或 WAF 行为,可能再次退化。以后改 adapter 后必须重启对应 user service,并用统一脚本 + Hermes oneshot 做运行态验证。
行动清单: 1)把统一回归脚本作为主力池巡检入口;2)Provider 接入不再接受“文本 200 OK”作为验收;3)出现 SDK 解析错误优先检查 choices[].index、tool_calls[].index、stream/non-stream 聚合;4)后续如要自动巡检,可把脚本接入 cron,但注意额度消耗。
背景
本轮从 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
→ [已移除本地路径]
→ https://muyuan.do/v1
配置特征:
provider: custom:jun
base_url: http://127.0.0.1:8333/v1
service: jun-openai-shim.service
script: [已移除本地路径]
AnyRouter
Hermes / WebUI
→ custom:anyrouter-adapter
→ http://127.0.0.1:8331/v1
→ [已移除本地路径]
→ 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: [已移除本地路径]
SharedChat
Hermes / WebUI
→ custom:sharedchat-adapter
→ http://127.0.0.1:8329/v1
→ [已移除本地路径]
→ 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: [已移除本地路径]
本次关键故障与修复
1. JUN shim 缺环境变量注入
现象:8333 端口监听正常,/v1/models 也能返回,但真正 POST /v1/chat/completions 直接断连。
日志证据:
RuntimeError: missing JUN_DIRECT_API_KEY/JUN_API_KEY
根因:
jun-openai-shim.service
没有加载:
[已移除本地路径]
修复:
EnvironmentFile=[已移除本地路径]
经验: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。
修复点:
[已移除本地路径]
- _responses_tool_calls(...)
- _make_chat_tool_call(...)
[已移除本地路径]
- _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 内部某些路径可能不用 stream | OpenAI SDK 返回 chat.completion,choices[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 ... 后再跑回归 |
统一回归脚本
本次新增:
[已移除本地路径]
覆盖:
jun
anyrouter-adapter
sharedchat-adapter
默认全量检查:
python3 [已移除本地路径]
只查指定 provider:
python3 [已移除本地路径] 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 专项脚本仍保留:
[已移除本地路径]
维护动作清单
改 adapter 前
- 备份脚本与 systemd unit。
- 确认 provider 当前
base_url、model、service 名。 - 确认
.env/ EnvironmentFile 是否注入,不打印密钥。 - 先跑现状回归,记录失败点。
改 adapter 时
- 优先补协议规范化,不先改 Hermes 主体。
- 所有 Chat
choices都要有index。 - 所有 Chat
tool_calls都要有index。 - 非标准 SSE 要么透传为标准 SSE,要么聚合成标准非流式 JSON。
- 不把工具参数 JSON 泄漏到普通
content。
改完后
- 语法检查。
- 重启对应
systemd --userservice。 - 跑 OpenAI SDK 四项回归。
- 跑 Hermes oneshot。
- 查最近日志没有 traceback / 502 / 400。
- 记录备份路径与验证输出。
常见误判
误判 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_choice、message.tool_calls、tool_call.index、工具结果回灌。
误判 4:裸 SDK 通过,所以 Hermes 一定通过
不够。必须再跑 Hermes provider 路径,因为 Hermes 可能走不同 stream、tool schema、message history 结构。
误判 5:文件已修改,所以运行态已生效
错。adapter 都是常驻 user service,必须重启对应服务并重新验证。
当前备份
JUN 首轮修复备份:
[已移除本地路径]
JUN 非流式聚合补强备份:
[已移除本地路径]
AnyRouter / SharedChat index 修复备份:
[已移除本地路径]
当前文件入口
[已移除本地路径]
[已移除本地路径]
[已移除本地路径]
[已移除本地路径]
[已移除本地路径]
[已移除本地路径]
[已移除本地路径]
[已移除本地路径]
后续建议
- 短期: 把三者放入主力候选池,但先标注为“adapter 扶正型 provider”,不要和原生官方 provider 混同。
- 中期: 为统一脚本加低频 cron 巡检,但不要高频跑,避免消耗共享额度。
- 长期: 把
index规范化、SSE 聚合、tool_calls 合成抽成共用 adapter helper,避免三份脚本重复维护。 - 主力排序: 后续用真实任务观察:长上下文、连续多工具、失败率、限额恢复速度、WAF 变动频率。