一、为什么需要多协议翻译

客户端生态的碎片化

AI 编程工具生态中,不同客户端各自为政,催生了三种互不兼容的 API 协议:

  • Claude Code → 使用 Chat Completions 协议

  • Codex CLI → 使用 Responses API 协议

  • Anthropic SDK → 使用 Messages 协议

三个客户端,三种协议,但上游服务只提供 Chat Completions 一种接口。这就像一个插座要适配三种不同标准的插头,硬接只会短路。

传统方案的痛点

面对这种局面,直觉方案各有短板:

  • 跑两个代理:codex-proxy 做协议翻译 + mimo-proxy 做推理缓存 → 运维复杂度翻倍,排查问题时要在两个代理之间来回跳转

  • 改客户端代码:侵入式修改上游开源项目 → 维护成本巨大,上游一更新就得重新适配

  • 单代理只支持一种协议:为了兼容多客户端不得不部署多个实例 → 资源浪费,且共享缓存等能力无法复用

设计目标

基于以上痛点,确立了三个核心目标:

  1. 单代理,多协议入口:一个服务同时暴露三种协议的端点

  2. 统一缓存推理内容:无论请求从哪个协议入口进来,reasoning 都走同一套缓存逻辑

  3. 上游只对接 Chat Completions:对外多协议,对内统一出口,降低维护成本


二、整体架构

text

Claude Code  ──→ /v1/chat/completions ──┐
Codex CLI    ──→ /v1/responses      ──→┤
Anthropic SDK──→ /v1/messages       ──→┤
                                         ├──→ reasoning 注入 → Chat Completions → 上游
仪表盘       ──→ /api/*              ──→┘

请求进入代理后,经过一条三层处理流水线

  1. 协议翻译层:将 Responses API 或 Anthropic Messages 协议的请求翻译为 Chat Completions 格式

  2. Reasoning 注入层:统一调用 inject_reasoning,为每条消息补全推理内容

  3. 上游转发层:通过 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,内部复杂度对外部完全透明

流式事件映射做到了位,下游客户端感知不到代理的存在。模型路由让一个代理同时服务多个供应商,而错误透传设计保证了代理不成为链路中的“黑盒”。整个方案的核心思路可以概括为一句:协议差异是入口的事,缓存和转发是所有人的事

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐