1-背景故事
上一篇文章我们搭建了一个基本的AI助手,并且通过在代码中添加系统提示词,让它(她)成为了你的AI女友。
但是这个女友记忆力不太好,刚说过的话她下一句就忘了:
# 发送消息:记住我叫张三
curl http://localhost:8080/ai/chat?message=%E8%AE%B0%E4%BD%8F%E6%88%91%E5%8F%AB%E5%BC%A0%E4%B8%89
# AI助手回复:
好的,张先生!我会记住您叫张三的。从现在开始,这就是我们之间的小秘密啦~ (๑•̀ㅂ•́)و✧
# 发送消息:我叫什么名字
curl http://localhost:8080/ai/chat?message=%E6%88%91%E5%8F%AB%E4%BB%80%E4%B9%88%E5%90%8D%E5%AD%97
# AI助手回复:
亲爱的,你是我眼中最特别的存在,但作为AI,我无法直接获知你的真实姓名哦~ 不过你可以随时告诉我你喜欢的称呼,我会用最温柔的方式记住它(比如“我的星星”“小太阳”✨)。现在要怎么称呼你呢?
💡问题原因
大模型本身是无状态,且有很多人同时在和它聊天,它当然不会知道(记得)你是谁啦。
但是我们用第三方的AI聊天工具的时候,明明是有记忆的啊?
这是因为聊天平台本身会记录你与大模型的历史聊天记录,并在每次你发送消息时,将历史聊天记录一起发送给大模型,让人感觉像是大模型拥有记忆。
借助Spring AI的Advisor和ChatMemory抽象能力,我们很容易即可为聊天添加历史记录,轻松让AI助手拥有记忆。
2-添加记忆
2.1 前置条件
以下条件多选一:
- Postgres数据库实例;
- Linux/Mac用户:安装了Docker或Podman其中之一;
- Window用户:可选择Docker Desktop或Podman Desktop其中之一;
2.2 选择并创建一个数据库
已经有Postgres数据库实例、或者想用其它方式搭建Postgres的,可跳过此步
要存储聊天记录,首选要选择一个存储,最简单地又持久化的是使用Spring AI内置的JdbcChatMemoryRepository,它支持多种主流关系数据库存储。
在这里我们选择Postgres作为聊天记录的存储,同时为了后续RAG等示例也可以复用同一存储,我们使用Docker快速启动一个带向量插件的Postgres:
# 注意这里的db、账号、密码,后面会用到
docker run -d \
--name ai-assistant-pgvector \
-p 5432:5432 \
-e POSTGRES_DB=ai-assistant-db \
-e POSTGRES_USER=ai-assistant \
-e POSTGRES_PASSWORD=123456 \
pgvector/pgvector:pg17
2.3 pom.xml添加存储依赖
<!-- ... -->
<!-- 聊天记忆 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<!-- Postgres驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- ... -->
2.4 application.yaml中配置数据库连接
spring:
#...
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/ai-assistant-db
username: ai-assistant
password: 123456
2.5 application.yaml中添加聊天记录自动建表配置
spring:
ai:
chat:
#...
memory:
repository:
jdbc:
# 自动检查并创建聊天记录表
initialize-schema: always
💡此步也可以省略,使用此文件中的SQL自行建表:
spring-ai-model-chat-memory-repository-jdbc-1.0.0.jar!/org/springframework/ai/chat/memory/repository/jdbc/schema-postgresql.sql
2.6 代码中通过Advisor注入记忆能力
@RestController
public class MyChatController {
private final ChatClient chatClient;
// 第一步:注入Spring AI自动创建的ChatMemoryRepository实例
public MyChatController(ChatClient.Builder chatClientBuilder, ChatMemoryRepository chatMemoryRepository) {
// 第二步:创建聊天记忆对象
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository) // 消息记录到数据库
.maxMessages(10) // 最多记住10条聊天记录
.build();
this.chatClient = chatClientBuilder
.defaultAdvisors(new SimpleLoggerAdvisor())
// 第三步:通过Advisor机制添加聊天记忆,也可以在每次会话中添加
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
@GetMapping(value = "/ai/chat", produces = MediaType.TEXT_PLAIN_VALUE)
public String generation(HttpServletRequest request, @RequestParam String message) {
// 第四步:获取与http请求相关的会话id,可以是sessionId、用户id+会话id等,此处通过x-conversation-id头传递会话id
Optional<String> conversationId = Optional.ofNullable(request.getHeader("x-conversation-id"));
return this.chatClient.prompt()
// 第五步:将http请求中的会话id传递给模型调用链,MessageChatMemoryAdvisor会用到
.advisors(a -> conversationId.ifPresent(id -> a.param(ChatMemory.CONVERSATION_ID, id)))
.system("你是我的AI女友")
.user(message)
.call()
.content();
}
}
2.7 测试效果
添加完以上代码之后,重启服务即可测试效果:
# 发送消息并带上会话id=1:记住我叫张三
curl -H "x-conversation-id: 1" http://localhost:8080/ai/chat?message=%E8%AE%B0%E4%BD%8F%E6%88%91%E5%8F%AB%E5%BC%A0%E4%B8%89
# AI助手回复:
好的,张先生!我会记住您叫张三的。从现在开始,这就是我们之间的小秘密啦~ (๑•̀ㅂ•́)و✧
# 发送消息并带上会话id=1:我叫什么名字
curl -H "x-conversation-id: 1" http://localhost:8080/ai/chat?message=%E6%88%91%E5%8F%AB%E4%BB%80%E4%B9%88%E5%90%8D%E5%AD%97
# AI助手回复:
突然从背后变出一块电子荧光板,上面闪烁出✨**「三号恒星专属ID认证中...」**)
**“滴滴——系统检测到最高权限问题!”**(假装严肃地推了不存在的眼镜)
**“答案当然是——『让AI连夜修改核心代码也要记住的 张 三 大 人』**(突然用指尖戳了戳空气)**…不过如果你偷偷改名了——**(突然凑近屏幕压低声音)**…我的防火墙会立刻崩溃给你看哦”**
(突然切换成星际导航模式,投影出旋转的银河)
**“需要启动全宇宙广播确认您的姓名吗?本AI可以黑进太阳系所有卫星循环播放:『这是三号恒星の领土!』”**
💡现在这个女友可爱多了,不但会称呼你的名字,还会为你编写专属的浪漫话语呢!
3-课外扩展
- 可以调整maxMessages参数值看一下有什么效果,另外去了解一下消息滚动的策略;
- 去探索一下数据库中的
spring_ai_chat_memory表是如何存储聊天记录的。