【别再手搓大模型协议转换了:一个打通 OpenAI、Claude、Gemini 的 Go SDK 实战】
一套 Go SDK,打通 OpenAI / Claude / Gemini / Responses 协议互转:OpenTrans 设计与性能实战
你有没有遇到过这种场景:
上游是 OpenAI Chat Completions,请求到了网关以后,要转成 Claude Messages;
或者某条链路要兼容 OpenAI Responses,另一条链路要接 Gemini;
再或者你想做一个统一的大模型 API 网关、代理层、中间件,但每家协议都不一样,字段结构完全不统一。这时候最麻烦的不是“发请求”,而是“协议互转”。
项目地址
go get github.com/xy200303/OpenTrans
导入:
import opentrans "github.com/xy200303/OpenTrans"
最近我做了一个 Go 项目:OpenTrans。
它的目标很明确:
- 把 OpenAI Chat
- OpenAI Responses
- Anthropic Claude
- Google Gemini
这几种主流 LLM 协议的请求体、响应体、流式事件统一到一套中间结构,再按目标协议重新输出。
这个项目不是代理服务,不是完整网关,而是一个专门做协议互转的 Go SDK。
如果你在做:
- LLM API 网关
- 多模型路由
- 协议兼容层
- 聚合代理
- 统一日志 / 缓存 / 审计
- Gin 中间件适配
那它会非常顺手。
一、为什么要做这个库?
现在的大模型生态有一个很现实的问题:
模型能力越来越接近,但协议层越来越碎。
比如:
1. OpenAI Chat 与 Responses 不是一回事
虽然都属于 OpenAI 体系,但:
- Chat Completions 用的是
messages - Responses 用的是
input - tool call / tool result 的结构也不一样
2. Claude 协议和 OpenAI 差异很大
Claude 的请求一般是:
systemmessagescontent里是text / image / tool_use / tool_result
而 OpenAI 常见是:
messagescontent支持字符串或数组- 工具调用是
tool_calls - 工具结果靠
tool消息承载
3. Gemini 更不一样
Gemini 的输入结构通常是:
contentspartsinline_datafile_datafunctionCallfunctionResponse
如果你想做一个统一接入层,最笨的方法当然也能做:
- 每种协议都写一套转换逻辑
- A 转 B 写一份
- A 转 C 写一份
- B 转 C 再写一份
- 再加上响应和流式事件……
最后代码会指数级膨胀。
所以更合理的方式是:
所有协议先转成统一中间结构,再从中间结构转出去。
这就是 OpenTrans 的核心思路。
二、OpenTrans 是什么?
OpenTrans 是一个用 Go 编写的多协议 LLM 互转 SDK,当前支持:
- OpenAI Chat
- OpenAI Responses
- Claude
- Gemini
并且支持三类核心对象:
- 请求体
Request - 响应体
Response - 流式事件
StreamEvent
项目地址导入方式:
import opentrans "github.com/xy200303/OpenTrans"
它既可以处理:
[]byte原始 JSON body 互转
也可以直接处理:
- 各 provider 的结构体模型互转
也就是说,你既能把它当成:
- API 网关里的协议转换器
也能把它当成:
- 应用内部的模型适配层
三、核心设计:不是用 OpenAI 当中间层,而是独立 canonical 结构
很多人做协议兼容时,喜欢直接“统一成 OpenAI 格式”。
这个做法初期简单,但很快就会遇到问题:
- Claude 的
tool_result怎么表示? - Gemini 的
file_data怎么表示? - 多模态内容怎么统一?
- provider 特有字段怎么保留?
所以 OpenTrans 最终没有选择“OpenAI 作为中间结构”,而是定义了一套独立的标准化中间结构。
例如请求结构核心就是:
type Request struct {
Model string
Messages []Message
Temperature *float64
TopP *float64
MaxTokens *int
Stream bool
Tools []Tool
Metadata map[string]any
Extra map[string]any
}
消息里的内容统一为 ContentPart:
type ContentPart struct {
Type string
Text string
ImageURL string
MIMEType string
Data string
CallID string
Name string
Arguments json.RawMessage
Result json.RawMessage
}
当前这套中间结构已经覆盖了常见能力:
textimage_urlimage_base64tool_calltool_result
这意味着不同协议之间的公共能力,都可以先收敛到同一种表达方式。
四、这个库现在支持到什么程度?
目前能力矩阵可以概括成下面这张表:
图例
✅已支持🟡部分支持❌未完整支持
| 能力 | OpenAI Chat | OpenAI Responses | Claude | Gemini |
|---|---|---|---|---|
| 请求文本输入 | ✅ | ✅ | ✅ | ✅ |
| 请求图片 URL 输入 | ✅ | ✅ | ✅ | ✅ |
| 请求 Base64 图片输入 | ❌ | ❌ | ✅ | ✅ |
| 请求工具定义 | ✅ | ✅ | ✅ | ✅ |
| 请求工具调用消息 | ✅ | ✅ | ✅ | ✅ |
| 请求工具结果消息 | ✅ | ✅ | ✅ | ✅ |
| 非流式文本响应 | ✅ | ✅ | ✅ | ✅ |
| 非流式图片/视频输出 | ❌ | ❌ | ❌ | ❌ |
| 流式文本增量 | ✅ | ✅ | ✅ | ✅ |
| 流式开始/结束/usage 事件 | ✅ | ✅ | ✅ | ✅ |
| provider 特有高级字段透传 | 🟡 | 🟡 | 🟡 | 🟡 |
这个覆盖面对于以下场景已经足够好用了:
- 文本聊天
- 多轮对话
- 图片输入
- 工具调用
- 工具结果回传
- 基础 SSE 流式转发
五、最实用的几个能力
1. 原始 body 直接互转
最常见的场景,就是你已经拿到了 HTTP body,想直接转协议。
例如把 OpenAI 请求转成 Claude:
src := []byte(`{
"model": "gpt-4.1-mini",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Write a haiku about Go"}
],
"temperature": 0.4
}`)
out, err := opentrans.ConvertRequestBody(
src,
opentrans.ProtocolOpenAI,
opentrans.ProtocolClaude,
)
if err != nil {
panic(err)
}
fmt.Println(string(out))
这个接口非常适合:
- 网关
- 中间件
- HTTP 代理层
- 兼容 API 层
2. 先标准化,再二次处理
如果你不只是想“转协议”,还想中间做点逻辑,比如:
- 审计
- 路由
- 模型名替换
- 消息裁剪
- 加 metadata
- 缓存 key 生成
那就可以先标准化:
req, err := opentrans.NormalizeRequest(opentrans.ProtocolGemini, body)
if err != nil {
panic(err)
}
req.Model = "gpt-4.1-mini"
out, err := opentrans.MarshalRequest(req, opentrans.ProtocolOpenAI)
if err != nil {
panic(err)
}
这比“每种协议写一套业务逻辑”要优雅太多。
3. 支持工具调用链路互转
这是很多协议互转库最容易做残的地方。
OpenTrans 现在已经把这条链路打通了:
OpenAI Chat
assistant.tool_callstool消息
OpenAI Responses
function_callfunction_call_output
Claude
tool_usetool_result
Gemini
functionCallfunctionResponse
而且中间结构里引入了 CallID,所以工具调用和结果能对应起来,不是简单“只保留名字”。
4. 支持图片 URL / Base64 输入
现在请求侧图片输入已经比较完整:
- OpenAI
image_url - Claude URL / base64 图片 source
- Gemini
file_data/inline_data
尤其是这次做完之后:
OpenAI -> Claude的图片 URL 输入已经支持OpenAI -> Gemini的图片 URL 输入已经支持Gemini file_data -> image_urlClaude image source(type=url) -> image_url
这点对做多模态网关非常重要。
5. 直接接入 Gin 中间件
如果你正在做 Gin 网关,这个点会特别香。
项目内置了 pkg/opentrans/ginx:
ginx.ConvertRequestBodyginx.ConvertResponseBodyginx.ConvertStreamEventBody
示例:
router.POST(
"/v1/chat/completions",
ginx.ConvertRequestBody(opentrans.ProtocolOpenAI, opentrans.ProtocolClaude),
func(c *gin.Context) {
c.Status(200)
},
)
流式场景也支持 SSE data: 帧转换。
这意味着你可以很快搭一个:
- OpenAI 兼容入口
- Claude / Gemini 后端
- 自动协议转换
而不用自己手搓每个字段。
六、为了性能,我做了哪些优化?
协议互转这件事,很容易一不小心就写成:
json.Unmarshal- 转成 map
- 再组 map
- 再
json.Marshal
功能是能跑,但性能会非常差。
OpenTrans 在这方面做了不少约束和优化:
1. 优先结构体转换,而不是 JSON 往返桥接
如果已经拿到了结构体,直接走结构体适配,不需要先转 []byte 再解析回来。
2. 用独立 canonical 结构,而不是 provider 套 provider
避免 OpenAI -> Claude 先转 OpenAI struct,再桥 Claude struct,再中间反复绕。
3. 运行时热路径切到高性能 JSON
内部已经用 jsoniter 处理核心 JSON 热路径,而不是一直用标准库默认实现。
4. body 输出默认走 compact JSON
避免无意义 pretty-print 带来的额外开销。
5. Gin SSE 路径做了减分配优化
例如:
- 去掉多余切片复制
- 避免
string(...)来回转换 - 用
bytes.Buffer原地消费 - 用 typed struct 代替热路径里的
map[string]any
七、性能到底怎么样?
这个项目我专门做了 benchmark 目录,拆成:
request_benchmark_test.goresponse_benchmark_test.gostream_benchmark_test.goginx_benchmark_test.gocomparison_benchmark_test.go
1. OpenTrans 自身整链路性能
比较接近真实网关场景的整链路转换,大致是这种量级:
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
BenchmarkConvertRequestBodyOpenAIToClaude |
1807 |
1113 |
21 |
BenchmarkConvertResponseBodyClaudeToOpenAI |
1978 |
1322 |
22 |
BenchmarkConvertStreamEventBodyClaudeToOpenAI |
720.1 |
606 |
9 |
BenchmarkConvertStreamEventBodyOpenAIToClaude |
917.8 |
638 |
11 |
这意味着:
请求体 / 响应体 / 流式事件的协议转换已经稳定在微秒级。
对于 API 网关或代理层,这个开销是完全可接受的。
2. 与常见函数实现方式对比
这里我没有拿“完整代理服务”来硬比,因为那样口径不公平。
而是选了三种纯函数实现方式做同口径对比:
OpenTransencoding/json + typed struct bridgeencoding/json + map[string]any bridge
纯文本 OpenAI -> Claude
| 实现 | ns/op | B/op | allocs/op |
|---|---|---|---|
OpenTrans |
4048 |
1715 |
32 |
StdJSON struct bridge |
6919 |
1409 |
27 |
Map bridge |
10183 |
3659 |
76 |
多模态 OpenAI -> Claude
样本包含:
system文本user文本image_url图片 URL 输入
| 实现 | ns/op | B/op | allocs/op |
|---|---|---|---|
OpenTrans |
8832 |
3694 |
74 |
StdJSON struct bridge |
11233 |
2009 |
41 |
Map bridge |
15839 |
5925 |
111 |
3. 怎么看这组数字?
我觉得可以总结成四句话:
- 对纯文本协议互转,OpenTrans 已经明显快于常见手写桥接实现
- 对多模态请求,OpenTrans 目前也快于这两种常见基线
map[string]any风格的动态桥接,在速度和分配上都最差- OpenTrans 虽然不是所有场景分配最少,但换来的是更统一、更可扩展、更完整的协议兼容能力
这也是我做这个项目时很看重的一点:
不是为了极端 micro benchmark 而牺牲协议表达能力,
而是在“功能完整”和“运行足够快”之间做平衡。
八、这个项目适合谁用?
我觉得特别适合下面几类开发者:
1. 做 LLM API 网关的人
你要统一入口协议,比如:
- 前端全走 OpenAI
- 后端可能接 Claude / Gemini / Responses
那 OpenTrans 可以直接放在入口转换层。
2. 做多模型路由的人
你希望业务逻辑只看统一结构,而不是关心每家 provider 的字段差异。
3. 做兼容代理的人
比如做 OpenAI-compatible API,但实际后端不是 OpenAI。
4. 做日志 / 缓存 / 审计的人
统一中间结构特别适合:
- 提取关键信息
- 生成缓存 key
- 做输入输出审计
- 做 tool call 追踪
5. 想把 LLM 能力嵌入自己业务系统的人
你不想为了支持多个模型供应商,把整个业务代码写成一锅协议适配 if-else。
九、当前还有限制吗?
当然有,而且我觉得明确写出来比“假装全能”更重要。
目前还存在这些边界:
- 非流式图片 / 视频输出还没完全统一打通
- provider 特有高级字段目前还是部分透传
- 某些复杂多模态输出还需要继续补
- 完整官方 SDK 级别的所有边角字段还没有完全覆盖
不过从工程角度看,当前版本已经足够覆盖绝大多数:
- 文本聊天
- 多轮消息
- 图片输入
- 工具调用
- 工具结果
- 流式文本
这已经能支撑不少真实项目。
十、我最满意的地方
如果让我总结这个项目最有价值的几个点,我会选这几个:
1. 不是“代理应用”,而是“协议互转 SDK”
这个定位很关键。
你可以把它嵌到任何架构里,而不是必须整套接管你的服务。
2. 不用 OpenAI 充当中间层
而是抽象出独立 canonical 结构,扩展性更好。
3. 请求、响应、流式三条线都打通了
很多项目只做请求,响应和 stream 很容易烂尾。
4. 工具调用链路处理得比较完整
尤其是 CallID 这条线打通后,已经不只是“能转”,而是“能正确表达”。
5. 性能不是嘴上说说,是真的做了 benchmark
这一点我自己也很看重。
能跑、能测、能对比,才能持续优化。
十一、最后
如果你也在做:
- OpenAI / Claude / Gemini 多协议兼容
- LLM API 网关
- 模型代理
- 协议适配层
- 工具调用桥接
那 OpenTrans 这个思路应该能给你一些启发:
协议碎片化不可避免,但中间结构可以统一。
把协议互转从“散落在各处的临时代码”,收敛成一个独立 SDK,本身就是一种工程收益。
如果后面继续迭代,我下一步会重点看:
- 非流式图片/视频输出统一
- 更多 response / stream 的协议对齐
- 更进一步的多模态性能优化
- 更多 provider 特有字段透传
项目地址
go get github.com/xy200303/OpenTrans
导入:
import opentrans "github.com/xy200303/OpenTrans"
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)