一站式了解Spring AI Alibaba的Memory机制


文章目录


引言

我相信大家在平时一定使用过网页对话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

https://juejin.cn/post/7596181746062508059

2.逃出结构化思维:从向量,向量数据库到RAG

https://juejin.cn/post/7587074669818134554

SAA本质上是一个AI应用开发框架,可以开发agent,multi-agent和工作流等等,那么必然需要使用底层LLM,然而我们前面已经说过了LLM实际上是无状态的,那么LLM应用的相关记忆必定是由我们上层应用来开发和管理的。

即如果你使用的是 Qwen 的 API(如 dashscope) ,你需要自己维护对话历史并作为输入传入。

SAA的Memory机制的架构

下面是SAA实现两种Memory的架构图,其中左下角的箭头(指向ModelHook)应该放置到中间的,画错了就将就看吧

维护短期记忆

如何添加短期记忆

在 Spring AI Alibaba 中,要向 Agent 添加短期记忆(会话级持久化),你需要在创建 Agent 时指定 checkpointer

java 复制代码
import 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 中访问和修改状态来扩展记忆功能

java 复制代码
import 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(滑动窗口): 只保留最近的 N N N 轮对话,丢弃最早的记录。
  • Summary Memory(摘要记忆): 当对话过长时,调用大模型对之前的对话做一个简短总结,后续只带上这个 Summary
  • Vector Database(向量数据库/RAG): 将历史对话存入向量库(如 Milvus, Pinecone)。当用户提问时,通过语义搜索检索出最相关的历史片段(Top-K),塞进 Context 传给模型。

维护长期记忆

长期记忆的检索过程

对于后端开发人员来说,长期记忆的检索过程其实就是一个标准的 "语义检索 + RAG (Retrieval-Augmented Generation)" 工作流。

你可以把它类比为:全文搜索(Elasticsearch)的升级版 。不同于关键词匹配,它是基于"意思"来匹配


整个过程可以分为 "存入""检索" 两个阶段:

1. 存入阶段(写入/索引)

当一段对话结束或产生新的知识时:

  1. 切片 (Chunking) :将长文本切分成合适的大小(例如 512 tokens)。
  2. 向量化 (Embedding) :调用 Embedding 模型(如 OpenAI 的 text-embedding-3-small)将文本转为高维向量(如 1536 维的浮点数数组)。
  3. 入库 :将向量和原始文本、Metadata(如 user_id, timestamp)存入向量数据库(Redis Vector Search, Milvus, 或 Pinecone)。
2. 检索阶段(读取/召回)

当用户提出一个新问题,系统需要"回想起"以前的事情:

  • Step A: 问题向量化

    将用户的提问(Query)同样通过 Embedding 模型转为一个向量 V q u e r y V_{query} Vquery。

  • Step B: 向量空间搜索 (ANN Search)

    在数据库中寻找与 V q u e r y V_{query} Vquery 距离最近(相似度最高)的 K K K 个向量。通常使用余弦相似度 (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 或其他标签,以便更容易地组织信息。

这种结构支持记忆的层次化组织。通过内容过滤器支持跨命名空间搜索。

java 复制代码
import 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是门学问,也不可能一文能讲清楚所有场景该怎么做,比如短期记忆和长期记忆混合该怎么使用,那就等着读者去探索了。

如果你感觉这篇入门文章给你带来了不错的体感,那就给我点赞+收藏+关注吧❤️

相关推荐
打工的小王2 小时前
Langchain4j(二)RAG知识库
java·后端·ai·语言模型
2501_941507942 小时前
YOLO11-C3k2-ODConv玻璃缺陷检测与分类任务详解
人工智能·分类·数据挖掘
2501_941837262 小时前
财务文档类型识别与分类 _ 基于改进YOLO11-C2PSA-EDFFN模型的文档智能识别系统
人工智能·分类·数据挖掘
qunaa01012 小时前
改进YOLOv5结合SwinTransformer实现青香蕉手指部分自动识别与分类
人工智能·yolo·分类
巫婆理发2222 小时前
Keras简介
人工智能·深度学习·keras
Serverless 社区2 小时前
进阶指南:BrowserUse + Agentrun Sandbox 最佳实践指南
人工智能·云原生·serverless
min1811234562 小时前
AI从工具向自主决策者的身份转变
大数据·网络·人工智能·架构·流程图
李慕婉学姐2 小时前
【开题答辩过程】以《基于springcloud的空气质量监控管理系统》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
后端·spring·spring cloud