新增 Provider
如何接入一个新的 LLM Provider。
新增 Provider 的核心是 实现 relay/adaptor 接口 + 通过 init() 注册。整个流程零数据库迁移;build 后立即出现在「新建渠道」中。
1. 文件结构
text
relay/adaptor/provider/<provider_id>/
├── register.go # init() 注册 ChannelMeta
├── adaptor.go # 实现 Adaptor 接口
├── main.go # Handler / StreamHandler(响应处理)
└── constants.go # ModelList + 默认 token / 其它常量
relay/adaptor/openai/是 OpenAI 兼容协议基线;多数新 Provider 只需继承它(嵌入openai.Adaptor)。
2. 最小实现
参考 relay/adaptor/provider/deepseek/:
go
// relay/adaptor/provider/deepseek/register.go
package deepseek
import (
"github.com/modelbus/one-api-pro/relay/adaptor"
"github.com/modelbus/one-api-pro/relay/registry"
)
func init() {
registry.Register(registry.ChannelMeta{
ID: "deepseek",
Name: "DeepSeek",
DefaultBaseURL: "https://api.deepseek.com",
LegacyType: 36, // 选个未占用的整数,与历史 type 表兼容
}, func() adaptor.Adaptor { return &Adaptor{} })
}go
// relay/adaptor/provider/deepseek/adaptor.go
package deepseek
import (
"io"
"net/http"
"github.com/gin-gonic/gin"
"github.com/modelbus/one-api-pro/relay/adaptor"
"github.com/modelbus/one-api-pro/relay/adaptor/openai"
"github.com/modelbus/one-api-pro/relay/meta"
)
type Adaptor struct {
openaiAdaptor openai.Adaptor
}
func (a *Adaptor) Init(meta *meta.Meta) {}
func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { return a.openaiAdaptor.GetRequestURL(meta) }
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, m *meta.Meta) error { return a.openaiAdaptor.SetupRequestHeader(c, req, m) }
func (a *Adaptor) ConvertRequest(c *gin.Context, mode int, req *model.GeneralOpenAIRequest) (any, error) {
return a.openaiAdaptor.ConvertRequest(c, mode, req)
}
func (a *Adaptor) ConvertImageRequest(req *model.ImageRequest) (any, error) {
return a.openaiAdaptor.ConvertImageRequest(req)
}
func (a *Adaptor) DoRequest(c *gin.Context, m *meta.Meta, body io.Reader) (*http.Response, error) {
return adaptor.DoRequestHelper(a, c, m, body)
}
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, m *meta.Meta) (*model.Usage, *model.ErrorWithStatusCode) {
if m.IsStream {
err, _, usage := StreamHandler(c, resp)
return usage, err
}
err, usage := Handler(c, resp, m.PromptTokens, m.ActualModelName)
return usage, err
}
func (a *Adaptor) GetModelList() []string { return ModelList }go
// relay/adaptor/provider/deepseek/constants.go
package deepseek
var ModelList = []string{
"deepseek-chat",
"deepseek-reasoner",
}3. 三个关键决定
3.1 ID 与 LegacyType
ID是新渠道的字符串唯一标识(channels.type字段)。LegacyType是历史整型映射;新 Provider 可填任意未占用的正整数(relay/channeltype/define.go是历史分配表,新 Provider 选一个不会被自动复用的即可)。Name显示在 UI。DefaultBaseURL渠道默认值。
3.2 是否完全 OpenAI 兼容
- 完全兼容:像 DeepSeek、Groq、Mistral、
GrokYunwu等,嵌入openai.Adaptor即可,只在main.go写自己的 Usage 解析(如果字段不同)。 - 部分兼容:保留
Adaptor全部方法,自己实现ConvertRequest/DoResponse。 - 完全不同(如 Anthropic 单独的
/v1/messages协议):参考relay/adaptor/anthropic/,重写全部方法。
3.3 流式响应
流式(IsStream=true)通常用 bufio.Scanner 读 SSE,每行解析 JSON 然后 render.StringData(c, data) 透传;[DONE] 哨兵行必须原样转发。
非流式:直接 io.Copy(c.Writer, resp.Body),用 convertDeepSeekUsage 等提取 Usage。
4. 自注册如何被发现
init() 在包被导入时执行;只要新目录被以下任意位置引用,编译即可自动 link:
go
// relay/adaptor/import.go(推荐显式 import,便于 grep)
import (
_ "github.com/modelbus/one-api-pro/relay/adaptor/provider/deepseek"
_ "github.com/modelbus/one-api-pro/relay/adaptor/provider/<your_provider>"
)或隐式(被 controller / monitor 引用的某 provider 间接导入)。
不要在
relay/adaptor/openai/register.go::registerLegacy加你的 Provider:那是历史 OpenAI 兼容 alias 的专属位置,会与下游 ID 冲突。
5. 验证
bash
# 后端构建
go build ./...
# 跑 channeltype 单测,确保 ID
go test ./relay/...
# 重启后到「新建渠道」页面,下拉里应该出现 <provider>6. 提交
按 提交规范 §3 一文件一 commit,body 写明:
text
feat(channel): 新增 <provider> 适配器
Add <provider> adaptor.
- 实现 Adaptor 接口:嵌入 openai.Adaptor,复用 ConvertRequest
- 自定义 DoResponse:解析 <provider> usage 字段
- 注册到 registry:ID="<id>" / LegacyType=<N>
- 常量 ModelList:["<model1>", "<model2>"]7. 进阶
- 图片/Embedding/Audio:若 Provider 支持,复用
openai.Adaptor的ConvertImageRequest或自己实现。 - 特殊 Header:如 Azure 需要
api-key而不是Authorization: Bearer,重写SetupRequestHeader。 - Balance Update:若 Provider 有余额查询接口,在
monitor/channel_balance.go注册一个 fetcher(一般不需要,开箱支持是 OpenAI 系)。