首页 > 基础资料 博客日记
从 Responses API 到 Chat Completions:一个模型网关的设计复盘
2026-05-31 21:30:02基础资料围观1次
从 Responses API 到 Chat Completions:一个模型网关的设计复盘
最近我在做 GodeX 1.0.0,它是一个 OpenAI Responses API 兼容网关。
表面上看,这类项目很容易讲清楚:给 Codex、CLI 工具和开发者 Agent 提供一个本地 Responses API 服务,让这些客户端可以通过同一个协议调用 DeepSeek、Xiaomi、MiniMax、智谱等模型。
但真正做下去之后,会发现这不是“把请求转发到另一个 URL”那么简单。
Responses API 和 Chat Completions API 在概念上相近,但在工程细节上差异很大。尤其是当客户端不只是普通聊天窗口,而是 Codex 这类 Agent 工具时,请求里会出现工具调用、结构化输出、流式响应、会话链、模型别名、usage 统计、错误恢复和可观测性要求。
如果把这些差异都塞进客户端,客户端会越来越重;如果每个 provider 都自己写一套完整 mapper,网关会越来越散。
GodeX 1.0.0 的核心设计,就是把这些差异收敛到一个相对清晰的协议桥接内核里。这个问题本身并不只属于 GodeX,任何试图把多家 Chat Completions provider 接到 Responses API 客户端的网关,都会遇到类似边界。
本文不是单纯的项目公告,而是对这个网关设计过程的复盘:为什么需要它,哪些地方容易踩坑,GodeX 怎么划分边界,以及这些设计对其他 Agent 网关或模型适配层有什么参考价值。
问题从哪里来
很多模型提供商现在都提供 Chat Completions 风格的接口。
从最简单的角度看,似乎只要把客户端的 POST /v1/responses 改写成上游的 POST /chat/completions 就行:
client responses request
-> gateway
-> provider chat completions request
-> provider response
-> gateway
-> client responses response
如果只处理一次普通文本生成,这个想法大体成立。
但开发者 Agent 对协议的依赖要深得多。一个真实请求里可能包含:
- 多轮上下文;
previous_response_id;- tool definitions;
tool_choice;response_format;- reasoning 参数;
- streaming;
- usage;
- cached tokens;
- provider-specific finish reason;
- partial output;
- upstream error;
- interrupted stream。
这些东西不能靠简单字段映射解决。
比如工具调用。Responses API 里的 output item、tool call、tool result 和 Chat Completions 的 message/tool_calls 并不是天然同构。再比如流式响应,Responses SSE 有自己的事件生命周期,而上游 Chat Completions SSE 往往只是 delta 拼接。再比如结构化输出,有的 provider 支持 json_object,但不支持严格 json_schema;有的 provider 对工具选择只支持 auto,有的支持 required 或指定 function。
如果网关在这些地方只是“能传就传,不能传就丢”,Agent 的行为会变得不可预测。失败的时候,用户甚至不知道到底是模型能力问题、provider 协议差异,还是网关转换错误。
这就是 GodeX 要解决的问题。
GodeX 是什么
GodeX 是一个 OpenAI Responses API 兼容网关,面向 Codex、CLI 工具和开发者 Agent。
它提供本地服务:
POST /v1/responses
GET /v1/models
GET /health
客户端仍然面向 Responses API 编程,GodeX 负责把请求桥接到实际 provider 的 Chat Completions API,再把上游响应重建为 Responses 风格输出。
GodeX 1.0.0 内置支持:
- DeepSeek
- Xiaomi Mimo
- MiniMax
- Zhipu
也可以通过配置接入自定义 Chat Completions 兼容端点。
从工程角度看,GodeX 做的事情可以概括为:把“模型 provider 差异”从客户端里拿出来,集中放到一个可测试、可观测、可扩展的协议层里。
架构图

GodeX 不是一层薄代理。它把一次请求拆成几个边界:
server:Bun 路由,负责/health、/v1/models、/v1/responses。context:创建请求级ResponsesContext,保存 request id、response id、provider、model、session、diagnostics。resolver:把客户端模型名解析为 provider/model,支持别名。session:处理previous_response_id会话链,支持 memory 和 SQLite。bridge:共享 Responses-to-Chat 策略,包括兼容性规划、请求归一化、工具规划、结构化输出、响应重建和流式状态机。providers:provider-specific 能力声明、endpoint、auth、hooks 和协议 DTO。responses:同步和流式 pipeline 编排。trace:记录请求、usage、event 和 error。
这几个边界里,最关键的是 bridge 和 providers 的关系。
GodeX 不希望每个 provider 都复制一套完整的 adapter。共享协议策略应该在 bridge kernel 中集中处理;provider 只声明自己的能力和差异。例如:
- 支持哪些 tool choice;
- 支持哪些 response format;
- reasoning 怎么表达;
- usage 和 cached tokens 从哪里读;
- stream delta 的结构有什么特殊点;
- finish reason 怎么映射。
这样新增 provider 时,不需要再造一个 mapper 森林;修改公共兼容策略时,也不需要每个 provider 各改一遍。
组件交互图

一次 /v1/responses 请求的大致流程如下:
- 客户端提交 Responses 请求。
- Bun server 解析并校验请求体。
ResponsesContext创建请求上下文。ModelResolver将模型名解析为 provider 和上游模型。- 如果存在
previous_response_id,session store 恢复会话链。 - Registrar 找到对应 provider 的
ProviderEdge。 ResponsesBridgeRuntime进入同步或流式 pipeline。ProviderExchange构建 provider request,记录 trace,并调用 provider。- provider 返回 JSON response 或 SSE chunks。
- 同步路径重建
ResponseObject;流式路径通过状态机重建 Responses SSE events。 - pipeline 校验输出契约、记录 usage、持久化 session,并返回客户端。
这条链路里有几个地方特别容易被低估。
第一,session 恢复必须发生在构建 provider request 之前。因为 provider 收到的是 Chat Completions messages,而客户端传入的是 Responses input 加一个 previous_response_id。网关必须先恢复历史,再转换为 provider-neutral messages。
第二,compatibility plan 必须在 provider request 构建阶段产生,而不是等失败后再猜。比如某 provider 不支持严格 json_schema,GodeX 要提前决定是降级为 json_object,还是拒绝请求。
第三,streaming 不是输出字符串拼接。流式状态机需要知道什么时候创建 response,什么时候创建 output item,什么时候写 delta,什么时候结束,什么时候把错误转换为 response.failed。
为什么不能只做字段转发
以几个典型差异为例。
Tool Choice
OpenAI 风格请求里可能出现:
{
"tool_choice": "required"
}
也可能指定某个 function。
但不是所有 provider 都支持这些语义。有的只支持 auto,有的支持 none,有的支持 function 指定,有的字段结构还不同。
这里有三种处理方式:
- 直接报错;
- 静默降级;
- 根据 provider capability 规划,并把降级或拒绝写入 diagnostics。
GodeX 选择第三种。
原因很简单:Agent 请求里,工具调用不是装饰品,而是执行路径的一部分。静默降级可能会让模型从“必须调用工具”变成“随便聊聊”,这类问题排查起来非常痛苦。
Structured Output
结构化输出也类似。
有些 provider 支持 json_object,但不支持严格 json_schema。如果客户端要求 strict schema,网关需要做一个明确决策:
- provider 原生支持,就直接传;
- provider 只支持 JSON object,就降级并注入 schema 指令;
- provider 连 JSON object 都不支持,就拒绝或诊断。
GodeX 当前对 strict 降级 schema 的处理是:上游使用 json_object,同时在 provider prompt 前言中加入格式指令,最终输出阶段检查 JSON 语法。
这不是完整 JSON Schema 校验,但至少能保证“降级行为是显式的,并且最终输出不会完全失控”。
Streaming
Chat Completions SSE 通常是 provider delta。
Responses SSE 则是一个更完整的事件模型。客户端可能期待:
- response created;
- output item added;
- content part added;
- output text delta;
- content part done;
- output item done;
- response completed;
- response failed。
如果只是把上游 delta 原样转发给客户端,客户端无法把它当成标准 Responses stream 使用。
所以 GodeX 在 bridge/stream 中维护状态机,把 provider chunks 转成 Responses events。这里还要处理中断、finish reason、usage、工具调用和最终输出校验。
ProviderSpec:让 provider 只描述差异
GodeX 的 provider 目录形态是:
src/providers/<name>/
spec.ts ProviderSpec declaration
client.ts ProviderEdge construction with ChatProviderClient
hooks.ts Provider-specific patching, accessors, usage, stream deltas
protocol/ Provider DTOs when needed
index.ts Public exports
这里的设计目标是让 provider package 尽量小。
一个 provider 主要回答这些问题:
- endpoint 在哪里;
- auth 怎么做;
- 默认模型是什么;
- 支持哪些 tool choice;
- 支持哪些 response format;
- reasoning 参数怎么映射;
- usage 怎么读取;
- finish reason 怎么读取;
- stream delta 怎么识别;
- provider 是否需要 request patch。
共享的策略,例如“当 provider 不支持 strict schema 时如何降级”“工具 ID 如何恢复”“Responses output item 如何重建”,不放在 provider 里。
这能避免两个问题。
第一,provider 之间复制逻辑。复制一开始省事,后来会让兼容策略失控。
第二,provider hooks 里出现公共决策。provider 一旦开始决定“哪些请求应该降级、哪些请求应该拒绝”,bridge kernel 的边界就被打穿了。
GodeX 1.0.0 的一个重要约束就是:provider hooks 暴露协议差异,bridge 决定支持、降级、拒绝和诊断。
模型别名:把路由策略留在本地
GodeX 支持在 godex.yaml 中配置模型别名:
server:
port: 5678
host: 0.0.0.0
default_provider: deepseek
models:
aliases:
gpt-5.5: "deepseek/deepseek-v4-pro"
gpt-5.4: "deepseek/deepseek-v4-pro"
gpt-5.4-mini: "zhipu/glm-5.1"
gpt-5.3-codex: "deepseek/deepseek-v4-pro"
gpt-5.3-codex-spark: "zhipu/glm-5.1"
这里需要强调一点:这些 alias 是本地 routing policy,不代表与 OpenAI 原模型能力等价。
它解决的是客户端稳定性问题。
例如 Codex 侧只知道自己使用 gpt-5.5,但 GodeX 可以把这个别名路由到 deepseek/deepseek-v4-pro。以后如果你想切换 provider 或模型,只需要改 GodeX 配置,而不是改每个客户端。
对团队内部工具来说,不同场景可以用不同模型:
- 复杂编码任务走强代码模型;
- 子任务或轻量生成走更快更便宜的模型;
- 特定业务场景走私有化 provider;
- 测试环境走 mock 或自定义兼容端点。
会话链:previous_response_id 不是 mutable cursor
Responses API 的 previous_response_id 很容易被误解成“当前会话 ID”。
在 GodeX 里,它更像父指针。
每次 response 都可以指向上一个 response,形成一条链。下一轮请求进来时,GodeX 根据 previous_response_id 解析历史,恢复 provider-neutral 的上下文,然后再构建 Chat Completions messages。
这个设计带来几个工程要求:
- 父节点缺失要报错;
- 链条成环要检测;
- 深度要有限制;
- incomplete response 不能被当作正常上下文;
store: false要跳过当前轮持久化;- session store 里保存的是 API-shaped snapshot,而不是某个 provider 的私有 message。
这些细节看起来不像亮点,但它们决定了多轮 Agent 是否可靠。
如果 session store 里保存的是 provider-specific 格式,后续切换 provider 或调整 bridge 策略都会变得困难。GodeX 选择把 provider-specific 转换留在 bridge 阶段,session store 只保存更中性的 API 快照。
Trace:为什么网关需要可观测性
Agent 请求的调试难点在于链路长。
一次失败可能来自:
- 客户端请求不合法;
- 模型别名解析错误;
- provider 不支持某个能力;
- request downgrade 后模型没有遵守格式;
- 上游 HTTP 错误;
- 上游 stream 中断;
- tool call 没有正确恢复;
- output contract 校验失败;
- session chain 缺失或成环。
没有 trace 时,这些问题很容易混在一起。
GodeX 默认启用 SQLite trace,记录:
- provider request 元数据;
- provider request / response 摘要 payload;
- 原始和转换后的 stream event;
- usage 详情,包括 cached tokens;
- route error 和 provider error。
Payload 捕获默认是摘要模式。也可以开启 trace.capture_payload: true 保存更完整 payload,但这会涉及敏感信息,需要谨慎。
从实践上看,trace 的价值不只是“出了问题能看日志”。它还能帮助检查兼容性规划是否符合预期:某个请求为什么降级、某个工具为什么恢复成这个 ID、某次 stream 为什么以 failed 结束,都应该能被还原出来。
首发支持的 Provider
GodeX 1.0.0 内置 provider 情况如下:
| Provider | Default Model | Reasoning | Tool Choice | Response Format | Cached Tokens |
|---|---|---|---|---|---|
| DeepSeek | deepseek-v4-pro |
native | auto, none, required, function |
text, json_object |
支持 |
| Xiaomi | mimo-v2.5-pro |
boolean | auto |
text, json_object |
支持 |
| MiniMax | MiniMax-M2.7 |
none | auto, none, required, function |
text, json_object |
支持 |
| Zhipu | glm-5.1 |
boolean | auto, none |
text, json_object |
支持 |
这些 provider 的能力声明不是为了做一张漂亮表格,而是直接参与 bridge 的 compatibility plan。
同一个客户端请求,在不同 provider 上可能得到不同规划:
- DeepSeek 可以使用更完整的 tool choice;
- Xiaomi 的 tool choice 能力更窄;
- MiniMax 没有 reasoning;
- Zhipu 支持布尔 reasoning 但 tool choice 范围不同。
网关的职责是让这些差异可见、可控,而不是假装它们不存在。
和 Codex 集成
GodeX 可以作为 Codex 的本地模型 provider。
安装:
npm install -g @ahoo-wang/godex
godex init
godex serve --config ./godex.yaml
在 ~/.codex/config.toml 中添加自定义 provider:
model = "gpt-5.5"
model_provider = "godex"
[model_providers.godex]
name = "GodeX"
base_url = "http://127.0.0.1:5678/v1"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = false
之后 Codex 请求 gpt-5.5,GodeX 会根据配置路由到真实 provider/model。
这样做的好处不是“多包了一层”,而是把 Codex 和 provider 之间的耦合降下来:
- Codex 只认 Responses API;
- GodeX 负责 provider 适配;
- 模型路由策略在
godex.yaml; - trace 和 session 在本地;
- provider 能力差异集中诊断。
安装与快速开始
npm 安装
npm install -g @ahoo-wang/godex
godex --help
源码运行
git clone https://github.com/Ahoo-Wang/GodeX.git
cd GodeX
bun install
bun run dev
bun run dev 默认使用 13145 端口;运行时配置默认端口是 5678。
初始化配置
godex init
godex serve --config ./godex.yaml
健康检查与模型列表
curl http://localhost:5678/health
curl http://localhost:5678/v1/models
/health 可以看到 provider 注册状态;/v1/models 会暴露已配置模型别名。
Docker 部署
GodeX 也提供 Docker 镜像。
Docker Hub:
docker pull ahoowang/godex:latest
使用配置文件运行:
docker run -d \
--name godex \
-p 5678:5678 \
-e ZHIPU_API_KEY=your-key \
-e DEEPSEEK_API_KEY=your-key \
-e MINIMAX_API_KEY=your-key \
-e MIMO_API_KEY=your-key \
-v ./godex.yaml:/etc/godex/godex.yaml:ro \
-v godex-data:/data \
ahoowang/godex:latest
容器默认使用 5678 端口,配置文件路径为 /etc/godex/godex.yaml,数据目录为 /data,适合把 session 和 trace 持久化下来。
对类似项目的几点经验
如果你也在做模型网关、Agent runtime 或 provider 适配层,我觉得 GodeX 这次有几条经验值得单独拿出来。
1. 不要让 provider 决定公共策略
Provider 可以告诉你“我支持什么”“我的字段怎么读”“我的 delta 长什么样”。
但 provider 不应该自己决定公共协议策略。否则每个 provider 都会变成一个小网关,最后共享行为会失控。
更好的方式是:
- provider 暴露 capability 和 hooks;
- bridge kernel 统一规划支持、降级、拒绝和 diagnostics;
- pipeline 统一处理 trace、session、logging 和 output validation。
2. 流式响应要按状态机设计
流式协议不要靠“看到什么就输出什么”拼出来。
只要涉及工具调用、结构化输出、usage、错误恢复,状态机会比临时 if/else 稳定得多。
至少要明确:
- response 何时 created;
- output item 何时 added;
- content part 何时 added/done;
- delta 如何归属;
- finish reason 如何映射;
- incomplete 和 failed 如何表达;
- 最终 usage 在哪里出现。
3. 降级必须可诊断
兼容层经常需要降级。
降级本身不是问题,静默降级才是问题。
当 strict schema 变成 json_object,当 required tool choice 无法原生支持,当 reasoning 参数被 provider 忽略时,网关应该能告诉调用方发生了什么。
这对 Agent 系统尤其重要,因为 Agent 的失败往往不是一个明确异常,而是行为偏离预期。
4. Session store 不要保存 provider 私有格式
如果 session store 保存的是某个 provider 的 message 格式,后续会很难切换 provider,也很难调整 bridge 行为。
更稳妥的方式是保存 API-shaped snapshot,让 provider-specific 转换发生在请求构建阶段。
5. Trace 要从第一版开始做
网关类项目越早有 trace,后面越省时间。
尤其是流式场景,只看最终字符串几乎没法定位问题。记录原始 provider event、转换后 Responses event、usage 和 error,对排查协议桥接问题非常有帮助。
什么时候需要类似网关
如果只是偶尔调用一次模型 API,单独引入网关未必有必要。
但如果系统里出现下面这些需求,就值得考虑在客户端和 provider 之间放一个协议层:
- Codex 或内部 Agent 需要接入多家模型提供商;
- 模型路由策略需要独立于客户端配置;
- 多个客户端都需要统一 Responses API;
- 工具调用、结构化输出、流式事件和 usage 统计需要稳定语义;
- Agent 请求链路需要 trace;
- Chat Completions provider 需要以统一方式扩展;
- 团队内部需要维护一个轻量模型网关。
这类网关的定位不是替代模型厂商,而是把 provider 差异隔离在一个更容易测试和诊断的地方。
写在最后
GodeX 1.0.0 是一个阶段版本,也是一组设计取舍的阶段性收敛。
它最重要的不是“支持了几家 provider”,而是把 Responses API 到 Chat Completions API 的桥接问题拆出了几个清楚边界:
- bridge kernel 管公共协议策略;
- provider hooks 管 provider 差异;
- session store 管 API-shaped 会话快照;
- pipeline 管同步/流式编排;
- trace 管请求可观测性。
这套边界不一定适合所有项目,但它解决了一个很现实的问题:当 Agent 客户端、OpenAI 风格协议和多模型 provider 同时出现时,适配逻辑必须有地方安放。
对 GodeX 来说,这个地方就是 bridge kernel。
后续我会继续围绕 provider 扩展、Responses API 兼容性和 Agent 网关设计补充更多实现细节。
相关资料
英文文档:
中文文档:
GitHub:
Gitee:
https://gitee.com/AhooWang/GodeX
npm:
https://www.npmjs.com/package/@ahoo-wang/godex
Docker Hub:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:
上一篇:20253904 2025-2026-2 《网络攻防实践》第九周作业
下一篇:没有了
相关文章
最新发布
- 从 Responses API 到 Chat Completions:一个模型网关的设计复盘
- 20253904 2025-2026-2 《网络攻防实践》第九周作业
- 上位机程序发布打包成安装包---Inno Setup
- 【EF Core】继承策略——TPT
- Solon Server 启动模式深度解析:从 0.3MB 内核到 10+ Server 插件
- Agent工厂与A2A网络——AgentMesh设计思路
- AT_abc460_f 解题报告
- [开源] 全屏时钟 / Full Clock:放弃 time.is,用 Svelte 5 写了一个极致纯净的全屏时钟,解决秒数焦虑
- [Python]标准库argparse解析命令行参数使用介绍
- 2026御网杯线上挑战赛Pwn的wp

