Skip to content

一、为什么要进行上下文管理

在设计和开发大模型应用时,当模型可以获取到足够的上下文时,设计的重点不再是“获取”,而是“整理”。此时需要关注的是如何对上下文进行有效的管理,通过修剪,压缩,删除等方式,使保留下来的上下文既足够相关,又能被高效利用

有的开发者会停留在获取的阶段,因为模型的上下文窗口一直在持续的扩大,只要上下文窗口足够大,那么获取的信息就不用在整理了,直接将全部的上下文输入给大模型

但是这种方式一定会达到你想要的效果吗?

实际上,更长的上下文并不会生成更好的响应,过度加载上下文会导致你的代理和应用以意识不到的方式失败。上下文可能会被污染,分散模型注意力,令模型困惑或者上下文内容是相互冲突的,这对于依赖上下文来收集信息、推理和协调行动的代理或应用来说是“致命的危险”


接下来我们一起来看看上下文失控的各种情况

1.1、上下文污染

🌟 定义:上下文污染是指幻觉或其他错误进入上下文,并被反复引用的情况

当上下文被污染的时候,代理会发展出无意义的策略,并重复行为,以追求一个无法实现的目标

用户记忆的上下文中,我们会借助用户输入的历史记录,对这些记录使用大模型进行整理,归纳总结出来用户记忆数据,并且将这些数据存储到记忆模块中,但是如果在整理的时候出现“误解”,那么这个“错误的用户记忆”就一直存储在用户记忆模块中,持续污染相关的上下文查询

例如用户只是一次情绪化的表达:“这节课烦死了,根本没用”

那么系统整理出来的记忆变为:“用户认为《课程 C》没有价值。

后续检索相关用户记忆的时候,这条记忆就可能一直伴随《课程 C》进行回答,就会在长期的交互中误导大模型进行回答

  1. 有可能用户后面更换了态度,因为那次情绪表达不一定是因为课程的问题,也有可能因为在看这节课程之前,用户的心情已经很糟糕
  2. 可能用户表达的”这节课“可能是这个知识点或者这章而已,不是整个课程体系

工具调用错误处理中,我们会向 LLM 输入工具调用错误并且会触发重试机制,但是有些开发者没有用心设计工具调用的错误显示和输出,导致LLM 接收到的工具调用错误信息,并不一定能准确反映工具出错的真正原因。所以模型反复在“错误”中重试

📝:关于上下文污染的解决方案,其实更多的会从大模型应用的架构设计开始考虑,例如:

  1. 在工具错误重试的机制中引入最大的重试次数,同时引入 LLM 反馈机制,当重试失败之后,模型可以将错误信息和原因反馈给用户,让用户输入新的内容来重新调整
  2. 在记忆模块导致的“过时问题”,那在设计记忆模块的时候,应该有记忆更新和精简的机制

1.2、上下文干扰

定义:上下文干扰是指当上下文变得过长时,模型过度关注上下文,忽视了在训练期间学到的内容。

在代理工作流程中,随着上下文增长,模型会收集更多的信息并累积,累积的上下文可能会变得让模型过度关注上下文信息中的过去动作,而较少利用其训练得到的策略

这个对于在创造性的代理和应用中是非常危险,会让模型的输出结果“固化”并且是较低质量的结果固化

在李继刚中提出的“场域共振”的理念中

我的理解关于场域共振:

  • 场域:在你和大模型之间建立一个联系,这个联系就是一个场域,场域是一个交流通道,一个允许你和大模型进行交流的空间
  • 共振:这个是结果,不是过程,能够触发这个结果的动作是“在这个场域中让大模型能理解我想做什么,并且它能够留出一定的发挥空间,它能够在这个空间中自由发挥”

场域共振的结果并不满足理解,而是超越理解,例如:我说了一句话,你说了一句话,你这话很懂我,这个本质上还是理解,场域共振最终是一种超越

所以过多的上下文会干扰这个场域中的信号,留给大模型的空间会越来越小,最后的结果无论如何调整也只是“还行,不是太烂”

那如何有效的在提示词或者上下文中输入更多的信息给大模型呢?

“极致压缩,并且在大模型内部发生连锁信息爆炸”

整个过程有点像小时候玩的“深水炸弹”,当把“深水炸弹”放入水中的时候,它在水中像“炸开”一样了,整杯水都变得有味道了

我虽然只是传递了几个单词而已在提示词中,几个概念而已,但是在大模型的内部,其借助训练时学到的内容,这些概念和词汇或许已经炸开成为好几本书了,无意中我传递了更多的信息给大模型

📝关于上下文的干扰的解决方案:

  1. 上下文的修剪
  2. 上下文的总结

这两个方案都是实际可行的

1.3、上下文混淆

定义:上下文混淆是指模型使用上下文中多余的内容来生成低质量的响应

当你把某些信息放入上下文中,模型就会关注它。这可能是不相关的信息或无用的工具定义,模型都会把这些考虑在内

在工具调用的 LLM 中,传入工具定义给 LLM 是必须的的,但是过多的无关工具和工具定义会导致模型变得混乱,最终选择出来的工具不是正确的

所以在工具定义的上下文中,定义一定要清晰,有边界,同时当工具过多的时候,要适当使用 RAG 的方式去为用户输入检索相关的工具定义上下文给 LLM

📝 关于上下文混淆的解决方案:

  1. 使用 RAG 技术来检索相关的上下文
  2. 增强工具模块的定义

1.4、上下文冲突

定义:当你在上下文中加入新的信息或工具时,这些内容与上下文中已有的信息产生矛盾或冲突

这些新的信息不是无用的,而是有效的,只不过和之前的信息发生了冲突

这个在多智能体架构的设计中是最可能出现的,因为多智能的上下文是彼此分开进行的,但是在使用的时候智能体之间会互相使用彼此输出的结果,A 智能体有可能会使用 B 智能体的结果,或者 A 智能体会使用 B 和 C 智能体的结果

例如:现在有一个查询检索的请求,A 智能体并行调用 B 和 C 智能体,B 和 C 智能体可能由于信息来源的问题导致得到的结果是冲突的,那么这个时候冲突的上下文组装输入给了 A 智能体,那么 A 智能体输出的结果就会变得不乐观

📝 关于上下文冲突的解决方案:

  1. 可以使用上下文隔离的策略,将“大任务”拆分为不同方向的子任务,并且交给不同的 LLM
  2. 在设计 代理的架构时,可以仔细选择“单智能体架构”还是“多智能体架构”根据应用场景选择

二、上下文管理的策略

在上一节中,我们介绍了上下文带来的问题,接下来我们来探讨使用哪些管理策略可以减少这些问题在大模型应用中产生

2.1、RAG

检索增强生成(RAG)是指选择性的添加相关信息,以帮助 LLM 生成更好的回复


关于 RAG 后续上下文工程里面会仔细讲解,在这里就不深入讨论了

每当上下文窗口上限增加的时候,就会诞生一场新的“RAG 已死”的争论,其实 RAG 也会改变,无论上下文的窗口上限如何增加,在大模型应用开发的过程中RAG 或许都会有”一席之地“

正如一句话:“

2.2、工具配置和定义

在上下文的工具定义部分,应只加入与当前输入相关的工具定义,提供给 LLM 使用

选择相关工具最简单的方法是将 RAG 应用在你的工具描述中,通过将工具描述存储到向量数据库中,RAG 能够根据输入来检索选择最相关的工具

在对 DeepSeek-v3 进行提示时,团队发现:当可用工具数量超过 30 个时,选择合适的工具就变得至关重要。超过 30 个之后,工具的描述开始互相重叠,导致混淆。而当工具数量超过 100 个时,模型几乎必然无法通过测试。利用 RAG 技术将工具数量筛选到 30 个以内,可以显著缩短提示长度,并将工具选择的准确率提升多达 3 倍

但是如果你的工具定义不多,并且边界清晰,那其实没有必要使用 RAG 来检索工具,完整的将你的工具定义提供给 LLM 效果会更好

2.3、上下文隔离

上下文隔离(Context Quarantine)指的是将上下文隔离到各自专用的线程中,每个线程由一个或多个 LLM 独立使用


当我们的上下文太长并且都是有效的时候,无法进行压缩裁剪,这个时候我们可以使用将任务分解成为更小、更隔离的工作给 LLM,每一个 LLM 都有自己的上下文

在 Anthropic 的博客文章中有详细的介绍

2.3、上下文修剪

上下文修剪是从上下文中移除无关或不需要的信息的行为

代理在触发工具和组装积累上下文的时候,在正式将上下文输入给 LLM 之前,进行上下文的内容评估并且移除冗余部分时值得的,因为这样会让上下文更加精简的同时发挥模型更大的推理能力

有一些实际开发策略时有效的:Token 压缩策略,根据不同的模型来决定压缩上下文不同的部分,并且会有一个压缩的策略,先压缩中间,如果还是不够,在压缩上或者下,主要目的还是根据不同场景保留最相关的上下文信息给 LLM,详细的 Token 压缩策略会在下文解释

2.3、上下文总结

上下文总结是将累积的上下文浓缩成简要摘要的行为

当上下文尤其是会话的历史记录超过最大的上下文长度时,这个时候可以针对历史记录生成一个简短的摘要,最好在这个简单的摘要中能够保留核心的信息

三、上下文管理的实现方案

3.1、上下文管理容器设计

一个开源项目中的上下文管理容器的实现和设计思路:

  1. 消息添加的处理流程:
    1. 添加用户|助手|工具的消息
    2. 调用增加消息的通用方法
    3. 对消息进行格式的验证
    4. 将消息存储到多后端设计的数据库中
    5. 更新消息上下文 Token 的总数
  2. LLM 获取上下文的流程:
    1. 调用系统提示词管理容器获取系统提示词
    2. 调用多后端容器从数据库中获取会话历史的消息
    3. 消息的 Token 判断是否压缩
    4. 消息格式化为 LLM
    5. 传入 LLM 中执行
  3. Token 压缩流程:
    1. 判断是否有必要需要压缩
    2. 执行压缩
    3. 保留每一次的压缩历史存储到数据库中
  4. 消息格式化的流程:统一将内部的消息格式转换为 LLM 提供商需要的 API 消息格式
    1. 获取 LLM 调用的提供商
    2. 调用特定 LLM 提供商的格式化的方法

3.2、Token 压缩策略

ContextManager 中的压缩机制是一个复杂的智能系统,远不止"判断是否需要压缩,需要的话就压缩"这么简单。它包括:

  1. 多种压缩策略:根据对话特征选择最适合的压缩方法
  2. 消息优先级系统:基于消息角色、内容、位置等因素分配优先级
  3. 压缩历史记录:保留最近10次压缩操作的详细记录,用于监控、分析和优化
  4. 压缩验证机制:确保压缩结果质量,不会移除关键信息
  5. 统计和监控:提供丰富的压缩统计信息,帮助了解系统性能

系统实现了三种压缩策略,每种策略适用于不同的场景:

3.1 中间移除策略 (MiddleRemovalStrategy)

  • 原理:保留对话的开始和结束部分,移除中间的消息
  • 适用场景:保持对话流程和最近上下文最重要的情况
  • 实现特点
    • 保留前 preserveStart 条和后 preserveEnd 条消息
    • 从中间部分开始移除消息,直到达到目标令牌数
    • 确保不删除系统消息和标记为保留的消息

3.2 最旧移除策略 (OldestRemovalStrategy)

  • 原理:优先移除最早的消息,保留较新的消息
  • 适用场景:长对话中最近上下文最重要的情况
  • 实现特点
    • 按时间戳排序,优先移除旧消息
    • 考虑消息优先级,优先移除低优先级消息
    • 确保保留最小消息数量

3.3 混合策略 (HybridStrategy)

  • 原理:智能结合中间移除和最旧移除策略
  • 适用场景:根据对话特征自动选择最佳策略
  • 实现特点
    • 分析对话特征(长度、消息分布、压缩严重程度)
    • 根据分析结果选择最适合的策略
    • 在不确定时尝试两种策略并选择效果更好的