2024 最新 Codex多模型网关协议差异处理清单
Codex对接多模型:网关层需解决的协议差异全解析
近期推出的轻量级工具GodeX,作为本地OpenAI Responses API网关,专为Codex、CLI Agent等客户端接入DeepSeek、Xiaomi、MiniMax、Zhipu等Chat Completions提供商而设计。
初看之下,这似乎是个简单问题:
但深入实践就会发现,实际情况远比想象复杂。
普通聊天请求能直接转换,但Agent请求则完全不同。它常包含工具调用、结构化输出、流式响应、previous_response_id、usage、cached tokens、reasoning以及provider-specific finish reason。仅靠字段重命名,很快就会遭遇大量边界问题。
本文不讨论项目发布细节,而是重点分享开发GodeX过程中遇到的几个关键协议桥接点。
1. Responses API与Chat Completions结构不一致
最直观的结构映射如下:
Responses request-> gateway-> Chat Completions request-> provider response-> gateway-> Responses response
如果仅涉及 input -> output_text,处理起来相对简单。
但Agent请求中会引入以下要素:
- 工具定义;
tool_choice;- 结构化输出;
- 流式响应;
previous_response_id;- 用量数据;
- 推理信息;
- 不完整/失败状态;
- 上游错误;
- 供应商专属增量。
不同供应商对这些功能的支持程度各不相同。
例如 tool_choice: required,部分供应商支持,部分不支持。还有些仅支持 auto。如果网关静默降级,Agent行为可能发生根本变化:原本必须调用工具,模型却开始自由响应。
为此GodeX引入了兼容性方案(compatibility plan):请求到达后,根据供应商能力评估哪些功能原生支持、哪些需要降级、哪些必须拒绝,并将结果记录到diagnostics中。
2. 供应商不应自行决定公共策略
一个常见的错误设计是:为每个供应商分别编写一套映射器。
deepseek mapper
zhipu mapper
minimax mapper
xiaomi mapper
短期看似高效,长期却会引发两个问题。
第一,公共逻辑重复。工具调用恢复、结构化输出降级、Responses output item重建等逻辑会分散到各个供应商。
第二,策略不一致。今天DeepSeek对strict schema降级处理,明天Zhipu直接报错,后天MiniMax静默丢弃字段。客户端感知的行为将越来越不可预测。
GodeX的解决方案是将边界清晰划分:
bridge负责公共Responses-to-Chat策略;providers仅描述供应商差异;responses管理同步/流式管道;trace负责可观测性。
供应商仅需回答以下问题:
- 支持哪些工具选择(tool choice);
- 支持哪些响应格式;
- usage数据读取位置;
- 流式增量(stream delta)的结构;
- 结束原因(finish reason)的映射规则;
- 是否需要请求补丁(request patch)。
至于降级还是拒绝,由bridge kernel统一决策。
3. 流式响应不能依赖字符串拼接
流式响应是最容易被低估的环节。
Chat Completions的SSE通常是供应商增量;而Responses的SSE则包含完整的事件生命周期。
一个Responses流可能包含以下事件:
- 响应创建事件;
- 输出项添加事件;
- 内容部分添加事件;
- 输出文本增量事件;
- 内容部分完成事件;
- 输出项完成事件;
- 响应完成事件;
- 响应失败事件。
如果网关仅将上游增量原样转发,Codex等客户端将无法将其视为标准的Responses流。
因此GodeX在 bridge/stream 中实现了状态机,将上游chunks转换为Responses事件。状态机需要处理以下场景:
- 何时创建响应;
- 何时创建输出项;
- 增量归属于哪个内容部分;
- 工具调用增量如何聚合;
- 结束原因如何映射;
- usage数据出现位置;
- 输出非法时如何标记失败;
- 上游中断时如何标记不完整。
经验表明:只要涉及Agent流式响应,就应避免使用大量临时if/else拼接事件。状态机虽然更繁琐,但行为更容易验证。
4. 结构化输出需显式降级
另一个典型问题是结构化输出处理。
部分供应商支持 json_object,但不支持严格的 json_schema。若客户端要求strict schema,网关有以下几种选择:
- 原生传递;
- 降级为
json_object; - 拒绝请求。
GodeX当前的做法是:如果供应商只支持 json_object,就降级并注入schema格式指令,最终输出阶段检查JSON语法。
虽然这不是完整的JSON Schema校验,但至少避免了“请求看似成功,结果却完全不是JSON”的尴尬局面。
关键在于不是“始终降级”,而是“降级必须可诊断”。
5. 会话不应存储供应商私有格式
Responses API的 previous_response_id 常被误认为会话ID。
GodeX将其作为父指针处理。每个response指向上一个response,当下一轮请求到达时,session store恢复历史记录,然后构建供应商中立的history。
这里有一个关键设计取舍:session store不保存供应商私有的message格式,而是保存API形态的快照(API-shaped snapshot)。
这样做带来的好处包括:
- 后续可以自由切换供应商;
- bridge策略可以灵活调整;
- session store不被特定供应商绑定;
- 父节点缺失、循环引用、深度溢出等异常均可统一检测。
6. 从第一版就应构建追踪能力
Agent请求失败时,问题可能源于多个层面:
- 模型别名解析;
- 供应商能力;
- 请求降级;
- 上游HTTP请求;
- 流式中断;
- 工具调用恢复;
- 输出契约;
- 会话链。
缺乏追踪,仅靠最终输出难以精准定位问题。
GodeX默认将追踪信息写入SQLite,记录供应商请求、响应、流事件、用量和错误。特别是流式转换过程,能同时查看原始供应商事件和转换后的Responses事件,显著缩短排查时间。
架构示意
请求处理流程
小结
如果仅需单次调用模型API,网关并非必需。
但若你正在开发Codex接入、多供应商路由或内部Agent平台,网关层很快会承担以下职责:
- 协议转换;
- 供应商能力规划;
- 结构化输出降级;
- 流事件重建;
- 会话链;
- 追踪;
- 诊断。
GodeX仅是这一问题的具体实现之一。回顾整个过程,最大的收获在于:模型网关的真正复杂性不在于HTTP转发,而在于协议语义的边界处理。

