ARTICLE DETAIL

资讯详情

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

上下文压缩后代理失忆?long_mem长期记忆机制实战

上下文压缩后代理失忆?long_mem长期记忆机制实战 1. 这个实验到底在折腾什么先交代背景。我做 AI 编码代理的集成和调优差不多两年了从最早的补全插件一路跟到现在的 agent 形态。这次实验的起点很朴素上下文压缩之后代理接不上活。具体场景是这样的。你在一个稍大的代码库里跑一个编码代理比如让它改一个跨了七八个文件的接口。对话轮次一多上下文窗口就顶到上限了。这时候系统会触发上下文压缩——把前面的历史对话摘要成一段短文本腾出空间继续跑。问题就出在这个摘要上压缩完之后代理经常表现得像失忆了一样明明十分钟前刚确认过的函数签名它转头就写错明明已经排除掉的方案它又绕回去重试。我一开始以为是模型能力问题换了好几个模型发现都差不多。后来才意识到压缩丢的不是信息量而是结构。摘要能把我们在改用户模块这件事保留下来但保留不了用户模块的鉴权函数叫 verify_token参数是 user_id 和 scope返回 bool这种精确的、可执行的知识。代理要接着干活靠的恰恰是后者。所以这个实验想验证一件事在上下文被压缩之后能不能用一套外挂的长期记忆机制把代理接回到之前的工作状态。我给它起了个名字叫 long_mem核心组件叫 engram——engram 这个词借的是神经科学里的记忆痕迹概念指的是一条可以被重新激活的记忆单元。整个实验跑了 10 天公开记录了 430 条交互日志。为什么是 430 条因为这是我实际跑完三个真实项目、累计触发压缩 60 多次之后自然产生的记录量不是我凑的数。下面我把这套东西的设计思路、实现细节、踩过的坑一条条摊开讲。适合谁看如果你在用 Codex 这类编码代理跑真实项目遇到过聊着聊着它就傻了的情况这篇对你有用。如果你只是想了解上下文压缩的原理也能看但我会更偏实操。2. 为什么压缩之后会断片先把病根找出来2.1 上下文压缩到底压掉了什么要解决问题先得看清楚压缩干了什么。主流编码代理的压缩策略本质上是把 token 序列做有损摘要。它通常按这样的优先级丢弃信息最早期的对话轮次优先被摘要成一段自然语言工具调用的原始返回比如文件内容、命令输出被截断或概括中间过程的试错记录大量被丢弃这个策略对闲聊型对话没问题但对施工型对话是灾难。因为编码代理的工作状态很大一部分藏在工具调用的原始数据里而不是藏在自然语言里。举个我日志里的真实例子。第 3 天那次压缩前代理刚执行过一次文件读取拿到了一个 200 行的配置文件。压缩后摘要里只写了读取了配置文件确认了数据库连接参数。结果代理下一步要改连接池大小时它不知道参数名到底是pool_size还是max_connections只能重新读一遍文件。这一读又吃掉几百 token等于压缩白做了。注意压缩的收益是省 token但代价是代理要重新获取信息。如果重新获取的成本高于压缩省下的这次压缩就是负收益。很多代理框架不算这笔账导致越压越慢。2.2 接不上的三种典型表现我把 430 条记录里所有断片事件做了分类归成三类占比大概是断片类型占比典型表现事实丢失约 52%忘记已确认的函数名、变量名、文件路径决策丢失约 31%重复尝试已被否决的方案或推翻已定的架构决策进度丢失约 17%不知道当前改到哪一步重复已完成的工作事实丢失最好理解就是精确信息没了。决策丢失更隐蔽——代理不是忘了做过什么而是忘了为什么不做某个方案。比如它之前试过用正则解析某个格式发现边界情况太多放弃了压缩后它又兴冲冲地回去写正则。进度丢失则表现为原地打转明明已经改完三个文件它又从第一个文件开始检查。这三类问题的共同点是它们需要的都不是概括性记忆而是可检索的精确记忆。这就直接决定了 long_mem 的设计方向。2.3 为什么现成的方案不够用有人会问向量数据库检索不行吗RAG 不就是干这个的我试过直接说结论通用 RAG 对编码代理的记忆需求匹配度不高。原因有三。第一编码场景的检索键往往是符号而不是语义比如你要找的是verify_token这个函数而不是那个验证令牌的东西语义检索反而容易召回一堆不相关的。第二编码代理需要的是结构化状态不是一堆文本片段检索回来五段相关代码代理还得自己拼。第三延迟敏感代理每一步都要查记忆如果每次检索要几百毫秒整个循环就卡死了。所以 long_mem 没有走通用 RAG 路线而是走了一条更土但更稳的路结构化记忆 符号索引 轻量激活。下面详细讲。3. long_mem 与 engram 的设计把记忆做成可激活的单元3.1 engram 的数据结构长什么样engram 是整个系统的原子单位。我把它设计成一个带类型标签的结构化记录而不是一段自由文本。一条 engram 大概长这样用伪结构表示{ id: eng_20240612_0031, type: fact, symbol: verify_token, scope: auth/user.py, content: { signature: verify_token(user_id: str, scope: str) - bool, notes: scope 为空时默认校验 read 权限 }, confidence: 0.95, created_at: step_142, last_activated: step_389, activation_count: 7 }关键字段解释一下。type分四类fact事实、decision决策、progress进度、artifact产物比如某个文件的关键片段。symbol是符号索引键专门给代码场景用的让检索能精确命中。confidence是置信度因为有些记忆是代理推测出来的不能和确认过的事实同等对待。activation_count和last_activated是给激活策略用的越常被激活、越近被激活的记忆优先级越高。为什么用结构化而不是纯文本因为结构化让重新注入上下文这件事变得可控。压缩后我要把记忆塞回上下文如果记忆是文本我只能整段塞如果是结构化的我可以只塞signature字段省 token 又精确。3.2 记忆是怎么被写进去的写入时机很关键。我试过两种策略一种是每步都写一种是关键节点写。前者噪音太大后者容易漏。最后采用的是混合触发工具调用返回后如果返回内容包含新的符号定义函数、类、配置项自动抽取成fact类型 engram代理显式表达决策时比如我们决定用方案 B抽取成decision类型每完成一个文件修改写一条progress类型压缩触发前强制做一次全量扫描把当前工作状态固化这里有个细节值得说抽取不是靠另一个模型调用。我一开始想用一个小模型来做信息抽取实测下来延迟太高而且不稳定。后来改成基于规则的抽取——正则匹配函数定义、配置项赋值、文件路径配合代理输出里的固定标记比如我在系统提示里要求它用特定格式声明决策。规则抽取的召回率不如模型但精确率高、零延迟、可预测对编码场景够用了。实操心得不要迷信用模型处理模型。在代理的每一步都插一次模型调用延迟会累积到无法接受。能用规则解决的坚决用规则。3.3 记忆是怎么被读回来的读取分两个通道这是 long_mem 的核心设计。通道一符号精确匹配。代理当前正在处理的符号比如它刚读了verify_token的调用点直接拿符号去索引里查命中就返回对应 engram。这个通道快、准覆盖大部分事实类需求。通道二激活扩散。当符号匹配不到或者需要的是决策/进度类记忆时走激活扩散。简单说就是从一个种子 engram 出发沿着相关关系往外扩一跳。相关关系怎么建我用了三个维度同一文件、同一符号前缀、时间上相邻step 差小于 20。这个策略借鉴了记忆的激活扩散理论实测比纯向量检索在编码场景下召回质量高不少。两个通道的结果会合并、去重、按confidence × recency × activation_count排序取前 N 条注入上下文。N 我设的是 8超过 8 条注入的 token 成本就划不来了。3.4 为什么这套设计能接得上回到最初的问题压缩后代理为什么接不上因为它丢了精确的、结构化的、可检索的工作状态。long_mem 做的事情本质上是把这份状态从易失的上下文里搬到了持久的记忆库里。压缩可以随便压上下文因为真正重要的东西已经落库了。压缩后代理需要什么就按需从库里取什么。这就把压缩和记忆解耦了——压缩只管省 token记忆只管保状态各司其职。这个解耦带来的一个额外好处是记忆可以跨会话复用。同一个项目今天跑一半关了明天重开engram 还在代理能接着昨天的进度干。这一点在 10 天实验里帮我省了大量重复劳动。4. 10 天实验的完整实操过程4.1 实验环境与项目选择环境没什么特别的就是一台开发机跑编码代理的 CLI接的是本地可用的模型服务。三个实验项目分别是项目 A一个中等规模的 Python 后端服务约 1.2 万行主要改鉴权和数据层项目 B一个前端组件库约 8000 行主要做重构项目 C一个脚本工具集约 3000 行主要加功能选这三个是因为它们的压缩压力不同。项目 A 文件多、依赖深压缩触发最频繁项目 C 简单压缩少。这样能覆盖不同场景。4.2 关键参数与计算过程参数不是拍脑袋定的我列一下几个核心参数的来由。记忆注入条数 N8。这个是从 token 预算倒推的。我的上下文窗口假设是 32k token压缩后留给记忆注入的预算大概 2k token。一条 engram 平均 250 token2000/250 8。如果 engram 更精简只注入 signature能塞更多但信息完整度下降权衡后取 8。激活扩散的跳数 1。试过 2 跳召回的相关记忆数量暴涨但精确率掉得厉害注入一堆弱相关的东西反而干扰代理。1 跳是精确率和召回率的平衡点。recency 衰减半衰期 50 步。意思是 50 步之前的记忆权重减半。这个值是根据日志里代理实际回看多远统计出来的——大部分有效回看发生在最近 50 步内更早的记忆命中率明显下降。confidence 阈值 0.6。低于这个值的记忆不注入避免代理被自己的推测带偏。4.3 一次完整的压缩-恢复流程记录拿第 5 天项目 A 的一次真实流程举例这是日志里比较典型的一次。压缩前状态代理正在改鉴权模块已经确认了三个函数的签名否决了用装饰器统一鉴权的方案因为和现有中间件冲突改完了两个文件。压缩触发上下文到 90% 阈值系统执行摘要压缩历史对话被压成一段 300 字的概述。记忆固化压缩前钩子触发扫描当前状态写入 6 条 engram3 条 fact三个函数签名、1 条 decision否决装饰器方案及原因、2 条 progress两个文件已改完。压缩后第一步代理要改第三个文件需要知道第一个文件的接口。它读取调用点提取符号verify_token走符号匹配通道命中对应 engram拿到精确签名。压缩后第二步代理准备动手激活扩散把那条 decision 也带出来了提示它装饰器方案已否决它就没再往那个方向想。结果这次压缩-恢复全程没有出现重复劳动代理无缝接上。对比实验里没开 long_mem 的对照组同样的压缩点对照组重复读了一次文件、重试了一次装饰器方案多花了约 12 步。4.4 430 条记录里我关注的核心指标10 天下来我重点盯三个指标指标含义实验组对照组压缩后重复劳动步数压缩后重做已完成工作的步数平均 1.3 步平均 6.8 步压缩后首次命中率压缩后第一步就取到正确记忆的比例78%不适用单任务总步数完成一个任务的总交互步数平均 41 步平均 53 步重复劳动步数从 6.8 降到 1.3是我最满意的结果。总步数降了约 22%说明记忆机制确实在省事而不是在添乱。注意这些数字是在我这套特定配置下测的换模型、换项目规模绝对值会变。但趋势——开了记忆比不开强——在三个项目上是一致的。5. 踩过的坑与排查技巧实录5.1 记忆污染最坑的一个问题实验第 2 天我发现代理开始记错东西。它坚称某个函数返回int实际返回bool。查了半天发现是记忆污染代理在探索阶段猜了一个签名被规则抽取误当成 fact 写进了库后面一直用这个错的。这个坑的根源是规则抽取分不清确认的事实和代理的猜测。我的解决办法是引入来源标记。只有来自工具调用返回真实文件内容的才标confidence0.95来自代理自然语言表述的标confidence0.7来自代理推测的标confidence0.5。低于 0.6 的不注入。同时当真实文件内容与已有 engram 冲突时以文件内容为准覆盖旧记忆。这个文件优先原则很重要。记忆库永远不能比真实代码更权威否则代理会基于错误记忆改出错误的代码。5.2 激活扩散的雪崩第 4 天遇到一次性能问题某一步激活扩散突然召回了几百条记忆排序和去重花了明显时间。排查发现是某个高频符号比如main作为种子一跳扩散把半个库都带出来了。解决办法是给扩散加度数上限一个符号关联的 engram 超过 30 条时不再扩散只做精确匹配。同时对高频符号做特殊处理降低其作为扩散种子的权重。这个改动之后再没出现过雪崩。5.3 常见问题速查表把 10 天里遇到的问题整理成表方便对照排查现象可能原因排查方向解决代理重复已否决的方案decision 类记忆没写入或没召回查压缩前钩子是否触发补写 decision检查扩散通道代理用错函数签名记忆污染或未覆盖查 engram 的 confidence 和来源以文件内容覆盖降权推测记忆压缩后变慢记忆注入过多或检索慢查注入条数和检索耗时降 N加度数上限记忆库膨胀重复写入同一事实查是否有去重逻辑按 symbolscope 去重保留最新跨会话接不上记忆没持久化查存储层落盘按项目隔离5.4 几条压箱底的经验第一记忆的写入比读取更重要。读取策略再花哨写进去的是垃圾召回也是垃圾。我后来把大部分精力放在写入的精确性上收益比优化检索大得多。第二不要试图记住一切。我一开始想全量记录结果库膨胀、检索变慢、噪音变多。后来只记四类核心信息反而效果好。记忆系统的价值在于选择性遗忘不在于全记住。第三给记忆加过期机制。有些 progress 类记忆任务完成后就没用了留着只会干扰。我加了个规则任务标记完成后相关 progress 记忆降权到不注入。第四实测永远比理论靠谱。我设计时觉得激活扩散是核心实测发现符号精确匹配才是主力扩散只是补充。如果不实测我可能会在扩散上过度投入。6. 关于 Codex 这类代理的接入细节既然热搜里 Codex 相关词很多我顺带说说这套记忆机制怎么接到 Codex 这类编码代理上。核心是找到压缩钩子和工具调用钩子两个切入点。压缩钩子负责在压缩前固化记忆工具调用钩子负责在每次工具返回后抽取记忆。这两个钩子大部分代理框架都提供了扩展点如果没有就得在代理循环外面包一层。接入时要注意几点。一是别改代理的核心循环记忆机制应该是旁路的出问题能随时关掉不影响主流程。二是记忆注入要放在系统提示之后、用户输入之前位置不对代理可能忽略。三是控制注入格式我用的是紧凑的结构化文本不是 JSON因为 JSON 的括号和引号很费 token。至于 Codex 安装、登录、模型配置这些属于基础环境问题和记忆机制是两码事。环境跑通了记忆机制才有意义。我见过有人环境都没配好就折腾记忆那是本末倒置。这套东西后续还能往两个方向扩。一个是跨项目记忆共享把通用的编码约定比如这个团队用 snake_case抽成全局记忆。另一个是记忆的主动整理定期把零散的 fact 合并成更高层的知识。这两个我还没做但思路是通的。最后分享一个我自己的体会上下文压缩不是敌人记忆机制也不是万能药。真正管用的是想清楚什么必须记住、什么可以忘。这个判断做对了机制怎么实现都是次要的。我花了前三天才想明白这一点后面七天就顺多了。
返回列表