1. 概述
本教程將使用 Spring AI 框架和 RAG(檢索增強生成)技術構建一個聊天機器人。藉助 Spring AI,我們將與 Redis 向量數據庫 集成,用於存儲和檢索數據,以增強 LLM(大型語言模型)的提示。當 LLM 接收到包含相關數據的提示後,它將以自然語言生成對用户查詢的響應,並利用最新的數據。
2. What Is Rag?
LLM 是在互聯網上廣泛使用的大型數據集上預訓練的機器學習模型。為了使 LLM 在私營企業中發揮作用,必須使用組織特定的知識庫對其進行微調。然而,微調通常是一個耗時且需要大量計算資源的過程。此外,微調後的 LLM 生成不相關或誤導性響應的可能性很大。這種行為通常被稱為 LLM 的幻覺。
在這種情況下,RAG 是一種極好的技術,可以限制或使 LLM 的響應得到上下文化。
向量數據庫在 RAG 架構中發揮着重要作用,為 LLM 提供上下文信息。但是,在應用程序可以使用 RAG 架構之前,必須使用 ETL(提取、轉換和加載)過程對其進行填充:
The Reader retrieves the organization’s knowledge base documents from different sources. Then, the Transformer splits the retrieved documents into smaller chunks and uses an embedding model to vectorize the contents.
最後,作者將向量或嵌入加載到向量數據庫中。向量數據庫是專門用於存儲這些嵌入的多維空間中的數據庫。
在 RAG 中,LLM 可以在知識庫定期更新時響應幾乎實時的實時數據。
一旦向量數據庫已準備好數據,應用程序就可以使用它來檢索用户查詢的上下文數據:
應用程序將用户查詢與來自向量數據庫的上下文數據組合成提示,然後將其發送到 LLM。 The LLM generates the response in natural language within the boundary of the contextual data and sends it back to the application.
3. 使用 Spring AI 和 Redis 實現 RAG
Redis 堆棧提供向量搜索服務,我們將使用 Spring AI 框架與它集成,並構建一個基於 RAG 的聊天機器人應用程序。 此外,我們還將使用 OpenAI 的 GPT-3.5 Turbo LLM 模型來生成最終響應。
3.1. 前置條件
為了認證 OpenAI 服務,ChatBot 服務需要 OpenAI 服務的 API 密鑰。 我們將在創建 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 中。
現在,讓我們來看看涉及的類:
<a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/document/DocumentReader.html"><em>DocumentReader</em></a> 是 Spring AI 接口,用於讀取文檔。我們將使用內置的 <a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/reader/pdf/PagePdfDocumentReader.html"><em>PagePdfDocumentReader</em></a> 實現,該實現是 <em>DocumentReader</em></a> 的。 類似地,<a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/document/DocumentWriter.html"><em>DocumentWriter</em></a> 和 <a href="https://docs.spring.io/spring-ai/docs/current/api/org/springframework/ai/vectorstore/VectorStore.html"><em>VectorStore</em></a> 是用於將數據寫入存儲系統的接口。 <a href="https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html"><em>RedisVectorStore</em></a> 是許多內置 <em>VectorStore</em></a> 實現之一,我們將用於在 Redis Vector DB 中加載和搜索數據。 我們將使用 Spring AI 框架中討論的類編寫 <em>DataLoaderService</em></a>。
3.3. 實現數據加載服務
讓我們瞭解 load() 方法在 DataLoaderService 類中的作用:
@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 框架自動配置 VectoreStore 接口,通過 配置屬性 在命名空間 spring.ai.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 向量數據庫準備就緒,我們就可以檢索與用户查詢相關的上下文信息。隨後,這些上下文被用於構建 LLM 生成最終響應的提示。下面我們來看關鍵類:
searchData() 方法位於 DataRetrievalService 類中,它接收查詢並從 VectorStore 中檢索上下文數據。ChatBotService 使用這些數據,結合 PromptTemplate 類構建提示,然後將其發送到 OpenAI 服務。 Spring Boot 框架從 application.yml 文件中讀取相關的 OpenAI 相關屬性,然後自動配置 OpenAIChatModel 對象。 在本文中,我們設置了 Spring 的活動配置文件為 "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();
}
}SpringAI 框架使用 ChatModel Bean,並配置 OpenAI 的配置屬性,在 spring.ai.openai 命名空間中。
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密鑰,這是一種更安全的選項。我們可以啓用以“embedding”文本開頭的密鑰,從而創建 OpenAiEmbeddingModel Bean,該 Bean 用於從知識庫文檔中創建向量嵌入。
為了確保OpenAI服務的提示語句明確無歧義,我們在提示藍圖中嚴格指示僅從上下文信息中生成響應,即 PROMPT_BLUEPRINT。
在 chat()方法中,我們檢索文檔,並從Redis向量數據庫中匹配查詢。然後,我們使用這些文檔和用户查詢,在 createPrompt()方法中生成提示。最後,我們調用 ChatModel類中的 call()方法,以接收來自OpenAI服務的響應。
現在,讓我們通過向員工手冊(先前加載到Redis向量數據庫中的)提問來檢查聊天機器人服務的運行情況:
@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.該輸出與員工手冊 PDF 文檔在 Redis 向量數據庫中的加載結果一致。
讓我們看看如果詢問的內容不在員工手冊中,會發生什麼:
@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 是一種理想的解決方案,用於存儲和對文檔向量執行相似性搜索。 此外,對文檔進行分塊同樣重要,以便檢索正確的記錄並限制提示標記的使用成本。