一、为什么需要多协议翻译
客户端生态的碎片化
AI 编程工具生态中,不同客户端各自为政,催生了三种互不兼容的 API 协议:
Claude Code→ 使用 Chat Completions 协议
Codex CLI→ 使用 Responses API 协议
Anthropic SDK→ 使用 Messages 协议
三个客户端,三种协议,但上游服务只提供 Chat Completions 一种接口。这就像一个插座要适配三种不同标准的插头,硬接只会短路。
传统方案的痛点
面对这种局面,直觉方案各有短板:
跑两个代理:codex-proxy 做协议翻译 + mimo-proxy 做推理缓存 → 运维复杂度翻倍,排查问题时要在两个代理之间来回跳转
改客户端代码:侵入式修改上游开源项目 → 维护成本巨大,上游一更新就得重新适配
单代理只支持一种协议:为了兼容多客户端不得不部署多个实例 → 资源浪费,且共享缓存等能力无法复用
设计目标
基于以上痛点,确立了三个核心目标:
单代理,多协议入口:一个服务同时暴露三种协议的端点
统一缓存推理内容:无论请求从哪个协议入口进来,reasoning 都走同一套缓存逻辑
上游只对接 Chat Completions:对外多协议,对内统一出口,降低维护成本
二、整体架构
text
Claude Code ──→ /v1/chat/completions ──┐ Codex CLI ──→ /v1/responses ──→┤ Anthropic SDK──→ /v1/messages ──→┤ ├──→ reasoning 注入 → Chat Completions → 上游 仪表盘 ──→ /api/* ──→┘
请求进入代理后,经过一条三层处理流水线:
协议翻译层:将 Responses API 或 Anthropic Messages 协议的请求翻译为 Chat Completions 格式
Reasoning 注入层:统一调用
inject_reasoning,为每条消息补全推理内容上游转发层:通过
stream_proxy完成流式或非流式的向上游转发
三层各司其职,协议差异被封印在翻译层内部,后续链路无感知。
三、Responses API 翻译核心
请求翻译:extract_messages
Responses API 的input数组包含三种 item 类型,需要一一映射:
| 原始类型 | 翻译后 |
|---|---|
type: "message" | role: user或role: assistant |
type: "function_call" | 合并为一条assistant+tool_calls[] |
type: "function_call_output" | role: tool |
几个关键处理细节:
连续 function_call 合并:当多条 function_call 连续出现时,合并为同一条 assistant 消息,tool_calls 按序排列——这是 Chat Completions 协议对多工具调用的标准表示
instructions → system message:将 Responses API 顶层的系统指令字段映射为 Chat Completions 的 system 角色消息
消息重排:确保
tool消息紧跟在对应的assistant消息之后,避免上游因消息顺序不当而拒绝请求Schema 字段清理:剔除
additionalProperties和strict等 Chat Completions 不兼容的字段,防止校验报错
流式响应翻译:stream_responses_sse
这是整个翻译层最精细的部分——需要将 Chat Completions 返回的 SSE 事件块,实时重组为 Responses API 规定的事件序列。
纯文本路径的事件序列:
text
response.created → response.in_progress → response.output_item.added (type=message) → response.content_part.added → response.output_text.delta (×N 次增量) → response.output_text.done → response.content_part.done → response.output_item.done → response.completed
工具调用路径的事件序列:
text
response.output_item.added (type=function_call) → response.function_call_arguments.delta (×N 次增量) → response.function_call_arguments.done → response.output_item.done → response.completed
每一个 delta 事件都精确对应 Chat Completions chunk 中的内容增量,下游客户端完全感知不到中间经过了一层协议转换。
四、Anthropic Messages 翻译核心
请求翻译:extract_anthropic_messages
Anthropic 的 Messages 协议在结构上与 Chat Completions 有显著差异,映射关系如下:
| Anthropic 字段 | Chat Completions 字段 |
|---|---|
system(顶层字段) | role: system |
content[].type: text | role: assistant+content |
content[].type: tool_use | assistant+tool_calls[] |
content[].type: tool_result | role: tool |
tools[].input_schema | tools[].function.parameters |
Anthropic 将文本、工具调用、工具结果全部平铺在content数组中,而 Chat Completions 是按角色分层的。翻译层需要完成这种“扁平 → 分层”的结构转换。
流式响应翻译:stream_anthropic_sse
Anthropic 的流式事件同样有其独特的事件类型序列:
text
message_start → content_block_start (type=text) → content_block_delta (text_delta ×N 次增量) → content_block_stop → content_block_start (type=tool_use) → content_block_delta (input_json_delta ×N 次增量) → content_block_stop → message_delta → message_stop
每个content_block对应一段独立的内容块(文本或工具调用),块内通过 delta 事件逐步传递增量内容,块之间通过 start/stop 事件明确边界。
五、统一 Reasoning 缓存
架构上最优雅的一点在于:无论请求从哪个协议入口进来,最终都汇入同一条缓存链路。
协议翻译层将各类请求统一转成 Chat Completions 格式
随后与原生 CC 路由走完全相同的
inject_reasoning逻辑无需为 Responses API 或 Anthropic 单独维护缓存代码
save_reasoning在流结束时统一触发,将本轮推理内容持久化
这种“翻译先行,缓存统一”的设计,让新增一个协议入口的成本降到最低——只需实现翻译层,缓存能力自动复用。
六、模型路由:上游调度
每个上游服务声明一个模型标识,实现请求的精准分发:
yaml
upstreams: - name: "DeepSeek" model: "deepseek-chat" url: "https://api.deepseek.com/v1" - name: "MiMo" model: "mimo-v4" url: "https://api.mimo.com/v1"
路由逻辑简洁明确:请求中的model字段与上游配置精确匹配,未命中时自动回退到活跃上游。配合 SQLite 持久化的上游列表,模型的新增和切换都可以在线完成,无需重启。
七、错误透传设计
代理层刻意去掉了内部的自动重试循环,采用“透明代理”的错误处理哲学:
上游返回 503 → 原样返回 503 给下游
下游客户端(Claude Code、Codex CLI 等)各自按自己的指数退避策略重连
不吞错误、不包装错误信息、不修改状态码
流式请求中发生错误时,抛出
UpstreamError→ 路由层捕获 → 以原始状态码响应客户端
这样做的好处是:客户端看到的是“直连上游”的错误行为,不会因为代理的额外重试而引入超时累积或重复请求,也避免了“代理层重试成功但客户端已超时断开”的尴尬局面。
八、代码结构概览
| 文件 | 职责 |
|---|---|
src/responses.py | Responses API 请求翻译 + 流式响应事件映射 |
src/anthropic_translate.py | Anthropic Messages 请求翻译 + 流式响应事件映射 |
src/routes.py | 路由注册 + 请求分发(按路径指向不同翻译层) |
src/proxy.py | reasoning 注入与缓存 + 流式转发核心逻辑 |
src/upstreams.py | 上游模型路由 + SQLite 持久化上游列表 |
每个模块职责单一,协议翻译逻辑与核心代理逻辑完全解耦,新增协议只需增加翻译文件并注册路由。
九、总结
这套多协议翻译管线实现了三个“统一”:
统一入口:一个代理同时承载三种协议的端点,彻底告别多代理运维的噩梦
统一缓存:外来请求协议各不相同,但在推理缓存层面殊途同归,一份代码服务所有入口
统一出口:上游始终只对接 Chat Completions,内部复杂度对外部完全透明
流式事件映射做到了位,下游客户端感知不到代理的存在。模型路由让一个代理同时服务多个供应商,而错误透传设计保证了代理不成为链路中的“黑盒”。整个方案的核心思路可以概括为一句:协议差异是入口的事,缓存和转发是所有人的事。