1. 核心能力速览
这次我们来看一个很有意思的 LLM Agent 方向,项目名称叫Zero-Mem: Zero-Token Memory Operations for LLM Agents。一句话概括:它想解决大模型 Agent 记忆功能里最痛的一个问题——读写记忆时消耗大量 token 的问题。
在 Agent 场景中,记忆不是可选项。多轮工具调用、跨会话任务、长对话管理,都需要把历史信息存下来、再在合适的时机读回来。传统做法是让 LLM 在推理时把记忆内容作为上下文的一部分拼接进 prompt,于是每读写一次记忆,就要为记忆本身付出 token 成本。一次两次不明显,Agent 任务一旦密集起来,token 消耗会非常快,延迟和费用同步上升。
Zero-Mem 的思路很直接:能不能让记忆操作不占用 token 预算?从项目给出的方向来看,它把记忆的存储、更新和检索从 LLM 的 token 上下文里剥离出来,用额外的记忆管理层完成,并且设计成与 LLM 无关的记忆操作接口,让 LLM 只需要通过特定指令触发记忆能力,而不需要把记忆内容反复搬运进上下文。
| 能力项 | 说明 |
|---|---|
| 项目定位 | LLM Agent 记忆管理机制,Zero-Token 记忆读写操作 |
| 核心特点 | 降低记忆操作 token 消耗、减少上下文冗余、支持结构化记忆 |
| 适用对象 | 研究 LLM Agent 记忆机制、多轮对话、任务型 Agent 的开发者 |
| 记忆类型 | 长期记忆、短期记忆、会话摘要、实体关系记忆(按常见 Agent 记忆框架判断) |
| 启动方式 | 需要以代码库/论文复现方式运行,无 WebUI(按项目性质判断) |
| 硬件门槛 | 无独立模型推理部分,主要依赖宿主 LLM 的推理能力 |
| 是否支持 CPU | 与宿主 LLM 所在环境一致 |
| 是否支持 API | 可封装为 Agent 记忆服务接口 |
| 是否支持批量任务 | 可支持,记忆操作可设计为批处理调用 |
| 适合场景 | Agent 长任务、多轮工具调用、会话历史管理、低成本记忆读写 |
从项目名称看,零 token 记忆操作是通过把记忆内容“外置”到独立存储层,而不是让 LLM 每次重新生成或重新读取整段历史。这样做如果能落地,收益非常明确:Agent 的上下文窗口可以更多地留给工具调用结果和当前任务,而不是被历史记录占用。
2. 适用场景与使用边界
2.1 适合谁
- 在 Agent 框架里维护多轮对话状态的开发者。你不再需要自己手工拼接长长的历史消息列表。
- 需要给 Agent 加长期记忆能力的开发者。比如让 Agent 记住用户偏好、记住任务进度、跨会话保持状态。
- 关注 token 成本的团队。Agent 记忆操作如果占用大量 token,在规模化运行时会显著拉高成本。
- 研究 RAG、Agent 记忆、prompt 压缩方向的算法工程师。Zero-Mem 提供了一种不把记忆塞进上下文的新思路。
2.2 能解决什么问题
- 上下文膨胀:Agent 每轮对话都把历史记录塞进 prompt,Zero-Mem 可以降低这部分开销。
- 记忆更新困难:传统做法要在上下文里写一整段更新后的记忆文本,Zero-Mem 通过结构化存储来避免重复搬运。
- 记忆污染:记忆内容混在对话历史里,容易被后续指令干扰,独立记忆层可以避免这个问题。
2.3 不适合什么场景
- 需要深入语义理解的任务,比如 Agent 要根据记忆内容进行复杂推理,这类场景记忆仍然需要进入 LLM 上下文。
- 对解释性要求极高的场景,例如审计要求能看到 LLM 推理时使用的完整记忆内容。
- 记忆内容本身很小、上下文窗口非常宽裕的场景,使用 Zero-Mem 带来的收益不明显。
2.4 版权、隐私与安全边界
记忆层会存储用户对话、业务数据甚至敏感信息。使用 Zero-Mem 时需要注意:
- 记忆数据必须按隐私规范处理,必要时加密存储。
- 涉及个人信息的记忆内容需要在用户授权范围内使用,不能跨场景滥用。
- 如果 Agent 部署在公共服务中,要避免把隐私信息直接写入外置记忆库而不做脱敏。
- 记忆的删除不能只做应用层删除,要考虑到底层存储是否已经被清理。
3. 环境准备与前置条件
从目前公开资料来看,Zero-Mem 还处于研究/早期发布阶段,大概率以论文仓库或代码库的形式提供,而不是成熟的 WebUI 项目。因此环境准备主要围绕复现实验和接入 Agent 框架来展开。
3.1 通用环境清单
| 环境项 | 说明 |
|---|---|
| 操作系统 | Linux 首选,macOS / Windows 也可以,取决于宿主 LLM 环境 |
| Python | 建议 3.10 及以上 |
| LLM 服务 | 本地 Ollama / vLLM / 云 API 均可 |
| Agent 框架 | LangChain、LlamaIndex、AutoGen 或自建 Agent 调度环境 |
| 存储层 | SQLite、Redis、向量数据库均可(取决于记忆类型) |
| 依赖管理 | pip / conda / poetry |
| 磁盘空间 | 取决于 LLM 模型和记忆库大小,一般预留 10GB 以上 |
3.2 LLM 环境准备
无论你是在本地跑一个小模型,还是接入云端 API,先把 LLM 服务跑通是关键一步。常见的本地启动方式:
# 使用 Ollama 启动本地 LLM 服务 ollama pull qwen2.5:7b ollama serve# 使用 vLLM 启动 OpenAI 兼容服务 python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --port 8000启动成功后,用 curl 验证服务:
curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen/Qwen2.5-7B-Instruct", "messages": [{"role": "user", "content": "你好"}] }'这一步的目的是确认 LLM 服务可访问。Zero-Mem 本身不负责推理,它只负责记忆的读写。
3.3 记忆存储层准备
Zero-Mem 如果使用外置记忆,底层存储可以选择:
- SQLite:轻量,适合单机测试。
- Redis:适合高频读写、状态缓存。
- 向量数据库:适合语义检索类型的记忆,比如 milvus、chromadb、qdrant。
建议先用 SQLite 或内存存储跑通逻辑,再根据实际需要更换存储后端。
# 使用 sqlite3 作为记忆存储的通用示例 import sqlite3 conn = sqlite3.connect("agent_memory.db") cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS memory ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT UNIQUE, value TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) conn.commit()4. 部署启动与记忆操作流程
Zero-Mem 具体的使用方式需要以项目 README 为准。在没有完整源码前,可以按下面的通用流程把它接入 Agent 记忆框架,理解它的工作方式。
4.1 总体架构
用户输入 │ ▼ Agent 调度层 │ ├──► LLM 推理(上下文只保留当前任务和工具结果) │ └──► Zero-Mem 记忆层 ├── 写入记忆(不消耗 token) ├── 读取记忆(按需返回精简结果) └── 更新记忆(覆盖或合并旧记忆)关键差异在于:传统 Agent 会把“历史对话 + 记忆摘要”全部拼进 prompt,Zero-Mem 模式下,LLM 的上下文里几乎只有当前轮次的任务信息,记忆通过外部操作完成。
4.2 记忆写入示例
# Zero-Mem 风格的记忆写入伪代码 def write_memory(key: str, value: str, overwrite: bool = True): """ 写入记忆到外部存储层。 这个过程不向 LLM 上下文添加任何 token。 """ if overwrite: cursor.execute( "INSERT INTO memory (key, value) VALUES (?, ?) " "ON CONFLICT(key) DO UPDATE SET value = excluded.value", (key, value) ) else: cursor.execute( "INSERT OR IGNORE INTO memory (key, value) VALUES (?, ?)", (key, value) ) conn.commit()写记忆时,LLM 只需要生成一次“保存这个信息”的意图,后续的实际存储操作完全由代码层完成,不需要 LLM 再次生成或读取完整的记忆文本。
4.3 记忆读取示例
# Zero-Mem 风格的记忆读取伪代码 def read_memory(key: str) -> str | None: """ 从外部存储读取记忆。 读取结果只返回关键信息,不携带无关上下文。 """ cursor.execute("SELECT value FROM memory WHERE key = ?", (key,)) row = cursor.fetchone() return row[0] if row else None读取时,返回给 LLM 的内容只包含与当前任务相关的记忆条目,而不是整段历史记录。这也是 Zero-Token 思路的落地表现。
4.4 Agent 调用流程整合
# 把 Zero-Mem 接入 Agent 工作流的通用示例 class AgentWithMemory: def __init__(self, llm_client, memory_store): self.llm = llm_client self.memory_store = memory_store def run(self, user_input: str) -> str: # 1. 从记忆库读取相关的记忆条目 relevant_memory = self.memory_store.read(user_input) # 2. 构建 prompt,只拼接当前输入和必要的记忆 prompt = self.build_prompt(user_input, relevant_memory) # 3. 调用 LLM response = self.llm.chat(prompt) # 4. 解析 LLM 是否有新的记忆保存需求 key, value = self.parse_memory_intent(response) if key: self.memory_store.write(key, value) return response def build_prompt(self, user_input, memory): # 记忆压缩后只占少量空间 if memory: return f"[相关记忆] {memory}\n[用户] {user_input}" return f"[用户] {user_input}"这里需要注意:如果是真正实现 Zero-Token,连[相关记忆] {memory}这段内容也不应该出现在 prompt 里,记忆检索的结果应该交给外部逻辑或工具函数调度使用,而不是直接喂给 LLM。具体实现视项目最终设计而定。
5. 功能测试与效果验证
拿到项目之后,建议按下面的维度做功能测试,重点观察 token 消耗变化和记忆准确性。
5.1 基础记忆写入测试
| 测试项 | 操作 | 预期结果 |
|---|---|---|
| 记忆写入 | 让 Agent 保存一条用户信息 | 记忆库中存在对应 key-value |
| 记忆覆盖 | 再次写入相同 key 的新值 | 旧值被替换,不产生重复记忆 |
| 记忆唯一性 | 多次写入同一 key | 数据库没有重复条目 |
write_memory("user_preference", "喜欢简洁的回答风格") write_memory("user_preference", "喜欢详细的技术讲解") print(read_memory("user_preference")) # 预期输出:喜欢详细的技术讲解5.2 多轮会话记忆测试
执行一个 10 轮以上的多轮对话,每轮都让 Agent 产生可记忆的信息,然后查看:
- 经过 10 轮后,LLM 请求中的历史 token 占比是否明显低于传统方式。
- 记忆读取是否能在后续轮次准确命中。
- 记忆写入是否影响正常回答延迟。
判断标准:同样的任务,对比传统把历史记录全部塞进 prompt 的做法,Zero-Mem 方式下每轮请求 token 数应该显著降低,且回答准确率不下降。
5.3 token 消耗对比测试
在接入 Zero-Mem 前后,分别记录相同任务集的 token 消耗:
# 统计一次 Agent 运行的 token 消耗示例 def count_tokens(messages): total = 0 for message in messages: total += len(message.get("content", "")) return total用同一组任务分别跑传统模式和 Zero-Mem 模式,记录每次调用的输入 token 数量,最后对比平均值。这是验证 Zero-Mem 效果最直接的手段。
5.4 记忆检索准确率测试
准备一组记忆条目,包含相似但不完全相同的语义信息:
- 比如记忆 A:“用户喜欢用 Python 写数据处理”
- 记忆 B:“用户更喜欢 Rust 开发 CLI 工具”
然后分别查询“用户喜欢什么语言做数据工作”和“用户最近学什么语言”,检查返回的记忆条目是否准确。
5.5 长任务稳定性测试
设计一个包含 20 步工具调用的复杂任务,让 Agent 在每一步之间都需要读写记忆。重点观察:
- 长任务是否出现记忆丢失。
- 是否出现记忆串扰(前一任务的记忆被错误加载到后一任务)。
- 记忆操作在长时间运行后是否有性能下降。
6. 接口 API 与批量任务
Zero-Mem 作为一个记忆管理模块,如果封装成服务,可以给 Agent 团队提供一个统一的内存服务接口。
6.1 记忆服务接口设计建议
| 接口 | 方法 | 参数 | 返回 |
|---|---|---|---|
| /memory/write | POST | key, value, namespace | 写入结果 |
| /memory/read | GET | key, namespace | 记忆内容 |
| /memory/delete | DELETE | key, namespace | 删除结果 |
| /memory/search | POST | query, namespace | 匹配记忆列表 |
# 记忆服务 API 示例(FastAPI) from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class MemoryItem(BaseModel): key: str value: str namespace: str = "default" class SearchQuery(BaseModel): query: str namespace: str = "default" @app.post("/memory/write") def write_memory(item: MemoryItem): # 写入逻辑 return {"status": "ok", "key": item.key} @app.get("/memory/read") def read_memory(key: str, namespace: str = "default"): # 读取逻辑 return {"key": key, "value": read_from_store(namespace, key)} @app.post("/memory/search") def search_memory(query: SearchQuery): # 语义搜索逻辑 return {"results": search_from_store(query.query, query.namespace)}6.2 curl 调用示例
curl -X POST http://127.0.0.1:8610/memory/write \ -H "Content-Type: application/json" \ -d '{ "key": "user_goal", "value": "完成数据分析报告", "namespace": "project_x" }'curl -X GET "http://127.0.0.1:8610/memory/read?key=user_goal&namespace=project_x"6.3 批量记忆写入
批量导入历史会话数据时,可以直接调用批量接口:
import requests items = [ {"key": "user_1_pref", "value": "偏好表格输出"}, {"key": "user_2_pref", "value": "偏好代码示例"}, {"key": "project_deadline", "value": "2025-04-30"}, ] for item in items: response = requests.post( "http://127.0.0.1:8610/memory/write", json={**item, "namespace": "batch_import"} ) print(response.status_code)批量写入建议加一个进度日志,方便中途失败时断点重跑。
# 批量导入日志示例 2025-01-20 10:00:01 [INFO] 写入 user_1_pref 成功 2025-01-20 10:00:02 [ERROR] 写入 user_2_pref 失败,连接超时 2025-01-20 10:00:03 [INFO] 写入 project_deadline 成功对于失败的条目,记录到单独的错误文件,重试时只处理失败的 key。
7. 资源占用与性能观察
Zero-Mem 本身不做 LLM 推理,所以它的资源占用主要来自三个部分:
- LLM 服务本身的显存/CPU/内存占用。
- 记忆存储层的内存和磁盘占用。
- Agent 调度层的进程开销。
7.1 观察指标
| 指标 | 观察方式 |
|---|---|
| LLM 显存占用 | nvidia-smi |
| 记忆服务内存 | ps aux | grep memory |
| 存储层占用 | df -h / sqlite 数据库文件大小 |
| 请求耗时 | API 调用时间统计 |
| token 消耗 | LLM 服务日志返回的 usage 字段 |
以本地 7B 模型为例,显存占用通常比 13B 模型低很多,具体数值取决于量化方式和推理框架。如果你用的是 Ollama 默认 4bit 量化,通常比 16bit 占用低一倍左右。但这些数值请以本机实际测试为准,不同驱动、不同 CUDA 版本都会影响最终结果。
7.2 CPU 推理与 GPU 推理的差异
- GPU 推理:延迟低,显存占用高,适合交互式 Agent 场景。
- CPU 推理:无显存压力,延迟高,适合测试和离线批量任务。
Zero-Mem 的记忆读写操作本身不受 CPU/GPU 影响,但记忆检索如果是语义搜索,向量检索在 CPU 上也能跑,速度取决于数据量。
7.3 性能瓶颈分析
如果 Agent 任务响应变慢,优先排查:
- LLM 推理耗时是否增加。
- 记忆检索是否超时。
- 记忆存储层是否有锁冲突。
- 上下文中的记忆信息是否过多。
7.4 降低资源占用建议
- 使用量化模型,减少显存占用。
- 记忆存储使用索引,加速查询。
- 定期清理过期记忆。
- 将记忆服务与 LLM 服务拆开部署,方便独立扩容。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Agent 回答时记忆不生效 | 记忆 key 不匹配 | 检查记忆库中 key 和读取 key 是否一致 | 统一命名规范,增加 key 规范化处理 |
| 记忆写入了,但读不到 | 写入失败没有报错 | 查看写入返回状态 | 增加写入确认机制 |
| token 消耗没有下降 | 记忆仍然被拼接进 prompt | 检查 prompt 构建逻辑 | 把记忆读取改为外部逻辑调用 |
| 显存不足 | 模型量化位数过高 | nvidia-smi 查看显存占用 | 使用更低精度量化 |
| API 调用超时 | 记忆服务未启动或网络不通 | curl 测试记忆服务 | 检查服务状态和端口 |
| 批量任务卡住 | 存储层锁冲突 | 查看数据库锁状态 | 使用队列串行写入 |
| 记忆内容串扰 | namespace 使用混乱 | 检查调用时 namespace 参数 | 强制每个任务使用独立命名空间 |
| 长任务记忆丢失 | 记忆更新逻辑有误 | 查看日志中写入记录 | 增加关键步骤写入断点 |
| 启动后服务立即退出 | 依赖缺失或端口占用 | 查看启动日志 | 安装依赖或更换端口 |
| Agent 回答质量下降 | 记忆内容不完整或缺失 | 检查记忆库内容 | 补充完整记忆或调整检索策略 |
8.1 依赖安装失败
pip install -r requirements.txt如果网络环境安装失败,可以换源:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple8.2 CUDA 版本问题
本地跑 LLM 时,CUDA 版本和 PyTorch 版本不匹配是常见问题。
python -c "import torch; print(torch.cuda.is_available())"如果输出False,需要重新安装与 CUDA 匹配的 PyTorch 版本。
8.3 端口冲突
lsof -i :8610如果有进程占用,可以换端口:
uvicorn memory_server:app --host 127.0.0.1 --port 86118.4 进程残留
杀掉残留的 Python 进程需要谨慎,不要误杀其他服务:
ps aux | grep memory_server kill <PID>9. 最佳实践与使用建议
9.1 第一次先小规模验证
不要一上来就跑大规模批量任务。先写几个测试 key,验证读写正常,再扩展数据量。
# 冒烟测试示例 assert read_memory("user_preference") == "喜欢详细的技术讲解" assert read_memory("non_exist_key") is None print("冒烟测试通过")9.2 保留一套最小可运行配置
把以下内容固定下来,方便重复实验:
- LLM 模型名称和启动命令。
- 记忆库路径。
- API 端口和访问地址。
- 测试任务集。
建议写成一个config.yaml:
llm: provider: openai_compatible base_url: http://127.0.0.1:8000/v1 model: Qwen/Qwen2.5-7B-Instruct memory: backend: sqlite database: ./agent_memory.db namespace: agent_test server: host: 127.0.0.1 port: 86109.3 模型文件、输入素材、输出结果分目录管理
project/ ├── config.yaml ├── models/ # 模型文件 ├── inputs/ # 测试输入 ├── outputs/ # 实验结果 ├── memory/ │ └── agent_memory.db └── logs/ └── agent.log9.4 批量任务要加日志和失败重试
import time from loguru import logger def batch_write_with_retry(items, max_retries=3): for item in items: for attempt in range(max_retries): try: write_memory(item["key"], item["value"]) logger.info(f"写入成功: {item['key']}") break except Exception as e: logger.warning(f"第 {attempt+1} 次写入失败: {e}") time.sleep(1) else: logger.error(f"写入失败,重试耗尽: {item['key']}")9.5 接口服务要限制访问范围
如果记忆服务部署在服务器上,不要直接暴露在公网:
# 只监听本地 uvicorn memory_server:app --host 127.0.0.1 --port 8610 # 如果必须远程访问,建议加 Token 鉴权# 增加简单的 API Token 鉴权 from fastapi import Header, HTTPException API_TOKEN = "your-secret-token" def verify_token(authorization: str = Header(None)): if authorization != f"Bearer {API_TOKEN}": raise HTTPException(status_code=401, detail="Invalid token")9.6 记忆内容要设计好 key 命名规范
推荐格式:
{namespace}:{entity}:{attribute}示例:
project_x:user:preferenceproject_x:task:deadlineproject_x:file:location
这样可以在不增加复杂结构的情况下,快速定位和过滤记忆。
9.7 涉及人脸、声音、版权素材时必须确认授权
虽然 Zero-Mem 不是人脸/声音项目,但 Agent 可能会记录包含敏感信息的交互内容。如果 Agent 被用在涉及肖像、声音、版权数据的场景,要确保:
- 数据来源合法。
- 用户知情同意。
- 存储和传输加密。
- 支持删除和遗忘。
9.8 发布或商用前要做效果复核
Agent 的记忆内容如果被错误读取,可能导致回答偏差。发布前需要做一个全流程的测试用例复核,覆盖高频场景和异常场景。
10. 总结与下一步
Zero-Mem 这个方向解决的是 Agent 记忆操作中的 token 浪费问题。传统做法把记忆越攒越多、每次都要重读,Zero-Mem 的思路是把记忆操作从 token 上下文中剥离出来,用外部存储和调度逻辑实现记忆读写。最值得尝试的点是:在同一个 Agent 任务集上,对比传统记忆拼接与 Zero-Token 记忆操作的 token 消耗差异,这个数字会直接说明问题。
最先应该验证的功能是基础记忆写入和读取。先确认记忆能存、能取、能覆盖,然后观察 token 变化,再进入长任务稳定性测试。
最容易踩的坑有两个:
- 记忆 key 命名不统一。写的时候用
user_preference,读的时候用UserPreference,结果永远读不到。 - 记忆还是被塞进了 prompt。表面上是 Zero-Mem 架构,实际构建 prompt 时又把历史记忆全部拼接回去了,token 消耗没有变化。
后续可以继续扩展的方向:
- 用向量检索代替精确 key 匹配,让 Agent 记忆支持语义查询。
- 增加记忆过期和遗忘机制,避免存储无限膨胀。
- 结合 RAG,让 Agent 能从外部知识库读取事实信息。
- 设计更完善的记忆合并策略,同类记忆自动去重更新。
建议收藏备用。如果你也在做 LLM Agent 的记忆管理,欢迎按本文的测试流程跑一遍,用数据判断 Zero-Mem 值不值得引入你的项目。