relay 中继核心:适配器与协议互转

专栏:new-api 源码拆解 · 第 6 / 12 篇
new-apirelay协议转换

:::info 学习目标 完成本篇后你能够:说出 Adaptor 接口的全部方法与调用时机;解释 OpenAI 格式请求如何变成 Claude 渠道请求再变回来;描述参数覆盖引擎的能力边界;照着清单新增一个上游提供方。 前置:第 2、5 篇完成。预计时长:75 分钟。 :::

:::note 本章术语速查(新手建议先读)

  • relay(中继):网关的核心动作——把客户端请求转换后转发给上游,再把响应转回来。
  • Adaptor(适配器):每个上游提供方的”翻译官”,实现统一的接口方法集。
  • DTO(Data Transfer Object):专门用来传输数据的结构定义。
  • SSE Scanner:逐行读取流式响应的组件。
  • PassThrough(透传):不做任何转换、原样转发请求体的模式。
  • param_override:渠道级的”请求参数改写规则”,不改代码就能强制修改转发的参数。 :::

网关的技术心脏是 relay 层:客户端用 OpenAI 格式说话,上游可能是 Claude/Gemini/AWS Bedrock/Ollama 等 40+ 种方言——中继层负责双向翻译。它的设计分两层:relay/(宿主层:HTTP 编排、计费交互、渠道生命周期)与 relaykit/(2025 年拆分的独立 Go module:纯协议 DTO 与转换,不依赖 gin 与数据库,README 给出 OpenAI Chat / Responses / Claude / Gemini 的 4×4 转换质量矩阵——Good/Fair/Discouraged,诚实标注哪些互转有损)。

协议与 handler 全景

controller/relay.gorelayInfo.RelayFormat 分发到各 handler:

RelayFormathandler入口
OpenAI(chat)relayHandler / compatible_handler/v1/chat/completions
Clauderelay.ClaudeHelper/v1/messages
GeminigeminiRelayHandler/v1beta/...:generateContent
OpenAIResponsesrelay.ResponsesHelper/v1/responses
Embedding / Rerank / Image / Audio各专用 handler对应端点
Realtimerelay.WssHelper/v1/realtime(WebSocket)
Task / MjProxyrelay_task.go / mjproxy_handlerMidjourney、视频(Kling/Sora/Vidu…)

Adaptor 接口:一个提供方要实现什么

// relay/channel/adapter.go:16-39(节选)
type Adaptor interface {
    Init(*types.ChannelMeta) error
    GetRequestURL(meta *types.ChannelMeta, request *http.Request) (string, error)
    SetupRequestHeader(c *gin.Context, req *http.Request, meta *types.ChannelMeta) error
    ConvertOpenAIRequest(...)      // 8 个转换入口:OpenAI / Rerank / Embedding /
    ConvertClaudeRequest(...)      // Audio / Image / Responses / Claude / Gemini
    ...
    DoRequest(c *gin.Context, info *RelayInfo, request *http.Request) (*http.Response, error)
    DoResponse(c *gin.Context, resp *http.Response, info *RelayInfo) (usage any, err error)
    GetModelList() []string
    GetChannelName() string
}

以 Claude 适配器为例,三种入口格式的转换统一委托给 relaykit 的转换注册表——适配器不再各写一套格式转换:

// relay/channel/claude/adaptor.go:118-127
func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo,
    request *dto.GeneralOpenAIRequest) (any, error) {
    result, err := service.ConvertRequest(c, info, types.RelayFormatClaude, request)
    if err != nil {
        return nil, err
    }
    return result.Value, nil
}

新增一个提供方的五步清单(源自源码结构):① constant/channel.go 加渠道类型常量与显示名;② common/api_type.goChannelType2APIType 加映射;③ 新建 relay/channel/<provider>/adaptor.go 实现接口;④ relay/relay_adaptor.goGetAdaptor(40+ 个 case)加分支;⑤ 异步任务类(视频/音乐生成)再实现 TaskAdaptor(多了 ValidateRequestAndSetAction / EstimateBilling / FetchTask 等方法)。仓库里 40+ 个现成适配器就是 40 份参考答案。

SSE 流式处理与参数覆盖

流式核心是 relay/helper/stream_scanner.goStreamScannerHandlerbufio.Scanner 逐事件读取上游 SSE,带超时 ticker、ping 心跳、写锁与 goroutine 清理,把每个 data: 行回调给适配器的 handler(如 Claude 的 ClaudeStreamHandler)。是否流式由上游响应的 Content-Type: text/event-stream 判定;转换分两类路径——入口协议=出口协议直接透传(还有 PassThrough 模式连 body 都原样重放),不同协议则在 DoResponse 里逐事件转换。

请求侧还有一个常被忽略的引擎:param_overriderelay/common/override.go,约 2200 行)。渠道级 JSON 请求体改写,支持旧版键值 set/delete(gjson/sjson 路径语义)和新版 operations 操作列表(带条件上下文与审计记录器)——不改代码就能给某个渠道强制注入/覆盖任意参数(如强制 temperature、注入系统消息)。每个 handler 在请求序列化后调用 ApplyParamOverrideWithRelayInfo

一次转换的完整时序

图表(newapi-relay-adaptors.md)

与其他模块的联系

  • ← Distribute(第 2、7 篇):从上下文拿到渠道 key/type/setting/param_override/model_mapping——适配器自身不做选择;
  • → 计费(第 8 篇)DoResponse 返回的 usage 是结算的输入;流式场景下 usage 随最后一个事件到达;
  • → 渠道熔断(第 7 篇)DoRequest/DoResponse 返回的错误带状态码,供 processChannelError 判定是否重试与禁用;
  • → 插件(第 11 篇):TaskPlugin 渠道实现的是同一套 Adaptor 语义,只是执行体是 JS 插件。

常见踩坑

  1. 新增提供方忘了 GetAdaptor 的 case——编译通过但运行时报未知渠道类型;
  2. 没看转换质量矩阵——Gemini↔Claude 某些参数(reasoning 细节等)是 Discouraged 级别,转换后行为差异要有预期;
  3. 流式响应缓冲了 body——SSE 必须边读边转发,攒整个响应会超时;
  4. param_override 写了非法 JSON 路径——开启 ParamOverrideReturnError 时会转成 SkipRetry 错误直接失败,先在测试渠道验证。

随堂练习(带验收标准)

  1. relay/channel/openai/adaptor.go 全文,标注每个方法被调用的时机。验收:能对照第 2 篇的生命周期图说出顺序;
  2. 配置一个 Claude 格式渠道,用 OpenAI SDK 流式请求它。验收:OpenAI 格式的流式回复正常(底层经历双向格式转换);
  3. 用 param_override 给某渠道强制注入一个 temperature: 0。验收:上游收到该参数(可通过上游日志或响应行为验证);
  4. 照五步清单给一个假想提供方写最小 adaptor 骨架,编译通过即达标。

← 返回文章列表