智能体记忆类型及存储与重嵌入成本
用一张表比较语义、情景和程序记忆,并说明每类在 VPS 上的存储、索引与重嵌入成本,帮助您选择键值存储、保留期限和代码仓库。
三种智能体记忆类型
智能体记忆分为三类,而且每一类对您付费使用的硬件都有不同影响:语义记忆保存事实,情景记忆保存发生过的事情,程序记忆保存完成任务的方法。下表通过服务器示例定义了每一类。后文讨论通常被忽略的另一半内容:每种类型需要多少存储成本,以及重建它需要付出什么代价。
The data behind this chart
[
{
"label": "Semantic",
"what_it_holds": "Facts the agent should treat as currently true",
"server_example": "The database listens on 10.8.0.4:5432 and the nightly dump runs at 03:15 UTC"
},
{
"label": "Episodic",
"what_it_holds": "A record of one past event or session",
"server_example": "On 2026-08-11 the deploy failed because the disk was full, and rotating logs fixed it"
},
{
"label": "Procedural",
"what_it_holds": "How to carry out a task, as steps the agent can run",
"server_example": "The restore runbook: stop the service, load the dump, run migrations, start the service"
}
]这些名称借用了人类心理学中的概念,但对应关系并不严格。区分这三类记忆具有实际意义:它们的规模和修复路径不同,因此将它们全部放入同一个向量存储会使每一类记忆的效果都变差。
语义记忆规模较小,需要手动编辑
关于您自己的服务器,几百条事实只有几十 KB 文本。存储不是问题,修正才是。语义记忆中的错误事实会影响代理之后给出的每个答案,因此存储必须支持按名称查找单条事实、修改该事实,并确认旧值已被删除。
这意味着应使用键值存储:带主键的 Postgres 表,或 Git 中由小型 Markdown 文件组成的目录。两者都支持执行一次查询、查看值并直接编辑。相似性搜索无法提供这种能力,因为它按相似程度检索,而不是按键检索。“更改数据库端口”会变成“查找所有提到数据库端口的文本块”,而您无法证明已经找全。请为事实设置键。如果还需要宽松召回,也可以为事实生成嵌入,但应将带键的副本视为事实来源。
过时事实不会主动提示自己。端口已经更改,但数据行仍然保留,因此代理会继续回答一个在 6 月份还正确的数字。代理记忆的过时和清理策略是本页的另一部分;在表还较小时设计这项策略,成本低得多。
无需限制增长的情节记忆
情节记忆就是日志,日志会不断增长。每个会话、每次工具调用和每条失败的命令都可能成为一个情节。代理每轮写入一行时,一个月写入的行数会远超任何人实际能阅读的数量;成本也不只有磁盘空间:每个嵌入后的情节还会加入搜索需要遍历的索引。
创建表的当天就确定保留规则,此时删除数据仍然没有成本。两个问题通常就能确定大部分规则。第一,哪些内容值得写入:会话摘要通常值得写入,而一次 ls -la 的完整输出通常不值得写入。第二,每类情节保留多长时间:例如,原始情节保留 30 天,会话摘要保留 1 年。
为每行情节添加一个 created_at 时间戳和一个 source 列。没有 created_at,就无法按时间删除数据。没有 source,就无法删除来自某个问题来源的全部数据;当你发现某个网页或工单一直在向记忆写入指令时,这正是必须执行的操作。
DELETE FROM episodes WHERE created_at < now() - interval '30 days';通过 systemd timer 执行该操作,然后观察行数和表大小是否确实发生变化。无人执行的保留策略只是注释。
psql -d agentmem -c "SELECT count(*) FROM episodes;"
psql -d agentmem -c "SELECT pg_size_pretty(pg_total_relation_size('episodes'));"将操作流程记入代码仓库
操作流程记忆描述代理如何完成任务:可以是 shell 脚本、技能文件,或包含编号步骤的运行手册。这些内容属于代码,因此应存放在代码所在的位置:具备评审、版本管理和可读差异对比功能的 git 仓库中。
将运行手册存储为嵌入分块,只能得到近似副本。检索会返回得分最高的分块,因此代理可能执行第 2 步和第 5 步,而第 3 步从未被检索到;同时也没有任何记录能说明运行的是哪个版本的流程。在 git 中,git log 可以同时回答这两个问题。其存储成本几乎为零,这也是不应为它支付向量存储成本的另一个原因。
磁盘上实际需要多少嵌入存储空间
The data behind this chart
[
{
"label": "bge-small-en-v1.5 (384 dims)",
"bytes_per_vector": 1544,
"mib_per_100k_rows": 147.2
},
{
"label": "bge-base-en-v1.5 (768 dims)",
"bytes_per_vector": 3080,
"mib_per_100k_rows": 293.7
},
{
"label": "bge-large-en-v1.5 (1024 dims)",
"bytes_per_vector": 4104,
"mib_per_100k_rows": 391.4
},
{
"label": "text-embedding-3-small (1536 dims)",
"bytes_per_vector": 6152,
"mib_per_100k_rows": 586.7
},
{
"label": "text-embedding-3-large (3072 dims)",
"bytes_per_vector": 12296,
"mib_per_100k_rows": 1172.6
}
]pgvector 将 vector 存储为每个维度 4 bytes,外加 8 byte header。因此,这个计算结果是固定的,加载数据前就可以据此规划空间。一个 384 dimension vector 占用 1544 bytes,因此 100,000 个 chunk 需要 147.2 MiB 的向量存储空间。同一批数据使用 3,072 dimensions 嵌入时,需要 1172.6 MiB,每行占用 12296 bytes。文本相同,但存储空间接近 8 倍。
这还只是向量列。chunk 文本、primary key、行开销和索引都需要额外空间,其中索引最容易被忽略。HNSW(hierarchical navigable small world,pgvector 构建的图索引)会保存它所连接的向量副本,因此,建立索引后的存储空间通常会明显超过上述数值的 2 倍。请测量自己的数据,不要凭估算。
SELECT pg_size_pretty(pg_total_relation_size('memories')) AS total,
pg_size_pretty(pg_relation_size('memories_embedding_idx')) AS idx;RAM 决定搜索是否足够快,因为只有图驻留在内存中时,遍历图才会很快。当索引大于 Postgres 可容纳的内存时,搜索会开始从磁盘读取数据,延迟随之升高。构建索引也有自己的限制,即 maintenance_work_mem。当图超过该限制时,构建过程会报告这一情况并变慢:
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 61440 tuples
HINT: Increase maintenance_work_mem to speed up builds.有两种方式可以缩小同一批数据。第一种是选择更小的模型,因为 384 dimensions 的存储成本只有 1,536 dimensions 的四分之一;对于重新查找自己的笔记,准确率差异通常小到可以接受。第二种是存储 half precision:halfvec 类型每个维度占用 2 bytes,header 仍占用 8 bytes,因此向量列及其索引的总大小几乎可以减半。
选择模型前,还需要了解一个限制。截至 August 2026,vector 列最多可以为 2,000 dimensions 建立索引。因此,3,072 dimensions 的嵌入可以存入该列,但无法建立索引:
ERROR: column cannot have more than 2000 dimensions for hnsw indexhalfvec 最多支持 4,000 dimensions 的索引,因此通常的解决方法是为类型转换后的值建立索引:
CREATE INDEX ON memories USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);Postgres 优于独立记忆服务的情况
如果服务器已经运行 Postgres,向量只需安装一个软件包并执行一条语句。
psql -V
sudo apt install postgresql-16-pgvectorCREATE EXTENSION vector;软件包名称中的数字表示 Postgres 主版本。Ubuntu 24.04 使用 16,因此输入命令前先阅读 psql -V。
将记忆保存在该数据库中,可以在同一时间为记忆和应用数据创建一个备份,使用一个连接池,并利用事务:事实及其描述行要么一起提交,要么一起失败。独立服务无法提供这种保证。
满足以下任一条件时,应改用专用记忆服务。搜索负载与应用竞争资源,需要使用独立机器。多个主机上的多个代理需要共享同一份记忆。或者,您希望使用成熟产品自带的提取和去重逻辑;这也是选择 自托管 Mem0 记忆服务器的理由。对于单个代理以及包含数百万个以内数据块的语料库,在现有服务器上运行 pgvector 更易于运维,也更不容易出问题。引擎选择本身,以及各引擎对 RAM 的需求,参见 在 VPS 上运行向量数据库。
更换模型时重新嵌入的成本
两个不同模型生成的向量不可比较,因此不能使用新模型嵌入新记忆,同时保留旧行不变。混合表会返回无意义的结果,因为在两个不同坐标系之间计算的距离没有实际含义。更换模型意味着必须重新嵌入整个语料库。
这项成本包括4部分:令牌费用(API 费用,或自有服务器上的 CPU 和 GPU 时间)、运行期间占用的实际时间、回填期间同时保存两列所需的磁盘空间,以及最后重建索引的成本。安全的操作顺序是:添加新列,分批回填,切换查询,然后删除旧列及其索引。
请在自己的硬件上测量处理速率,不要直接相信已发布的数据,因为小型 VPS 上仅使用 CPU 进行嵌入的速度,远低于使用 GPU 运行同一模型的速度。先测量一个具有代表性的文本块所需的时间,再乘以语料库大小。
ollama pull nomic-embed-text
time curl -s http://localhost:11434/api/embed \
-d '{"model": "nomic-embed-text", "input": "one representative chunk of your corpus"}' > /dev/null本地嵌入模型还会将模型权重存储在与记忆存储相同的磁盘上,Ollama 存储已拉取模型的位置会说明这些空间的用途。
这一切能够实现的前提是:在每个向量旁边保留源文本。只保存向量的存储根本无法重新嵌入,因为已经没有内容可供新模型处理。如果无法回答“生成这一行的文本是什么”,就只能从文本最初的来源完整重建。
运行记忆后需要关注的事项
随着存储库不断填充,发生变化的不只是成本。旧事实会逐渐过时,旧事件也会挤占有用结果,这又回到了剪枝问题。记忆存储库还是代理未来行为的可写入输入,因此任何获准向其中写入内容的来源,都可能在之后影响代理的行为。如果网页或工单中的文本会进入记忆,请先阅读代理记忆中毒的工作原理,再扩大允许写入的来源。
每个请求的检索规模也会直接影响 token 消耗,而保持代理运行成本可预测就从这里开始。
FAQ
我需要使用向量数据库存储智能体记忆吗?
存储事实时不需要。语义记忆规模较小,而且需要按名称修正,因此使用键值表或 Git 中的 Markdown 文件目录更合适,因为您可以直接查看并编辑某个值。只有在语料库过大、无法逐项列出,且必须按含义检索时,嵌入向量才值得付出成本。这通常适用于情景记忆和文档。如果您已经运行 Postgres,CREATE EXTENSION vector 无需增加其他需要运维的服务即可满足需求。
智能体记忆存储会占用多少磁盘空间?
在 pgvector 中,向量的存储量可以准确估算:每个维度占 4 bytes,另加 8 byte 的标头。768 维时,每 100,000 行占用 293.7 MiB;384 维时占用 147.2 MiB。然后还要加上分块文本、行开销,以及会自行保存一份向量副本的 HNSW 索引。因此,规划空间时至少按向量占用量的两倍计算,并使用 pg_total_relation_size 测量实际占用量。
过程记忆应存放在哪里?
存放在 Git 仓库中,作为智能体可以直接运行的脚本或技能文件。过程需要精确回忆和版本历史,而相似度搜索无法提供这两点。分块后的运行手册返回得分最高的片段,可能导致第 2 步和第 5 步返回,而第 3 步缺失;同时也不会记录实际运行的是哪个版本。
更换嵌入模型的成本是什么?
需要对整个语料库重新生成嵌入,因为不同模型生成的向量无法相互比较。您需要预留 token 费用或 GPU 时间,同时为旧列和新列并存准备磁盘空间,并重建索引。添加新列,分批回填数据,切换查询,然后删除旧列。所有这些操作都要求您将源文本与每个向量一同保留。