本文介绍如何使用 Spring AI 的对话记忆实现多轮对话:按会话 ID 持久化历史消息,每次请求时把该会话的历史一并发给模型,让回复能联系上下文。本文使用 MessageChatMemoryAdvisor + JDBC 存储,并通过 Cookie 区分不同会话。
源代码
如果您想自己尝试,可以查看我的源代码。为此,您必须克隆我的示例 GitHub 仓库 。然后,您只需按照我的说明操作即可。
为什么需要对话记忆
单轮对话每次只发当前用户输入,模型看不到之前的问答,无法实现「接着上一句说」、追问、澄清等。对话记忆的作用是:
- 按会话隔离:同一会话内的多轮消息归为一组(用 conversationId 区分)。
- 持久化:历史消息写入数据库(本示例用 PostgreSQL),重启后仍可继续同一会话。
- 自动注入:每次调用时,Advisor 根据 conversationId 取出历史,拼进本次 Prompt,再发给模型。
本示例使用 DeepSeek(OpenAI 兼容 API)和 PostgreSQL 存储记忆;前端提供简单聊天页面,通过 Cookie 携带会话 ID。
项目结构概览
- ChatMemoryApplication:Spring Boot 入口。
- ChatController:
POST /api/chat,接收用户输入,按 Cookie 中的会话 ID 调用带记忆的 ChatClient,并回写 Cookie。 - IndexController:
GET /,返回聊天页面(Thymeleaf 模板index.html)。 - ChatMemory:由
spring-ai-starter-model-chat-memory-repository-jdbc自动配置,基于 JDBC 的ChatMemoryRepository实现。 - docker-compose.yaml:提供 PostgreSQL,供 JDBC 记忆存储使用。
核心依赖
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId></dependency><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId></dependency><dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-docker-compose</artifactId> <scope>runtime</scope> <optional>true</optional></dependency>- openai:调用 DeepSeek(或其它 OpenAI 兼容模型)。
- chat-memory-repository-jdbc:提供
ChatMemoryBean 和 JDBC 持久化,并依赖数据源。 - postgresql:JDBC 驱动。
- docker-compose:可选,用于启动本地 Postgres。
后端:ChatClient + MessageChatMemoryAdvisor
在构造 ChatClient 时注册 MessageChatMemoryAdvisor,并注入 ChatMemory(由 JDBC starter 提供)。每次请求时通过 Advisor 参数传入当前会话 ID,Advisor 会按该 ID 读写历史并拼入 Prompt。
@RestController@RequestMapping("/")class ChatController {
private final ChatClient chatClient;
ChatController(ChatClient.Builder builder, ChatMemory chatMemory) { this.chatClient = builder .defaultAdvisors( MessageChatMemoryAdvisor.builder(chatMemory).build(), new SimpleLoggerAdvisor() ) .build(); }
@PostMapping("/api/chat") ResponseEntity<Output> chat(@RequestBody @Valid Input input, @CookieValue(name = "X-CONV-ID", required = false) String convId) { String conversationId = convId == null ? UUID.randomUUID().toString() : convId;
var response = this.chatClient.prompt() .user(input.prompt()) .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .call().content();
ResponseCookie cookie = ResponseCookie.from("X-CONV-ID", conversationId) .path("/") .maxAge(3600) .build(); Output output = new Output(response); return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()).body(output); }
record Input(@NotBlank String prompt) {} record Output(String content) {}}要点:
- MessageChatMemoryAdvisor.builder(chatMemory).build():在默认 Advisor 链中加入对话记忆,会根据
ChatMemory.CONVERSATION_ID参数读写该会话的历史。 - advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)):本次请求使用
conversationId;若无 Cookie 则用新 UUID,并在响应里通过 Set-Cookie 下发给浏览器,后续请求自动带上同一会话 ID。 - SimpleLoggerAdvisor:可选,便于在日志中查看请求/响应。
这样,同一 Cookie 下的多轮请求都会带上对应会话的历史,模型即可基于上下文回复。
前端:会话 ID 与 Cookie
聊天页面(templates/index.html)使用 jQuery 调用 POST /api/chat,请求体为 { "prompt": "用户输入" }。浏览器会自动携带 Cookie,因此:
- 首次访问无
X-CONV-ID,服务端生成新 conversationId 并写入 Cookie。 - 之后同一浏览器内的请求都会带上该 Cookie,服务端用同一 conversationId 读写记忆,实现多轮对话。
前端只需正常发 POST,无需自己维护会话 ID。
配置
application.properties 示例:
spring.application.name=chat-memory
spring.ai.openai.base-url=https://api.deepseek.comspring.ai.openai.api-key=${DEEPSEEK_API_KEY}spring.ai.openai.chat.options.model=deepseek-reasoner
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
# 启动时自动创建 Chat Memory 所需表结构spring.ai.chat.memory.repository.jdbc.initialize-schema=alwaysdocker-compose.yaml(本地 Postgres):
services: postgres: image: postgres:18 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=postgres ports: - "5432:5432"使用 Docker Compose 时,Spring Boot 会按服务自动配置数据源,无需再写 spring.datasource.*。
运行与验证
环境变量:设置 DeepSeek API Key。
export DEEPSEEK_API_KEY=<your-api-key>启动 Postgres(若使用 docker-compose):
cd chat-memory && docker compose up -d启动应用:
mvn spring-boot:run打开聊天页面:浏览器访问
http://localhost:8080/,在页面中连续多轮提问,模型应能结合上文回答。API 测试(同一会话需在请求中带相同 Cookie,或先请求一次拿到 Set-Cookie 再复用):
# 首次请求,从响应头中拿到 Set-Cookie 中的 X-CONV-ID,后续请求可携带该 Cookiecurl -s -X POST http://localhost:8080/api/chat \-H "Content-Type: application/json" \-d '{"prompt":"我叫小明,请记住。"}' -c cookies.txt -b cookies.txt -D -curl -s -X POST http://localhost:8080/api/chat \-H "Content-Type: application/json" \-d '{"prompt":"我叫什么名字?"}' -b cookies.txt查看数据库。可以看到数据库里面创建一个
spring_ai_chat_memory表:
小结
- 对话记忆:通过 ChatMemory + MessageChatMemoryAdvisor,按会话 ID 持久化并注入历史消息,实现多轮上下文对话。
- 会话划分:本示例用 Cookie
X-CONV-ID区分会话,无 Cookie 时生成新 UUID 并回写 Cookie。 - 存储:
spring-ai-starter-model-chat-memory-repository-jdbc使用 PostgreSQL 存储历史,表结构由initialize-schema=always自动创建,创建的表名为spring_ai_chat_memory。 - 前端:Thymeleaf 提供聊天页,正常调用
POST /api/chat即可,Cookie 由浏览器自动维护。
在 Spring AI 中接入对话记忆,只需引入 JDBC 记忆 starter、配置数据源、在 ChatClient 中加上 MessageChatMemoryAdvisor 并在每次请求中传入 conversationId 即可。