之前在做一个 BI 智能体项目时,我们遇到过一个比较典型的长任务问题:
Agent 执行时间越长,越容易重复前面已经做过的计算和推理。
表现很明显:
- 前面已经分析过的数据,后面又重新分析;
- 已经得到的结论,过一段时间后又重新推导;
- 执行链路越来越长;
- Token 消耗持续增加;
- Agent 最后甚至会逐渐偏离原来的任务。
我们最开始怀疑过模型、Prompt 和任务规划,后来把完整上下文拆开分析,最终定位到了 Context Compaction(上下文压缩)。
问题的本质是:
压缩发生以后,一些已经确认的重要结论没有被保留下来。Agent 在后续阶段看不到这些结论,只能重新计算。
针对这个问题,我们主要做了两个调整:
| 问题 | 原来的方式 | 调整后的思路 |
|---|---|---|
| 压缩时机不合理 | Token 达到阈值立即压缩 | 让模型感知任务阶段,在阶段切换时优先压缩 |
| 压缩内容不准确 | 使用框架通用压缩 Prompt | 固定业务 Prompt + Agent 动态生成压缩要求 |
调整之后,项目里原来比较明显的重复计算、重复推理问题减少了很多,长任务执行也稳定了一些。
下面具体说这两个问题。
1. 我们最开始遇到的问题
这个智能体主要会执行一些比较长的数据分析任务。
一个任务不是简单问一句、答一句,而是可能经历:
理解问题
→ 查询数据
→ 分析数据
→ 得出阶段结论
→ 根据结论继续查询
→ 再分析
→ 生成最终结果
随着任务执行,上下文会越来越长。
Agent 框架为了避免上下文无限增长,会在达到一定条件后对前面的内容进行压缩。
正常情况下,这没有问题。
问题出在压缩结果。
举个简化后的例子。
Agent 在第一个阶段完成了:
查询数据 A
→ 查询数据 B
→ 对比分析
→ 得出结论:
“指标下降主要来自区域 X,
后续应该重点分析区域 X。”
接着发生了一次上下文压缩。
压缩结果可能保留了:
已经查询指标数据。
对多个区域进行了分析。
下一步继续进行详细分析。
但真正重要的:
指标下降主要来自区域 X。
被压掉了。
进入下一个阶段以后,Agent 看到当前上下文,只知道:
前面分析过,但不知道分析结果是什么。
那它最合理的选择就是重新分析。
于是又出现:
重新查询
→ 重新计算
→ 再次比较
→ 再次得出区域 X 的结论
如果后面再次压缩时又丢失类似信息,就会继续重复。
最终我们看到的执行链路就变成:
分析
→ 得出结论 A
→ 压缩
→ 结论 A 丢失
→ 重新分析
→ 再次得出结论 A
→ 再次压缩
→ 结论再次丢失
→ 再次分析……
这也是为什么当时 Agent 看起来像“走偏了”。
实际上它并不一定是真的推理错了。
它只是已经不知道自己前面做过什么。
2. 第一个改造:优化压缩触发时机
我们先看了框架原来的压缩机制。
比较常见的一种实现方式是根据 Token 数量触发。
例如:
上下文 < 50K Token
继续执行
上下文 >= 50K Token
触发压缩
这个方案最大的优点是简单。
对于通用 Agent 框架来说也很合理,因为框架不需要理解具体任务,只需要保证上下文不要超过限制。
但在我们的场景里,这种方式有一个问题:
Token 阈值只知道上下文有多长,不知道 Agent 当前执行到了哪里。
例如 Agent 正在完成:
查询数据
→ 计算
→ 分析
→ 形成最终结论
执行到“分析”这一步时,刚好达到 Token 阈值。
框架此时直接触发压缩,就相当于在一个任务还没有真正结束的时候,把当前上下文重新总结了一遍。
而这个阶段最重要的结论可能还没有完全形成。
即使已经形成,也可能和大量过程数据混在一起。
这时候进行压缩,比较容易丢掉任务状态。
我们的处理方式
后来我们的思路是:
不要只让 Token 数量决定压缩时机,而是让模型参与判断当前是否适合压缩。
因为模型能够通过上下文判断当前任务阶段。
例如:
阶段一:经营数据分析
查询数据
→ 数据计算
→ 原因分析
→ 输出阶段结论
→ 阶段完成
接下来准备:
阶段二:基于阶段结论进行深入分析
“阶段一结束、阶段二开始”这个位置,就比“Token 恰好达到 50K”更适合做压缩。
于是我们的压缩思路从:
Token 达到阈值
→ 立即压缩
调整成更偏:
上下文已经较长
+
当前任务阶段已经完成
→ 执行压缩
核心不是取消 Token 控制,而是尽量避免在一个关键任务执行到一半的时候直接压缩。
在我们的场景里,这个调整比较重要。
因为很多业务分析任务本身就天然存在阶段:
取数
→ 分析
→ 得出结论
结论
→ 下一轮分析
→ 新结论
最终汇总
→ 输出结果
既然任务本身存在阶段,压缩最好也尽量沿着任务阶段发生。
3. 第二个改造:优化压缩 Prompt
解决压缩时机以后,我们继续看另一个问题:
压缩的时候到底保留什么?
我们查看了之前使用的 Agent 框架底层实现,发现它的压缩 Prompt 比较通用。
这其实很正常。
一个通用 Agent 框架并不知道使用者是在:
- 写代码;
- 查资料;
- 做数据分析;
- 生成报告;
- 还是处理业务流程。
所以压缩 Prompt 通常只能要求模型:
总结当前上下文,并保留重要信息。
但这里有一个很关键的问题:
什么叫“重要信息”?
对于不同任务,答案完全不一样。
4. 我们真正想保留的是“结果”,而不是完整过程
拿 BI 分析举例。
一个阶段可能执行了:
调用多个查询
→ 获取大量数据
→ 做多次计算
→ 尝试不同分析方向
→ 排除部分原因
→ 最终得出三个结论
对于后续阶段来说,真正重要的通常不是:
调用过哪些工具
每一次查询返回了什么
中间试过哪些错误方向
每一步计算过程是什么
真正重要的是:
已经确认的事实是什么
已经得出的结论是什么
哪些假设已经被排除
当前任务进行到了哪里
下一步还需要做什么
换句话说:
过程数据可以压缩,但任务状态和关键结论不能丢。
原来的通用 Prompt 很难准确知道这个区别。
所以我们对压缩 Prompt 做了改造。
5. 压缩 Prompt 拆成两部分
我们最终把压缩 Prompt 分成了两部分。
第一部分:固定 Prompt
第一部分是我们根据自己的业务场景写的固定规则。
主要告诉模型:
压缩时重点保留:
1. 已确认的业务事实
2. 已经得出的关键结论
3. 后续任务仍然需要的数据
4. 当前任务已经完成到什么程度
5. 尚未完成的问题
6. 下一阶段需要继续使用的信息
同时允许减少:
1. 重复的数据查询过程
2. 临时计算过程
3. 已经被否定的中间尝试
4. 对后续任务没有影响的过程信息
这一部分相当于给压缩增加了一个比较明确的业务方向。
第二部分:Agent 动态生成压缩要求
只有固定 Prompt 仍然不够。
因为同一个 BI Agent,每一次任务也可能完全不同。
因此我们又增加了一部分:
让 Agent 根据当前完整上下文,自己生成这一次压缩需要重点保留的信息。
原因很直接。
执行到这个阶段时,最清楚当前任务状态的,其实就是模型自己。
它知道:
- 刚才做了哪些分析;
- 哪些只是中间过程;
- 哪些结论已经确认;
- 哪些信息下一阶段还需要;
- 当前还有什么没有完成。
所以在真正压缩之前,我们先让 Agent 根据当前任务生成一份“本次压缩要求”。
最终的压缩 Prompt 变成:
固定业务压缩规则
+
当前 Agent 根据任务生成的压缩要求
=
最终 Context Compaction Prompt
固定部分解决:
我们这个类型的业务,一般应该保留什么。
动态部分解决:
这一次具体任务,最不能丢的是什么。
两部分结合以后,比只用一套通用 Prompt 更符合我们的场景。
6. 两个改造其实解决的是两个完全不同的问题
后来复盘这个问题,我觉得有一点比较容易被混在一起。
上下文压缩至少有两个不同的问题:
问题一:什么时候压缩
解决的是:
不要把正在执行的关键任务从中间切断。
我们采用的是:
任务阶段感知。
问题二:压缩什么
解决的是:
压缩以后,后续任务仍然能够知道前面已经完成了什么。
我们采用的是:
固定业务规则 + Agent 动态压缩要求。
可以简单总结成:
Context Compaction
├── When
│ └── 尽量在任务阶段结束时压缩
│
└── What
└── 保留结论、状态和后续依赖
减少过程性信息
这也是我们这次改造最核心的两个点。
7. 实际效果
改完以后,我们重点观察的不是“压缩率提高了多少”,而是 Agent 的执行行为。
之前比较明显的问题是:
已经完成分析
→ 压缩
→ 忘记结果
→ 再次分析
调整之后,这类重复计算和重复推理明显减少。
同时因为很多已经没有价值的过程数据不再继续传递,后续阶段的上下文也更干净。
最终带来的变化主要有两个:
- 无意义的 Token 消耗减少;
- Agent 长任务的执行链路没有以前那么容易反复。
这对我们的 BI 场景比单纯追求更高的上下文压缩比例更有价值。
最后的一点感受
这次问题让我对 Agent 上下文压缩有了一个比较大的认知变化。
以前很容易把它理解成:
Context 太长了,所以想办法总结短一点。
但真正做长任务以后,我觉得更准确的理解应该是:
上下文压缩其实是在给 Agent 做阶段交接。
上一阶段结束以后,下一个阶段最需要知道的不是:
前面一共发生了多少事情。
而是:
前面已经确认了什么,现在进行到了哪里,下一步应该接着做什么。
如果这些信息丢了,上下文即使压得再短也没有意义。
甚至可能因为丢失状态,让 Agent 花更多 Token 把已经做过的事情重新做一遍。
所以现在我判断一次上下文压缩好不好,不太关注它:
从 50K 压到了 10K,还是压到了 5K。
我更关注的是:
压缩完成以后,Agent 能不能不回头,直接继续往下做。
对长任务智能体来说,我觉得这个指标可能比单纯的压缩率更重要。