当前位置: 首页 > article >正文

从源码深入理解One-API框架:适配器模式实现LLM接口对接

1. 概述

one-api 是一个开源的 API 框架,基于go语言开发,旨在提供统一的接口调用封装,支持多种 AI 服务平台的集成。通过 Gin 和 GORM 等框架,框架简化了多种 API 服务的调用流程。通过适配器模式实现了与多种 大模型API 服务的集成,而无需每次都重新编写调用逻辑。使得开发者能够专注于业务逻辑,而不是各个平台间的差异化处理。
在本文中,将深入解读 one-api 框架的工作原理,详细讲解框架的结构与实现,并通过集成阿里灵积DashScope大模型服务 API 为例,展示其适配器实现。
在这里插入图片描述

2. 路由与控制器

在 one-api 中,所有的 API 请求都会通过 Gin 框架来处理。以下是一个典型的请求路由:
​ relayV1Router.POST("/chat/completions", controller.Relay) ​
在这里插入图片描述

和大模型交互相关的核心逻辑都集中在 controller.Relay函数中。
​​​​在这里插入图片描述
如上图是Relay函数的实现,主要完成三个事情:
(1)获取relayMode
(2)根据repayMode请求LLM接口
(3)异常处理及重试

3. GetByPath函数

GetByPath 函数会根据请求路径返回不同的 relayMode,比如文本生成、图像生成或音频处理等。
在这里插入图片描述

4. relayHelper函数

根据获取到的 relayMode,框架会调用不同的处理方法。具体代码如下:
在这里插入图片描述
如果 relayMode 为文本请求,则调用 controller.RelayTextHelper(c);如果是图像生成请求,则调用 RelayImageHelper 等。

5. RelayTextHelper函数

RelayTextHelper 是 one-api 框架中负责处理文本请求的核心函数,它包含了一系列的操作步骤来完成整个请求的处理流程。

  • 从请求中获取并验证 textRequest
  • 获取待调用的模型名称
  • 设置system prompt
  • 获取请求的配额和使用限制
  • 预消耗配额
  • 根据APIType获取适配器adaptor
  • 构建对应adaptor的请求体
  • 使用对应的adaptor的向大模型发起请求
  • 处理封装请求结果
  • 最终消耗扣减配额
    整个过程绝大多数的过程都是在​做参数转换及参数设置,比较关键的一步是“构建实际的请求体”。使用适配器模式对各种大模型接口进行适配,根据不同的apiType得到不同的适配器对象,如openai.Adaptor、ali.Adaptor、baidu.Adaptor、zhipu.Adaptor等。

注:apiType的获取方式为:
meta.APIType = channeltype.ToAPIType(meta.ChannelType),即与在one-api管理页面上配置的渠道类型一致,如果配置的是“阿里通义千问”,那么apiType就为apitype.Ali。具体可参见源码中的relay/channeltype/helper.go中的ToAPIType(channelType int) int函数,在此不做赘述。
在这里插入图片描述
Adaptor是一个接口,定义了如下方法:
在这里插入图片描述
接入第三方LLM接口的时候只需要实现对应的适配器即可完成对接,截止发文当日one-api已经集成了近40家AI厂商的大模型接口,包括国内的baidu、ali、doubao、zhipu等。
在这里插入图片描述

6. 阿里灵积DashScope适配

以下是阿里灵积DashScope大模型服务的适配器实现:

package ali

type Adaptor struct {
	meta *meta.Meta
}

func (a *Adaptor) Init(meta *meta.Meta) {
	a.meta = meta
}

func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
	fullRequestURL := ""
	switch meta.Mode {
	case relaymode.Embeddings:
		fullRequestURL = fmt.Sprintf("%s/api/v1/services/embeddings/text-embedding/text-embedding", meta.BaseURL)
	case relaymode.ImagesGenerations:
		fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/text2image/image-synthesis", meta.BaseURL)
	default:
		fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/text-generation/generation", meta.BaseURL)
	}

	return fullRequestURL, nil
}

func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error {
	adaptor.SetupCommonRequestHeader(c, req, meta)
	if meta.IsStream {
		req.Header.Set("Accept", "text/event-stream")
		req.Header.Set("X-DashScope-SSE", "enable")
	}
	req.Header.Set("Authorization", "Bearer "+meta.APIKey)

	if meta.Mode == relaymode.ImagesGenerations {
		req.Header.Set("X-DashScope-Async", "enable")
	}
	if a.meta.Config.Plugin != "" {
		req.Header.Set("X-DashScope-Plugin", a.meta.Config.Plugin)
	}
	return nil
}

func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.GeneralOpenAIRequest) (any, error) {
	if request == nil {
		return nil, errors.New("request is nil")
	}
	switch relayMode {
	case relaymode.Embeddings:
		aliEmbeddingRequest := ConvertEmbeddingRequest(*request)
		return aliEmbeddingRequest, nil
	default:
		aliRequest := ConvertRequest(*request)
		return aliRequest, nil
	}
}

func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (any, error) {
	if request == nil {
		return nil, errors.New("request is nil")
	}

	aliRequest := ConvertImageRequest(*request)
	return aliRequest, nil
}

func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBody io.Reader) (*http.Response, error) {
	return adaptor.DoRequestHelper(a, c, meta, requestBody)
}

func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Meta) (usage *model.Usage, err *model.ErrorWithStatusCode) {
	if meta.IsStream {
		err, usage = StreamHandler(c, resp)
	} else {
		switch meta.Mode {
		case relaymode.Embeddings:
			err, usage = EmbeddingHandler(c, resp)
		case relaymode.ImagesGenerations:
			err, usage = ImageHandler(c, resp)
		default:
			err, usage = Handler(c, resp)
		}
	}
	return
}

func (a *Adaptor) GetModelList() []string {
	return ModelList
}

func (a *Adaptor) GetChannelName() string {
	return "ali"
}

这段代码实现了 Adaptor 接口,适配了阿里云 AI 服务的请求和响应处理。通过一系列方法,Adaptor 能够完成以下任务:

  • 根据请求模式生成 API 请求 URL。
  • 设置 HTTP 请求头,包括授权信息、流式请求标识等。
  • 将通用请求数据转换为阿里云特定的请求格式。
  • 向阿里云服务发起请求,并处理响应。
  • 获取支持的模型列表和通道名称。
  • 它为 one-api 框架提供了对阿里云 AI 服务的无缝集成,简化了与阿里云接口交互的代码。

总结

综合上述内容,通过一张时序图来说明整个调用流程:
在这里插入图片描述

参考说明

one-api:https://github.com/songquanpeng/one-api
gorm: https://gorm.io/zh_CN/docs/index.html
gin: https://gin-gonic.com/zh-cn/docs/


http://www.kler.cn/a/523184.html

相关文章:

  • gesp(C++六级)(6)洛谷:P10109:[GESP202312 六级] 工作沟通
  • Flutter_学习记录_基本组件的使用记录
  • 1.26学习
  • ZZNUOJ(C/C++)基础练习1011——1020(详解版)
  • 9.8 实战:使用 GPT Builder 开发定制化 ChatGPT 应用
  • 汇编的使用总结
  • python Flask-Redis 连接远程redis
  • GWO优化决策树分类预测matlab
  • 硬脂酸单甘油酯(GMS)行业分析
  • LeetCode:509.斐波那契数
  • Linux - 进程间通信(2)
  • python flask 使用 redis写一个例子
  • STranslate 中文绿色版即时翻译/ OCR 工具 v1.3.1.120
  • C语言数据类型及取值范围
  • 构建一个有智能体参与的去中心化RWA零售生态系统商业模型
  • go理论知识记录(入门2)
  • 一文大白话讲清楚webpack进阶——4——webpack原理
  • 云计算技术深度解析与代码使用案例
  • Vue.js组件开发-实现下载时暂停恢复下载
  • 团体程序设计天梯赛-练习集——L1-024 后天
  • 自动化运维在云环境中的完整实践指南
  • LeetCode 16. 排列序列
  • 安卓入门四十四 其他动画
  • 每日 Java 面试题分享【第 15 天】
  • Leetcode::119. 杨辉三角 II
  • Golang :用Redis构建高效灵活的应用程序