引言
我想很多人在开发agent的时候都会遇到过这样的场景,和agent对话50轮甚至更多时,上下文累计超过100k,这个时候你会面临很多疑问:
- 是全发给api还是删掉一部分?
- 删掉要怎么删?
- 哪些东西该删?
- 用什么标准评判哪些该删?
虽说现在的模型上下文能力越来越强,最强的Gemini已经达到了恐怖的1M,但是很多实践证明如果不做上下文优化会不可避免的遇到以下问题:
- api调用成本的不断增加
- 过大首Token延迟
- 最致命的回答不准确、lost in middle、幻觉等问题
如何在有限的上下文窗口内保留关键信息、剔除噪声,这是个很复杂值得深思的系统工程。
一、Token消耗到哪了?
通常来说我们的token消耗主要有以下部分:
- System Prompt + 工具说明(name、description)
- 每轮对话的input/output
- 思考过程(开启thinking)
- 工具调用记录
其实从token消耗的来源我们可以反推,节约token的方法。比如精简系统提示词和工具说明、控制输出token限制、压缩上下文、关闭thinking、精简工具调用记录——记录关键信息,本文主要从压缩上下文角度来讲。
二、信息的取舍
信息的取舍主要依托于信息的价值,信息价值如何评估是关键
what—历史消息的分类
为什么要做分类?因为不同类型的信息复用率不同,用户信息可能要经常被提起,有些内容则几乎不被提及,所以将他们区分开做不同方案的取舍
| 类型 | 取舍方案 | 说明 |
|---|---|---|
| 用户消息 | 全部保留 | 用户的消息可能第1轮提出,到后面第50轮还要回溯,所以十分重要,要原封不动保留 |
| 关键状态 | 做结构化摘要 | 所谓关键状态就是关键决策、任务完成、失败等情况的节点,例如:用户提出的登陆功能已经完成 |
| 推理过程 | 激进压缩保留结论 | 模型可能会思考多种方案来解决用户的问题,只需要保留最终结论就可以,例如:方案a成本高,方案b实现困难,方案c最佳平衡,使用c来解决用户问题 |
| 失败记录 | 保留原因删除过程 | 要记录下一些尝试的失败原因,不关心过程如何实现,只记录原因避免后续犯同样的错误 |
其实直接使用这个方案就可以做到不错的压缩效果,但是其实可以更进一步,做到更高效、精确的压缩,保留更具有价值的信息,请接着往下看。
how—方案进一步优化
其实每条消息可能不是我们上面提到的某种单一的类型,比方说:
ai输出了一次测试的记录,这条消息中包含了任务已经完成(关键状态)以及他的思考过程,还有他测试的时候试了其他方案结果失败了。
这个时候肯定是不可以按照单一的消息类型来处理,我们可以引入权重(weight)机制,用户的消息最重要权重最高,关键状态次之,推理过程不重要权重小,失败记录权重也不高。除此之外也可以考虑时间衰减的影响,时间越久远的消息越不重要,那么我们可以很容易得到一个算法来计算消息的价值:
value = (weight1 + weight2 + weight3+....) * time_decay
具体的权重的话是需要去测试才能找到一个比较好的边界值的,这种东西没有一个固定的value可以让不同业务场景下去套用,切记思想才是关键,不要死记数据。
接下来进行压缩的时候就可以对消息列表进行一个遍历,对所有消息进行打分、排序,设置一个多个阈值进行不同的处理,最后用压缩后的消息替换原始消息,便可以释放上下文空间
暂时无法在飞书文档外展示此内容
When—何时触发摘要
摘要的时机很重要,太频繁会导致llm调用成本增加,但是太晚的话会导致上下文溢出消息丢失,一般来说我们可以在上下文达到上限的80%左右进行压缩,这是一个比较平衡效果和成本的策略,但是具体的时机要根据业务的实际情况去摸索出边界
总结
总结一下信息取舍的全流程吧
-
将消息分类
-
设计不同类别消息的权重
-
达到信息取舍阈值,进行压缩
- 计算每一条消息的价值
- 根据价值出发对应的摘要逻辑
-
替换原文释放消息列表空间