引言
我相信大家在平时一定使用过网页对话chatbot,国产的有deepseek、Qwen等,国外的有ChatGPT、Gemini、Grok等。在我们使用的期间,我们会发现这些chatbot会记住我们这次对话的上下文,甚至之前的对话的某些信息,如果你们对为什么LLM能记住我们的信息感兴趣,那么接着往下看吧!
错误纠正
很多人不了解大模型或者不了解大模型应用开发的,就常常认为大模型底层就是会有记忆机制的,即内置Memory。这种想法是错误的,大模型底层并没有记忆机制,而是stateless(无状态)。
1.基础大模型(如 Qwen)的行为(LLM)
- 默认情况下,大语言模型(包括 Qwen 系列)是无状态的:每次请求都是独立的,模型不会自动记住你之前的对话内容。
- 如果你在一次对话中发送多轮消息(例如通过 API 的 messages 字段传入历史),那是由调用方(即你的应用)负责维护对话历史,而不是模型自己存储。
2.Memory 机制是应用层的功能
- “Memory”通常是指在构建智能体(Agent)或对话系统时,为了实现上下文记忆、长期记忆、用户偏好记录等功能而额外引入的模块。
- 例如,在 LangChain、LlamaIndex 或阿里云百炼平台中,开发者可以配置短期记忆(如最近几轮对话)或长期记忆(如向量数据库存储的历史信息),并在每次生成回复前将相关记忆注入 prompt。
- 这种 Memory 管理是由应用框架或开发者代码实现的,不是模型原生能力。
Memory
那么像chatbot这些大模型应用,是如何维持记忆呢?那么维护记忆万一超过了模型能承受的上下文,怎么办?
要了解这些问题,我们首先得搞懂,什么是Memory。
Memory分为短期记忆和长期记忆,接下来我们分别说说,尽量简单易懂。
短期Memory
短期记忆(Short-term Memory)
- 定义:指在单次对话会话(session)中保留的上下文信息。
- 典型实现:将用户与系统单次会话中的最近几轮的对话历史(prompt + response)作为上下文,一起输入给大模型。
- 特点:
- 仅在当前会话有效;
- 会话结束后通常被丢弃(除非主动保存);
- 受限于模型的上下文长度(如 Qwen-Max 支持 32768 tokens);
- 是实现多轮对话连贯性的基础。
- 例子:
- 用户:我叫小明。 助手:你好,小明! 用户:我今年多大? → 助手能回答,是因为“我叫小明”这句还在短期记忆(即对话历史)中
短期记忆一般大模型应用都会自动开启的,但是每个应用对短期记忆的策略都会不一样,所以体验上也不一样。
长期Memory
长期记忆(Long-term Memory)
- 定义:跨越多个会话、持久化存储的用户信息或交互历史。
- 典型实现:
- 将关键信息(如用户偏好、身份、历史任务)提取后存入数据库或向量库;
- 下次对话时通过检索(Retrieval)机制召回相关内容,并注入到 prompt 中。
- 特点:
- 需要显式设计(如使用向量数据库 + embedding 检索);
- 不是所有系统都默认开启;
- 涉及隐私和数据安全,通常需用户授权;
- 属于高级功能,常见于智能体(Agent)或个性化助手。
- 例子:
- 上周用户说:“我喜欢喝美式咖啡。” 一周后再次对话,系统说:“今天还想来一杯美式吗?” → 这说明系统通过长期记忆记住了偏好。
以Gemini为例子,你可以在这里添加想让Gemini记住你的相关信息,或者在某次对话中直接prompt让其记住也是可以的。这样就是长期Memory,每次Gemini都会在某些情景下触发相关的长期记忆,策略不同,输出也会不同
SAA的Memory机制
ok,现在回归正题,来讲讲SAA的Memory机制,如果还有想有更深的了解的兄弟可以先去看看这两篇文章,再回来应该能更好的理解
1.Spring AI vs Spring AI Alibaba
juejin.cn/post/759618…
2.逃出结构化思维:从向量,向量数据库到RAG
juejin.cn/post/758707…
SAA本质上是一个AI应用开发框架,可以开发agent,multi-agent和工作流等等,那么必然需要使用底层LLM,然而我们前面已经说过了LLM实际上是无状态的,那么LLM应用的相关记忆必定是由我们上层应用来开发和管理的。
即如果你使用的是Qwen 的 API(如 dashscope),你需要自己维护对话历史并作为输入传入。
SAA的Memory机制的架构
下面是SAA实现两种Memory的架构图,其中左下角的箭头(指向ModelHook)应该放置到中间的,画错了就将就看吧
维护短期记忆
如何添加短期记忆
在 Spring AI Alibaba 中,要向 Agent 添加短期记忆(会话级持久化),你需要在创建 Agent 时指定 checkpointer
javaimport com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver; import org.redisson.api.RedissonClient; // 配置 Redis checkpointer RedisSaver redisSaver = new RedisSaver(redissonClient); ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .tools(getUserInfoTool) .saver(redisSaver) .build(); // 使用 thread_id 维护对话上下文 RunnableConfig config = RunnableConfig.builder() .threadId("1") // threadId 指定会话 ID .build(); agent.call("你好!我叫 Bob。", config);
扩展短期记忆
我们还可以修改和管理记忆。 默认情况下,Agent 使用状态通过 messages 键管理短期记忆,特别是对话历史。
你可以通过在工具或 Hook 中访问和修改状态来扩展记忆功能
javaimport com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.RunnableConfig; import org.springframework.ai.chat.messages.Message; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; // 在 Hook 中访问和修改状态 public class CustomMemoryHook extends ModelHook { @Override public String getName() { return "custom_memory"; } @Override public HookPosition[] getHookPositions() { return new HookPosition[]{HookPosition.BEFORE_MODEL}; } @Override public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) { // 访问消息历史 Optional<Object> messagesOpt = state.value("messages"); if (messagesOpt.isPresent()) { List<Message> messages = (List<Message>) messagesOpt.get(); // 处理消息... } // 添加自定义状态 return CompletableFuture.completedFuture(Map.of( "user_id", "user_123", "preferences", Map.of("theme", "dark") )); } @Override public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) { return CompletableFuture.completedFuture(Map.of()); } }
管理短期记忆
一个会话中,如果用户一直对话,整个会话的上下文就会越来越长,最后就会超过该大模型所能承受的上下文限制。
保留所有对话历史是实现短期记忆最常见的形式。但较长的对话对历史可能会导致大模型 LLM 上下文窗口超限,导致上下文丢失或报错。
即使你在使用的大模型上下文长度足够大,大多数模型在处理较长上下文时的表现仍然很差。因为很多模型会被过时或偏离主题的内容"分散注意力"。同时,过长的上下文,还会带来响应时间变长、Token 成本增加等问题。
在 Spring AI ALibaba 中,ReactAgent 使用 messages 记录和传递上下文,其中包括指令(SystemMessage)和输入(UserMessage)。在 ReactAgent 中,消息(Message)在用户输入和模型响应之间交替,导致消息列表随着时间的推移变得越来越长。由于上下文窗口有限,许多应用程序可以从使用技术来移除或"忘记"过时信息中受益,即 “上下文工程”。
作为后端,我们通常采取以下几种策略来模拟“记忆”:
- Sliding Window(滑动窗口):只保留最近的 NNN 轮对话,丢弃最早的记录。
- Summary Memory(摘要记忆):当对话过长时,调用大模型对之前的对话做一个简短总结,后续只带上这个 Summary。
- Vector Database(向量数据库/RAG):将历史对话存入向量库(如 Milvus, Pinecone)。当用户提问时,通过语义搜索检索出最相关的历史片段(Top-K),塞进 Context 传给模型。
维护长期记忆
长期记忆的检索过程
对于后端开发人员来说,长期记忆的检索过程其实就是一个标准的“语义检索 + RAG (Retrieval-Augmented Generation)”工作流。
你可以把它类比为:全文搜索(Elasticsearch)的升级版。不同于关键词匹配,它是基于“意思”来匹配。
整个过程可以分为“存入”和“检索”两个阶段:
1. 存入阶段(写入/索引)
当一段对话结束或产生新的知识时:
- 切片 (Chunking):将长文本切分成合适的大小(例如 512 tokens)。
- 向量化 (Embedding):调用 Embedding 模型(如 OpenAI 的 text-embedding-3-small)将文本转为高维向量(如 1536 维的浮点数数组)。
- 入库:将向量和原始文本、Metadata(如 user_id, timestamp)存入向量数据库(Redis Vector Search, Milvus, 或 Pinecone)。
2. 检索阶段(读取/召回)
当用户提出一个新问题,系统需要“回想起”以前的事情:
- Step A: 问题向量化
- 将用户的提问(Query)同样通过 Embedding 模型转为一个向量 VqueryV_{query}Vquery。
- Step B: 向量空间搜索 (ANN Search)
- 在数据库中寻找与 VqueryV_{query}Vquery 距离最近(相似度最高)的 KKK 个向量。通常使用余弦相似度 (Cosine Similarity) 计算:
- similarity=A⋅B∥A∥∥B∥\text{similarity} = \frac{A \cdot B}{\|A\| \|B\|}similarity=∥A∥∥B∥A⋅B
- Step C: 召回与过滤
- 数据库返回最匹配的原始文本片段。作为后端,你通常会在这里根据 user_id 做 Metadata Filter,确保 A 用户的提问不会检索到 B 用户的记忆。
- Step D: 上下文注入 (Context Injection)
- 将检索出来的“记忆片段”拼接到当前的 Prompt 中。
SAA的长期记忆存储
Spring AI Alibaba 将长期记忆以 JSON 文档的形式存储在 Store 中。
每个记忆都在自定义的 namespace(类似于文件夹)下组织,并使用唯一的 key(类似于文件名)。命名空间通常包含用户或组织 ID 或其他标签,以便更容易地组织信息。
这种结构支持记忆的层次化组织。通过内容过滤器支持跨命名空间搜索。
javaimport com.alibaba.cloud.ai.graph.store.stores.MemoryStore; import com.alibaba.cloud.ai.graph.store.StoreItem; import java.util.*; // MemoryStore 将数据保存到内存字典中。在生产环境中请使用基于数据库的存储实现 MemoryStore store = new MemoryStore(); String userId = "my-user"; String applicationContext = "chitchat"; List<String> namespace = List.of(userId, applicationContext); // 保存记忆 Map<String, Object> memoryData = new HashMap<>(); memoryData.put("rules", List.of( "用户喜欢简短直接的语言", "用户只说中文和Java" )); memoryData.put("my-key", "my-value"); StoreItem item = StoreItem.of(namespace, "a-memory", memoryData); store.putItem(item); // 通过ID获取记忆 Optional<StoreItem> retrievedItem = store.getItem(namespace, "a-memory"); // 在此命名空间内搜索记忆,通过内容等价性过滤,按向量相似度排序 List<StoreItem> items = store.searchItems( namespace, Map.of("my-key", "my-value") );
SAA的长期记忆管理
SAA还可以进行对长期记忆的管理,我们可以使用 ModelHook 在模型调用前后自动加载和保存长期记忆。
同时,我们也可以在tool里面进行读取和写入长期记忆。
长期记忆的写入和召回等核心流程就如上面说到的一样,这里由于篇幅就不详细赘述。
总结
本文讨论了Memory的概念,两种不同记忆机制的区别以及SAA如何处理不同记忆。
但是大模型应用开发Memory是门学问,也不可能一文能讲清楚所有场景该怎么做,比如短期记忆和长期记忆混合该怎么使用,那就等着读者去探索了。