在之前已经学习完了如何优化上下文中的静态前缀和动态轨迹中的skill如何插入,但只是优化提示词仍没有解决agent的上下文膨胀的这一难题,由于agent的注意力是有限的。为了解决这一难题,我们采用了agent状态栏和上下文压缩策略。
一,agent状态栏
在生产过程中仅仅依靠模型本身是不可以的,因为模型缺乏对目前状态和进度的感知,容易产生无限循环,状态遗忘,目标偏移等问题,所以就需要使用一个agent状态栏来实时标定agent现在执行的位置、状态,以及未完成的目标。
由于模型更擅长于检索而非总结,所以常常会遗忘状态,比如之前限定agent最多拨打一个号码三次,然而agent通过上下文窗口只能看见拨打记录而不会数具体拨打了几次,所以这时候就需要一个agent状态栏来标定现在的状态,也就是将上下文进行提炼。这种机制将上下文中的隐形状态直接提炼成一个直接使用的显示知识。且根据注意力机制模型更容易注意到头尾的部分又为了保证kv cache不被破坏所以agent状态栏需要放在轨迹末尾。
设计状态栏时需要注意以下三点:首先状态栏必须要使用代码来维护,最好别用模型自己来总结,若必须要使用模型则需要让模型逐条抽取,最后由代码来汇总。其次在删除上下文时需要保证状态栏覆盖了删除部分的所有会被问的问题。最后必须把状态栏当作一项生产指标,这是因为模型会无条件的相信状态栏,状态栏说是什么模型就会信什么,它不会自己去重新统计,就像之前说的模型更倾向于检索而非总结,它会把状态栏的内容当作真实情况。
接下来就是状态栏中具体需要装些什么,首先是任务规划由TODO来构建任务列表,将已完成的任务打勾。接着是事件的侧信道信息,这是将真实的物理环境给定,在这里可以放入时间,上次agent回复间隔,可以让agent做出更符合当前情景的决策。然后是当前的环境状态每一个操作执行了多少次,给定当前的执行状态,每个工具使用了多少是否达到上限。最后是能力清单,也就是之前说的skill中的name和describe放在此处。
二,上下文压缩策略
在上下文中通常会出现两种情况,一个是上下文膨胀导致窗口装不下,另一个是上下文中充满大量碎片或者无效信息导致模型注意力分散无法提取关键点,这两种情况被称为上下文溢出和上下文腐化,为了解决这两个问题就需要使用的到上下文压缩策略,上下文压缩策略的关键因素是提炼,就像之前说的那样模型更倾向于查找而不是提炼,当去除模型的思考链时模型连基本的统计也无法进行,所以进行提炼能大大减少token的消耗也可以提升agent输出的效率和质量。
总的来说压缩优势就在于可以将原始的数据转为更适合模型使用的形式,还将思考后的结论转变为可直接检索的知识。那该如何进行上下文压缩呢。
上下文压缩是用专门的llm模型调用来总结,但与其被动的让agent自己在过去的轨迹中检索,不如用一个新的来进行上下文的提炼转为适合模型使用的显示知识。上下文压缩也有自己的策略:1,首先是工具的返回结果,比如当工具返回了1000行的日志结果,只保留前五十和后五十行其他的全部放入本地磁盘中,后续如需要读取再到磁盘中获取信息。2,噪音删除,价值的部分直接删除,比如在之前大量的搜索过程中只用到了几行的内容,这部分可以直接删除。3,使用api编辑上下文的结果,这个常常适用于窗口即将填满需要编辑前缀的情况,这会将指定的工具结果直接删除。4,归档摘要,这是将之前的对话的脉络给保留下来,清晰的编辑之前没轮对话是做什么的,但需要简短就像git log那样。5,llm全量压缩,这一步是直接将上下文的所有内容交由模型处理,但也分为两个阶段,首先是先进行会话记忆的压缩,若不行在将包含会话工具调用的全部内容进行压缩处理,这里还需要设计一个熔断机制防止模型反复困在失败的循环中。压缩还需要保证语意完整,任务相关(保证检索时得到正确的信息),压缩后的内容即使理解。
这里需要说明的是上下文压缩是作为一个tool result被调用,虽然会替换位置之后会让缓存失效,但之前以及存入的缓存仍然存在,上述说的删除都是在工具中进行不会影响之前的缓存。但频繁压缩也会导致缓存的频繁失效,所以一般是当上下文窗口即将溢出时才进行上下文的压缩。
更好的做法则是分出一个子agent先来进行上下文的检索只将返回的那个几百token的结论性摘要返回给agent,不让中间工具调用的这些结果直接进入上下文,这就是隔离由于压缩。