1. 概述
在本教程中,我们将使用Spring AI框架和RAG(检索增强生成)技术构建一个ChatBot。借助Spring AI,我们将与Redis Vector数据库集成以存储和检索数据,以增强LLM(大语言模型)的提示。一旦LLM收到包含相关数据的提示,它就会有效地生成包含最新数据的自然语言响应来响应用户查询。
2. RAG是什么?
LLM是使用来自互联网的大量数据集进行预训练的机器学习模型,要使LLM在私营企业中发挥作用,我们必须使用特定于组织的知识库对其进行微调。然而,微调通常是一个耗时的过程,需要大量的计算资源。此外,微调后的LLM很有可能对查询产生不相关或误导性的响应;这种行为通常被称为LLM幻觉。
在这种情况下,RAG是一种限制或情境化LLM响应的绝佳技术。向量数据库在RAG架构中起着重要作用,可以为LLM提供上下文信息。但是,在应用程序可以在RAG架构中使用它之前,必须先进行ETL(提取、转换和加载)过程来填充它:
读取器从不同来源检索组织的知识库文档;然后,转换器将检索到的文档拆分成更小的块,并使用嵌入模型对内容进行向量化。最后,写入器将向量或嵌入加载到向量数据库中。向量数据库是可以将这些嵌入存储在多维空间中的专用数据库。
在RAG中,如果向量数据库定期从组织的知识库更新,LLM就可以响应几乎实时的数据。
一旦向量数据库准备好数据,应用程序就可以使用它来检索用户查询的上下文数据:
应用程序将用户查询与向量数据库中的上下文数据相结合形成提示,并最终将其发送给LLM,LLM在上下文数据的边界内以自然语言生成响应并将其发送回应用程序。
3. 使用Spring AI和Redis实现RAG
Redis栈提供向量搜索服务,我们将使用Spring AI框架与其集成并构建基于RAG的ChatBot应用程序。此外,我们将使用OpenAI的GPT-3.5 TurboLLM模型来生成最终响应。
3.1 先决条件
对于ChatBot服务,为了认证OpenAI服务,我们需要API Key。在创建OpenAI帐户后,我们将创建一个:
我们还将创建一个Redis Cloud帐户来访问免费的Redis Vector DB:
为了与Redis Vector DB和OpenAI服务集成,我们将使用Spring AI库更新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中的关键类
在Spring Boot应用程序中,我们将创建用于从Redis Vector DB加载和检索数据的组件。例如,我们将员工手册PDF文档加载到Redis DB中。
现在,我们来看看涉及的类:
DocumentReader是用于读取文档的Spring AI接口,我们将使用开箱即用的PagePdfDocumentReader实现DocumentReader。同样,DocumentWriter和VectorStore是用于将数据写入存储系统的接口,RedisVectorStore是VectorStore的众多开箱即用实现之一,我们将使用它在Redis Vector DB中加载和搜索数据。我们将使用迄今为止讨论过的Spring AI框架类编写DataLoaderService。
3.3 实现数据加载器服务
让我们了解一下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 Vector DB,Spring AI框架使用命名空间spring.ai.vectorstore中的配置属性自动配置VectorStore接口:
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 Vector DB中。
3.4 生成最终响应的关键类
一旦Redis Vector DB准备就绪,我们就可以检索与用户查询相关的上下文信息。之后,此上下文用于形成LLM的提示以生成最终响应。让我们看看关键的类:
DataRetrievalService类中的searchData()方法接收查询,然后从VectorStore检索上下文数据。ChatBotService使用此数据通过PromptTemplate类形成提示,然后将其发送到OpenAI服务。Spring Boot框架从application.yml文件中读取与OpenAI相关的相关属性,然后自动配置OpenAIChatModel对象。在本文中,我们将Spring的Profile设置为“airag”。
让我们直接进入实现部分来详细了解。
3.5 实现聊天机器人服务
我们来看看ChatBotService类:
@Service
public class ChatBotService {
@Qualifier("openAiChatModel")
@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();
}
}
Spring AI框架使用命名空间spring.ai.openai中的OpenAI配置属性创建ChatModel Bean:
spring:
ai:
vectorstore:
redis:
# Redis vector store related properties...
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,这是一个非常安全的选项。我们可以启用以文本嵌入开头的Key来创建OpenAiEmbeddingModel Bean,该Bean用于从知识库文档中创建向量嵌入。
OpenAI服务的提示必须明确,因此,我们在提示蓝图PROMPT_BLUEPRINT中严格指示仅从上下文信息中形成响应。
在chat()方法中,我们检索与Redis Vector DB中的查询匹配的文档。然后,我们使用这些文档和用户查询在createPrompt()方法中生成提示。最后,我们调用ChatModel类的call()方法来接收来自OpenAI服务的响应。
现在,让我们通过向之前加载到Redis Vector DB中的员工手册询问一个问题来检查聊天机器人服务的实际运行情况:
@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 Vector DB中的员工手册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. 总结
在本文中,我们讨论了使用Spring AI框架实现基于RAG架构的应用程序。使用上下文信息形成提示对于从LLM生成正确的响应至关重要,因此,Redis Vector DB是存储和对文档向量执行相似性搜索的绝佳解决方案。此外,对文档进行分块对于获取正确的记录和限制提示Token的成本同样重要。
Post Directory
