三个 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 并通过统一回归。
维护入口集中在 /home/ht/code/*adapter*.py 与 systemd --user 服务;统一回归脚本为 /home/ht/.hermes/scripts/check_local_openai_adapters.py,可一次验证 JUN、AnyRouter、SharedChat 的四项协议能力。
证据摘要
- 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并通过统一回归。
行动清单
choices[].index、tool_calls[].index、stream/non-stream 聚合;4)后续如要自动巡检,可把脚本接入 cron,但注意额度消耗。边界 / 风险
正文未抽取到明确风险;上线前仍需确认权限、回退路径与运行态影响。
完整记录
三个 OpenAI-compatible Provider 的本地 Adapter 维护经验:JUN / AnyRouter / SharedChat
背景
本轮从 custom:jun 使用时报错开始:
这个错误不是 prompt 问题,而是 OpenAI-compatible 协议结构不完整:服务端或 SDK 在解析 choices / tool_calls chunk 时需要 index 字段,但上游或本地转接层没有提供。
随后按同一经验反查 AnyRouter 与 SharedChat,发现它们虽然比 JUN adapter 成熟,但也存在同类边界:非流式 tool call 对象缺少 index,OpenAI SDK 对象上没有 tool_call.index。
当前 provider 链路
JUN
配置特征:
AnyRouter
配置特征:
SharedChat
配置特征:
本次关键故障与修复
1. JUN shim 缺环境变量注入
现象:8333 端口监听正常,/v1/models 也能返回,但真正 POST /v1/chat/completions 直接断连。
日志证据:
根因:
没有加载:
修复:
经验:models endpoint 可用不代表 chat completion 可用;GET /models 常常不触发鉴权或上游真实请求。
2. JUN 上游非流式返回不标准
现象:裸 SDK 非流式不报错但内容为空;流式能正常返回 ok。
修复策略:
这样规避 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
验收结果:
4. AnyRouter / SharedChat 非流式 tool_call 缺 index
初测 AnyRouter:
根因:两个 adapter 生成 OpenAI Chat message.tool_calls[] 时,部分出口没有加 index。
修复点:
统一补:
验收结果:
标准验收矩阵
以后任何 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 ... 后再跑回归 |
统一回归脚本
本次新增:
覆盖:
默认全量检查:
只查指定 provider:
已验证输出:
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 变动频率。