
做过一段时间 AI Agent 开发的人大概率经历过这样一幕Agent 在前半段对话里表现得思路清晰能记住用户刚提的需求可一旦对话拉长或者换了一个会话它就像完全失忆一样把之前聊过的约束条件、偏好和进度忘得干干净净。更让人头疼的是这类问题会随着任务复杂度急剧放大。做文档分析时几千行材料还没读完就报API error: 400 this models maximum context length is 1048576 tokens.写代码时Codex 干到一半提示ran out of room in the models context window本地部署则经常出现context overflow and auto-compaction is disabled这类冷冰冰的报错。很多人第一反应是“是不是模型不够强”于是去换更大参数的模型、买更贵的接口结果发现根子根本不在这里。这篇文章想给出一个明确判断Agent 的失忆病根几乎不在模型而在记忆系统设计。模型权重保存的是知识不是记忆真正让 Agent“记得住”的是上下文管理和长期记忆架构。接下来我会从 Context Window 的局限讲起逐步拆解企业级记忆系统的模块、代码实现和工程落地方案最后给出可直接复用的排查清单和最佳实践。如果你正在做 AI Agent 应用开发、准备把 Agent 接入生产环境或者已经被上下文超限问题折磨过几次这篇文章值得收藏。1. 你以为是模型问题其实是记忆架构缺失先看一个常见场景。用户在电商助手 Agent 里说“帮我找一款适合露营的便携咖啡机预算 800 以内要能支持车载电源。”Agent 推荐了几款用户选了其中一款接着说“就它吧顺便看看有没有配套的磨豆机”。到这里一个合格的 Agent 应该还知道“预算 800 以内”和“便携露营”是硬条件。但如果 Agent 每次都把用户的完整需求塞进一长段 system prompt而不是主动维护一个会话状态那么对话一长问题就来了上下文窗口被历史消息占满新的关键信息进不来。模型注意力被大量无关内容稀释抓不住真正重要的约束。会话结束、进程重启、服务切换后历史记忆全部归零。多人协同或跨部门使用时A 用户的信息可能被 B 用户的请求干扰。这一连串问题的共同点是什么不是模型推理能力不够而是没有任何一层机制负责“记住什么、忘记什么、检索什么”。模型只负责推理不负责档案管理。对比真实的团队协作一个有经验的项目助理不会把每个字都记在脑子里而是会记下结论、关键约束、待办事项和重要偏好并在需要时主动翻看历史记录。AI Agent 需要的是同样的机制这套机制在工程上就叫记忆系统。2. Context Window、短期记忆与长期记忆先把概念对齐2.1 Context Window 是什么Context Window上下文窗口指的是模型在一次推理中最多能处理的文本范围通常用 Token 数来衡量。不同模型的上下文窗口差异非常大有的只有几千 Token有的宣称支持上百万 Token。具体数值请以模型官方文档为准不要轻信任何二手数据。可以这样理解Context Window 就是模型的“工作台”。工作台上能同时铺开的材料越多模型能“看着”的东西就越多。但工作台有一个硬边界材料堆不下时要么裁掉旧材料要么让后续任务无法继续也就是我们常说的 Context Overflow。2.2 短期记忆不是“短期记住”而是会话内状态管理严格来说短期记忆Short-term Memory在 Agent 里有两层含义第一层是纯粹的消息队列即把过去几轮对话原样拼接到下一次请求里。这是最朴素的做法也是 Context Window 被迅速占满的元凶。第二层是从会话消息中提炼出关键信息比如用户偏好、当前任务目标、已完成步骤然后只保留这些“精华”。这一层属于会话状态管理很多 Agent 框架里的ConversationBufferMemory、ConversationSummaryMemory就是在做这件事。2.3 长期记忆是跨会话持久化长期记忆Long-term Memory解决的是“换个会话、换个进程、换个用户之后信息依然存在”的问题。它通常不是以原始聊天记录的形式保存而是通过摘要、结构化字段和向量嵌入等方式把信息转换成可检索、可更新的知识条目。三者关系可以用一张表格概括层级存储位置生命周期典型实现典型问题Context Window模型输入单次推理Token 拼接超限、成本高、注意力稀释短期记忆会话上下文一个会话内摘要、消息缓存会话间丢失、摘要丢失关键信息长期记忆数据库/文件跨会话向量库 结构化存储检索不准、数据质量差、权限混乱这里引出一个关键误区很多人以为记忆系统做得好就是尽可能多地检索相关内容塞进 Context。这个思路在简单原型里勉强能用但放到企业级场景会立刻崩掉——检索越多噪声越多噪声越多模型越容易把注意力放到无关信息上。我在整理素材时看到 RippleMem 这类新项目的思路它说得非常直接“记忆系统不是把更多东西检索出来而是让 Agent 学会‘回忆’。”回忆和检索的区别在于回忆是有重点、有路径、有取舍的而不是把所有相关文档都翻出来铺在桌面上。这个思路对设计企业级记忆系统非常有参考价值。3. 企业级记忆系统的架构拆解先看整体视图。一个可落入生产环境的 Agent 记忆系统至少包含六个模块记忆采集层拦截对话、事件、任务结果筛选哪些信息值得写入记忆仓库。记忆编码层对原始信息做摘要、打标签、规范化生成适合存储的格式。向量化层将文本转成 embedding 向量供语义检索使用。存储层同时使用关系型数据库和向量数据库结构化信息和语义向量各司其职。检索与召回层根据当前任务生成查询请求按相关度召回记忆片段。遗忘与维护层处理记忆过期、冲突、优先级和删除避免记忆仓库变成垃圾场。再加一层贯穿始终的权限审计层企业级系统必须知道“谁写了什么记忆、谁读到了什么记忆”。3.1 为什么不能只靠向量数据库近两年向量数据库很热但真实项目中不建议把所有东西都往向量库倒。原因很简单结构化信息比如用户 ID、预算范围、偏好标签用关系型存储更可靠。长文档摘要和关键结论用 KV 存储更清晰。只有需要语义模糊匹配的内容才适合向量化。一个相对合理的策略是“关系型 向量型”双写。用户资料、订单状态、会话摘要放 PostgreSQL/MySQL非结构化的文本片段、文档片段、历史结论做 embedding 后放到向量库。3.2 记忆的粒度与优先级记忆不是越细越好。企业级场景里每一条记忆都应该有类型、元数据、过期时间和权重强约束记忆例如“预算不能超 800”权重最高必须每次注入。偏好记忆例如“喜欢户外品牌”可以按相关性召回。中间产物记忆例如“候选方案 A 的对比表”可以短期保存。噪声记忆例如临时的寒暄内容不应写入长期库。分层记忆的核心是让 Agent 在“必需信息”和“候选信息”之间取得平衡这也正是 Ripple 式“回忆”机制的设计理念不是把所有记忆都激活而是沿着一张记忆关联网找到最相关的那条路径。4. 环境准备与前置条件本文示例代码基于 Python 3.9 及以上环境运行。依赖项按需安装版本请以实际安装时官方发布为准以下命令只演示安装方式# 创建虚拟环境推荐 python3 -m venv agent-memory-demo source agent-memory-demo/bin/activate # 安装基础依赖 pip install chromadb openai tiktoken # 数据库驱动按实际需求安装 pip install sqlalchemy pysqlite3说明几点chromadb是向量数据库适合本地开发验证生产环境可以换成 Redis 向量模块、Milvus 或云厂商向量服务接口思路类似。openai库用于调用大模型接口如果你用的是本地推理服务或兼容 OpenAI 协议的平台只需要改base_url即可。tiktoken用于估算 Token 数量避免请求超限。在开始写代码前建议把模型 API 的访问凭据配置到环境变量不要硬编码在代码中。对于本地模型场景可以启动一个兼容 OpenAI 协议的推理服务然后在代码中指定base_url指向本地地址。5. 从 Context 到 Long-term Memory核心代码实现5.1 示例一Context Window 监控与超限准备很多 AI Agent 运行时报错“已达到输出 token 上限回答被截断”所以第一步要实现对上下文窗口的监控。# 文件路径demo/context_monitor.py import tiktoken def count_tokens(text: str, model: str gpt-4) - int: 估算文本的 token 数量具体结果以模型返回为准 try: enc tiktoken.encoding_for_model(model) except KeyError: enc tiktoken.get_encoding(cl100k_base) return len(enc.encode(text)) def fit_context(messages: list[dict], max_tokens: int, reserve_ratio: float 0.2) - list[dict]: 在调用模型前检查上下文是否可能超限。 reserve_ratio 为给模型输出预留的 token 比例。 reserve int(max_tokens * reserve_ratio) usable max_tokens - reserve fitted [] total 0 for msg in reversed(messages): content msg.get(content, ) cost count_tokens(content) if total cost usable: break fitted.insert(0, msg) total cost return fitted这段代码实现了两件事用tiktoken估算每段消息的 Token 数量。从最近的消息开始向前截取保证较早的历史消息被优先丢弃而不是把最新的消息丢掉。实际使用时max_tokens要根据你使用的模型上下文窗口上限填写。注意这里只是兜底策略真正要减少截断需要靠摘要和长期记忆来压缩历史。5.2 示例二一个简单的 Long-term Memory 类在企业级场景中长期记忆需要存储结构化摘要和元数据。下面的例子用 SQLite 保存记忆条目并提供了写入、查询、过期淘汰三个基本能力。SQLite 只是最小示例生产环境建议换成 MySQL、PostgreSQL 或其他团队已有存储。# 文件路径demo/long_term_memory.py import json import sqlite3 import time from typing import Optional class LongTermMemory: def __init__(self, db_path: str agent_memory.db): self.conn sqlite3.connect(db_path) self._init_table() def _init_table(self): self.conn.execute( CREATE TABLE IF NOT EXISTS memory_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, memory_type TEXT NOT NULL, content TEXT NOT NULL, metadata TEXT NOT NULL, weight REAL DEFAULT 1.0, created_at INTEGER NOT NULL, expires_at INTEGER ) ) self.conn.commit() def remember( self, user_id: str, memory_type: str, content: str, metadata: dict | None None, weight: float 1.0, ttl: int | None None, ): 写入一条长期记忆。 memory_type 推荐使用constraint / preference / progress / fact ttl 为可选过期时间秒。 now int(time.time()) expires_at now ttl if ttl else None self.conn.execute( INSERT INTO memory_items (user_id, memory_type, content, metadata, weight, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?), ( user_id, memory_type, content, json.dumps(metadata or {}, ensure_asciiFalse), weight, now, expires_at, ), ) self.conn.commit() def recall(self, user_id: str, memory_type: Optional[str] None, limit: int 20) - list[dict]: 按用户和类型检索有效记忆。 实际项目中可结合 embedding 做语义排序这里先按权重和新鲜度排序。 now int(time.time()) sql SELECT * FROM memory_items WHERE user_id ? AND (expires_at IS NULL OR expires_at ?) params: list [user_id, now] if memory_type: sql AND memory_type ? params.append(memory_type) sql ORDER BY weight DESC, created_at DESC LIMIT ? params.append(limit) rows self.conn.execute(sql, params).fetchall() return [ { id: r[0], user_id: r[1], memory_type: r[2], content: r[3], metadata: json.loads(r[4]), weight: r[5], created_at: r[6], } for r in rows ] def forget(self, memory_id: int): 删除一条记忆生产环境建议改为软删除并记录审计日志。 self.conn.execute(DELETE FROM memory_items WHERE id ?, (memory_id,)) self.conn.commit() if __name__ __main__: memory LongTermMemory(demo.db) memory.remember(user_001, constraint, 预算不超过 800 元, weight5.0, ttl86400) memory.remember(user_001, preference, 喜欢轻量便携的露营装备, weight3.0) results memory.recall(user_001) for item in results: print(item[content], | 权重, item[weight])建议先跑通这个最小示例理解记忆条目的生命周期管理再往里面加入向量检索。因为“写入什么、保留多久、如何淘汰”比“如何检索”更容易被忽略也更容易出问题。5.3 示例三向量检索式记忆召回当记忆条目增多后精确匹配就不够用了。用户说“上次那个很轻的冲咖啡工具”这句描述和“喜欢轻量便携的露营装备”在字面上并不一致需要语义召回。这里引入 ChromaDB 做向量存储与相似度检索。# 文件路径demo/vector_memory.py import chromadb from chromadb.utils import embedding_functions class VectorMemory: def __init__(self, collection_name: str agent_memory): # 默认使用本地 embedding 模型之一依赖会自动下载 self.client chromadb.Client() self.collection self.client.get_or_create_collection( namecollection_name, embedding_functionembedding_functions.DefaultEmbeddingFunction(), ) def add_memory(self, memory_id: str, text: str, metadata: dict | None None): self.collection.add(ids[memory_id], documents[text], metadatas[metadata or {}]) def search(self, query: str, top_k: int 5) - list[dict]: results self.collection.query(query_texts[query], n_resultstop_k) docs results.get(documents, [[]])[0] metas results.get(metadatas, [[]])[0] distances results.get(distances, [[]])[0] return [ {document: doc, metadata: meta, distance: dist} for doc, meta, dist in zip(docs, metas, distances) ] if __name__ __main__: vm VectorMemory() vm.add_memory( mem_001, 用户喜欢轻量便携的露营装备偏好户外品牌, {user_id: user_001, memory_type: preference}, ) vm.add_memory( mem_002, 当前购物车包含便携咖啡机预算 800 元以内, {user_id: user_001, memory_type: progress}, ) result vm.search(用户对购买咖啡机有什么预算限制) for item in result: print(item[document], | 距离, item[distance])注意代码中DefaultEmbeddingFunction()在本地会下载模型首次运行耗时较长。生产环境通常会用专门的 embedding API 或预加载模型避免每次启动都拉取权重。5.4 三种存储配合使用的策略结合上述代码一个完整的记忆写入流程可以这样设计对话过程中通过规则或模型判断哪些内容值得记住。结构化信息写入LongTermMemorySQLite/PostgreSQL如用户 ID、预算上限、当前进度。需要语义召回的自由文本写入VectorMemoryChromaDB/Milvus。每次模型调用前从两个存储中同时检索相关记忆注入到 system prompt 或 chat context。这里最忌讳的是把所有记忆都拼进 system prompt。因为即使 Context Window 足够大模型在长文本里的注意力也可能被稀释。应该做的是先根据当前任务过滤记忆再决定注入哪些内容。6. 运行验证与效果对比6.1 运行最小示例首先运行 Long-term Memory 示例cd agent-memory-demo python demo/long_term_memory.py预期输出预算不超过 800 元 | 权重 5.0 喜欢轻量便携的露营装备 | 权重 3.0判断标准两条记忆都查出来了而且权重高的排在前面。然后运行向量检索示例python demo/vector_memory.py预期输出是一段包含“用户喜欢轻量便携的露营装备”和“当前购物车包含便携咖啡机预算 800 元以内”的结果。如果输出为空第一步检查 embedding 模型是否下载完整第二步检查 ChromaDB 版本和网络环境。6.2 对比有长期记忆 vs 无长期记忆以电商导购场景为例。没有任何记忆机制的 Agent每次请求只看到当前消息用户说“上次那款再说一下”时它无法理解“那款”指的是什么。接入长期记忆后Agent 从记忆库中检索到“候选咖啡机是 XX 品牌型号”才能继续回答。这也是评估记忆系统效果最简单的方式构造一个跨多轮、跨会话的测试用例看 Agent 能否在上下文被清空后仍然使用之前保存的关键信息。如果测试不通过优先检查记忆是否真正写入了数据库用存储层查询核对。注入记忆时是否有格式前缀或标记模型能否区分记忆和当前输入检索条件是否过窄导致相关记忆没有召回7. 常见问题与排查方法把开发中最高频的问题放在一张表里供读者直接对照问题现象可能原因排查方式解决方案调用 API 报maximum context length超限历史消息和检索结果拼接过多超出模型上下文窗口先统计请求中的 token 数量确认哪部分占用过高用摘要压缩历史对召回结果设置数量上限按重要性过滤后再注入本地框架报context overflow and auto-compaction is disabled上下文压缩功能被关闭或使用的推理服务不支持自动压缩查看框架日志中当前的 token 占用和限制开启 auto-compaction或在应用层提前做消息裁剪Codex / Copilot 提示ran out of room in the models context window单轮对话累积了大量代码上下文查看工具提示中的 token 占用状态拆分子任务把已完成文件写入记忆系统而不是长期放在窗口里对话输出被截断提示“已达到输出 token 上限”请求的 prompt 太长剩余输出空间不足检查上下文中预留的输出比例通过reserve_ratio预留输出 token 窗口或升级限制更高的模型向量检索结果不相关embedding 模型与业务领域不匹配段落过大含有噪声打印召回的 top_k 结果检查文本切分粒度改用领域微调的 embedding 模型先对文档做章节切分再向量化用户 A 的记忆被用户 B 读到检索时没有过滤 user_id 等租户字段检查向量库 metadata 过滤条件在向量化和 SQL 查询中强制附加租户隔离条件记忆写入很多但精度不高没有筛选机制什么内容都被记下来了抽样检查记忆入库日志增加规则和模型判断只保留约束、偏好、结论、进度等关键信息生产环境重启后记忆丢失使用了内存型数据库且未持久化检查持久化配置和备份策略改用磁盘持久化数据库配置定时备份和回滚方案需要强调一点很多 Context Overflow 问题并不是模型不够好而是工程层没有做消息筛选和记忆压缩。先用上面的表格定位问题再针对性处理不要盲目调模型参数。另一个容易被忽略的问题是记忆冲突。用户今天说“预算 800 以内”明天说“预算可以到 1000”如果记忆系统只做追加不做更新老约束会和新约束同时存在模型就会给出互相矛盾的答案。设计记忆表结构时一定要有“版本覆盖”或“冲突消解”机制至少要做到对同一用户的同一类型记忆做去重和更新。8. 最佳实践与工程建议8.1 先定义记忆类型再写代码最稳妥的做法是在项目开始时定义一套记忆分类标准。一个可参考的体系constraint用户明确给出的限制条件权重最高。preference用户偏好可通过对话逐步更新。progress当前任务的进度、中间结论、下一步计划。fact用户基本事实比如身份、公司规模、所在城市。artifact已经生成的代码、文档、配置片段。给每条记忆打上类型和权重后续的遗忘策略才能落地。8.2 记忆注入保持固定格式把记忆内容注入大模型时建议使用统一的标记格式让模型能区分“当前对话”和“历史记忆”。例如[记忆] - 用户预算不超过 800 元时间2025-06-01 - 用户偏好喜欢轻量便携装备来源4月对话 [当前对话] 用户上次那款咖啡机再说一下。格式统一后模型更不容易把记忆误当成当前指令。8.3 生产环境必须做权限与审计企业级记忆系统最怕的是信息泄露和越权访问。在工程上要遵守三条底线所有记忆仓库查询必须带租户或用户过滤条件禁止“查全部再过滤”。删除操作尽量采用软删除保留审计日志。记忆内容涉及敏感信息时建议先脱敏再存储例如把身份证、手机号等替换为掩码字段。如果团队有合规要求记忆写入前还需要做内容审核避免违规信息进入数据库后难以清除。8.4 设置合理的过期与容量策略长期记忆不是永久保存。每个项目都应该回答三个问题一条记忆多久后无效记忆仓库总量达到多少时触发归档冲突记忆出现时以什么规则判断新旧建议给所有记忆条目增加expires_at字段并配置定时任务清理过期数据。对于权重特别高的强约束记忆可以延长生命周期但不能设置为“永久有效”而不做复审。8.5 回归测试不能只测“对话顺不顺”接入长期记忆后要专门写回归测试用例覆盖以下场景跨会话记忆是否仍然存在。记忆更新后旧约束是否被新约束替代。多个用户并发使用记忆是否相互隔离。注入大量记忆后响应延迟是否可接受、注意力是否被稀释。向量库检索失败时系统是否有降级策略。其中第 4 条尤其重要。有些团队把记忆系统做成“越多越好”结果每次请求都塞进几十条记忆模型反而抓不住重点推理质量下降、成本上升。正确做法是根据当前任务动态选择记忆注入数量强约束记忆每次注入偏好记忆最多注入 3 到 5 条其余按权重和新鲜度排序后截断。8.6 降级策略要提前设计向量数据库不是永远可靠的embedding 服务也可能超时。在企业级项目中检索失败不能拖垮主流程要设计降级路径向量检索失败时回退到 SQL 结构化记忆查询。长期记忆服务不可用时只使用短期会话记忆继续对话同时记录告警。记忆写入失败不影响主对话可以在后台重试。这一点经常被忽略也是生产事故的高发点。记住记忆系统是辅助系统不能因为它的故障让 Agent 完全不可用。9. 收尾三个建议一个提醒这篇文章想传递的核心内容可以浓缩成三句话第一Agent 的“失忆”是工程问题不是模型能力问题。Context Window 是工作台不是档案室把记忆寄托在窗口里迟早会爆。第二长期记忆系统要做到“记得住、找得准、忘得掉”。能存储只是第一步可靠检索和合理遗忘同样重要。企业级系统尤其要控制记忆的粒度和数量不能让记忆仓库成为新的垃圾桶。第三落地时先跑通最小闭环再逐步增加复杂度。先用 SQLite 记录结构化记忆用 ChromaDB 做语义召回验证跨会话信息能被正确注入然后再考虑权限、审计、容量策略和分布式部署。最后提醒一句如果你现在正被“Agent 总忘记上下文”折磨先别急着换模型。花一个下午把记忆链路梳理清楚把消息裁剪、摘要压缩、长期记忆存储这三件事做好大概率能解决八成以上的失忆问题。记忆系统不是一个可选项而是 AI Agent 走向生产环境的必经之路。