1. 概述
我們經常需要在與 AI 應用的對話中獲得類似人類的交互體驗。因此,維護與 LLM 模型的對話是必要的,Spring AI 通過其聊天記憶功能來解決這個問題。
在本教程中,我們將探索 Spring AI 中提供的各種聊天記憶選項,並提供如何將聊天記憶與聊天客户端集成的示例。
2. 聊天記憶
大型語言模型 (LLM) 是無狀態的,不具備任何記憶功能。 每次向 LLM 發送的提示都被視為一個獨立的查詢,這意味着模型不會記住任何之前的消息。
在人工智能應用程序中,保持之前的對話至關重要,以便 LLM 能夠產生有意義的響應。 此時,聊天記憶的作用就是彌補這一不足,提供:
- 語境理解 – 這使得 LLM 能夠根據整個對話產生響應。
- 個性化 – 這有助於基於聊天記憶提供個性化響應。
- 持久性 – 根據實現方式,聊天記憶可以跨多個會話持久存在。
3. 聊天記憶存儲庫
Spring AI 提供 ChatMemory 接口以及一些現成的實現,以幫助我們輕鬆地將聊天記憶集成到我們的應用程序中。
首先,添加 Maven 依賴項 spring-ai-starter-model-openai 以啓用 OpenAI 集成。 此依賴項將轉義導入 Spring AI 的核心庫:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.0.0</version>
</dependency>當我們創建聊天記憶時,必須提供 ChatMemoryRepository 的實現,該接口負責將聊天消息持久化到存儲中:
ChatMemoryRepository chatMemoryRepository;
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();Spring AI 提供不同類型的聊天記憶存儲庫供我們根據項目的技術棧進行選擇。我們將在下面討論其中兩個。
3.1. 內存存儲器
如果未明確定義聊天內存,Spring AI 默認使用內存存儲器。它會在 ConcurrentHashMap 中內部存儲聊天消息,其中會話 ID 作為鍵,值是一個該會話中的消息列表:
public final class InMemoryChatMemoryRepository implements ChatMemoryRepository {
Map<String, List<Message>> chatMemoryStore = new ConcurrentHashMap();
// other methods
}內存倉庫非常簡單,在不需要任何持久化存儲的情況下效果良好。 如果需要持久化存儲,則需要選擇其他方案。
3.2. JDBC 倉庫
JDBC 倉庫用於在關係型數據庫中持久化聊天消息。 Spring AI 提供對多個關係型數據庫的內置支持,包括 MySQL、PostgreSQL、SQL Server 和 HSQLDB。
如果需要將聊天內存存儲在關係型數據庫中,則需要包含 Maven 依賴項 以支持它:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-model-chat-memory-repository-jdbc</artifactId>
<version>1.0.0</version>
</dependency>每個內置支持的數據庫都有其自身的方言實現,它為聊天記憶表上的 CRUD 操作提供 SQL 語句。在初始化 JdbcChatMemoryRepository 時,我們需要提供方言。
JdbcChatMemoryRepositoryDialect dialect = ...; // The choose repository dialect
ChatMemoryRepository repository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(dialect)
.build();對於沒有內置支持的數據庫,我們需要實現 JdbcChatMemoryRepositoryDialect 接口並提供每個 CRUD 操作的 SQL 語句:
public interface JdbcChatMemoryRepositoryDialect {
String getSelectMessagesSql();
String getInsertMessageSql();
String getSelectConversationIdsSql();
String getDeleteMessagesSql();
}對於 Spring AI 中實現的方言,CRUD 操作遵循標準 SQL 且不依賴於特定供應商。因此,我們可以直接使用提供的實現,例如 MysqlChatMemoryRepositoryDialect,而無需實現我們自定義的方言。
在使用之前,我們需要初始化 schema。對於支持的方言,Spring AI 也提供了 schema 創建腳本。這些腳本可以在 classpath:org/springframework/ai/chat/memory/repository/jdbc 中找到。
4. 將 Chat 記憶應用到 Chat 客户端
Spring AI 提供 Chat 記憶的自動配置,在 ChatMemoryAutoConfiguration 中。 如果我們選擇使用內存倉庫,則無需顯式定義,因為這是默認設置。
但是,如果我們想使用 JDBC 倉庫,則需要為 ChatMemoryRepository 的 bean 方法提供覆蓋默認內存倉庫的定義。
@Configuration
public class ChatConfig {
@Bean
public ChatMemoryRepository getChatMemoryRepository(JdbcTemplate jdbcTemplate) {
return JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new HsqldbChatMemoryRepositoryDialect())
.build();
}
}
請注意,我們無需顯式定義 ChatMemory 的 Bean 方法,因為該方法已在 ChatMemoryAutoConfiguration 中定義。
讓我們在 Spring Boot 中創建一個 ChatService:
@Component
@SessionScope
public class ChatService {
private final ChatClient chatClient;
private final String conversationId;
public ChatService(ChatModel chatModel, ChatMemory chatMemory) {
this.chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
this.conversationId = UUID.randomUUID().toString();
}
public String chat(String prompt) {
return chatClient.prompt()
.user(userMessage -> userMessage.text(prompt))
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
}
}
在構造函數中,Spring Boot 會自動注入 ChatMemory 實現。我們通過使用 MemoryChatMemoryAdvisor 初始化 ChatClient。
我們定義 chat 方法來接受提示並向聊天模型發送消息。此外,我們還將會話 ID 作為聊天顧問參數傳遞,以便根據當前會話唯一標識對話。
需要注意的是,我們必須使用 @SessionScope 註解標記服務,以便其實例可以在多個請求之間保持持久化。
5. 與 OpenAI 集成
在我們的演示中,我們將集成聊天記憶功能與 OpenAI,並考察 Spring AI 如何調用 OpenAI API,以及採用 HSQL DB 作為持久化存儲。
讓我們為 application.yml 添加屬性,以添加 OpenAI API 密鑰、設置數據庫連接以及在應用程序啓動期間初始化模式:
spring:
ai:
openai:
api-key: "<YOUR-API-KEY>"
datasource:
url: jdbc:hsqldb:mem:chatdb
driver-class-name: org.hsqldb.jdbc.JDBCDriver
username: sa
password:
sql:
init:
mode: always
schema-locations: classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-hsqldb.sql
現在,配置已全部設置完成。讓我們創建一個 REST 端點,以便我們可以調用我們之前定義的 ChatService:
@RestController
public class ChatController {
private final ChatService chatService;
public ChatController(ChatService chatService) {
this.chatService = chatService;
}
@PostMapping("/chat")
public ResponseEntity<String> chat(@RequestBody @Valid ChatRequest request) {
String response = chatService.chat(request.getPrompt());
return ResponseEntity.ok(response);
}
}ChatRequest 是一個簡單的 DTO,它包含 prompt 作為字符串:
public class ChatRequest {
@NotNull
private String prompt;
// getter and setter
}6. 測試運行
現在我們準備好向 REST 端點發送請求。我們將使用 Postman 向 REST 端點發送請求,並利用 HTTP 工具包 攔截 Spring Boot 應用程序與 OpenAI 之間的 HTTP 請求,以瞭解它們如何協同工作。
6.1. 首次請求
讓我們在 Postman 中發起一個請求,要求一個笑話,並檢查響應:
當我們觀察 HTTP 工具包中攔截的請求時,我們會看到發往 OpenAI 的 HTTP 請求:
{
"messages": [
{
"content": "Tell me a joke",
"role": "user"
}
],
"model": "gpt-4o-mini",
"stream": false,
"temperature": 0.7
}這是一個相當簡單的請求,它使用用户角色發送我們的提示負載。
6.2. 第二請求
現在,讓我們發起另一個請求以進行比較:
當我們這次讀取攔截的 OpenAI HTTP 請求時,我們發現 Spring AI 不僅將我們的提示負載發送到 OpenAI,還發送了之前的提示和響應:
{
"messages": [
{
"content": "Tell me a joke",
"role": "user"
},
{
"content": "Why did the scarecrow win an award? \n\nBecause he was outstanding in his field!",
"role": "assistant"
},
{
"content": "Tell me another one",
"role": "user"
}
],
"model": "gpt-4o-mini",
"stream": false,
"temperature": 0.7
}在此示例中,我們觀察到 Spring AI 將整個聊天曆史發送到聊天模型。 這種方法有助於聊天模型保持整個對話的上下文,使交互感覺更自然。
7. 結論
在本文中,我們學習了 Spring AI 如何通過聊天記憶來增強對話體驗,從而在多個聊天請求之間保持聊天曆史。
我們探討了不同的記憶存儲庫,並説明了如何將聊天記憶與 Spring AI 和 OpenAI 集成。我們還研究了 Spring AI 聊天記憶在 OpenAI 背後是如何工作的。