1、概览 {#1概览}
在本教程中,我们将使用 Spring AI 框架和 RAG(检索增强生成)技术构建一个 ChatBot(聊天机器人)。在 Spring AI 的加持下,我们将与 Redis Vector(向量)数据库集成,以存储和检索数据,从而增强 LLM(大型语言模型)的提示功能。一旦 LLM 接收到包含相关数据的提示,它就会有效地用自然语言生成带有最新数据的响应,以回应用户的查询。
2、RAG 是什么? {#2rag-是什么}
LLM 是根据互联网上的大量数据集预先训练的机器学习模型。要使 LLM 在私营企业中发挥作用,我们必须根据特定组织的知识库对其进行微调。然而,微调通常是一个耗时的过程,需要大量的计算资源。此外,经过微调的 LLM 很有可能会对查询生成不相关或误导性的响应。这种行为通常被称为 LLM 幻觉(LLM Hallucinations)。
在这种情况下,RAG 是一种优秀的技术,用于限制或将 LLM 的响应置于特定上下文中。向量数据库在 RAG 架构中发挥着重要作用,为 LLM 提供上下文信息。但是,在 RAG 架构中使用矢量(向量)数据库之前,应用必须通过 ETL(提取、转换和加载)流程对其进行填充:
Reader 从不同源检索组织的知识库文档。然后,Transformer(转换器)将检索到的文档分割成小块,并使用嵌入模型对内容进行矢量化。最后,Writer 将向量或 Embedding 加载到向量数据库。向量数据库是专门的数据库,可以在多维空间中存储这些 Embedding。
在 RAG 中,如果矢量数据库定期从组织的知识库中更新,那么 LLM 就能对几乎实时的数据做出响应。
一旦矢量数据库中的数据准备就绪,应用就可以使用它来检索用户查询的上下文数据:
应用将用户查询与矢量数据库中的上下文数据相结合形成提示,最后将其发送给 LLM。LLM 在上下文数据的范围内用自然语言生成回复,并将其发送回应用。
3、使用 Spring AI 和 Redis 实现 RAG {#3使用-spring-ai-和-redis-实现-rag}
Redis Stack 提供矢量搜索服务,我们将使用 Spring AI 框架与之集成,并构建一个基于 RAG 的 ChatBot(聊天机器人)应用。此外,我们还要使用 OpenAI 的 GPT-3.5 Turbo LLM 模型来生成最终响应。
3.1、先决条件 {#31先决条件}
对于 ChatBot,需要 API Key 来认证 OpenAI 服务。在创建 OpenAI 账号 之后,创建一个API Key。
还要创建一个 Redis Cloud 账户,以访问免费的 Redis Vector DB:
我们使用 Spring AI 整合 Redis 向量数据库和 OpenAI 服务。
Maven 依赖如下:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M1</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-transformers-spring-boot-starter</artifactId>
<version>1.0.0-M1</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-spring-boot-starter</artifactId>
<version>1.0.0-M1</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
<version>1.0.0-M1</version>
</dependency>
3.2、将数据加载 Redis 的关键类 {#32将数据加载-redis-的关键类}
在 Spring Boot 应用中,我们要创建用于从 Redis Vector DB 加载和检索数据的组件。例如,将把员工手册 PDF 文档加载到 Redis 数据库中。
所涉及的关键类如下:
DocumentReader
是 Spring AI 用于读取文档的接口。我们使用 DocumentReader
开箱即用的实现,PagePdfDocumentReader
。同样,DocumentWriter
和 VectorStore
也是将数据写入存储系统的接口。RedisVectorStore
是 VectorStore
的众多开箱即用实现之一,我们用它来加载和搜索 Redis Vector DB 中的数据。
我们使用前面介绍过的 Spring AI 框架类来编写 DataLoaderService
。
3.3、实现 DataLoaderService {#33实现-dataloaderservice}
先来了解一下 DataLoaderService
类中的 load()
方法:
@Service
public class DataLoaderService {
private static final Logger logger = LoggerFactory.getLogger(DataLoaderService.class);
@Value("classpath:/data/Employee_Handbook.pdf")
private Resource pdfResource;
@Autowired
private VectorStore vectorStore;
public void load() {
PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(this.pdfResource,
PdfDocumentReaderConfig.builder()
.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
.withNumberOfBottomTextLinesToDelete(3)
.withNumberOfTopPagesToSkipBeforeDelete(1)
.build())
.withPagesPerDocument(1)
.build());
var tokenTextSplitter = new TokenTextSplitter();
this.vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));
}
}
load()
方法使用 PagePdfDocumentReader
类读取 PDF 文件并将其加载到 Redis 向量数据库。Spring AI 框架会使用命名空间 spring.ai.vectorstore
中的 配置属性 自动配置 VectoreStore
接口:
spring:
ai:
vectorstore:
redis:
uri: redis://:PQzkkZLOgOXXX@redis-19438.c330.asia-south1-1.gce.redns.redis-cloud.com:19438
index: faqs
prefix: "faq:"
initialize-schema: true
框架会将 RedisVectorStore
对象(VectorStore
接口的实现)注入 DataLoaderService
。
TokenTextSplitter
类分割文档,最后,VectorStore
类将分块加载到 Redis 向量数据库中。
3.4、生成最终回复的关键类 {#34生成最终回复的关键类}
Redis 向量数据库准备就绪后,我们就可以检索与用户查询相关的上下文信息。之后,这些上下文信息将用于形成提示,供 LLM 生成最终响应。
来看看关键的类:
DataRetrievalService
类中的 searchData()
方法会接收查询,然后从 VectorStore
中检索上下文数据。ChatBotService
利用这些数据,使用 PromptTemplate
类形成提示,然后将其发送给 OpenAI 服务。Spring Boot 框架会从 application.yml
文件中读取与 OpenAI 相关的属性,然后自动配置 OpenAIChatModel
对象。
下面来详细了解一下实现过程。
3.5、实现 ChatBotService {#35实现-chatbotservice}
ChatBotService
类如下:
@Service
public class ChatBotService {
@Autowired
private ChatModel chatClient;
@Autowired
private DataRetrievalService dataRetrievalService;
private final String PROMPT_BLUEPRINT = """
Answer the query strictly referring the provided context:
{context}
Query:
{query}
In case you don't have any answer from the context provided, just say:
I'm sorry I don't have the information you are looking for.
""";
public String chat(String query) {
return chatClient.call(createPrompt(query, dataRetrievalService.searchData(query)));
}
private String createPrompt(String query, List<Document> context) {
PromptTemplate promptTemplate = new PromptTemplate(PROMPT_BLUEPRINT);
promptTemplate.add("query", query);
promptTemplate.add("context", context);
return promptTemplate.render();
}
}
SpringAI 框架使用命名空间 spring.ai.openai
中的 OpenAI 配置属性创建 ChatModel Bean:
spring:
ai:
vectorstore:
redis:
# Redis 向量存储相关属性...
openai:
temperature: 0.3
api-key: ${SPRING_AI_OPENAI_API_KEY}
model: gpt-3.5-turbo
#embedding-base-url: https://api.openai.com
#embedding-api-key: ${SPRING_AI_OPENAI_API_KEY}
#embedding-model: text-embedding-ada-002
框架还可以从环境变量 SPRING_AI_OPENAI_API_KEY
中读取 API KEY,这是一种更为安全的方式。我们可以启用以 "embedding" 文本开头的 KEY 来创建 OpenAiEmbeddingModel
Bean,该 Bean 用于将知识库文档转换为向量 Embedding。
对于 OpenAI 服务的提示必须是明确的。因此,在提示蓝图 PROMPT_BLUEPRINT
中,我们严格要求仅从上下文信息中形成响应。
在 chat()
方法中,我们从 Redis 向量数据库中检索与查询匹配的文档。然后,使用这些文档和用户查询在 createPrompt()
方法中生成提示。最后,调用 ChatModel
类的 call()
方法来接收 OpenAI 服务的响应。
现在,让我们通过向之前加载到 Redis Vector 数据库中的员工手册问一个问题,来检查聊天机器人服务的运行情况:
@Test
void whenQueryAskedWithinContext_thenAnswerFromTheContext() {
String response = chatBotService.chat("How are employees supposed to dress?");
assertNotNull(response);
logger.info("Response from LLM: {}", response);
}
然后,我们可以看到输出结果:
Response from LLM: Employees are supposed to dress appropriately for their individual work responsibilities and position.
输出结果与加载到 Redis 向量数据库中的员工手册 PDF 文档一致。
来看看问一些员工手册上没有的问题,会发生什么情况:
@Test
void whenQueryAskedOutOfContext_thenDontAnswer() {
String response = chatBotService.chat("What should employees eat?");
assertEquals("I'm sorry I don't have the information you are looking for.", response);
logger.info("Response from the LLM: {}", response);
}
输出结果如下:
Response from the LLM: I'm sorry I don't have the information you are looking for.
LLM 在所提供的上下文中找不到任何内容,因此无法回答询问。
4、总结 {#4总结}
本文介绍了使用 Spring AI 框架实现基于 RAG 架构的应用。利用上下文信息形成提示对于从 LLM 生成正确的响应至关重要。因此,Redis Vector(向量)数据库是存储和执行文档向量相似性搜索的绝佳解决方案。此外,对文档进行分块处理对于获取正确的记录和限制提示 Token 的成本也同样重要。
Ref:https://www.baeldung.com/spring-ai-redis-rag-app