ARTICLE DETAIL

资讯详情

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

Zero-Mem:为LLM Agent打造零Token成本的记忆操作

Zero-Mem:为LLM Agent打造零Token成本的记忆操作 假设你正在开发一个具备多轮对话能力的 LLM Agent用户前一秒还在问“把会议室订到明天下午三点”后一秒就问“我刚才说的会议安排还能改吗”。如果 Agent 没有记忆第二个问题就会直接脱节但如果我们把整段历史都塞进上下文每次请求的 Token 消耗又会呈线性上涨。如何让 Agent 既能记住必要信息又不让记忆读写变成 Token 黑洞这正是 Zero-Mem 这类“零 Token 记忆操作”设计想要解决的问题。这篇文章会从 Agent 记忆的痛点切入讲清楚 Zero-Mem 的核心思想、与 RAG/向量数据库/长期记忆的关系并带大家用 Python 实现一个最小可运行的 Zero-Mem Agent 示例最后给出工程落地时的常见问题和最佳实践。适合正在做 LLM Agent 应用开发、想要优化上下文成本的后端开发者阅读。1. 背景为什么 Agent 需要记忆又为什么记忆很贵1.1 LLM Agent 是什么LLM Agent大语言模型智能体并不是简单的“对话机器人”而是让大模型在理解用户意图后自主决定调用哪些工具、执行哪些步骤、如何根据运行结果修正下一步动作的系统。一个典型的 Agent 通常包括大模型、工具集、规划器和记忆模块。没有记忆的 Agent 相当于一个“每次上岗都失忆”的实习生它能临时理解你当前这句话但结合不了上下文。比如用户说“帮我把订单列表里金额最大的那个标记为已处理”Agent 上一轮已经查到了订单列表但下一轮如果要继续操作就需要知道“金额最大的那个订单”是哪个。用户说“以后生成周报时都用表格格式”这种偏好需要跨会话保存。因此记忆是 Agent 从“能用”走向“好用”的关键能力。1.2 Agent 的“记忆”包含哪些内容在工程上Agent 的记忆大致可以分为四类会话上下文当前多轮对话中的用户输入、模型输出、工具调用结果。事实记忆用户资料、项目信息、业务规则等长期稳定数据。工作记忆当前任务执行过程中产生的临时状态比如已经执行到第几步、上一步返回了什么中间结果。行为偏好用户习惯的格式、语气、常用操作等。不同记忆类型对实时性和持久性要求也不同。会话上下文和工作记忆往往需要快速读写事实记忆和行为偏好则更适合存储在外部介质中并按需检索。1.3 记忆的 Token 成本困境把记忆直接塞进 Prompt 是最简单的做法但持续运行后会出现三个问题上下文窗口有限。主流大模型虽然有几十万 Token 的上下文窗口但把无限增长的历史全部塞进去终究会有填满的一天。费用随输入长度上涨。对于按 Token 计费的大模型 API输入越长每一次请求的费用越高。如果 Agent 每天调用千次历史记忆的累加会带来不小的成本压力。无关历史干扰模型判断。大量重复的、过时的历史记录会让模型更难聚焦当前任务甚至出现回答质量下降。所以我们需要一种机制让 Agent 能够读写记忆但记忆本身不要占太多 Token最好能接近“零 Token”。1.4 Zero-Mem 的定位与价值Zero-Mem 并不是某个固定版本的开源项目而是一类“零 Token 记忆操作”的设计思想。这里的“Zero-Token”指的不是整个系统完全不消耗 Token也不是大模型直接免费而是指记忆的存储、检索、写入、更新、删除等 Memory Operations不经过大模型生成 Token 来完成。传统做法中Agent 可能会让 LLM 自己输出“把记忆更新为……”或者把整段历史交给 LLM 去总结这都会产生新的 Token 消耗。Zero-Mem 的思路是记忆操作交给外部程序化模块执行LLM 只负责决策和生成最终回复对所有记忆内容进行压缩、筛选、结构化处理后再注入上下文让记忆相关的运行时开销趋近于零。这种设计不仅降低了成本还能让 Agent 的运行更稳定、更快因为外部存储和检索模块可以做到毫秒级响应而无需等待模型额外生成内容。2. 理解 Zero-Token Memory Operations2.1 什么是 Memory Operation在 Agent 上下文管理中“Memory Operation”指对记忆数据执行的一组操作包括写入记忆把用户偏好、事实信息、任务结果保存下来。读取记忆根据当前问题从记忆库中取出相关片段。更新记忆修改已有的记忆内容比如用户改了会议时间。删除记忆清理过期、无用的记忆。检索记忆通过关键词、向量相似度、规则等方式定位相关记忆。遗忘记忆定期清理低价值或过期的数据控制记忆库体积。这些操作如果全部由提示词加 LLM 来完成每次都要消耗 Token。比如“请总结上面的对话并存入记忆”这条指令本身会占用输入模型生成的摘要又会占用输出。当 Agent 高频运转时这种开销会放大。2.2 为什么可以做到“零 Token”Zero-Mem 的核心思想很简单能由代码完成的事就不要让模型“说”出来。具体来说系统可以这样设计记忆存储使用数据库、对象存储或文件系统而不是写在 Prompt 里。记忆检索使用传统搜索算法、向量索引或规则引擎而不是让 LLM 先回忆。记忆写入由程序从工具调用结果中提取结构化字段而不是让 LLM 用自然语言重写一遍。只有当最终需要利用记忆生成回复时才把少量精简的检索结果拼接到 Prompt 中。这样可以做到除了最终拼接的少量压缩上下文外记忆本身的操作过程不再产生额外的输入输出 Token。这也是“Zero-Token Memory Operations”的实际含义。当然这里要强调一下向量检索如果使用了文本嵌入模型会产生 Embedding 费用定期生成摘要如果交给 LLM也会产生少量 Token。所以工程中要做到真正的“零 Token”通常是把记忆写入和更新做成规则化、结构化的而把摘要任务拆成异步或低频任务。2.3 与 RAG、向量数据库、Memory 框架的区别很多同学会把 Zero-Mem 和 RAG 混在一起。RAGRetrieval-Augmented Generation指的是在生成前先从外部知识库检索相关内容再注入上下文。RAG 重点解决“大模型不知道的最新知识/私有知识”问题它仍然需要通过 Embedding 和向量召回来构造上下文。Zero-Mem 更偏重“记忆操作本身不产生 Token”它并不排斥 RAG。你可以用搜索引擎、SQL、Redis 来做记忆读取也可以使用向量数据库做语义检索。区别在于Zero-Mem 强调不要让 LLM 去完成“记忆的读写总结”这一层动作。常见的 Agent Memory 框架如 LangChain 的 Memory 模块、MemGPT、Letta 等解决的是“记忆怎么组织和管理”。Zero-Mem 则可以理解为这些框架在设计上的一种优化原则记忆操作尽量用确定性程序完成减少模型参与。2.4 适用场景Zero-Mem 机制适合以下场景高频多轮客服机器人需要记住用户工单信息但不想每次携带全部历史。办公助手读取用户日程、偏好跨会话保持一致性。代码 Agent需要记住项目结构、最近修改的文件但上下文有限。数据分析 Agent把中间查询结果写入临时存储而非塞进对话历史。在这些场景中记忆通常是结构化、可按关键词或语义检索的适合用程序化操作替换“提示词模型生成”的流程。3. Zero-Mem Agent 的架构设计3.1 模块划分一个 Zero-Mem Agent 通常包含四个核心模块用户交互层接收用户输入返回最终回复。控制器Agent Controller决定当前步骤是调用工具、读取记忆还是生成回复。记忆操作模块Memory Operations提供写入、读取、更新、删除、检索等接口。存储层Memory Store实际保存记忆数据可以是 SQLite、Redis、向量数据库或普通文件。控制器可以是大模型本身也可以是大模型加规则引擎。为了减少 Token常用做法是让控制器只负责“决策”而把“执行”交给代码函数。3.2 记忆存储层的可选方案根据数据量级和实时性要求存储层可以选择不同方案内存字典适合原型验证数据不持久化进程重启即丢失。SQLite适合单机应用读写简单支持结构化查询。Redis适合高并发、需要过期时间的场景。向量数据库如 FAISS、Milvus、Chroma适合语义检索。PostgreSQL pgvector兼顾结构化数据和向量检索。在最小实现中我们可以先用 Python 字典或 SQLite 完成 Memory Store重点演示记忆操作不经过 LLM。3.3 记忆调度的规则与策略好的记忆调度策略是避免 Token 膨胀的关键。常见规则包括写少读多只有用户明确表达偏好、或者工具返回关键结果时才写入记忆。按需读取每次请求先做一次快速检索只提取和当前问题相关的记忆。压缩与摘要当记忆数据量过大时通过异步任务生成摘要并把原始记录归档。过期清理给记忆增加时间戳和有效期定期删除不再需要的记录。这些规则都可以用代码来实现不需要每次调用模型判断从而降低 Token 消耗。3.4 Agent 主循环中的记忆流程一个典型的 Zero-Mem Agent 主循环如下接收用户输入。记忆检索模块根据用户输入的关键词或向量从记忆库检索相关记忆。控制器把“用户输入 检索到的精简记忆 工具列表”拼装成 Prompt。大模型生成回复或工具调用指令。如果是工具调用执行工具后程序从工具结果中提取结构化信息写入记忆回到步骤 2。如果是最终回复返回给用户并在后台异步写入新的关键记忆。在这个流程里除了步骤 3 中拼装的精简记忆和步骤 4 的模型输出外记忆模块的读写、检索本身都不产生 LLM Token。4. 环境准备与运行基础4.1 运行环境本文的示例代码使用 Python 编写。版本需要根据你的机器实际情况调整下面以常见环境为例操作系统Windows / macOS / Linux 均可。Python 版本3.9 及以上。依赖库仅使用 Python 标准库sqlite3、json、datetime无需安装第三方包。编辑器VS Code、PyCharm 均可重点是能运行 Python 文件。如果你需要做向量检索可以额外安装 FAISS 或 Chroma但这不是本文最小示例的必需组件。为了避免不同版本的 API 差异示例代码先使用 SQLite 作为存储层。4.2 技术选型建议在实际项目中Zero-Mem 模块不一定非要用某个特定框架。你可以在自己的 Agent 框架中集成以下组件LLM 客户端OpenAI SDK、Anthropic SDK、国内大模型 SDK或自研模型推理服务。存储SQLite、Redis、PostgreSQL、向量库。调度LangGraph、AutoGen 或自研状态机。核心原则是不要把所有判断都交给模型能用代码表达的规则就写死。4.3 示例项目结构我们准备创建一个名为zero_mem_agent的目录结构如下zero_mem_agent/ ├── main.py # 启动入口 ├── memory_store.py # 记忆存储 ├── memory_ops.py # 记忆操作 └── agent.py # Agent 主循环如果你的依赖复杂还可以加入requirements.txt。本文示例仅用标准库因此不需要额外依赖文件。5. 实战用 Python 实现最小版 Zero-Mem Agent下面我们分步骤实现一个能记住用户偏好的最小 Agent。这个 Agent 不会调用真实的 LLM API而是用一个模拟函数代替目的是让大家看清“记忆操作如何不占用 Token”这一设计。5.1 定义记忆数据模型首先创建memory_store.py定义一个基于 SQLite 的记忆存储类。每条记忆包含 id、content、category、created_at 和 expires_at。# 文件路径zero_mem_agent/memory_store.py import json import sqlite3 from datetime import datetime, timedelta class MemoryStore: def __init__(self, db_path: str agent_memory.db): self.conn sqlite3.connect(db_path, check_same_threadFalse) self._init_table() def _init_table(self): self.conn.execute( CREATE TABLE IF NOT EXISTS memory ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, category TEXT NOT NULL DEFAULT general, created_at TEXT NOT NULL, expires_at TEXT NOT NULL ) ) self.conn.commit() def add_memory(self, content: str, category: str general, ttl_hours: int 24): created_at datetime.now().isoformat() expires_at (datetime.now() timedelta(hoursttl_hours)).isoformat() cur self.conn.execute( INSERT INTO memory (content, category, created_at, expires_at) VALUES (?, ?, ?, ?), (content, category, created_at, expires_at), ) self.conn.commit() return cur.lastrowid def search_memory(self, keyword: str , category: str , limit: int 5): now datetime.now().isoformat() sql SELECT id, content, category, created_at FROM memory WHERE expires_at ? params [now] if keyword: sql AND content LIKE ? params.append(f%{keyword}%) if category: sql AND category ? params.append(category) sql ORDER BY id DESC LIMIT ? params.append(limit) rows self.conn.execute(sql, params).fetchall() return [ { id: row[0], content: row[1], category: row[2], created_at: row[3], } for row in rows ] def delete_memory(self, memory_id: int): self.conn.execute(DELETE FROM memory WHERE id ?, (memory_id,)) self.conn.commit() def clear_expired(self): now datetime.now().isoformat() self.conn.execute(DELETE FROM memory WHERE expires_at ?, (now,)) self.conn.commit()这里是核心片段需要放入memory_store.py文件中。这个类封装了记忆存储的持久化和查询。注意search_memory使用了LIKE做简单的关键词匹配这足够演示 Zero-Mem 的“程序化读取”思路。实际生产中可以使用倒排索引或向量检索替换。5.2 实现记忆操作模块接下来创建memory_ops.py封装记忆操作。这样 Agent 主循环不会直接操作数据库而是通过统一接口读写记忆。# 文件路径zero_mem_agent/memory_ops.py from datetime import datetime from memory_store import MemoryStore class MemoryOps: def __init__(self, store: MemoryStore): self.store store def remember(self, content: str, category: str general, ttl_hours: int 24) - int: 写入一条记忆。这个操作是纯代码执行的不消耗 LLM token。 return self.store.add_memory(contentcontent, categorycategory, ttl_hoursttl_hours) def recall(self, query: str, category: str , limit: int 5) - str: 从记忆中检索相关片段返回拼装好的精简文本。 这里只做字符串拼接不调用大模型。 memories self.store.search_memory(keywordquery, categorycategory, limitlimit) if not memories: return lines [] for mem in memories: lines.append(f[{mem[category]}] {mem[content]}) return \n.join(lines) def forget(self, memory_id: int): self.store.delete_memory(memory_id) def update_memory(self, memory_id: int, new_content: str): # 为简化演示这里用 delete add 替代真正的 update self.store.delete_memory(memory_id) return self.store.add_memory(contentnew_content, categorygeneral)remember和recall都没有调用任何 LLM。也就是说记忆的写入与读取过程中Token 消耗为 0。5.3 实现 Agent 主循环现在创建agent.py。这里用一个mock_llm模拟大模型输出这样即使没有 API Key 也可以完整跑通流程。你在实际项目中可以在llm_generate函数中替换成真实的模型调用 SDK。# 文件路径zero_mem_agent/agent.py import json from memory_store import MemoryStore from memory_ops import MemoryOps def mock_llm_generate(system_prompt: str, user_content: str) - str: 模拟 LLM 生成回复。 真实项目中这里应该调用你的大模型 API并把 system_prompt 和 user_content 作为输入。 该函数只为演示流程。 if 记住 in user_content: return 好的我已经记住了。 return 好的现在需要我做什么 class ZeroMemAgent: def __init__(self, store: MemoryStore): self.memory_ops MemoryOps(store) def run(self, user_input: str, category: str general): # 1. 先从记忆中检索相关信息 recalled self.memory_ops.recall(queryuser_input, categorycategory, limit3) if recalled: memory_block f以下是相关的历史记忆\n{recalled} else: memory_block 暂无相关历史记忆。 # 2. 构造精简 prompt不携带全量历史 system_prompt 你是一个有帮助的 Agent。请基于记忆和当前输入回答。 user_content user_input \n\n memory_block # 3. 调用大模型 response mock_llm_generate(system_prompt, user_content) # 4. 写入新的记忆简单演示把用户输入中“记住”后面的内容存起来 if 记住 in user_input: content user_input.split(记住, 1)[1].strip() self.memory_ops.remember(contentcontent, categorycategory) return response这里最关键的步骤是第 2 步我们只把“检索到的精简记忆”拼接进 Prompt而不是把历史会话全部交给模型。mock_llm_generate中并没有模型真正去“记”东西记忆写入完全由步骤 4 的代码完成。5.4 创建启动入口最后创建main.py用于演示完整流程。# 文件路径zero_mem_agent/main.py from memory_store import MemoryStore from agent import ZeroMemAgent def main(): store MemoryStore(agent_memory.db) agent ZeroMemAgent(store) print(第一轮让 Agent 记住偏好) r1 agent.run(请记住周报发送时间改为每周五下午五点) print(Agent 回复, r1) print(\n第二轮检查记忆是否生效) r2 agent.run(周报什么时候发送) print(Agent 回复, r2) print(\n记忆库中的内容) for mem in store.search_memory(keyword周报): print(mem) if __name__ __main__: main()5.5 运行与验证在命令行中执行cd zero_mem_agent python main.py预期输出类似下面这样具体时间字段可能不同第一轮让 Agent 记住偏好 Agent 回复 好的我已经记住了。 第二轮检查记忆是否生效 Agent 回复 好的现在需要我做什么 记忆库中的内容 {id: 1, content: 周报发送时间改为每周五下午五点, category: general, created_at: 2025-01-01T10:00:00.123456}上面这个示例很简陋但它完整展示了 Zero-Mem 的关键流程记忆写入由代码完成记忆读取由 SQL 完成模型只负责生成最终回复。真实项目中你只需要把mock_llm_generate替换为真实的 API 调用并把规则化的记忆写入逻辑替换成更智能的信息抽取模块即可。5.6 结果说明这个最小实现证明了三点记忆操作不依赖 LLM。每次请求的 Prompt 中只携带检索后的少量记忆而不是完整历史。当记忆量变大时SQLite 查询依然会比把历史全部塞进上下文更节省 Token。你可以在此基础上增加向量检索、自动摘要、定时清理等功能。6. 常见问题与排查思路6.1 记忆总是查不到问题现象常见原因解决思路第二轮查询时记忆内容为空关键词匹配不上检查是否有同义词、大小写差异尝试使用全文索引或向量检索记忆写入失败插入时字段类型错误查看 SQLite 表字段类型确认参数正确记忆过期TTL 设置过短调大ttl_hours或让关键记忆不设置过期时间在实际项目中单纯使用“记忆里有没有出现关键词”并不可靠。用户往往不会用完全一致的词来描述同一个事物。如果预算允许建议在MemoryOps.recall中加入向量嵌入检索把文本转换成向量再用余弦相似度召回。6.2 检索结果太多Prompt 还是膨胀如果一次检索返回了大量记忆拼接后依然会消耗大量 Token。这时需要做两层优化降低limit只保留最相关的 3~5 条。对召回结果做去重和压缩比如只保留时间最近的一条相似记忆。你还可以在存储层给记忆增加权重字段让用户明确说“非常重要”的记忆优先级更高。6.3 “零 Token”被误解成免费这里需要澄清一个容易混淆的点Zero-Mem 不是让 LLM API 免费也不是说系统不产生任何 Token。它的意思是记忆读写这个动作本身不需要让大模型去生成中间内容。但是你在记忆召回后仍然要把检索结果拼接进 Prompt这部分会占用输入 Token。另外如果系统还需要定期生成摘要也会有少量摘要 Token。做成本评估时只统计最终 Prompt 和模型输出的 Token基本就可以把记忆开销控制在较低水平。6.4 记忆数据安全和隐私问题当 Agent 开始保存用户偏好、会议内容、工作文档时安全问题会立刻浮现。建议至少做到以下三点敏感信息加密存储不要以明文写入 SQLite 或 Redis。权限隔离不同用户/租户的数据要分区保存。提供遗忘接口用户有权要求删除自己的记忆数据。在写代码时删除操作要非常谨慎尽量使用软删除或先备份再删除避免误删导致用户数据永久丢失。6.5 记忆模块性能瓶颈当记忆表数据量超过几万条时LIKE %keyword%查询会明显变慢。这时候可以给category、created_at字段建立索引。使用 SQLite FTS5 全文搜索。引入 Elasticsearch、向量数据库等专业检索组件。这些都属于 Zero-Mem 架构中的存储层优化不会影响整体设计原则。7. 最佳实践与工程建议7.1 记忆分组与命名建议在写入记忆时使用明确的分类例如user_preference、task_state、project_info。这样召回时可以按分类过滤减少无关信息干扰。同时在记忆内容中尽量使用结构化描述例如user_preference: weekly_report_send_time Friday 17:00这种 KV 格式比自然语言更容易被代码解析也更容易在拼接 Prompt 时保持简洁。7.2 定期摘要与遗忘机制Zero-Mem 虽然减少了 Token 消耗但记忆库本身会不断增长。建议在系统设计中加入每日定时任务清理过期记忆。每条记忆增加访问频率字段长期不访问的低频记忆降低优先级甚至自动归档。会话摘要对已经结束的会话做一次异步摘要摘要结果作为新的长期记忆原始历史放入冷存储。这些任务可以放到 Agent 主循环之外异步执行不影响在线请求的延迟。7.3 明确的安全边界不要让 Agent 在记忆模块中执行任意 SQL。所有读写操作应通过MemoryOps封装避免拼接用户输入造成注入。示例代码中的LIKE已经使用了参数化查询这是正确做法。在生产环境还要注意数据库账号使用最小权限。不要在日志中打印用户敏感记忆。如果使用外部 API要遵循数据合规要求。7.4 可观测性为了让记忆系统可排查最好给每次记忆操作添加日志和 trace。例如记录写入记忆的类型、来源。记录每次检索的关键词、命中数量和实际注入 Prompt 的字符数。统计每次请求的记忆 Token 占比。有了这些数据你就能量化评估 Zero-Mem 的效果也可以定位检索不准、Token 异常增长等问题。7.5 从 MVP 到生产可以先从本文的最小示例开始把记忆存储换成 Redis 或 PostgreSQL再逐步加向量检索、自动摘要、任务调度。不要一开始就追求复杂架构因为 Agent 的记忆问题往往要跑到一定量级后才会暴露真正的瓶颈。8. 下一步可以怎么继续Zero-Mem 提供了一种很务实的设计视角不是所有能力都要靠模型生成记忆操作完全可以剥离出来用确定性代码完成。围绕这个方向你还可以继续研究 Long-Term Memory 框架、Agent 评估、上下文压缩算法、记忆冲突解决等话题。如果你正在做 LLM Agent 应用建议先把自己项目里的历史对话和状态迁移梳理清楚看看哪些记忆可以被结构化存储、哪些需要语义检索、哪些需要模型参与总结。把这层关系理清之后再用 Zero-Mem 思路去实现会明显感受到 Token 成本下降和响应速度提升。希望这篇文章能为你的 Agent 记忆优化提供有价值的参考。如果有更好的实践思路欢迎在评论区一起讨论。
返回列表