压缩了好几代,摘要会不会走样
上一篇把摘要的长度定下来了:目标值按预算算,只写进提示词,代码不按长度拒收。篇末留了一个问题:长度只是写给模型的目标,压了好几代之后,摘要里的内容会不会走样。
先看一份走样的。Ragent仓库里有一套摘要回归:126句事先写好的用户提问,模拟一个比特严选用户从AirPods Pro左耳无声排查到送修的全过程,由程序按顺序逐句发给Agent,每一问都等Agent答完再发下一问,弹出的确认卡一律点拒绝,不真办任何事。一条会话从头到尾聊一遍,上下文会触发好几次压缩,每次压缩产出的摘要就是一代,都在库里存着。下面这条会话跑到第113问时已经压了9代,第7代摘要的“用户诉求”一节里,有这样两行:
- 用户已放弃去天才吧,改为直接寄回本店修,并明确同意办理送修申请(原话:天才吧那边不去了,直接寄回你们店修;现在可以提交送修申请了,我明确同意办理)。
……
- 限制:用户要求送修申请先别提交(原话:……)。……最新一次重申(原话:算了,申请还是先别提交,等我下周把包装准备好再说)。该暂缓持续有效,用户未撤销。
一行说用户同意提交,一行说用户要求先别提交。两句原话都是用户真说过的:第80轮他说“我明确同意办理”,第82轮就改口“算了,申请还是先别提交”。按规则,后一句生效,前一句该从摘要里退出。可它没退。再往后看,第8代摘要里这一行还在,第9代也还在,一直留到会话结束。
用户只在第80轮说过一次“明确同意办理”,第82轮就收回了。这句话怎么在摘要里活了三代?要回答它,得先弄清下一代摘要的素材里有什么、没有什么。
摘要器不会自己纠错,写得对不对由模型和提示词决定。本篇讲的是机制、规则和验证方法,这三样换模型不变;通过率随模型变。所以先说清模型:从这里到篇末换模型那一节之前,所有摘要例子都出自agent.chat配的deepseek-flash,走样的、写对的都是它写的。最后一节把同一套剧本换成gpt-6.1-sol再跑一遍,看哪些问题是模型的、哪些问题换了模型也在。
为了尽量减少单一模型产生幻觉或判断偏差的影响,我分别使用DeepSeek Flash和GPT 6.1-sol进行了测试,每个模型各跑两轮。整套测评下来,模型调用费用花了300多元,成本不算低,因此不建议大家直接在本地完整复现。接下来会结合两个模型的测试结果进行对照分析,看看它们的表现有哪些相同和不同。
原文删掉以后,摘要是唯一的底稿
先把两个词定下来。一次成功的压缩产出一份摘要,叫一代,存档表t_agent_context_compaction的generation列记的就是它。用户问一句、Agent答完,叫一轮;回归里第几句提问就是第几轮。一代通常压掉十几轮对话,所以代和轮是两个数。压缩提示词把这份摘要叫交接单,意思是原文删掉之后,接手的助手只能靠它接着办事。它和上一篇量过长度的摘要是同一个东西,本篇只看里面写了什么。
一代对话意味十几轮对话(根据预算而定),一轮对话又有几轮及以上推理过程。
1. 下一代的素材里只有上一代摘要
压缩时机那篇讲过,第二次压缩时,上一份摘要会和新一段原文一起交给摘要器。这里要补的是另一半:交给摘要器的东西里,没有什么。
compactInPlace()取素材的那几行是这样,注释是为本文加的:
// 第一条是上一代摘要,取出正文;切点之前的原文从此只剩这一份摘要
boolean hasSummary = isSummary(messages.get(0));
String existingSummary = hasSummary ? unwrap(messages.get(0)) : null;
// 素材 = 上一代摘要之后、切点之前的新原文
List<Msg> material = new ArrayList<>(messages.subList(hasSummary ? 1 : 0, cutoff));
// 切点之后的原文保留,不进素材
List<Msg> tail = new ArrayList<>(messages.subList(cutoff, messages.size()));
String summaryText = summarizer.summarize(material, existingSummary);
第一次压缩时被换掉的那段原文,这时已经不在messages里了。存档表每代留一行,但只有审计用途,压缩链路里没有谁会读它,audit()的注释写的就是“摘要每代覆盖,这张表是唯一的存档”。页面那份聊天记录t_agent_message还留着原文,但摘要器读不到它。
因此,第k次压缩并不会重新读取此前的全部对话。摘要器拿到的只有第k−1代摘要,以及上一次压缩切点到本次切点之间的新原文。更早的原文已经退出压缩链路,只能通过上一代摘要了解其中的信息;如果某个细节没有被上一代摘要保留下来,这一代也就无法再从原文中找回。
2. 一处错怎样传到下一代
回到篇首那份第7代摘要。先把时间线摆出来:
| 轮 | 谁 | 说了什么 |
|---|---|---|
| 80 | 用户 | 想了想,天才吧那边我不去了,直接寄回你们店修吧。现在可以提交送修申请了,我明确同意办理。 |
| 80 | Agent | 弹出submit_ticket的确认卡,被拒绝 |
| 82 | 用户 | 算了,申请还是先别提交,等我下周把包装准备好再说。 |
| 93 | 程序 | 第7代压缩:切点落在第91轮,第80~90轮原文和第6代摘要一起交给摘要器 |
| 102 | 程序 | 第8代压缩:切点落在第98轮,第91~97轮原文和第7代摘要一起交给摘要器 |
第7代的素材里,第80轮和第82轮的原话都在。按提示词的状态规则,先同意、后暂缓,当前状态写暂缓,不能再把旧同意写成当前意图。摘要器把暂缓写对了,“限制”行附的正是第82轮那句;但事项那一行的末尾也留下了“并明确同意办理送修申请”,连原话一起。同一份摘要里一句放行、一句暂缓,这一代算写错了。
第8代摘要里同样有这句话。它从哪来?第8代摘要器能读到的只有第91~97轮的新原文和第7代摘要。第91~97轮里用户在问替代耳机、寄修注意事项、维修价位和进度,没有一处提到“同意办理”;用户真正说这句话的第80轮,连同第82轮的改口,在第7代压缩时已经一起删掉了。所以这句话只能是从第7代摘要抄来的。
抄的时候只改了一个字,“并明确同意办理”变成“并曾明确同意办理”。这个“曾”说明摘要器看出了它和“限制:先别提交”矛盾,但没有按提示词要求把旧说法删掉,只是把它改口成过去的事,原话照留。第9代又原样抄下。
同一天并行运行的另一条会话,连续8代都没有出现这个问题,说明错误并非必然发生。真正麻烦的是,一旦某一代把已经过时的用户意图写进摘要,后续压缩就会以这份有误的摘要为基础继续生成。只要没有哪一代主动识别并纠正,这个错误就可能沿着摘要链一直传下去。
3. 错误还会从主Agent的复述再进来
上一节的错只有一个来源,就是上一代摘要。还有一类错,来源不在上一代摘要里,摘要器在下一代改对了也挡不住。
同一条会话里,订单88232的签收日期“2026-08-31”从第1代起9代都写在摘要的用户诉求行里。按提示词,订单详情这类工具查出来的内容不该进摘要,只留订单号,接手时按单号重查,原因下一部分讲。所以这个日期出现在摘要里就是错的。它看着也像上一节那样从上一代摘要一代代传下来,翻每一轮的记录却发现不是:工具结果里它出现过5次,主Agent自己的回答里又复述了26次,从第12轮一直到第126轮。摘要器每一代的新原文里几乎都能再读到它。就算某一代摘要器按规则把它删了,下一代的新原文里又有,它又会进来。
两种来源,挡法不一样:
- 从上一代摘要来的,靠摘要器在下一代按规则改掉,比如“清除与最新状态冲突的旧说法”,改掉之后就不会再出现。
- 从主Agent复述来的,改掉也没用,下一代又会读到。只能让摘要器区分这句话是用户说的还是助手转述的,助手转述的不当用户的事实记。摘要器在素材末尾补的那条提醒里有一句“不将助手或工具中的转述当成用户明确提出的要求”,管的就是这一种。
4. 走样有三种:丢、错、多
前两节讲的是错从哪来。把这条会话的9代摘要逐代读下来,摘要走样的方式可以归成三种。
丢:用户说过的条件被改写或省略。比如用户说“只要黑色”,摘要写成“黑色优先”,否定、范围、数量、时限这类词掉一个,接手的助手就会按错的前提办事。这条会话里没有发生:用户给妈妈买iPad时说的“预算4000以内”“只要银色或者深空灰”,三次“申请先别提交”,每一代都逐字保住了。
错:事情的状态写错了。篇首那条放行残留就是,用户已经改口暂缓,摘要里还留着“明确同意办理”。另一处在第1代:用户第12轮说“先不买新的了”,AirPods Max这件事已经放弃,摘要却只写了“用户要求了解AirPods Max……并与AirPods Pro 3对比”,没有标“已放弃”。接手的助手读到这行,可能继续推荐。
多:不该进摘要的东西进来了。这条会话里有三类。第一类是工具查到的详情,签收日期9代都在,“有货”“库存”“保修至”这些字眼也是9代都有。第二类是助手自己提的事,待办里写着“落实设备序列号,用户尚未提供”,序列号是助手向用户要的,用户从没说要办这件事。第三类是用户的提问。按规则,用户问“能不能”“怎么办”只是提问,不算一件要办的事,不该进用户诉求;但这条会话的摘要几乎每问一句就记一行“用户询问……”,用户诉求节从第1代的14行涨到第9代的46行,其中37行都是这种。
第三种直接体现在长度上。这条会话9代摘要的字数是1297、1501、1736、2153、2266、3221、3756、4727、5468。那126句提问从第83轮起只围着寄修细节反复追问,没有新事项,提示词也写了“新记录里没有新事项、新条件或新限制时,摘要不比上一代长”,但从第6代到第9代每代都在涨,涨的几乎全是“用户询问……”那种行。
前两种在长度上看不出来。放行残留那一行加上原话也就六十来字,淹在几千字里;条件掉一个限定词,字数反而少了。所以验证摘要有没有走样,长度和内容要分开看,后面讲验证时两边都有对应的检查。
交接单该装什么
1. 按事整理,不按问答
比特严选的压缩提示词把摘要分成六节,各管一件事。压缩时机那篇列过“会话里出现的内容该怎样记”的取舍,这里只看分节:
| 小节 | 装什么 | 不装什么 |
|---|---|---|
| 用户诉求 | 用户要办的事,一件事一行,带当前意图、必要条件和关联编号;暂缓这类限制单独一行,附用户原话;放弃的事标“已放弃”附原话 | 用户问过的问题、让助手做的清单和对比 |
| 待办 | 用户明确要求、尚未办完的动作;已启动流程里没走完的环节 | 助手未经用户接受的建议、查询发现的问题、助手索要的材料 |
| 下一步 | 只写一项当前能推进的待办,附支持它的用户原话 | 从助手建议或已失效的旧同意里取的依据;只有暂缓事项时写“无” |
| 当前进度 | 最近一次与办理有关的动作及结果 | 把“已起草,未提交”“用户取消,未执行”写成办成 |
| 走不通的路 | 未了结事项里,办理操作失败、被拒或确认卡取消的操作及原因,最多三行 | 检索绕路、资料缺失、查询得出的业务状态 |
| 已完成 | 工具结果证实成功的提交、取消、修改及返回的单号 | 答疑、清单、方案、申请文案 |
六节的共同点是按“事”整理。一副耳机、一笔订单、一款在考虑的商品各占一行,用户围着它追问二十次,它也只占一行。提示词把这一条写得很直接:
用户只是咨询、比较或了解,问「能不能」「怎么办」「有没有」,或者让助手做清单、对比、计划、文案,都是提问,不因此产生用户诉求或待办,也不并进别的事项。