ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Zero-Mem 设计思路:为 LLM Agent 打造零 Token 记忆操作

Zero-Mem 设计思路:为 LLM Agent 打造零 Token 记忆操作 我最近在跑一个多步骤的 LLM Agent 用例让 Agent 自己去查接口、整理资料、生成测试用例再汇总成报告。任务本身不算复杂但跑到第三四轮的时候我发现一个特别尴尬的现象——每次请求的报文越来越长。每个工具返回、每段历史、每次中间总结都要在上一轮的基础上继续携带。等到第五轮真正让模型做判断的内容可能只占很小一部分大部分 token 都花在了“把之前发生的事情再讲一遍”上面。这其实不是一次性偶然。只要是多工具、多轮、长任务的 Agent基本都会撞上同一个问题模型本身无状态Agent 的状态全部活在 Prompt 里。而 Prompt 越长费用越高、延迟越明显、模型越容易忽略真正重要的信息。Zero-Mem 这个名字最近在 Agent 记忆相关讨论里被反复提及。它的完整表达是 Zero-Token Memory Operations for LLM Agents直译过来是“面向 LLM Agents 的零 Token 记忆操作”。从命名就能感受到它的目标让 Agent 在处理记忆时不为记忆操作本身支付 Token 成本。这个目标听起来很诱人但也极其容易误读。它不是简单地说“记忆不要钱”也不是否定记忆的必要性。在我看来Zero-Mem 真正值得关注的地方不是“省 Token”本身而是它把 Agent 的记忆从“上下文内文本”变成了“上下文外状态”。省 Token 只是结果背后是 Agent 工作流逻辑的一层转换。这篇文章会从问题根源、机制理解、工程实践和落地边界四个层面展开。需要先说明的是能够公开确认的细节很有限所以这里更多是把它当作一种设计思路来分析而不是给某个固定仓库写说明书。落地时还是要结合你自己用的 Agent 框架和业务场景来做取舍。1. Agent 的“记忆焦虑”Token 在运行时是怎么失控的1.1 记忆问题的本质模型无状态Agent 需要外部状态大语言模型本身不保留任何跨请求的状态。你上一次让它处理任务时得到的中间结果如果这一次请求里不带上它一概不知道。所谓 Agent 的记忆本质上就是框架把历史消息、工具返回、状态说明编进下一次请求的 Prompt。这个设计很简单直接但它藏着一个成本结构问题每一轮任务都会把之前的全部上下文重新传给模型被重新传的内容里有大量已经不再重要的中间输出工具调用越多上下文越“脏”模型越难聚焦在关键决策上。一个 N 轮的工具调用任务跑下来token 消耗基本是 O(N × 平均上下文长度)。这不是“线性可用”而是会随着任务复杂度快速膨胀。更麻烦的是这种膨胀并不是因为任务本身变复杂了而是因为状态表达的重复成本太高。这也是为什么很多人在本地小 demo 里觉得 Agent 很好用一旦放进真实业务流程里就开始头疼成本。问题不在模型能力而在于我们把“记忆”和“上下文”绑得太死了。1.2 现有记忆方案的共同软肋所有操作都在为 Token 买单目前常用记忆方案大概有四类长上下文、RAG、摘要记忆、向量库。每一类都有它存在的价值但如果放在“记忆操作”这个视角下看它们有一个共同点记忆正文最终还是要回到 Prompt 里。长上下文只是把容器变大没有改变“每轮重复传递”的模式。RAG检索到的知识片段还是要以 token 形式拼回上下文才能让模型阅读。摘要记忆摘要比原文短但摘要生成本身有成本且摘要内容仍然占 token。向量库检索效率确实高但“读回”这个动作依然要按 token 计费。换句话说这些方案解决的是“记忆内容怎么存、怎么找”但没有解决“记忆操作本身产生的 token 开销”这个问题。记忆越多花钱越多检索越频繁生成成本越高。我并不是说这些方案过时了。它们各有不可替代的用处。但从 Zero-Mem 的命名来看它想做一个变化把记忆操作从模型的文本生成中剥离出去让“写入、读取、删除、搜索”这些动作尽量不要把记忆正文带回 Prompt。1.3 Zero-Mem 想改变的不是记忆容量而是记忆的存取方式传统记忆系统的逻辑是“把记忆变成上下文的一部分”。Zero-Mem 这个名字暗示的逻辑更像是“把记忆变成 Agent 的一项外部能力”。模型不需要自己“记住”任务的关键状态也不需要把整段历史作为文本复述出来。它只需要知道我有一个外部记忆系统里面有之前写入的状态我可以调用几个短操作来读取或更新。真正执行操作的是宿主程序记忆正文留在存储层。这个思路变化看起来不大但它改变了成本曲线的形状。原来记忆成本随记忆规模线性增长现在如果操作设计得当成本可以变成“按调用次数和详情读取次数”计算的常量。2. “零 Token 记忆操作”到底在做什么2.1 记忆和记忆操作是两个完全不同的问题我们经常把“记忆”和“记忆操作”混在一起但这两者的性质完全不同。记忆是内容昨天的对话、用户偏好、工具返回、任务配置、中间结果。记忆操作是过程写入、读取、更新、删除、检索。传统 Agent 流程里两者高度耦合。比如你想让 Agent “记住这份报告的关键结论”模型先生成结论文本框架再把文本存进某个地方下一次需要用到时模型再根据上下文里的提示把结论重新复述出来。整个过程里每一次“我想让你记住”和“你帮我回忆”都是一次真实发生的文本生成token 就产生在这些环节。Zero-Mem 想做的是解耦记忆正文放在外部存储里记忆操作由宿主程序执行模型只接收非常短的操作状态。这句话值得多读几遍。它意味着模型不再承担“复述记忆”的职责只承担“基于操作结果做决策”的职责。2.2 零 Token 不是“记忆免费”而是“操作不进 Prompt”严格来说完全零 token 很难做到因为模型要和记忆系统交互至少需要知道有哪些操作可用、当前操作的返回结果是什么。这些信息本身是要占 token 的。所以更准确的理解是零 Token 指的是记忆正文不进上下文。写入时模型不需要看到完整正文检索时模型不需要看到全文更新时模型只需要确认动作已完成。可以这样类比你让一个助手去档案室找资料。助手回来后告诉你“找到了编号是 A-103标题是《项目配置》摘要里提到三个关键参数”。你不会一上来就让他把整箱资料复印一份。只有当你确实需要看细节时才让他把具体文件拿到桌上来。Zero-Mem 的设计思路就是给 Agent 配备这样一个“档案管理员”。模型与记忆系统之间的通信被压缩成两类短消息操作确认写入成功、删除成功、更新成功。检索签名命中了哪些条目、分数多少、每条的摘要。这两类消息都很短。真正占 token 的“记忆正文”默认停留在外部存储里。2.3 和 RAG、长上下文、摘要记忆的核心差异要理解 Zero-Mem最好把它和主流方案放在一张表里做对比方案核心思路记忆正文是否回到 Prompttoken 成本特征长上下文扩大窗口塞入更多历史是随轮次线性增长RAG根据查询检索知识片段并回填是检索越多回填越多摘要记忆把历史压缩成摘要是但比原文短摘要生成与读取都有成本向量库高效存储和语义检索是读回时收费存储便宜读取仍贵Zero-Mem 设计思路记忆正文留在外部操作返回短状态默认不回到详情按需取回随调用次数和详情次数变化RAG 的核心目标是“把外部知识搬回上下文让模型看得见”。Zero-Mem 的核心目标是“把 Agent 状态留在上下文之外让模型需要时再取”。一个是“投递”一个是“代管”。两者可以共存但解决的问题不一样。这也是我认为 Zero-Mem 最大的认知价值它提醒我们不是所有信息都需要进入上下文模型也不一定需要“看到”记忆才能“使用”记忆。3. 从工程角度设计一套接近“零 Token”的 Agent 记忆系统3.1 最小可用架构外部状态库、操作层、决策层如果把 Zero-Mem 的设计思路落到工程上一个最小可用系统通常包含三层第一层外部状态库。它负责真正保存记忆内容。可以是 SQLite、Redis、Postgres甚至是向量数据库。它不关心你的 Prompt 怎么写只关心能不能按 agent_id、key、时间等条件完成存取。第二层操作层。它把记忆能力封装成一组短接口写入、读取、删除、更新、检索。这些接口由宿主程序调用不由模型直接生成或控制。模型通过 function calling 或工具调用触发它们但执行过程不经过模型本身。第三层决策层。这仍然是模型的工作。模型根据任务上下文决定什么时候写、什么时候查、什么时候需要取详情。为了让模型用得好你要在这一层明确告知它记忆操作返回的是短状态不要复述记忆内容。这套架构不复杂但价值在于把“记忆的存储与执行”和“模型的推理”彻底分开。3.2 关键设计写入不进 Prompt检索只返签名详情按需展开三个关键设计点基本决定了这套方案能不能把 token 压下来。第一写入不进 Prompt。当 Agent 需要记录某个事实时模型只需要调用 write_memory(key, value)宿主程序把 value 直接写入存储然后向模型返回{status: ok, key: user_preference}。模型不需要看到 value 的完整内容也不需要确认“我已经记住啦”这句话。第二检索只返签名。当 Agent 需要回忆某类信息时它调用 search_memory(query) 外部检索服务先完成查询和排序再向模型返回命中的条目签名比如{key: task_config, score: 0.91, summary: 包含三个参数timeout、retry、output_dir}。模型根据签名判断哪条值得继续查看。第三详情按需展开。只有模型认为某个签名确实需要完整内容时才调用 get_memory(memory_id) 取回完整记录。这一步才真正把记忆正文放回上下文。换句话说从“记忆操作”变成“记忆使用”的时候才发生较大的 token 消耗。这三个设计组合起来效果就是模型在大多数轮次里接触到的记忆相关内容只是一些很短的 JSON 状态和摘要。真正的记忆正文不会被反复搬运。3.3 一个通用实现示例下面给出的是一个通用示例结构不是某个具体项目的 API。落地前要确认你的 Agent 框架支持哪种工具调用形式以及函数注册和返回格式是否符合约定。# 这是一个简化示例用于展示“零 token 记忆操作”的接口设计思路 class MemoryService: def __init__(self, store): self.store store def write_memory(self, agent_id, key, value, metadataNone): # 外部存储直接写入不把 value 转成模型可读文本 self.store.upsert(agent_idagent_id, keykey, valuevalue, metadatametadata) return {status: ok, action: write, key: key} def search_memory(self, agent_id, query, top_k3): # 检索发生在外部模型只收到签名列表 hits self.store.search(agent_idagent_id, queryquery, top_ktop_k) return [ { key: hit[key], score: round(hit[score], 3), summary: hit[summary], } for hit in hits ] def get_memory(self, agent_id, memory_id): # 只有模型明确需要时才取回完整内容 record self.store.get(agent_idagent_id, memory_idmemory_id) return {key: record[key], content: record[content]} def delete_memory(self, agent_id, memory_id): self.store.delete(agent_idagent_id, memory_idmemory_id) return {status: ok, action: delete, memory_id: memory_id}对应在 Prompt 里的工具说明可以简写成你可以使用以下记忆操作 - write_memory(key, value): 写入一条记忆不要复述 value 内容。 - search_memory(query): 搜索已有记忆返回命中条目的 key、score、summary。 - get_memory(memory_id): 当检索到的摘要不足以支撑决策时取出完整记忆内容。 - delete_memory(memory_id): 删除一条记忆。这里的重点是让模型养成“点到为止”的习惯。写入时不要复述检索时先看摘要确有必要再取全文。3.4 为什么这样设计能控制成本这样设计的核心是改变成本函数。原来 Agent 的记忆成本与记忆总量强相关历史越多每次请求都要连带支付一次全量记忆的 token 费用。而现在记忆正文不再自动进入上下文成本变成了每次记忆操作的调用结果通常只有几十个 token每次详情读取会带回一条或几条完整记忆摘要本身可能要在写入时生成一次后续使用不再重复生成。你会发现成本不再随“记忆总量”线性增长而是随“决策时真正需要的细节数量”变化。如果 Agent 在十轮任务里只取过两次详情那十轮下来记忆相关的 token 开销就是可控的不会随着历史累积而爆炸。这也是为什么我说Zero-Mem 最大的价值不是“省钱”而是把记忆成本从“规模相关”变成“使用相关”。4. 参数设计、适用场景和边界4.1 记忆库的关键参数分块、过期、优先级、冲突合并一个看起来简单的记忆库真正放进生产时会遇到不少设计决策。以下几个参数往往决定方案能不能长期使用命名空间 / agent_id多个 Agent 或多个会话如果共用一个库必须隔离。否则用户甲的数据可能被用户乙的 Agent 搜到。key 设计key 要可读、可预测、可覆盖。比如task_config、user_profile、stage_result。summary 生成策略为了让检索只返签名你需要在写入时为每条记忆生成一个短摘要。这个动作可以在写入时由宿主程序完成也可以让模型在写之前生成但那样会额外消耗一次调用。过期时间任务级短记忆可以设置 24 小时过期长期偏好可以按业务设定 30 天或更久。优先级同一 key 被多次写入时是覆盖还是保留历史建议保留版本号这样冲突时可回溯。top_k 与摘要长度检索结果返回多少条、每条摘要多长直接决定模型能不能快速判断也会影响上下文占用。给一套可参考的初始值具体要结合你的任务调整参数初始建议说明top_k3 ~ 5返回太少可能漏信息太多会挤占上下文summary 长度30 ~ 80 token足够判断是否取详情详情读取上限单次最多 1 ~ 2 条防止详情展开后上下文膨胀任务记忆过期24 小时适合大多数任务级状态长期记忆过期30 天或更久按业务频率调整命名空间强制使用多 Agent 场景必备4.2 适合这套方案的三类场景第一类多轮工具调用型 Agent。工具返回通常又长又杂塞进上下文会污染后续判断。把重要的工具返回摘要写入外部记忆模型只需要保留“已经完成某步”的短状态。第二类跨会话恢复场景。用户今天跑了一半任务明天回来想继续。外部记忆可以保存任务阶段、参数和中间结果新会话启动时只加载阶段摘要不必回放全部历史。第三类按阶段执行的长流程。比如一份调研任务分成三阶段资料收集、分析、产出。每个阶段结束模型把阶段结果写入记忆下一阶段只读取相关阶段的摘要和必要详情。这样每一阶段的上下文都很干净。4.3 不适合这套方案的场景如果你只是跑一个单轮、单工具的小任务引入外部记忆系统纯属过度设计。多一层存储就多一层故障风险。如果任务需要全局上下文推理比如“读取整个项目代码库找出所有涉及日志格式修改的地方”那么只给摘要是不够的。模型需要看到完整代码才能做出正确判断。这种场景下老老实实分批塞入或者使用长上下文更稳妥。如果你的项目连基本的日志和监控都没有也不要急着上外部记忆。外部记忆一旦失败Agent 会进入“以为自己知道但其实不知道”的状态没有可观测性时会非常难排查。注意Zero-Token 是一个优化目标不是一个默认事实。真正进入生产后摘要生成、详情读取、操作返回汇总仍然会产生 token只是量级比全量记忆回放小得多。目标是把记忆成本从“线性膨胀”压缩成“可控常量”而不是把它归零。5. 最容易踩的坑从“零 Token”到“看不见的记忆”5.1 状态漂移Agent 以为写入了库里却没有这是外部记忆系统最典型的问题。Agent 调用 write_memory 后接到了{status: ok}于是它认为状态已经落库。但如果底层写入是异步的事务没有提交或者写入操作因为网络问题静默失败那么实际上库里什么都没有。后续 Agent 再去检索自然什么都查不到。排查时第一步不是看模型而是看库查 operations 日志确认 write_memory 是否真的被触发查数据库记录确认 agent_id / key / value 是否真的存在查事务状态确认写入没有回滚。所以这里有个建议写入操作一定要在数据成功落库之后才能返回成功状态。不要做“发出请求就算成功”的乐观返回。5.2 摘要信息不足模型只知道“有”不知道“具体”如果 summary 生成得太短、太模糊模型在检索后只知道“我好像有相关记忆”但无法判断是否值得取详情。结果就是两种要么模型反复调用 get_memory把上下文撑大要么模型基于模糊信息做出错误判断。解决办法是摘要里要包含足够的决策信号。比如不是你写“包含用户配置”而是写“包含三个配置项timeout30, retry3, output_dir/tmp/report”。这样模型一眼就能判断哪些信息已经足够哪些还需要取全文。5.3 键冲突与数据污染多个阶段、多个任务共用同一个 key是最常见的污染来源。比如一个 Agent 在不同阶段都往result里写内容后一次写入把前一次覆盖掉后续阶段就读到了错误数据。解决方式很简单key 加入用途前缀。比如task_config任务配置stage_result.stage_1第一阶段结果user_profile用户偏好tool_cache.weather_api某个工具的缓存不要使用无意义的通用 key。命名空间是廉价的数据污染后的排查成本是昂贵的。5.4 检索效果差向量相似度排名不可靠语义检索不是银弹。搜索“用户的退款偏好”时很可能把“用户对退款流程的抱怨”排在前面因为语义相近。这样模型会读错记忆。更稳妥的做法是不要只依赖向量相似度加上 metadata 过滤按命名空间、时间范围、类型、优先级先粗筛再做语义排序。检索结果如果包含“score”等字段在 Prompt 里也要提示模型“分数低的不一定是错误可能只是相关度较低”。5.5 排查链路从现象到根因遇到“Agent 行为异常但看不出原因”的情况时按下面顺序排查看现象模型是重复调用工具、答非所问还是忽略记忆直接瞎猜看操作返回write_memory 是否真的返回成功search_memory 返回了几条分数是否合理看外部库对应 agent_id 和 key 下数据是否存在版本号是否最新是否已过期看检索接口query 经过了什么转换用了哪些过滤条件排序依据是什么看最终 Prompt模型实际看到的 summary 和 detail 是什么这些信息是否足够支撑决策看 token 计算对比开启外部记忆前后的输入 token 和任务成功率判断收益是否真实。这条链路的关键是不要一上来就怀疑模型“变笨了”。在外部记忆系统里多数问题出在状态、检索和上下文拼接这三层。6. 长期价值Agent 记忆的下一步不是更大的上下文而是更便宜的操作6.1 从“Prompt 工程”走向“状态工程”过去很长一段时间里Agent 开发的核心技巧是 Prompt 工程怎么把系统提示写清楚怎么把工具描述写完整怎么让模型更听话。但当你开始认真设计 Agent 记忆系统时你会发现更重要的问题变成了状态放在哪里什么时候写入什么时候让模型看到摘要什么时候把完整状态取回上下文如何让模型以最小代价感知状态变化这些问题已经不属于 Prompt 工程的范畴更像是一套“状态工程”。Zero-Mem 是这种变化的一个代表性方向。它不一定是最优解但它揭示了一个趋势Agent 正从“所有信息都进上下文”走向“上下文只放最小决策信息其余留在外部状态层”。6.2 一个可以复用的落地路径不要一上来就做一个复杂的记忆系统。从一个可衡量的痛点开始。第一步先统计现状。跑 10 次相同的 Agent 任务记录输入 token、输出 token、延迟、任务成功率。看看 token 大头到底花在“工具返回”、“历史重放”还是“最终输出”。第二步找出高频重复信息。比如用户偏好、任务配置、中间结果这些信息往往每轮都在历史里出现。第三步把高频信息迁到外部存储。用 write_memory 写入用 search_memory 返回签名先用小范围样例验证。第四步对比优化前后的 token 和任务成功率。注意token 下降是目标之一但如果任务成功率也下降说明摘要信息不足或检索信号太弱需要调整。第五步加监控。重点观察检索命中率、详情读取次数、写入失败率、搜索返回分数分布。这些指标能告诉你Agent 是在“用记忆”还是在“盲猜”。6.3 最后的判断回到开头的问题Agent 的上下文失控到底应该怎么解我的判断是不要执着于“把上下文变得更大”也不要迷信“所有记忆都要塞进 Prompt”。更合理的方向是让记忆的存取变得便宜、可控、可观测。Zero-Mem 这个名字有理想化成分真正严格意义上的零 Token 很难做到。但它指向一个非常实用的视角Token 应该被花在决策上而不是花在“回忆”上。如果你正在被 Agent 的长任务 token 成本和状态混乱困扰可以先不急着引入复杂框架。先把记忆操作从模型文本生成里拆出来让外部程序负责存取让模型只接收短状态。用最少的设计把成本从“线性膨胀”变成“可控常量”。这比追求一个绝对零成本的方案更值得落地。
返回列表