让 Agent 拥有长期记忆:基于 Tablestore 的轻量级 Memory 框架实践 - 今日头条 摘要
文档概览
这篇文章是一份围绕 Tablestore 的 Agent 存储框架实践总结,目标不是抽象讨论“记忆”概念,而是回答一个很工程化的问题:如何用一套相对轻量的存储抽象,同时支撑 Agent 的实时记忆与长期知识检索。
原文一开始先把 Agent 的基础组成简化为:LLM + 记忆(Memory)+ 规划技能(Planning skills)+ 工具使用(Tool use)。在这个前提下,文章强调 Agent 的“感知-决策-行动”闭环离不开两类核心存储能力:Agent Memory 与 Knowledge。
文章的主线是:
- 先区分 Memory 与 Knowledge 的不同需求。
- 再解释为什么传统 MySQL、PostgreSQL 在部分 Memory 诉求上会出现瓶颈。
- 然后给出以 Agent Memory SDK 为统一抽象层、底层由 Tablestore 同时承载 Memory 和 Knowledge 的整体架构。
- 接着分别展开 Memory 与 Knowledge 的表设计思路。
- 最后用聊天场景与 RAG 场景做代码示例,并补充线上性能、规模、成本和行业案例。
关键事实
Agent 需要两类不同的存储能力
原文明确把 Agent 的存储分成两大类:
- Agent Memory:作为“回忆”,用于情景记录、情景摘要、实时数据抽取等;强调毫秒级响应、高并发、动态 Schema 扩展。典型场景包括用户与大模型的会话上下文管理、实时状态信息同步等。
- Knowledge:作为“知识”,用于知识库原文、摘要等内容的存储与检索;核心能力是语义检索,此外规模、性能、成本也同样重要。典型场景包括 RAG 知识库、多模态搜索等。
这一区分非常关键。原文不是把所有 Agent 数据都塞进一种数据库范式,而是承认“记忆”和“知识”是两种不同工作负载:前者更偏高频写入、上下文回放、实时筛选,后者更偏全文/向量检索与知识召回。
为什么引出 Tablestore 方案
文章指出,在处理 Memory 场景时,需要特别关注以下要求:
- 大规模数据集下的低成本
- 稳定性能
- 扩展性
- 灵活的检索能力
原文认为,传统数据库方案如 MySQL、PostgreSQL 在很多场景当然“表现良好”,但当这些要求同时出现,尤其是面对大规模数据集和复杂查询时,会不同程度地遇到瓶颈。文章没有给出逐项对比实验数据,但明确把这类瓶颈作为选型背景,从而引出基于 Tablestore 的 Agent Memory 框架。
换句话说,原文不是说 MySQL、PostgreSQL 不能做,而是说当系统同时要求大规模、低成本、稳定时延、可扩展和更灵活的检索能力时,需要一种更贴近该工作负载的底层方案。
框架设计目标
文章给出 3 个设计目标:
1. 轻量化设计
- 面向开发者友好。
- 抽象通用存储接口,降低业务开发复杂度。
- 平衡技术深度与易用性。
- 用户不需要直接参与底层数据库和存储接口调用,可以更快开发并看到结果。
2. 场景驱动设计
当前主要支持两类场景:
- Memory 的实时记忆存储
- Knowledge 的长期语义检索
而且文章特别提到,不只是满足“底层存储”本身,还会继续提供贴近业务的场景方案,例如:
- Summary 记录
- 事实数据提取
- 用户画像/标签挖掘
3. 业务价值验证
- 用户不需要做复杂技术调研,就能快速复用成熟方案。
- 可以更快在自己的业务里做价值验证。
- 团队会把头部客户案例做技术沉淀。
文中列举的沉淀方向包括但不限于:
- 通义千问 App
- 某头部浏览器的 AI 搜索 Memory
- 网盘
- 知识库 RAG
- 阿里巴巴 1688 商品 AI 搜索
- 阿里云 OSS 的 Meta 语义检索
- 钉钉的 AI 搜索
核心架构
文章给出的整体架构是:通过 Agent Memory SDK 屏蔽底层实现细节,开发者主要面向 SDK 编程;底层则基于 Tablestore 同时实现 Memory 与 Knowledge 两类能力。
原文对这个架构的强调点在于:
- SDK 负责隐藏底层复杂性。
- 底层不分裂为两套完全独立的开发接口,而是在统一抽象下承载两种场景。
- Memory 场景未来还会继续扩充新的细分能力。
这意味着它的定位不是单纯数据库封装,而是一个面向 AI 场景的数据访问与场景能力层。
Memory 设计
以情景记忆为主
原文说,Memory 最常见的场景是“情景记忆”,重点就是:
- 会话管理
- 历史消息存储
因此文中用这一场景来介绍 Tablestore 的 Memory 表设计,并明确指出这里有两张核心表:
- Session 表
- Message 表
文章还补充了一个边界条件:如果业务有特殊化需求,可以参考这套表设计重新设计,也可以联系研发协助设计。这说明 Session / Message 并不是唯一模式,但它是作者认为最典型、最具通用性的 Memory 起点。
Session 表的用途
从示例代码和描述可以还原出 Session 表主要承担:
- 标识一次会话
- 关联用户与会话
- 记录会话维度元信息
- 维护最近更新时间
update_time
示例里创建 Session 时至少包含:
user_idsession_idupdate_timemetadata,例如model_name = "qwen 2.5"
update_time 的作用在原文场景里非常具体:当用户发来新消息时,不仅要写入 Message,还要同步更新 Session 的 update_time,以反映会话最近活跃状态。
Message 表的用途
Message 表承担历史消息记录,示例里至少体现了这些字段:
session_id:消息归属的会话message_id:消息唯一标识create_time:创建时间,示例使用微秒时间戳content:消息内容metadata:例如message_type,区分“用户”与“大模型”
原文场景中每条消息都会变更 message_id,并根据消息来源设置不同的 message_type。后续按 metadata_filter 查询时,就是利用这个元数据字段筛选用户消息。
Knowledge 设计
Knowledge 侧的重点不是会话,而是围绕文档、多租户、全文检索与向量检索做设计。原文直接给出了几个核心设计逻辑。
1. DocumentID 作为分区键
文章说明把 DocumentID 作为表的分区键,原因是:
- 需要让文档离散到各个分区
- 全文 + 向量会让单行数据较大
- 把
DocumentID放在第一个主键并作为分区键,有助于让各个分区的读写压力更均衡
这说明设计重点是避免因为单文档较大或热点集中导致分区压力不均。
2. DocumentID + TenantID 组成唯一主键
原文写得很明确:
DocumentID和TenantID组合起来作为唯一主键- 如果不是多租户场景,
TenantID填默认值即可
也就是说,多租户不是额外外挂的逻辑,而是在主键设计阶段就被纳入模型中。
3. 多租户路由键
文章对多租户做了进一步说明:
- 开启多租户特性后,会把
TenantID设置为多元索引的路由键 - 这样同一租户的数据可以落到指定分区上
- 查询时不需要访问所有分区
- 从而降低查询消耗并减少毛刺
这里的“毛刺”可以理解为查询延迟或资源消耗上的抖动。原文强调的不是“支持多租户”这句空话,而是具体到:怎样通过路由键让查询避免全分区扫描。
4. 全文检索设计
全文检索部分,原文说明:
text文本字段在多元索引里会自动创建最大语义分词- 如果需要其他分词方式,可以随时修改
这意味着默认配置偏向开箱即用,但并没有封死检索策略,分词可以调整。
5. 向量检索设计
向量检索部分,原文说明:
embedding向量字段会自动创建向量索引- 内部会自动选择合适的索引结构进行索引
- 用户无需自行调优和优化
这与轻量化目标是一致的:开发者主要提供向量,不需要先研究底层索引参数组合。