切换主题
第 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. 记忆怎么流动
这个流程里有三个关键判断:
- 是不是同一个
threadId:不是同一条线,就不会接上同一份状态。 - Saver 能不能读回状态:内存 Saver 重启后会丢,生产要考虑 Redis、数据库或其他持久化。
- 状态里保存了什么:不只聊天记录,还可能有工具结果、结构化字段、下一步路由信息。
4. 记忆不是越多越好
很多人第一次做 AI 记忆,会忍不住把所有历史都塞进去。结果像开会时把过去三年的会议纪要全投到屏幕上:信息是全了,人也晕了。
生产里要做取舍:
| 问题 | 常见做法 |
|---|---|
| 历史太长 | 裁剪、摘要、只保留关键字段 |
| 多用户隔离 | threadId 和用户身份、租户信息绑定 |
| 服务重启后恢复 | 使用持久化 Saver |
| 工具结果是否保存 | 保存关键结果,不保存敏感或可重新查询的大对象 |
| 隐私和合规 | 明确记忆范围、过期策略和删除能力 |
记忆的目标不是“永远记住一切”,而是“在合适的时候拿回合适的信息”。
5. 新手容易误解什么
误解一:同一个用户就一定有记忆。
还差关键一步:运行时要传入同一个 threadId。用户身份和会话线程不是天然等号。
误解二:有 threadId 就万事大吉。
还要看 Saver 是否能保存和恢复。没有档案柜,档案袋编号再漂亮也没用。
误解三:记忆只等于聊天记录。
在 Agent/Graph 里,状态可能还包括工具结果、节点输出、结构化字段和下一步路由。它更像一张运行现场的工作台。
6. 本章小结
记忆的工程关系可以压成一句话:
text
threadId 决定是哪条线,Saver 决定状态存哪里,OverAllState 决定存什么。下一章我们让 Agent 不只会说自然语言,还能输出稳定结构:比如分类结果、摘要字段和 JSON。
7. 练习题
- 用同一个
threadId连续问两轮,再换一个threadId问同样问题,对比输出。 - 设计一个客服场景的
threadId规则:它应该和用户 ID、工单 ID、租户 ID 怎么组合? - 列出哪些信息应该进入记忆,哪些信息不应该进入记忆,并说明原因。
课后源码索引:想验证实现时再打开
| 你想验证的结论 | 源码锚点 |
|---|---|
| 官方记忆示例 | 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(...) |