Spring AI教程(二)Chat API之基于数据库的多Key轮询

基于数据库的多Key轮询

 在之前的文章中我们所使用的Key都是一个,但事实上,官方对Key会有一定的请求限制,在实际业务场景下,我们也不可能通过一个Key来保证我们的系统稳定运行,因为一旦超过请求限制,就会出现无法请求AI的情况,这时,就需要考虑实现一种多Key轮询进行请求,从而保证我们的系统不会出现因为达到请求限制而无法运行的情况。

 可惜的是,Spring AI目前还不支持多Key轮询的方式来调用大语言模型,因此需要我们自己实现。本篇将结合我对Spring AI的源码理解,来实现一个基于数据库的多Key轮询的接口。

6.1 核心类 OpenAiApi

 实现多Key轮询的方式并不难,我们将API和Key的信息存储在数据库中,每次发起请求时,通过向数据库中查询API和Key的数据,手动构建一个ChatClient或StreamChatClient进行调用即可。拿OpenAI为例,就是要手动构建OpenAiChatClient对象,因为OpenAiChatClient实现了ChatClientStreamChatClient接口。

 通过观察OpenAiChatClient的构造方法,发现有一个核心类:OpenAiApi,该类的对象创建恰好就需要API和Key。

 因此我们很容易想通创建OpenAiChatClient的流程:

  • 从数据库中查询API和Key的信息;
  • 利用API和Key构建OpenAiApi对象;
  • 通过OpenAiApi对象再构建OpenAiChatClient;
  • 再根据实际业务场景选择ChatClient和StreamChatClient进行调用;

6.2 环境准备

 为便于演示,我新建了一个spring-ai-key-polling-demo的模块。

  • 数据库使用的是Pgvector,它是Postgresql的扩展,可作为向量数据库,当然,本节还未涉及到向量数据库的使用,因此可以根据自己的喜好选择一个数据库;
  • ORM框架:Spring Data JPA,演示起来比较方便,可根据喜好选择一款ORM框架

核心依赖:

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

目录结构如下:

6.3 多Key轮询的基本实现

(1) 定义实体类

 这里我定义了一个KeyInfo用来存储我们的Key和API信息,属性较为简单,实际项目中需要结合实际的业务场景添加一些额外的属性,如是否禁用、创建时间等等。

package com.ningning0111.entity;import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table
public class KeyInfo {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(nullable = false)private String key;@Column(nullable = false,columnDefinition = "VARCHAR(50) DEFAULT 'https://api.openai.com'")private String api;// 描述这个Key干嘛的 可空private String description;}
(2) 定义Repository

 由于使用的是JPA,这里还需要创建Repository,对于Mybatis,需要创建Mapper。

package com.ningning0111.repository;import com.ningning0111.entity.KeyInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;@Repository
public interface KeyInfoRepository extends JpaRepository<KeyInfo,Long> {
}
(3) 配置文件

 接着我们需要配置application.yml文件:

server:port: 8321spring:datasource:driver-class-name: org.postgresql.Driverusername: postgrespassword: postgresurl: jdbc:postgresql://localhost/demojpa:hibernate:# 自动创建表ddl-auto: createshow-sql: trueopen-in-view: trueautoconfigure:exclude: org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration

 因为我们的Key和API都是从数据库中获取的,因此配置文件中就不需要配置了,为了防止相关属性因为无法自动配置而造成Spring启动不起来,就需要将OpenAi的自动装配排除。

(3) Service类
package com.ningning0111.service;import com.ningning0111.entity.KeyInfo;
import com.ningning0111.repository.KeyInfoRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.StreamingChatClient;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Random;@RequiredArgsConstructor
@Service
public class ChatService {private final KeyInfoRepository repository;// 初始化一些key 这些key应该是可调用的@PostConstructpublic void initData() {KeyInfo keyInfo = new KeyInfo();keyInfo.setKey("sk-W9kYxxxxxxxxxxxxxxxxxxxfAd460353Dc7a");keyInfo.setApi("https://api.mnzdna.xyz");keyInfo.setDescription("测试API和Key,请填写自己的Key");repository.save(keyInfo);}// 阻塞式public ChatClient getChatClient() {OpenAiApi openAiApi = randomGetApi();assert openAiApi != null;return new OpenAiChatClient(openAiApi);}// 流式public StreamingChatClient getStreamChatClient() {OpenAiApi openAiApi = randomGetApi();assert openAiApi != null;return new OpenAiChatClient(openAiApi);}// 随机获取一个OpenAiApiprivate OpenAiApi randomGetApi(){List<KeyInfo> keyInfoList = repository.findAll();// 如果数据库中没有KeyInfo对象,则返回nullif (keyInfoList.isEmpty()) {return null;}// 随机选择一个KeyInfo对象Random random = new Random();KeyInfo randomKeyInfo = keyInfoList.get(random.nextInt(keyInfoList.size()));return new OpenAiApi(randomKeyInfo.getApi(),randomKeyInfo.getKey());}
}
(4) Controller类
package com.ningning0111.controller;import com.ningning0111.service.ChatService;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequiredArgsConstructor
public class ChatController {private final ChatService chatService;@GetMapping("/demo")public String chatDemo(String prompt){ChatClient chatClient = chatService.getChatClient();String response = chatClient.call(prompt);return response;}
}

效果如下:

6.4 多Key轮询的优化

 上面的代码是多Key轮询实现的简单方式,但是,由于每次对话都需要从数据库中进行查询,再加上查询到结果后还需要发起网络请求等待响应,因此当Key的数量特别多时,会大大增加响应的时间。为此,我们有必要对其进行优化。这里简单介绍下我使用过的优化方式:

  • 容器启动时,先将数据库里的数据加载到内存中,即:使用List存储;
  • 针对实体KeyInfo的增删改操作,每次操作完成后重新从数据库中加载数据到内存中;
  • 每次轮询都是基于List进行的,而不再是基于数据库;
  • 当轮询到的Key失效时,将其从数据库中删除(删除后会重新加载)
  • 每次轮询前对List进行判空,若空,则重新从数据库加载数据

 具体的实现代码就不展示了,实现逻辑并不难,并且优化策略也有很多,可以结合实际情况实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/640500.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RTSP/Onvif视频监控平台EasyNVR如何提高匿名用户的用户名和密码安全性?

EasyNVR安防视频云平台是旭帆科技TSINGSEE青犀旗下支持RTSP/Onvif协议接入的安防监控流媒体视频云平台。平台具备视频实时监控直播、云端录像、云存储、录像检索与回看、告警等视频能力&#xff0c;能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、W…

python学习笔记B-07:序列结构之列表--列表的常用函数和方法

以xx_函数名(列表名)的形式出现的是函数&#xff1b;以xx_列表名.xx_方法名的形式出现的是方法。 列表常用函数如下&#xff1a; len()&#xff1a;计算列表元素数量 max()&#xff1a;获取列表元素最大值 min():获取列表元素最小值 sum():计算列表中各元素之和 列表常用方法如…

echart自定义图片填充柱状图

echart用自定义图片填充柱状图 关键代码 type: pictorialBar 加记得加image://或者直接使用bas64格式图片 const testIcon "image://"require(/assets/test.svg) svg图片 <?xml version"1.0" standalone"no"?><!DOCTYPE svg PUBL…

都2024年了,你还不知道git worktree么?

三年前 python 大佬吉多范罗苏姆(为 Python 程序设计语言的最初设计者及主要架构师)才知道 git worktree &#xff0c;我现在才知道&#xff0c;我觉得没啥丢人的。 应用场景 如果你正在 feature 的分支中开发新功能&#xff0c;线上版本紧急错误又需要你基于 master 做修复。…

交易系统的设计与实现

前言 在线交易是生活中必不可少的&#xff0c;交易包括支付和转账&#xff0c;简单理解就是个人或者商户的银行账户下资产的增减行为。 在线支付一般需要借助网联进行&#xff0c;网联是网联清算有限公司(NetsUnion Clearing Corporation,简称NUCC)运营的第三方支付机构统一…

NetFlow 监控工具

NetFlow 是流量数据统计使用最广泛的标准&#xff0c;它是由思科公司开发&#xff0c;用于监控和记录传入或传出接口的所有流量&#xff0c;NetFlow 分析其收集的流量数据&#xff0c;以提供流量和流量的可见性&#xff0c;并跟踪流量的来源、去向以及随时产生的流量。记录的信…

进程及进程地址空间

进程理解 概念&#xff1a;进程是程序的一个执行实例&#xff0c;其实启动一个程序&#xff08;静态&#xff09;本质就是启动了一个进程&#xff08;动态&#xff09;&#xff0c;进程具有独立性。 用户角度&#xff1a;进程代码数据内核数据结构&#xff08;PCB结构体页表操…

UI5:面向企业级应用的JavaScript框架

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

T31开发笔记: 移动侦测

若该文为原创文章&#xff0c;转载请注明原文出处。 最近在测试创安源IPC时发现摄像头的视频流有移动侦测功能 &#xff0c;拆解后发现使用的是T31,刚好手头上有淘宝买50多点的T31摄像头&#xff0c;就自己现在了个简易DEMO测试一下。 一、硬件和开发环境 1、硬件&#xff1a;…

Linux学习之路 -- 进程篇 -- 进程地址空间

目录 一、背景介绍 二、进程地址空间 1.看现象 2.先简单描述一下地址空间&#xff08;地址空间全在操作系统的内部&#xff09; 3.地址空间详细一点的描述 4.进程地址空间里面的内容&#xff08;部分&#xff09; 三、进程地址空间的转换机制 1.页表 2.进程地址空间和页…

YoloV8改进策略:卷积改进|DOConv轻量卷积,即插即用|适用各种场景

摘要 本文使用DOConv卷积&#xff0c;替换YoloV8的常规卷积&#xff0c;轻量高效&#xff0c;即插即用&#xff01;改进方法非常简单。 DO-Conv&#xff08;Depthwise Over-parameterized Convolutional Layer&#xff09;是一种深度过参数化的卷积层&#xff0c;用于提高卷…

【Web】HNCTF 2022 题解(全)

目录 Week1 Interesting_include 2048 easy_html What is Web Interesting_http easy_upload Week2 ez_SSTI easy_include ez_ssrf Canyource easy_unser easy_sql ohmywordpress Week3 ssssti Fun_php ez_phar QAQ_1inclu4e logjjjjlogjjjj …