Help us shape the future of Go! Take the 2026 Go Developer Survey

relaykit

module
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: AGPL-3.0

README

RelayKit

RelayKit 是一个 Go 模块,在 OpenAI Chat Completions、OpenAI Responses、Anthropic Messages 和 Gemini generateContent 四种协议之间转换请求、非流式响应和流式事件。

它从 new-api 中拆出,模块路径是 github.com/QuantumNous/new-api/relaykit,运行时依赖是 uuid、lo、gjson 和 sjson 四个包。它做协议数据的建模和转换,HTTP 收发、SSE 读写、渠道选择、鉴权和计费由宿主程序完成,所以可以嵌进其他 Go 网关或代理。

支持矩阵

四种协议两两之间都能转换,请求、非流式响应和流式响应覆盖同一张表:

源格式 \ 目标格式 OpenAI Chat OpenAI Responses Claude Messages Gemini
OpenAI Chat good fair fair
OpenAI Responses good fair fair
Claude Messages fair fair discouraged
Gemini fair fair discouraged

good 表示两种协议结构接近,字段基本一一对应。fair 表示主要内容都能转,协议独有的字段以诊断报告。其中 Gemini 转 Responses 的请求和响应、Claude 转 Responses 的响应、Responses 转 Gemini 的响应经 OpenAI Chat 中转。discouraged 表示 Claude 和 Gemini 之间请求和响应都经 OpenAI Chat 中转两跳,丢字段的机会更多。实际走的路径在转换结果的 Steps 里,等级在 Quality 里。

功能

自动选路

ConvertRequest 和 ConvertResponse 按传入 DTO 的具体类型判断源协议,NewResponseStreamState 按参数里的源格式和目标格式,都查表选直接转换器或多跳路径。结果里有源格式、目标格式、转换器 ID、质量等级、实际经过的每一步和诊断列表。要固定路径时用 ConvertRequestVia,要按 ID 执行时用 ConvertRequestByID、ConvertResponseByID 和 NewResponseStreamStateByID。

工具调用

function tool 的定义、调用和结果在四种协议之间互转。Responses 的 custom tool 和带 namespace 的工具发往 Chat、Claude、Gemini 上游时改成普通 function tool,编码方式记在 convmeta.ResponsesToolState 里,响应转换时按这份记录还原成 Responses 的形状。托管 web search 按目标协议重新编码:Chat 用 web_search_options,Responses、Claude 和 Gemini 各用自己协议的 web search 工具定义,宿主可以通过 Options.WebSearch 为某个上游改写编码。其他托管工具在目标协议里表达不了的部分,转换照常完成,缺失写进诊断。

推理设置

reasoning effort、thinking budget 和是否返回思考内容用 dto.ReasoningConversionState 在转换步骤之间传递,多跳路径上的每一步都能读到精确的 budget 和 effort。Claude 的 max_tokens 和 budget 超出模型范围时会调整,Gemini 的 thinkingConfig 按模型归一化,这些改动都以诊断返回,例如 claude_max_tokens_raised 和 claude_budget_adjusted。

多模态

图片和文件内容块在协议之间转换。需要下载 URL 或解析 data URL 时,RelayKit 调用宿主用 SetMediaResolver 注册的函数,未注册时转换返回错误。

Usage

每个响应转换都返回统一的 dto.Usage,含输入、输出、缓存命中,以及 Claude 缓存写入按 5 分钟和 1 小时的拆分。Usage.BillingUsage 同时带着上游原始的 usage 结构和它来自哪个协议,宿主可以按上游的计量规则计费。流式转换把分散在多个事件里的 usage 累加到 ResponseStreamState。dto.MergeUsageNonZero 一类函数合并两份 usage 时,后到的非零字段覆盖已有值,后到的零值保留已有的计数。

流式

每条上游流对应一个 ResponseStreamState,跨事件的工具调用拼接、usage 和结束状态都存在这里。上游结束后调用 FinalizeStreamResponse,转换器在这一步补发终止事件和最终 usage。目标是 Responses 时,上游中途出错可以调用 FailResponsesStream 生成 Responses 协议的错误事件,ResponseStreamOptions.EmitSequenceNumber 打开后每个事件带 sequence_number。

宿主钩子

JSON 编解码默认用 encoding/json,宿主可以在启动时用 kitutil.SetCodec 换成别的实现,DTO 上自定义的 MarshalJSON 和 UnmarshalJSON 也走同一个编解码器。数据形状异常的日志通过 kitutil.SetLogging 和 kitutil.SetSystemErrorLogging 交给宿主,默认写到 stderr。

安装

RelayKit 要求 Go 1.25.1 或更高版本。tag 的形式是 relaykit/vX.Y.Z,@latest 解析到最新的一个。

go get github.com/QuantumNous/new-api/relaykit@latest
包 内容
relaykit/dto 四种协议的请求、响应、流式事件和 usage 结构,以及 new-api 宿主共用的渠道、定价和用户设置结构
relaykit/types 协议格式、错误、诊断、文件来源等共享类型
relaykit/relayconvert 请求、响应和流式转换入口
relaykit/relayconvert/convmeta 转换上下文接口 Meta、默认实现 Values 和选项 Options
relaykit/relayconvert/reasoning 推理设置的解析、合并和渲染,包括模型名尾缀解析
relaykit/relayconvert/kitutil JSON 编解码和日志钩子
relaykit/reasonmap Claude 和 OpenAI 之间的结束原因映射

快速开始

下面把 OpenAI Chat Completions 请求转换成 Claude Messages 请求:

package main

import (
	"context"
	"fmt"

	"github.com/QuantumNous/new-api/relaykit/dto"
	"github.com/QuantumNous/new-api/relaykit/relayconvert"
	"github.com/QuantumNous/new-api/relaykit/relayconvert/convmeta"
	"github.com/QuantumNous/new-api/relaykit/types"
)

func main() {
	maxTokens := uint(1024)
	request := &dto.GeneralOpenAIRequest{
		Model: "claude-sonnet-4-5",
		Messages: []dto.Message{
			{Role: "user", Content: "Hello!"},
		},
		MaxTokens: &maxTokens,
	}

	meta := &convmeta.Values{
		OriginModelName:     "client-model",
		UpstreamModelName:   request.Model,
		ChannelMetaAttached: true,
	}

	result, err := relayconvert.ConvertRequest(
		context.Background(),
		meta,
		types.RelayFormatClaude,
		request,
	)
	if err != nil {
		panic(err)
	}

	claudeRequest, ok := result.Value.(*dto.ClaudeRequest)
	if !ok {
		panic(fmt.Sprintf("unexpected result type %T", result.Value))
	}

	fmt.Printf("model=%s messages=%d\n", claudeRequest.Model, len(claudeRequest.Messages))
}

ConvertRequest 按请求的具体 DTO 类型推断源格式。传入 nil、原始 JSON、map[string]any 或其他类型时返回错误。

非流式响应

响应转换用同一套目标格式参数:

result, err := relayconvert.ConvertResponse(
	ctx,
	meta,
	types.RelayFormatOpenAI,
	claudeResponse,
)
if err != nil {
	return err
}

openAIResponse := result.Value.(*dto.OpenAITextResponse)
usage := result.Usage

支持的响应 DTO:

格式 非流式响应 流式事件
OpenAI Chat dto.OpenAITextResponse dto.ChatCompletionsStreamResponse
OpenAI Responses dto.OpenAIResponsesResponse dto.ResponsesStreamResponse
Claude Messages dto.ClaudeResponse dto.ClaudeResponse
Gemini dto.GeminiChatResponse dto.GeminiChatResponse
流式响应

每条上游流创建一个 ResponseStreamState,上游结束后调用 FinalizeStreamResponse:

state, err := relayconvert.NewResponseStreamState(
	types.RelayFormatOpenAI,
	types.RelayFormatOpenAIResponses,
	relayconvert.ResponseStreamOptions{
		ID:                 "resp_123",
		Model:              "gpt-4.1",
		IncludeUsage:       true,
		EmitSequenceNumber: true,
	},
)
if err != nil {
	return err
}

for _, chunk := range upstreamChunks {
	results, err := relayconvert.ConvertStreamResponseChunk(ctx, meta, state, chunk)
	if err != nil {
		return err
	}
	for _, result := range results {
		emit(result.Value)
	}
}

finalResults, err := relayconvert.FinalizeStreamResponse(ctx, meta, state)
if err != nil {
	return err
}
for _, result := range finalResults {
	emit(result.Value)
}

usage := state.Usage()
diagnostics := state.Diagnostics()

SSE 的读取和写入由宿主完成:把每个上游事件解析成对应的 DTO 传进来,再把转换结果编码后发给下游。FinalizeStreamResponse 每条流都要调用,部分转换器在这一步才补发终止事件和最终 usage。

转换上下文

大多数基础转换的 convmeta.Meta 参数可以传 nil。需要模型名、推理设置、工具编码记录或流式状态时,用 convmeta.Values,或者在宿主里实现 convmeta.Meta。自己实现时,指针类型的每个方法都要能在 nil receiver 上返回零值,接口注释写了原因。

按请求传入的选项放在 convmeta.Options:

meta := &convmeta.Values{
	Options: &convmeta.Options{
		Claude: convmeta.ClaudeOptions{
			DefaultMaxTokens: func(model string) int {
				return 4096
			},
		},
		Gemini: convmeta.GeminiOptions{
			ThinkingAdapterEnabled: true,
		},
	},
}
选项 作用
Claude.DefaultMaxTokens 源请求没有 max_tokens 时,转 Claude 用这个函数取值
Claude.ThinkingAdapterEnabled、Gemini.ThinkingAdapterEnabled 模型名尾缀适配开关,见下文
Claude.ThinkingAdapterBudgetTokensPercentage、Gemini.ThinkingAdapterBudgetTokensPercentage 尾缀适配触发时,thinking budget 占 max_tokens 的比例
Claude.WebSearchToolVersion 转 Claude 时 web search 工具的版本,空值用 web_search_20250305
Gemini.FunctionCallThoughtSignatureEnabled 给 function call 部分附加 thoughtSignature
Gemini.SupportsImagine 判断模型是否支持生图,支持时切换响应模态
Gemini.SafetySetting 按分类返回 safetySettings 的阈值
ToolLossPolicy 跨协议丢失工具行为时的处理,见下文
OpenRouterDialect 上游是 OpenRouter 时打开,转换器会输出它额外接受的字段
WebSearch 为某个上游改写托管 web search 的编码
PreserveThinkingSuffix、PreserveEffortTail 判断哪些模型名要原样保留尾缀

转 Claude 时请求需要 max_tokens。源请求没有时,RelayKit 调用 Claude.DefaultMaxTokens 取值,两者都没有时返回错误。

RelayKit 按请求里的 Model 生成上游请求,调用前把它设成上游使用的模型名。带 -thinking、-nothinking、-thinking-<budget> 或 -high 这类尾缀的 Claude、Gemini 模型名由调用方在转换前解析:调用 reasoning.ParseClaudeModelSuffix 或 reasoning.ParseGeminiModelSuffix,allowSuffixAlias 参数传 ThinkingAdapterEnabled,把返回的 Intent 用 reasoning.StateFromIntent 写进 Values.ReasoningConversion,再把裁掉尾缀的模型名放进请求。开关关闭时这两个函数原样返回模型名,推理设置来自请求字段。

诊断与损耗策略

每个转换结果都带 Diagnostics,每条诊断有代码、字段路径、说明、级别和源目标格式。级别 warning 表示展示层面的差异,error 表示工具行为或内容发生了变化。常见代码有 hosted_web_search_omitted、trailing_assistant_omitted 和 unsupported_media_type。

Options.ToolLossPolicy 决定请求阶段怎么处理诊断:

  • allow 是默认值,转换完成,所有损耗以诊断返回。
  • safe 在请求阶段遇到 error 级诊断时返回 *types.ConversionLossError。
  • strict 在请求阶段遇到任何诊断都返回这个错误。

响应和流式转换在三种策略下都完成转换并返回诊断。流式用 state.Diagnostics() 读到目前为止累计的诊断,包括没有产生下游事件的那些。

媒体解析

跨协议的图片转换有时需要下载 URL 内容或解析 data URL。宿主在启动时注册解析函数:

relayconvert.SetMediaResolver(relayconvert.MediaResolver{
	GetBase64Data:        getBase64Data,
	DecodeBase64FileData: decodeBase64FileData,
})

两个函数的签名见 relayconvert.MediaResolver。需要媒体解析而没有注册时,转换返回错误。网络请求由这两个函数发起。

转换结果

请求转换返回 relayconvert.RequestResult,响应转换返回 relayconvert.ResponseResult,字段有:

  • Value:转换后的 DTO
  • From、To:源格式和目标格式
  • Converter:转换器 ID
  • Quality:质量等级
  • Steps:实际经过的每一步
  • Diagnostics:诊断列表
  • Usage、Stream:响应结果独有,统一 usage 和是否来自流式转换

开发

在 relaykit 目录运行:

GOWORK=off go build ./...
GOWORK=off go test ./...

GOWORK=off 让模块脱离仓库的 workspace 单独构建,这是 new-api 对 RelayKit 改动的检查项。转换矩阵由 golden test 覆盖,协议输出变化确认是预期行为后更新快照:

GOWORK=off go test ./relayconvert -run TestGolden -update

版本

RelayKit 处于 v0.x。公开 API 和 DTO 可能在小版本里变化,升级前对照发布说明和序列化结果。每对协议都有对方没有的字段,建议对业务里用到的工具调用、多模态、推理和流式场景加端到端测试。

许可证

RelayKit 是 new-api 项目的一部分,遵循项目根目录中的 GNU Affero General Public License v3.0。

Directories

Path Synopsis
convmeta
Package convmeta defines the conversion-context contract between format converters (future relaykit) and the hosting application.
Package convmeta defines the conversion-context contract between format converters (future relaykit) and the hosting application.
kitutil
Package kitutil holds the dependency-free helpers shared by the conversion kit packages (dto, types, relayconvert).
Package kitutil holds the dependency-free helpers shared by the conversion kit packages (dto, types, relayconvert).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL