做Agent开发的朋友,应该都有过这种体会:同一个模型,单轮问答表现惊艳,一旦放到真实业务的多轮流程里,就变得“失去记忆”,指令执行到一半开始跑偏,甚至把前面说过的话忘得一干二净。很多人第一反应是“prompt写得不够好”,于是反复改提示词,效果依然不稳定。直到你开始认真对待上下文工程和记忆工程这两个概念,才会意识到问题不在提示词本身,而在Agent的认知结构。
这篇内容,是我把一段时间里做大模型Agent开发踩过的坑、总结出来的方法,重新梳理成的一份“认知升级指南”。它不是什么高深理论,更偏“工程实践”,适合正在做Agent开发、或准备把大模型接入真实业务的人。核心就一句话:别再把上下文当垃圾桶,而是把它当成一个需要设计、存储、检索、更新的记忆系统来对待。
1. 从“上下文”到“记忆工程”:先搞懂认知链路
1.1 上下文窗口不是用来“装”的,是用来“算”的
很多初学者对“上下文窗口”有误解,觉得它是给模型“装聊天记录”的容器,只要不超token上限,塞进去就行。这在单轮问答里勉强能用,一旦涉及Agent,问题立刻暴露:模型不仅要“看”上下文,还要在每一步决策时对整段上下文做注意力计算。你塞进去的东西越多,模型真正关注关键信息的难度就越大。
我习惯用一个比喻:上下文窗口是一块白板,模型每次下笔前都要快速扫一眼白板上所有内容。如果白板上写满了历史对话、无关日志、重复说明,那它找到重点的速度会越来越慢,最终写出来的东西也会越来越“散”。这不是模型能力不够,而是你的信息编排方式让它不堪重负。
所以,上下文管理的本质不是“控制长度”,而是“控制信息密度”。你要主动决定:每一轮对话、每一个工具调用,往白板上写什么、擦掉什么。一个健康的上下文,应该始终只包含当前决策“必需”的信息,而不是“所有发生过”的信息。
1.2 记忆工程的核心层次:短期工作记忆、长期存储、检索与遗忘
一旦你接受了“上下文是白板”这个前提,自然就会思考:那些被擦掉的信息去哪了?人类大脑会把重要的事情转存到长期记忆,需要时再调取。Agent也一样,这就是记忆工程要做的事。
我习惯把Agent的记忆分为三个层次:
第一层是工作记忆,直接存在于当前上下文中,对应最近几轮对话和当前任务状态。它的特点是快、准、容易获取,但容量有限,而且对话一结束就消失。
第二层是长期存储,放在外部数据库或缓存里,比如向量数据库、键值存储、文件系统。它负责保存跨会话的用户偏好、任务历史、事实性知识。问题是从“存储”到“工作记忆”需要一次“检索”,检索质量决定了记忆能不能被用上。
第三层是遗忘机制。遗忘不是bug,是特性。长期存储里如果塞满了过期、冲突、低价值的记忆,检索时就会把噪声捞上来,反而污染上下文。所以记忆工程必须包含主动的过滤、更新、过期清理。
这三个层次缺一不可。很多人做记忆只做“存储”和“检索”,不做“遗忘”,结果就是记忆库越来越脏,效果越来越差。真正的Agent认知升级,是把这三层连城一条完整的链路:写入、存储、检索、更新、遗忘。
1.3 Agent“认知升级”到底升什么
如果只说“认知升级”这个抽象概念,很容易变成鸡汤。落在地上,我认为它体现在四个可观测的变化上:
第一,连续性:Agent能在多轮、跨会话的任务中保持一致的行为和目标,不会因为中间换了一轮对话就丢失主线。第二,个性化:同样的请求,对不同用户返回不同结果,因为Agent能记住用户偏好和习惯。第三,决策质量:Agent在工具调用、信息筛选时,能基于历史经验而不是单轮直觉做判断。第四,可解释性:当Agent做出一个奇怪决定时,你能查看到底是哪条历史记忆影响了它,而不是黑盒乱猜。
我见过一个典型的“认知未升级”的例子:某客服Agent,用户上周刚投诉过物流问题,这周再来询问订单状态,Agent完全不记得用户情绪敏感,回复依然机械官方,用户二次引爆。这不是模型情商不够,而是Agent根本没有记忆工程:它把每次对话都当成陌生人。认知升级之后,Agent会先检索用户最近的交互记录,识别出“敏感用户”标签,再调整回复策略,整个体验完全不同。
2. 上下文工程的实操细节:把token花在刀刃上
2.1 上下文压缩与结构化组织:让每一轮都轻装上阵
上下文工程不是简单地删聊天记录,而是“有损压缩”加“结构化保存”。我实际用的做法是,每一轮Agent执行完一个子步骤后,不把完整的中间产出塞回上下文,而是生成一条精简的“状态摘要”。
具体操作分三步。第一步,设定上下文预算,比如总token的三分之一留给历史摘要,三分之二留给当前任务的系统指令、工具结果和最新用户输入。第二步,对历史对话做分层摘要:最近3轮保留原文,3轮之前采用“要点抽取”,把用户意图、已确认的事实、已完成的动作转化为结构化字段。第三步,把摘要写回上下文时,用清晰的Markdown块或JSON结构组织,让模型能够快速定位。
举个例子,一个项目回顾Agent处理“帮我总结本周开发进度”时,上下文里只需要放:用户身份标签、本周提交的关键commit列表、之前的项目偏好(比如喜欢简洁报告、包含风险提示),而不是整个月的聊天记录。
这样做的收益很明显:模型在长会话中的指令跟随能力、工具调用准确率都会有大幅回升。我实测过,同样一个任务,把上下文从塞满8000 token压缩到3000 token后,最终输出质量和稳定性反而明显提升。
2.2 基于工具调用保持上下文精简
很多人做Agent时,喜欢把所有需要用到的数据一次性预置到上下文里,比如把用户的所有订单记录、商品详情、日志文本全塞进去,让模型自己“看着办”。这样做不仅浪费token,还会让模型在不需要这些信息的时候也受到干扰。
更好的做法是,把信息获取交给工具,让模型按需调用。模型上下文里只需要保留“工具描述”和“工具返回的关键结果”。举个例子,Agent要查询用户订单状态,不要让模型在记忆里翻找所有订单,而是给它一个query_order(user_id, date_range)工具。模型只需要在需要时调用这个工具,然后只把返回结果中的状态、物流单号等关键字段放进上下文。
这个思路的关键是:上下文里放的是“获取信息的方法”,不是“信息本身”。你自己可以做一个简单的估算:假设一份数据全量是5000 token,但前端只需要其中3个字段,那通过工具调用你就省了4900 token的上下文空间,模型注意力也能集中在真正需要判断的东西上。
当模型需要连续多个数据时,工具也可以返回“紧凑格式”,比如只返回字段名和值,不要返回无关说明。你甚至可以设计专门的summarize_tool_result()函数,把工具输出做截断、提炼后再进上下文。
2.3 上下文管理的常见坑:满窗截断、信息稀释、重复注入
上下文工程里最常见的坑有三个,我每个都踩过。
第一个坑是满窗截断。很多人简单粗暴地实现“如果超过context window,就丢弃最早的对话”。这会破坏任务连贯性。比如用户在第3轮说“记住用极简风格”,第10轮超过窗口后被截断了,后面模型就开始自由发挥。正确做法是,在截断前把早期对话做摘要压缩,确保重要指令、确认过的事实被保留。
第二个坑是信息稀释。上下文里塞了很多不相关的日志、报错堆栈、临时变量,模型抓不住重点。表现是:用户明明问了A,模型却在回答B。排查方法很简单,把上下文中每个信息块问一句“当前决策真的需要它吗?”,不需要的就去掉。
第三个坑是重复注入。同一个偏好或事实,在系统提示词、历史摘要、用户原话里反复出现,模型会认为它很重要,但同时也占用了大量注意力。解决办法是去重:把稳定的偏好放进系统提示词,把动态状态放进历史摘要,不要两边重复。可以定期用脚本检查重复片段,做一个去重流程。
3. 记忆工程的系统设计与落地
3.1 记忆分层:会话级、用户级、任务级
记忆工程的第一原则是“分层存储,按需取用”。不分层的记忆库很容易变成一锅粥。我常用的分层方式如下:
| 记忆类型 | 生命周期 | 典型内容 | 存储方式 | 使用时机 |
|---|---|---|---|---|
| 会话级记忆 | 单次会话存活 | 当前意图、最近N轮对话、临时决策 | 内存缓存 | 每轮对话直接放在上下文中 |
| 用户级记忆 | 跨会话长期保存 | 用户偏好、历史偏好、身份信息、常用说法 | 键值库或向量库 | 会话开始时加载,按需检索 |
| 任务级记忆 | 与某个任务绑定,任务结束可清理 | 项目上下文、目标状态、已完成步骤、关键产出 | 数据库表或文档索引 | 任务执行过程中检索、更新 |
分层的好处是:你能为不同层设置独立的写入、检索、过期策略。用户级记忆要稳重,宁缺毋滥;会话级记忆要灵敏,快速读写;任务级记忆要有清晰的生命周期边界,任务结束就归档或删除。
实际落地时,可以用一个MemoryManager统一管理这三个层级。收到用户输入后,先检查用户级记忆里有没有相关偏好;会话过程中,把关键事实写入会话缓存;任务到达里程碑时,把任务状态固化到任务库。这样各部分职责清晰,排查问题时也容易定位。
3.2 记忆存储与检索:向量化、关键词、混合召回
提到记忆检索,很多人的第一反应是“用向量数据库做语义搜索”。向量检索确实重要,但只靠向量会漏掉很多精确信息。比如用户偏好里的“不要打电话,用短信通知”,如果对话中说的是“上次打电话烦死了,以后发消息就行”,向量检索可能判断不够精准。更好的方案是混合检索:把关键词、正则、规则匹配和向量语义检索结合起来。
具体做法:第一,把每条记忆打上类型标签,比如preference,fact,task_state,emotion_tag,检索时先按标签过滤。第二,对短文本或专有名词用关键词索引,例如用倒排索引存储用户提到过的产品名、地名、人名。第三,对描述性内容用向量嵌入做语义召回。第四,把两个召回结果合并,做一次重排,去掉与当前用户意图关联度低的记忆。
我自己的经验是,一个轻量级的记忆检索系统,可以先用SQLite存结构化字段,再用一个本地向量库存嵌入,最后用几行代码合并结果。不需要一开始就上分布式系统。关键是让每条记忆带一个“新鲜度”分数,比如最近7天命中过就加分,否则降权,这样检索结果会自然偏向近期互动。
3.3 记忆写入与更新策略:什么时候写、什么时候改、什么时候忘
记忆工程最难的不是存储和检索,而是写入策略。什么时候把一段对话“升级”为长期记忆?如果每条消息都写入,记忆库会迅速充满噪声;如果都不写,那Agent又变回失忆状态。
我的经验是:采用“事件驱动 + 事后抽取”结合。事件驱动指,在某些关键节点主动触发写入,比如用户明确表达偏好、任务完成、用户情绪出现明显变化。事后抽取指,在会话结束时,让模型生成一段结构化记忆候选,然后用规则或另一层校验,决定是否入库。
举一个入库存活的例子。用户说“以后周报用表格形式发我”。这是一个明确的偏好陈述,应该在当轮对话结束后抽取成{type: "preference", key: "report_format", value: "table", source_timestamp: ...}写入用户级记忆。而用户随口说的一句“今天天气真热”,没有长期价值,就应该只留在会话级缓存中,随会话结束清空。
更新策略上也容易踩坑。当新记忆与旧记忆冲突时,不能简单覆盖。比如用户先说“我喜欢详细报告”,后说“这周开始只要摘要”——这是临时变化还是永久变化?我一般做法是:如果是“这周/这次/暂时”等时间限定词,就写入任务级或会话级记忆;如果没有时间限定,则更新用户级记忆,并保留旧值的历史版本,方便追溯。
遗忘机制同样重要。定期批量扫描记忆库,对超过保质期的记忆降权或删除。比如用户三个月没提到的临时偏好、已完成项目的过程记录,都应进入归档而非持续参与检索。遗忘策略可以简单用“最近访问时间 + 标签类型”判断,没必要一开始就做复杂模型。
4. Agent开发中的认知架构:从RAG到记忆编排
4.1 工作流示例:多阶段Agent如何串联记忆
与其说理论,不如看一个具体的编排案例。假设要做一个“项目周报助手”Agent,它需要在一次会话里完成:收集本周完成事项、对比上周计划、生成周报草稿、按用户偏好调整格式。如果每个步骤都从零开始,那Agent就不可能知道用户偏好和上周上下文。
我设计的执行流程如下:
第一阶段,会话初始化。Agent从用户级记忆加载该用户的两个偏好字段:报告风格(偏好表格+要点)、周报中包含风险提示。同时从任务级记忆找到“上周计划”的状态,作为对比基线。
第二阶段,任务执行。调用工具获取本周commit记录、任务清单,把工具返回结果做压缩后放入工作记忆。此时上下文包含:用户偏好、上周计划摘要、本周变更列表。模型开始生成草稿。
第三阶段,记忆更新。生成结束后,模型不直接把草稿放回记忆,而是抽取一条任务状态记忆:“已完成本周周报初稿,用户尚未审阅”,并记录生成的报告ID。同时把用户此次的修改意见(如果有)提取为新的偏好候选。
这样整个流程里,上下文每一阶段都是精简的,长期记忆始终可靠。关键路径是:从长期存储拉数据 -> 加入工作记忆 -> 执行 -> 写回长期存储 -> 清理工作记忆。
4.2 记忆工程与RAG的关系与边界
很多朋友会把记忆工程直接等同于RAG,这是现在最普遍的误区。RAG(检索增强生成)解决的是“让模型获取外部知识”,比如从文档库、知识库里查资料。记忆工程解决的是“让模型记住用户和任务的历史状态”。两者有重叠,但目标不同。
我习惯用一个边界判断:如果一段信息不依赖具体用户和具体任务,属于可公开查询的知识,那就进RAG;如果一段信息只对某个用户、某个任务有意义,比如用户偏好、项目进度、历史决策原因,那就进记忆系统。
举个例子,一个法律咨询Agent:法律条文、司法解释是RAG的数据源;某个用户之前的咨询历史、他的偏好选择、上个未完成的事项,则是记忆系统的事。如果把这些都塞进同一个向量库,检索时知识性内容和个性化内容会互相干扰,效果会很糟糕。
正确做法是,让RAG和记忆系统并行存在,Agent先判断当前需求是“查知识”还是“查历史”,然后分别检索,最后在工作记忆中合并。这样每个系统的数据都足够纯净,检索准确率也会更高。
4.3 扩展场景:多轮对话、个性化、工具使用
记忆工程对三个场景的影响最大,值得展开说说。
先说多轮对话。没有记忆的多轮对话只是“消息列表连续”,有了记忆系统之后,模型可以真正跨轮引用前置事实。比如用户在第1轮告诉Agent“我预算上限是5000”,中间经过几轮讨论物料和方案,第8轮Agent报价6000时,应该能主动识别超标并询问调整。这需要工作记忆里持续保存“预算上限”这个约束,即使不在最近的原始消息里。
再说个性化。个性化不是简单地在prompt里写“你是一个贴心的助手”,而是要有实际记忆数据支撑。比如用户习惯用缩写、喜欢简洁回复、讨厌表情包,这些都可以作为记忆条目,在会话初始化时注入。效果是可测量的:用户满意度、任务完成率都会有变化。
最后说工具使用。Agent在执行工具调用时,常常需要知道“此前已经调用过哪些工具、结果如何”。比如一个API调用型Agent,第一轮调用了天气查询工具,第二轮用户问“和昨天的天气比怎么样”,Agent需要从工作记忆中提取第一轮的工具返回结果,而不是让用户再重复一遍。这种情况下,工具调用历史的摘要几乎必须进上下文。
5. 踩坑实录与排查速查表
5.1 我踩过的三个坑
第一个坑:把全部历史对话一股脑塞进上下文,然后发现越到后面回答质量越差。后来我用token开销统计看了一眼,发现近80%的token都被历史对话占据,当前指令只占很小比例。改用摘要压缩后,问题立刻缓解。
第二个坑:记忆写入太积极。早期我给每个会话都自动抽取记忆并写入长期库,结果一周后检索出来的记忆很多是“用户今天说想吃火锅”这种一次性的信息,严重污染了用户偏好。后来我加入了写入校验:只有带有时效性短词或明确偏好关键词的内容才允许写入长期记忆;临时信息只留在会话缓存。
第三个坑:忽略记忆时效性。曾经有一个Agent记住了用户“偏好邮件沟通”这条旧记忆,但用户在后续对话里已经明确改成了“微信通知”,旧记忆没有失效,导致Agent反复用邮件模板回复。后来我在记忆表里加了valid_until字段,每次更新时把旧值标记为过期,检索时默认过滤过期项。
5.2 常见问题排查速查表
这里整理了一份我经常用来排查的速查表,不一定覆盖所有场景,但能解决大多数常见问题:
| 症状 | 可能原因 | 解决思路 |
|---|---|---|
| Agent在长对话后段“失忆” | 上下文被截断,没有保留早期关键信息 | 对早期对话做摘要压缩,提取用户约束和已完成动作 |
| 检索出来的记忆总是不相关 | 向量化单一召回,忽略了关键词和标签过滤 | 改成混合检索,增加类型标签过滤和重排 |
| 记忆库越来越大但效果变差 | 写入策略太宽松,垃圾记忆过多 | 收紧写入校验,增加遗忘机制 |
| Agent反复询问用户已给过的信息 | 信息只存在某轮对话中,未写入长期记忆 | 关键信息在对话结束时抽取并写入对应层级记忆 |
| 用户改变偏好后Agent仍用旧偏好 | 更新策略缺失,旧记忆未失效 | 更新记忆时保留历史版本,把旧版本标记过期或降权 |
| 工具调用频繁失败或返回重复数据 | 上下文里没有保存上一次工具调用的摘要 | 在上下文保留工具调用历史的关键结果摘要 |
使用这个表时,建议先检查日志里模型实际拿到的上下文和检索结果,再对照表格定位原因,不要一上来就调模型或换向量库。
5.3 更进一步的实验方向
走到这里,你已经有了一个能用的记忆系统。如果再往深走,有四个方向值得尝试。
第一个方向是记忆抽象。不要把原始对话直接存进长期库,而是引导模型生成更高层的“用户画像”“任务规律”。比如从十次对话中抽象出“用户偏好结构化报告”“用户在预算紧张时会优先选性价比方案”。抽象记忆的泛化能力更强,但也要注意过度抽象导致的失真。
第二个方向是记忆压缩与合并。长期运行之后,同类记忆会越来越多,比如用户多次表达“希望尽快交付”。与其存十条相似的记忆,不如做一次聚类合并,变成一条带权重的偏好纪录。这个过程可以定期离线执行,不需要在线实时做。
第三个方向是记忆评估。记忆工程好不好,不能只看“能不能搜到”,还要看对最终任务结果的贡献。可以设计一组指标:记忆命中率、记忆引入后的任务成功率、上下文平均长度变化、用户反馈分数。有评估体系,你才能优化迭代,而不是拍脑袋调参。
第四个方向是多Agent共享记忆。如果你的系统里有多个Agent协同,可以考虑让它们共享部分公共记忆,同时保留各自的私有记忆。例如所有Agent共享企业的知识库RAG,但每个Agent维护自己负责客户的用户记忆。这个架构能避免重复造轮子,也能让记忆更清晰。
最后分享一个我自己的小经验:做记忆工程,不要一开始就追求高大上的架构。先用一个最简单的脚本,把关键信息从对话中抽取出来,存成JSON文件,再写几十行代码做关键词检索,把结果拼到上下文里。跑通这条链路之后,再逐步替换成向量库、混合检索、分层存储。很多项目恰恰是因为一开始就把系统设计得太复杂,结果连基础链路都没跑通,就匆忙推进,最后返工。
我个人在实际操作中还有一个习惯:每次Agent执行完一个完整任务,我都会让它额外输出一条“本次会话的记忆摘要”,然后我会自己看一遍,确认哪些值得写进长期库,哪些应该丢弃。这个简单的“人工审核”步骤,能在初期阶段大幅提高记忆库的质量。等数据量和置信度上来了,再慢慢把审核自动化,最终形成“写入->检索->更新->遗忘”的完整闭环。