Skip to content

第 4 章:记忆与线程,threadId 是 Agent 的档案袋

前面两章,我们的 Agent 已经会回答、会用工具。但它还有个问题:如果每次调用都像第一次见面,那它更像临时窗口,不像一个助手。这章讲“记忆”,但不讲玄学,只讲工程链路。

本章边界

这一章只讲短期会话记忆:threadId 如何标识一段会话,Saver 如何保存状态,运行状态里到底可能装了什么。长期用户画像、向量记忆、跨系统数据治理先不展开。

1. 生活类比:档案袋比好记性靠谱

一个老师记得学生,不是靠玄学。他有花名册、课堂记录、作业本。学生下一次来,老师翻到同一个档案袋,前后信息就接上了。

在 Spring AI Alibaba 里:

档案系统框架概念作用
档案袋编号threadId标识同一段会话
带来的上下文RunnableConfig告诉本次运行使用哪个线程和配置
档案柜MemorySaver保存和读取 checkpoint
档案内容OverAllState保存消息、工具结果、节点输出等状态

所以记忆不是“模型突然变聪明了”,而是工程系统把同一条线上的状态接回来了。

2. 最小记忆示例

先准备一个内存版 Saver:

java
@Bean
MemorySaver memorySaver() {
    return new MemorySaver();
}

再把它挂到 Agent 上:

java
@Bean
ReactAgent memoryAgent(ChatModel chatModel, MemorySaver memorySaver) {
    return ReactAgent.builder()
            .name("memory_agent")
            .model(chatModel)
            .instruction("你是一个学习助教,需要在同一会话中保持上下文。")
            .saver(memorySaver)
            .build();
}

调用时关键是同一个 threadId

java
RunnableConfig config = RunnableConfig.builder()
        .threadId("lesson-thread-001")
        .build();

memoryAgent.call("我叫小李,请记住。", config);
AssistantMessage answer = memoryAgent.call("我刚刚叫什么?", config);

如果第二次换了 threadId,就像换了一个档案袋。你问“我刚刚叫什么”,系统可能真的一脸茫然。

3. 记忆怎么流动

这个流程里有三个关键判断:

  1. 是不是同一个 threadId:不是同一条线,就不会接上同一份状态。
  2. Saver 能不能读回状态:内存 Saver 重启后会丢,生产要考虑 Redis、数据库或其他持久化。
  3. 状态里保存了什么:不只聊天记录,还可能有工具结果、结构化字段、下一步路由信息。

4. 记忆不是越多越好

很多人第一次做 AI 记忆,会忍不住把所有历史都塞进去。结果像开会时把过去三年的会议纪要全投到屏幕上:信息是全了,人也晕了。

生产里要做取舍:

问题常见做法
历史太长裁剪、摘要、只保留关键字段
多用户隔离threadId 和用户身份、租户信息绑定
服务重启后恢复使用持久化 Saver
工具结果是否保存保存关键结果,不保存敏感或可重新查询的大对象
隐私和合规明确记忆范围、过期策略和删除能力

记忆的目标不是“永远记住一切”,而是“在合适的时候拿回合适的信息”。

5. 新手容易误解什么

误解一:同一个用户就一定有记忆。

还差关键一步:运行时要传入同一个 threadId。用户身份和会话线程不是天然等号。

误解二:有 threadId 就万事大吉。

还要看 Saver 是否能保存和恢复。没有档案柜,档案袋编号再漂亮也没用。

误解三:记忆只等于聊天记录。

在 Agent/Graph 里,状态可能还包括工具结果、节点输出、结构化字段和下一步路由。它更像一张运行现场的工作台。

6. 本章小结

记忆的工程关系可以压成一句话:

text
threadId 决定是哪条线,Saver 决定状态存哪里,OverAllState 决定存什么。

下一章我们让 Agent 不只会说自然语言,还能输出稳定结构:比如分类结果、摘要字段和 JSON。

7. 练习题

  1. 用同一个 threadId 连续问两轮,再换一个 threadId 问同样问题,对比输出。
  2. 设计一个客服场景的 threadId 规则:它应该和用户 ID、工单 ID、租户 ID 怎么组合?
  3. 列出哪些信息应该进入记忆,哪些信息不应该进入记忆,并说明原因。
课后源码索引:想验证实现时再打开
你想验证的结论源码锚点
官方记忆示例examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/MemoryExample.java,类 MemoryExample,方法 basicMemoryConfiguration()useMessageTrimming()useMessageDeletion()useMessageSummarization()accessMemoryInTool()
输入如何转成运行状态spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/Agent.java,类 Agent,方法 buildMessageInput(...)doInvoke(...)doStream(...)
状态如何保存和读取spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/OverAllState.java,类 OverAllState,方法 data()value(...)updateState(...)updateStateWithKeyStrategies(...)
checkpoint saver 如何配置spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/CompileConfig.java,类 CompileConfig,方法 builder()saverConfig(...)
内存版 saver 如何存取spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/MemorySaver.java,类 MemorySaver,方法 list(...)get(...)put(...)release(...)

Built with VitePress. Deployed on Cloudflare Pages.