引言:当AI的记性比金鱼还差
- 你:我叫张三,很高兴认识你
- AI:很高兴认识你,张三! 如果你有任何问题或者需要帮助,请随时告诉我
- 你:我叫什么
- AI:抱歉,我无法知道你的名字。不过你可以告诉我一些关于你的信息,如果你愿意分享的话!
AI的内心OS:爱谁谁,我反正不知道

如上图所示,默认的AI连一条只有7秒记忆的金鱼都比不上,每次对话都像是"初次见面"。本篇通过 Spring AI 让大模型具备记忆功能,让AI变成《盗梦空间》里的"记忆宫殿管理员"。
一、记忆分类
在大模型应用场景中通常把记忆分为短期记忆和长期记忆
- 短期记忆能记住当前对话中你提过的信息,但只限于这次聊天(关闭页面或刷新后消失);完全依赖你发给它的文字上下文(就像两个人聊天时,对方只能记住你刚才说过的话)
伪代码:模型输入自动包含历史对话
String prompt = "用户:我叫张三\n AI:你好 张三 \n 用户:我叫什么?"
output = chatClient.prompt(prompt).call().content() # 输出:"你叫张三。"
- 模型本身无法真正长期记住你(所有对话默认独立),但可以通过技术手段模拟长期记忆,需要外部工具辅助(比如数据库、用户账号系统)
user_data =toString ( db.query("SELECT * FROM users WHERE id='xxx'") )
prompt = "用户信息:{user_data}\n 当前问题:推荐一首我喜欢的音乐"
output = chatClient.prompt(prompt).call().content() # 个性化推荐
差异对比:短期记忆是模型的"临时便签",长期记忆是它的"外接硬盘"
对比维度 | 短期记忆 | 长期记忆 |
---|---|---|
存储位置 | 当前对话的上下文文本(临时输入) | 外部数据库/知识库(需开发实现) |
时效性 | 仅当前对话有效(关闭即消失) | 永久保留(除非手动删除) |
依赖技术 | 模型原生支持(上下文窗口) | 额外开发(如数据库、RAG、用户系统) |
容量限制 | 受模型上下文窗口限制(如128K tokens) | 理论上无限(取决于存储空间) |
修改方式 | 实时调整输入文本即可 | 需更新外部数据源 |
隐私性 | 默认不存储(相对安全) | 需主动管理用户数据(合规风险) |
典型场景 | 多轮对话、临时任务(如调试代码) | 个性化服务(如医疗记录、订单历史) |
用户感知示例 | "AI记得我刚才让改的代码风格" | "AI知道我是VIP客户,自动优先处理" |
实现成本 | 零成本(上下文传递) | 需开发资源(存储、API、安全措施) |
默认支持 | 所有大模型均支持 | 需自行开发或使用企业级工具 |
二、短期记忆
2.1 Advisor
Advisor是一种用于在Spring应用中拦截、修改和增强与大模型交互的灵活而强大的方式。
2.1.1 SimpleLoggerAdvisor
可以通过SimpleLoggerAdvisor
在大模型执行前后打印日志
添加logback.xml
文件,把日志输出级别调整为debug
xml
<!--
~ Copyright 2023-2024 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.springframework.ai.chat.client.advisor" level="DEBUG" additivity="true">
<appender-ref ref="STDOUT"/>
</logger>
</configuration>
设置Advisor:
java
@GetMapping("/log")
public String log(@RequestParam(value = "input", defaultValue = "讲一个笑话") String input) {
Prompt prompt = new Prompt(input);
return chatClient.prompt(prompt).advisors(new SimpleLoggerAdvisor()).call().content();
}
输出:发起请求日志格式 o.s.a.c.c.a.SimpleLoggerAdvisor -- request
,响应输出日志格式o.s.a.c.c.a.SimpleLoggerAdvisor -- response
,在响应日志中可以看到token消耗等各种额外信息
22:12:13.367 [http-nio-8080-exec-3] DEBUG o.s.a.c.c.a.SimpleLoggerAdvisor -- request: AdvisedRequest[chatModel=OpenAiChatModel [defaultOptions=OpenAiChatOptions: {"streamUsage":false,"model":"qwen-plus","temperature":0.7}], userText=讲一个笑话, systemText=null, chatOptions=OpenAiChatOptions: {"streamUsage":false,"model":"qwen-plus","temperature":0.7}, media=[], functionNames=[], functionCallbacks=[], messages=[], userParams={}, systemParams={}, advisors=[org.springframework.ai.chat.client.DefaultChatClientDefaultChatClientRequestSpec 1 @ 76 a 2010 f , o r g . s p r i n g f r a m e w o r k . a i . c h a t . c l i e n t . D e f a u l t C h a t C l i e n t 1@76a2010f, org.springframework.ai.chat.client.DefaultChatClient 1@76a2010f,org.springframework.ai.chat.client.DefaultChatClientDefaultChatClientRequestSpec 2 @ 62 e d 924 c , o r g . s p r i n g f r a m e w o r k . a i . c h a t . c l i e n t . D e f a u l t C h a t C l i e n t 2@62ed924c, org.springframework.ai.chat.client.DefaultChatClient 2@62ed924c,org.springframework.ai.chat.client.DefaultChatClientDefaultChatClientRequestSpec 1 @ 64 c 6 c e 4 a , o r g . s p r i n g f r a m e w o r k . a i . c h a t . c l i e n t . D e f a u l t C h a t C l i e n t 1@64c6ce4a, org.springframework.ai.chat.client.DefaultChatClient 1@64c6ce4a,org.springframework.ai.chat.client.DefaultChatClientDefaultChatClientRequestSpec2@60f9560a, SimpleLoggerAdvisor], advisorParams={}, adviseContext={}, toolContext={}]
22:13:15.899 [http-nio-8080-exec-4] DEBUG o.s.a.c.c.a.SimpleLoggerAdvisor -- response: {
"result" : {
"metadata" : { "finishReason" : "STOP", "contentFilters" : [ ], "empty" : true }, "output" : { "messageType" : "ASSISTANT", "metadata" : { "refusal" : "", "finishReason" : "STOP", "index" : 0, "id" : "chatcmpl-45f6e0df-a0ec-930f-9ed1-f79aa134a443", "role" : "ASSISTANT", "messageType" : "ASSISTANT" }, "toolCalls" : [ ], "media" : [ ], "text" : "好的!来一个轻松的笑话:\n\n有一天,小明去商店买饮料,他问老板:"老板,你们这里可乐摇一摇会爆炸吗?"\n\n老板笑着说:"不会不会,都是经过严格质量检查的。"\n\n小明想了想,又问:"那牛奶摇一摇会爆炸吗?"\n\n老板愣了一下,说:"牛奶摇一摇......可能会过期!" 😄" }
},
"metadata" : {
"id" : "chatcmpl-45f6e0df-a0ec-930f-9ed1-f79aa134a443", "model" : "qwen-plus", "rateLimit" : { "requestsLimit" : null, "requestsRemaining" : null, "requestsReset" : null, "tokensLimit" : null, "tokensRemaining" : null, "tokensReset" : null }, "usage" : { "promptTokens" : 11, "completionTokens" : 82, "totalTokens" : 93, "generationTokens" : 82, "nativeUsage" : { "completion_tokens" : 82, "prompt_tokens" : 11, "total_tokens" : 93, "prompt_tokens_details" : { "cached_tokens" : 0 } } }, "promptMetadata" : [ ], "empty" : false
},
"results" : [ {
"metadata" : { "finishReason" : "STOP", "contentFilters" : [ ], "empty" : true }, "output" : { "messageType" : "ASSISTANT", "metadata" : { "refusal" : "", "finishReason" : "STOP", "index" : 0, "id" : "chatcmpl-45f6e0df-a0ec-930f-9ed1-f79aa134a443", "role" : "ASSISTANT", "messageType" : "ASSISTANT" }, "toolCalls" : [ ], "media" : [ ], "text" : "好的!来一个轻松的笑话:\n\n有一天,小明去商店买饮料,他问老板:"老板,你们这里可乐摇一摇会爆炸吗?"\n\n老板笑着说:"不会不会,都是经过严格质量检查的。"\n\n小明想了想,又问:"那牛奶摇一摇会爆炸吗?"\n\n老板愣了一下,说:"牛奶摇一摇......可能会过期!" 😄" }
} ]
}
2.1.2 源码分析
查看SimpleLoggerAdvisor的关键代码实现:
aroundCall
在调用chain.nextAroundCall(advisedRequest)
之前触发before()
执行完成触发observeAfter
做日志输出,nextAroundCall
会执行大模型的调用(这个在上一章以解释过)aroundStream
是支持流式响应的输出,流式请求需要在整个请求响应完成后再执行日志打印,因此通过MessageAggregator
消息聚合器来采集返回结果,当有类似场景时可直接借用这段逻辑,避免重复造轮子
java
private AdvisedRequest before(AdvisedRequest request) {
logger.debug("request: {}", this.requestToString.apply(request));
return request;
}
private void observeAfter(AdvisedResponse advisedResponse) {
logger.debug("response: {}", this.responseToString.apply(advisedResponse.response()));
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
advisedRequest = before(advisedRequest);
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
observeAfter(advisedResponse);
return advisedResponse;
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
advisedRequest = before(advisedRequest);
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
return new MessageAggregator().aggregateAdvisedResponse(advisedResponses, this::observeAfter);
}
2.1.3 Advisor实体关系

Spring AI内置一个MessageChatMemoryAdvisor
来提供聊天记忆功能(Advisor机制在上一篇中已解读过,不熟悉可以再读一读)
2.2 MessageChatMemoryAdvisor
在Spring AI中内置很多Advisor
,其中MessageChatMemoryAdvisor
可以用来提供聊天记忆功能
MessageChatMemoryAdvisor
接收一个ChatMemory
对象,传递InMemoryChatMemory
是一个内存记忆的ChatMemory
类型,通过Map<String, List<Message>> conversationHistory = new ConcurrentHashMap<>()
实现
java
@Slf4j
@RestController
public class MemoryController {
private ChatClient chatClient;
public MemoryController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
private InMemoryChatMemory inMemoryChatMemory = new InMemoryChatMemory();
@GetMapping("/memory/chat")
public String chat(@RequestParam(value = "input", defaultValue = "讲一个笑话") String input) {
Prompt prompt = new Prompt(input);
return chatClient.prompt(prompt).advisors(new MessageChatMemoryAdvisor(inMemoryChatMemory)).call().content();
}
}
功能测试:先告诉它我是一名java程序员
然后让它推荐一本适合我的书籍
,在两次对话过程中,已经成功记住第一次预设的身份,应用重启后失效
GET http://127.0.0.1:8080/memory/chat?input=我是一名java程序员
你好,Java程序员 👨💻 很高兴认识同行~
作为开发者,我可以帮你:
🔸 调试代码:遇到报错或意外行为?贴片段我来分析
🔸 技术建议:从Spring到JVM优化,或最新框架选型(比如Quarkus vs Micronaut)
🔸 面试备战:算法题解、设计模式场景题、并发难题等
🔸 工具链:Maven/Gradle技巧、Docker集成、性能调优工具(Arthas?)
需要哪方面的支持?或者最近在做什么有趣的项目吗? 😄
(小提示:如果想分享代码,可以用代码块格式,我会更清晰阅读哦~
GET http://127.0.0.1:8080/memory/chat?input=推荐一本适合我的书籍
根据你的Java程序员身份,我会针对不同需求推荐几本经典书籍,并标注适用阶段和方向,供你选择:
一、核心进阶(必读)
《Effective Java》(Joshua Bloch)
👍 适合:中高级开发者
📌 内容:Java最佳实践(泛型、并发、设计模式等),第三版包含Java 8-9新特性
🌟 豆瓣评分:9.5
《Java并发编程实战》(Brian Goetz)
👍 适合:想深入JUC包、线程安全的开发者
📌 从理论到实战,涵盖锁、原子类、线程池等
⚠️ 注意:需要一定基础
二、架构与设计
《深入理解Java虚拟机》(周志明)
👍 适合:对JVM底层、GC调优感兴趣
📌 国内原创经典,涵盖类加载、内存模型等
《Spring实战》(Craig Walls)
👍 适合:Spring生态开发者(Boot/Cloud)
📌 案例驱动,新版涵盖响应式编程
三、新趋势与扩展
《Java函数式编程》(Venkat Subramaniam)
👍 适合:学习Lambda/Stream API、函数式思维
📌 幽默易懂,代码示例丰富
《云原生Java》(Josh Long)
👍 适合:云原生/微服务方向
📌 Spring Cloud + Kubernetes实战
四、补充经典
《Head First设计模式》(图文并茂入门设计模式)
《重构:改善既有代码的设计》(Martin Fowler,必读)
你的选择建议:
如果想优先读一本,推荐从 《Effective Java》 开始(常看常新),如果项目涉及并发则选Goetz的书。需要更具体的推荐可以告诉我你的当前目标(如面试/性能优化/新项目技术选型等) 😊
(电子版资源可能需要自行搜索哦~)
除了在ChatClient
发起调用时设置MessageChatMemoryAdvisor
,还可以在ChatClient
构建时设置默认的advisors
java
public class MemoryController2 {
private ChatClient chatClient;
private InMemoryChatMemory inMemoryChatMemory = new InMemoryChatMemory();
public MemoryController2(ChatClient.Builder builder) {
this.chatClient = builder.defaultAdvisors(new MessageChatMemoryAdvisor(inMemoryChatMemory)).build();
}
@GetMapping("/memory/chat2")
public String chat(@RequestParam(value = "input", defaultValue = "讲一个笑话") String input) {
Prompt prompt = new Prompt(input);
return chatClient.prompt(prompt).call().content();
}
}
三、记忆隔离:防止张冠李戴
问:如果多个用户同时聊天,如何防止AI把张三的咖啡订单发给李四?
答:可以通过会话ID来进行记忆隔离
java
@GetMapping("/memory/user/chat")
public String chatByUser(@RequestParam(value = "input", defaultValue = "讲一个笑话") String input, @RequestParam(value = "userId", defaultValue = "123456") String userId) {
Prompt prompt = new Prompt(input);
return chatClient.prompt(prompt).advisors(new MessageChatMemoryAdvisor(inMemoryChatMemory, userId, AbstractChatMemoryAdvisor.DEFAULT_CHAT_MEMORY_RESPONSE_SIZE)).call().content();
}
使用<font style="color:rgba(0, 0, 0, 0.9);">MessageChatMemoryAdvisor
的另外一个构造方法,<font style="color:rgba(0, 0, 0, 0.9);">defaultConversationId
表示会话ID,<font style="color:rgba(0, 0, 0, 0.9);">chatHistoryWindowSize
表示获取最新的N条记忆
java
public MessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize) {
this(chatMemory, defaultConversationId, chatHistoryWindowSize, Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER);
}
按照前后顺序发出如下三个请求来测试
GET http://localhost:8080/memory/user/chat?input=你好,我是张三&userId=zhangsan
你好,张三!😄 我是AI助手,你可以叫我小深,只要你愿意随时分享,我都会认真倾听、尽力帮忙!
GET http://localhost:8080/memory/user/chat?input=你知道我是谁吗?&userId=lisi
目前,我无法直接识别你的身份,因为我们之前的对话记录不会保留,而且我没有访问用户个人信息的权限。不过,如果你愿意,可以告诉我一些关于你的信息(比如兴趣、职业等),这样我可以更好地为你提供帮助! 😊
GET http://localhost:8080/memory/user/chat?input=你知道我是谁吗?&userId=zhangsan
你好,张三!😊 作为AI助手,我无法直接知道现实中的"你是谁"哦~每次对话都是新的开始,除非你主动告诉我更多信息(比如兴趣、需求等),我会根据聊天内容尽量提供贴合你的帮助!
有什么想聊的或需要建议的吗?比如:
日常:天气、心情、趣事分享
实用:学习/工作技巧、生活小窍门
深度话题:科技、哲学、冷知识...
等你随时开口~ 🌟
合理设计会话ID的生成规则、失效策略在生产环节中起到关键作用
java
public class SessionId {
/
* ID(需要有合适的规则)
*/
private String id;
/
* 失效时间,控制会话的有效性
*/
private Date expireTime;
/
* 创建时间
*/
private Date createTime;
}
四、永恒的记忆
InMemoryChatMemory
是内存记忆,应用重启后会发现记忆直接丢失, 如果想要把记忆永久保存下来,可以考虑使用Mysql
、Redis
等存储,那我们用Redis
来快速实现一个。
2.1 Redis的安装
采用Spring集成Docker
的方式引入Redis
,Docker
本地的安装在前面章节中已经完成,直接通过https://start.spring.io/ 引入依赖;

java
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
在compose.yaml
引入Redis
镜像信息,配置访问端口为6379

添加一段测试Redis
访问的代码
java
package com.lkl.test.spring.docker;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@Slf4j
@RestController
public class RedisController {
@Resource
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/add/test")
public String add() {
String key = "test";
String value = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(key, value);
return "添加成功";
}
@GetMapping("/query/test")
public String query() {
String key = "test";
return "查询成功:" + redisTemplate.opsForValue().get(key);
}
}
应用启动后会自动拉取镜像,在Docker
控制台可看到如下运行信息

Redis
采用默认配置因此不需要在application.properties
中配置参数信息;执行测试代码成功调用。

2.2 RedisChatMemory
InMemoryChatMemory
实现ChatMemory
接口,该接口定义如下
add
表示往记忆中添加一条或者多条信息,其中conversationId
表示会话IDget
表示获取记忆,lastN指定获取最新的n条记忆数据clear
清楚记忆 ;
java
public interface ChatMemory {
// TODO: consider a non-blocking interface for streaming usages
default void add(String conversationId, Message message) {
this.add(conversationId, List.of(message));
}
void add(String conversationId, List<Message> messages);
List<Message> get(String conversationId, int lastN);
void clear(String conversationId);
}
可以利用Reids的List操作来做一个简易版的记忆能力
java
package com.lkl.test.spring.docker.redis;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AbstractMessage;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class RedisChatMemory implements ChatMemory {
private final RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper;
private final String memoryKeyPrefix = "chat:memory:prefix:"; // Redis Key 前缀
public RedisChatMemory(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
this.objectMapper = new ObjectMapper();
}
/
* 获取指定会话的最新 N 条消息(按时间倒序)
*/
@Override
public List<Message> get(String conversationId, int lastN) {
String key = memoryKeyPrefix + conversationId;
long totalMessages = redisTemplate.opsForList().size(key);
if (totalMessages == 0) {
return Collections.emptyList();
}
// 计算起始和结束索引(获取最后 N 条)
long start = Math.max(0, totalMessages - lastN);
long end = totalMessages - 1;
// 获取指定范围的消息
List<String> messageJsons = redisTemplate.opsForList().range(key, start, end);
return messageJsons.stream().map(this::deserializeMessage).collect(Collectors.toList());
}
/
* 添加消息到会话历史
*/
@Override
public void add(String conversationId, Message message) {
String key = memoryKeyPrefix + conversationId;
String messageJson = serializeMessage(message);
// 使用 LPUSH 或 RPUSH 存储消息(LPUSH 表示最新消息在列表头部)
redisTemplate.opsForList().rightPush(key, messageJson);
// 可选:设置 Key 的过期时间(例如 30 天)
redisTemplate.expire(key, 30, TimeUnit.DAYS);
}
/
* 批量添加消息到会话历史(高性能实现)
*/
@Override
public void add(String conversationId, List<Message> messages) {
if (messages == null || messages.isEmpty()) {
return;
}
String key = memoryKeyPrefix + conversationId;
ListOperations<String, String> listOps = redisTemplate.opsForList();
// 序列化并批量添加消息
messages.forEach(message -> {
String messageJson = serializeMessage(message);
listOps.rightPush(key, messageJson);
});
// 设置 Key 的过期时间(30天)
redisTemplate.expire(key, 30, TimeUnit.DAYS);
// 可以考虑使用 Redis Pipeline 批量操作(减少网络开销)
}
/
* 清除指定会话的记忆
*/
@Override
public void clear(String conversationId) {
redisTemplate.delete(memoryKeyPrefix + conversationId);
}
private String serializeMessage(Message message) {
Map<String, Object> map = new HashMap<>();
AbstractMessage abstractMessage = null;
if (message instanceof AbstractMessage) {
abstractMessage = (AbstractMessage) message;
}
map.put("type", abstractMessage.getMessageType().getValue());
map.put("content", abstractMessage.getText());
try {
return objectMapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
private Message deserializeMessage(String json) {
try {
Map<String, String> map = objectMapper.readValue(json, Map.class);
String type = map.get("type");
String content = map.get("content");
return switch (type) {
case "user" -> new UserMessage(content);
case "assistant" -> new AssistantMessage(content);
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
构建RedisMemoryController
来测试依然保留了记忆。
java
@Slf4j
@RestController
public class RedisMemoryController {
private ChatClient chatClient;
private RedisChatMemory redisChatMemory;
public RedisMemoryController(ChatClient.Builder builder, RedisTemplate<String, String> redisTemplate) {
this.redisChatMemory = new RedisChatMemory(redisTemplate);
this.chatClient = builder.defaultAdvisors(new MessageChatMemoryAdvisor(redisChatMemory)).build();
}
@GetMapping("/memory/redis")
public String chat(@RequestParam(value = "input", defaultValue = "讲一个笑话") String input) {
Prompt prompt = new Prompt(input);
return chatClient.prompt(prompt).call().content();
}
}
该自定义的记忆能力还可以继续优化,比如做记忆压缩,对长篇大论做总结后记忆、对敏感信息做剔除,不记录用户的隐私信息、性能监控关注Redis的内存使用情况
2.3 自己动手
在Spring AI中还内置JdbcChatMemory
、CassandraChatMemory
、Neo4jChatMemory
,引入类似的pom即可;通过RedisChatMemory
的自定义实现,相信很容易这些拓展实现,自己可以动手试试吧~
java
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-jdbc</artifactId>
</dependency>
结语:从金鱼到最强大脑
通过 Spring AI 的记忆功能,我们成功让 AI:
- 记住了用户身份
- 实现了多用户隔离
- 做到了持久化存储
最终效果:
- 用户:"和上次一样" → AI 秒懂
- 用户:"你知道我的名字吗?" → AI:"当然,张三!"
AI的进化路线:
🐟 金鱼脑 → 🧠 最强大脑 → 🏛️ 记忆宫殿管理员