1. 概述
在开发 AI 应用时,我们经常需要实现类似人类的对话交互。这就要求我们与 LLM 模型保持连续对话,而 Spring AI 通过其聊天记忆功能完美解决了这个问题。
本文将深入探讨 Spring AI 提供的多种聊天记忆实现方案,并通过示例展示如何将聊天记忆集成到聊天客户端中。
2. 聊天记忆
大语言模型(LLM)本质是无状态的,不会记住任何内容。 每次发送给 LLM 的提示都被视为独立查询,模型不会保留任何历史消息。
在 AI 应用中,保存对话历史至关重要,这能让 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();
// 其他方法
}
内存存储库实现简单,适合不需要长期持久化的场景。 如果需要长期保存,就得考虑其他方案。
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 = ...; // 选择存储库方言
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 的方言实现使用标准 SQL,不依赖特定数据库厂商。因此可直接使用如 MysqlChatMemoryRepositoryDialect
等现成实现,无需自定义。
使用前需初始化数据库模式。对于支持的方言,Spring AI 提供了模式创建脚本,位于 classpath:org/springframework/ai/chat/memory/repository/jdbc
。
4. 将聊天记忆应用到聊天客户端
Spring AI 在 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 数据库作为持久化存储。
在 application.yml
中添加配置,设置 OpenAI API 密钥、数据库连接,并在应用启动时初始化模式:
spring:
ai:
openai:
api-key: "sk-1234567890abcdef" # 替换为你的实际 API 密钥
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,包含字符串形式的提示:
public class ChatRequest {
@NotNull
private String prompt;
// getter 和 setter
}
6. 测试运行
现在可以向 REST 接口发送请求了。我们将使用 Postman 发送请求,并用 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 不仅发送了当前提示,还包含了之前的提示和响应:
{
"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 协同工作的底层机制。
完整源代码可在 GitHub 获取。