SpringAI整合DeepSeek,阿里百炼大模型
1. DeepSeek账号注册,生成Api-Key
访问DeepSeek开放平台:[https://platform.deepseek.com/usage](https://platform.deepseek.com/usage),注册DS账号,获取Api-Key。
2. 阿里账号注册,生成Api-Key
访问阿里百炼开放平台:[https://www.aliyun.com/product/tongyi](https://www.aliyun.com/product/tongyi),注册阿里账号,获取Api-Key。
3. SpringAI对接大模型
搭建springboot项目,引入相关依赖。
注意:
需要引入springboot 3.x.x版本的依赖
JDK版本需要大于17
使用高版本Idea(低版本无法兼容JDK17)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
随后需要编写相关配置,例如:baseUrl、Api-key等(DS、阿里百炼均可使用OpenAI协议对接大模型)
spring:
application:
name: springai
#阿里百炼模型配置
ai:
openai:
api-key: xxxx(自己申请的api-key)
base-url: https://dashscope.aliyuncs.com/compatible-mode
chat:
options:
model: qwen-plus
#ds模型配置
ai:
openai:
api-key: xxxx(自己申请的api-key)
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
基础配置配置完成之后,需要在spring容器中新建一个ChatClient的Bean,用于托管相关固定配置,例如:日志、系统提示词、会话记忆等。
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
/**
* @Description:
* @Param:
* @Author:
* @Date:
*/
@Configuration
public class Client {
@Bean
public ChatMemory chatMemory(){
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(OpenAiChatModel openAiChatModel){
return ChatClient.builder(openAiChatModel)
.defaultSystem("你是一个智能助手,名字叫CJ")
.build();
}
}
新增接口,用于实现接口访问,调用远程大模型。
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class TestController {
private final ChatClient client;
/**
* 1.流式响应接口,内容不会一次性返回
* 2.若要一次性返回,则需要将stream()方法切换为call()方法
* 3.使用流失返回,需要注明接口返回提字符编码,否则会导致乱码!(produces = "text/html;charset=utf-8")
* @param prompt 用户提示词
* @return
*/
@GetMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chat(@RequestParam(value = "prompt") String prompt){
return client.prompt()
.user(prompt)
.stream()
.content();
}
}
至此,大模型会话功能的调用已经初步实现了,但是为了方便开发以及问题定位,我们需要引入相关日志组件,便于排查开发、调试用遇到的问题。
SpringAI给我们提供了多种日志工具,我们使用SimpleLoggerAdvisor组件。该组件是将日志输出在控制台上。
日志配置主要分为两步:
1.在ChatClient中配置Advisor,其原理是在在调用模型前后,利用AOP的原理执行相关动作;
2.配置日志级别。
@Bean
public ChatClient chatClient(OpenAiChatModel openAiChatModel){
return ChatClient.builder(openAiChatModel)
.defaultSystem("你是一个智能助手,名字叫CJ")
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
logging:
level:
org.springframework.ai.chat.client.advisor: debug
com.controller: debug
此时在控制台中可以看到相关的日志输出,但是缺乏提示词的记忆。例如:
步骤一:提示词内容为“我的名字叫xx”,发送请求大模型;
步骤二:提示词内容为“我叫什么?”,发送请求大模型;
输出结果:大模型不清楚我的名字是什么。。。
出现当前问题的原因是当前程序中并未开启相关会话记忆功能。
SpringAI给我们提供了相关会话记忆的工具(InMemoryChatMemory),同样是使用Advisor做会话记忆,将每次会话内容存储在内存中,再发送新消息是,默认发送用户之前输入、输出的历史会话内容。由此可见,与大模型对话次数越多,效率则会越差。
我们可以重写InMemoryChatMemory中的方法,来自定义会话内容的存储位置以及存储方式。目前仅介绍使用原生InMemoryChatMemory方法来做会话存储。
@Bean
public ChatMemory chatMemory(){
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(OpenAiChatModel openAiChatModel){
return ChatClient.builder(openAiChatModel)
.defaultSystem("你是一个智能助手,名字叫CJ")
.defaultAdvisors(new SimpleLoggerAdvisor(), new MessageChatMemoryAdvisor(chatMemory()))
.build();
}
现在,按照之前的步骤输入内容,大模型已经可以返回我们的名字是什么了。但是在一个成熟的人机会话交互产品中,仍然缺少很多东西。例如:可查询,可见的会话历史、开启新窗口而开启新的会话等;这些需要我们借助前端来实现。例如:前端在每一个用户下的会话窗口提供一个唯一ID,当前ID会传递到后端用于做会话标识存储起来(MySQL等),同时存储不同会话ID下的历史会话记录。
4.实现基于RAG索引实现本地向量知识库
-
RAG
在我们对接好大模型之后,会发现简单的问题,大模型可以给予我们帮助。但是对待一些专业性比较强的问题,大模型也无法就我们正确的答案,并且有时可能会出现大模型随意乱造结果(幻觉),影响用户的判断。为了解决这一问题,我们引入了RAG以及向量数据库,便于无需二次训练模型即可生成正确回答,减少幻觉发生的概率。Retrieval-Augmented Generation,检索、增强、生成。其中检索模块负责从外部知识库中搜索与用户问题最相关的文本内容。生成模块负责将检索到的相关信息整合成自然语言的答案。
-
RAG的工作流程
1.问题输入
用户向RAG系统提出一个自然语言问题。2.检索相关文档
RAG系统的检索模块根据用户的问题,从外部知识库中检索出最相关的文档或段落。3.信息整合
检索到的文档或段落被传递给生成模块,生成模块将这些信息与用户的问题结合起来。4.生成答案
生成模块利用预训练的语言模型,生成最终的自然语言答案。SpringAI给我们提供了一套实现方式,基于内存的向量数据库,基于Redis的向量数据库等。为了快速上手,我们先通过内存的方式。
引入依赖
<!-- 处理PDF文件 -> document文件 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
新增向量模型相关配置
spring:
application:
name: xx
ai:
openai:
api-key: xxxxx
base-url: https://dashscope.aliyuncs.com/compatible-mode
chat:
options:
model: deepseek-r1
#向量模型配置
embedding:
options:
model: text-embedding-v3
logging:
level:
org.springframework.ai.chat.client.advisor: debug
com.chinaunicom.controller: debug
到此,基本的环境已经具备,现在需要我们将本地PDF文件当做资料上传给我们的向量数据库中用作检索(SimpleVectorStore:基于内存存储的向量数据库)。
/**
* 初始化一个基于内存存储的向量数据库
* @param embeddingModel
* @return
*/
@Bean
public VectorStore vectorStore(OpenAiEmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel).build();
}
@Bean
public ChatClient chatClient(OpenAiChatModel openAiChatModel, ChatMemory chatMemory, VectorStore vectorStore) {
return ChatClient
.builder(openAiChatModel)
.defaultSystem("你是一个智能助手,名字叫CJ")
//日志横切面监控
.defaultAdvisors(
new SimpleLoggerAdvisor(),
new MessageChatMemoryAdvisor(chatMemory),
//配置向量搜索
new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().similarityThreshold(0.5f).topK(2).build()))
.build();
}
此时,基本的向量数据库已经搭建好了,现在需要我们上传相关资料。
/**
* 写入向量库
*/
@GetMapping(value = "/storePdf", produces = "text/html;charset=utf-8")
public String storePdf(){
Resource resource = new FileSystemResource("D:\\1.pdf");
//创建PDF读取
PagePdfDocumentReader reader = new PagePdfDocumentReader(
resource, // 文件
PdfDocumentReaderConfig.builder()
.withPageExtractedTextFormatter(ExtractedTextFormatter.defaults())
.withPagesPerDocument(1) // 每1页PDF作为一个Document
.build()
);
//转存入向量库
List<Document> documents = reader.read();
vectorStore.add(documents);
return "success";
}
/**
* 基于向量库的问答
* @param question
* @return
*/
@GetMapping(value = "/ragChat", produces = "text/html;charset=utf-8")
public Flux<String> ragChat(@RequestParam(value = "question")String question){
return client.prompt().user(question).stream().content();
}