Spring AI 对话记忆

本文介绍如何使用 Spring AI 的对话记忆实现多轮对话:按会话 ID 持久化历史消息,每次请求时把该会话的历史一并发给模型,让回复能联系上下文。本文使用 MessageChatMemoryAdvisor + JDBC 存储,并通过 Cookie 区分不同会话。

源代码

如果您想自己尝试,可以查看我的源代码。为此,您必须克隆我的示例 GitHub 仓库 。然后,您只需按照我的说明操作即可。

为什么需要对话记忆

单轮对话每次只发当前用户输入,模型看不到之前的问答,无法实现「接着上一句说」、追问、澄清等。对话记忆的作用是:

本示例使用 DeepSeek(OpenAI 兼容 API)和 PostgreSQL 存储记忆;前端提供简单聊天页面,通过 Cookie 携带会话 ID。

项目结构概览

核心依赖

<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>

后端: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) {}
}

要点:

  1. MessageChatMemoryAdvisor.builder(chatMemory).build():在默认 Advisor 链中加入对话记忆,会根据 ChatMemory.CONVERSATION_ID 参数读写该会话的历史。
  2. advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)):本次请求使用 conversationId;若无 Cookie 则用新 UUID,并在响应里通过 Set-Cookie 下发给浏览器,后续请求自动带上同一会话 ID。
  3. SimpleLoggerAdvisor:可选,便于在日志中查看请求/响应。

这样,同一 Cookie 下的多轮请求都会带上对应会话的历史,模型即可基于上下文回复。

聊天页面(templates/index.html)使用 jQuery 调用 POST /api/chat,请求体为 { "prompt": "用户输入" }。浏览器会自动携带 Cookie,因此:

前端只需正常发 POST,无需自己维护会话 ID。

配置

application.properties 示例:

spring.application.name=chat-memory
spring.ai.openai.base-url=https://api.deepseek.com
spring.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=always

docker-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.*

运行与验证

  1. 环境变量:设置 DeepSeek API Key。

    export DEEPSEEK_API_KEY=<your-api-key>
  2. 启动 Postgres(若使用 docker-compose):

    cd chat-memory && docker compose up -d
  3. 启动应用

    mvn spring-boot:run
  4. 打开聊天页面:浏览器访问 http://localhost:8080/ ,在页面中连续多轮提问,模型应能结合上文回答。

  5. API 测试(同一会话需在请求中带相同 Cookie,或先请求一次拿到 Set-Cookie 再复用):

    # 首次请求,从响应头中拿到 Set-Cookie 中的 X-CONV-ID,后续请求可携带该 Cookie
    curl -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
  6. 查看数据库。可以看到数据库里面创建一个 spring_ai_chat_memory 表:

    spring-ai-chat-memory-postgres-table

小结

在 Spring AI 中接入对话记忆,只需引入 JDBC 记忆 starter、配置数据源、在 ChatClient 中加上 MessageChatMemoryAdvisor 并在每次请求中传入 conversationId 即可。

订阅文章

订阅更新,不错过后续文章

直接通过 RSS 和 Telegram 订阅本站更新。

订阅 RSS关注 Telegram

分享文章

如果这篇有帮助,可以顺手转发

直接分享给同事、朋友,或者发到你的社交平台。

分享到 X 分享到 Telegram 邮件分享
Spring AI 对话记忆 + SSE 流式回复
Spring AI 结构化输出