☰
上下文工程实战:Agent 上下文管理与优化指南
2026/10/8 4:50:07 网站建设 项目流程

1. 上下文工程到底在解决什么问题

1.1 从一个真实翻车现场说起

去年帮一个团队调他们的客服 Agent,场景不复杂:用户问退货政策,Agent 查知识库,组织语言回复。Demo 阶段一切正常,上线第三天开始出问题。用户问“我上周买的鞋子能退吗”,Agent 回复了一段退货政策,但政策是三个月前下架的旧版。排查了半天,发现问题出在上下文组装环节——知识库检索返回了三条结果,两条新版一条旧版,拼接时旧版排在了最前面,模型优先采信了它。

这就是上下文工程的典型战场。模型本身没变,Prompt 模板没变,变的是喂给模型的那堆 token 里,什么内容排在什么位置、以什么形式呈现、带了多少噪声。

很多人把上下文工程等同于“写 Prompt”,这是最大的误解。Prompt 是你对模型说的话,上下文是模型在回答你之前看到的全部信息——系统指令、历史对话、检索文档、工具返回结果、few-shot 示例、格式约束,甚至包括那些你没注意到的分隔符和空行。上下文工程管的是这一整坨东西怎么组织、怎么裁剪、怎么排序、怎么在有限的窗口里塞进最有价值的信息。

1.2 为什么现在必须单独把它拎出来讲

三个变化凑到了一起。

第一,Agent 的上下文从“单轮”变成了“多轮 + 工具调用 + 检索”的复合结构。以前聊天机器人的上下文就是对话历史,现在一个 Agent 跑一轮,上下文里可能同时有系统提示、用户输入、检索到的五篇文档、两次工具调用的返回、上一次的反思记录。信息量翻了好几倍,但窗口没翻好几倍。

第二,模型对上下文的敏感度被反复验证。同一个问题,关键信息放在开头、中间、结尾,回答质量能差出一大截。业界常说的“lost in the middle”现象——模型对上下文中间部分的信息召回率明显低于首尾——这不是玄学,是大量实测得出的结论。

第三,成本。上下文越长,token 消耗越大,延迟越高,费用越贵。一个设计糟糕的 Agent,光是把无关的历史对话全量塞进去,一天就能烧掉设计良好版本的几倍成本。上下文工程不只是效果问题,也是钱的问题。

1.3 它和 Prompt 工程、RAG 的边界在哪

我用一个类比说清楚。把模型当成一个刚入职的聪明员工:

  • Prompt 工程是你教他怎么理解任务、用什么语气说话,相当于岗位培训。
  • RAG是给他配了一个资料库,让他遇到不懂的能去查,相当于给他开了数据库权限。
  • 上下文工程是决定每次他做决策前,你往他桌上放哪些材料、按什么顺序放、放多少页、哪些是重点标黄的。

三者有重叠,但上下文工程更偏“信息调度”这一层。RAG 负责“找得到”,上下文工程负责“放得对”。很多团队 RAG 做得不错,检索准确率挺高,但 Agent 效果还是差,问题往往就出在上下文组装这一步——检索回来的东西没被好好利用。

2. 上下文的组成结构与优先级设计

2.1 一个 Agent 的上下文里到底有什么

拆开看,一个典型 Agent 单轮推理的上下文通常包含这几块,我按重要性排个序:

层级内容类型作用是否可裁剪
L1系统指令 / 角色设定定义 Agent 身份、能力边界、输出格式不可裁剪,但可精简
L2当前任务与用户输入本轮要解决的核心问题不可裁剪
L3工具定义与调用规范告诉模型有哪些工具、怎么调可动态加载
L4检索/工具返回结果支撑回答的事实依据可裁剪、可重排
L5历史对话维持多轮连贯性可摘要、可丢弃
L6few-shot 示例引导输出风格与格式可裁剪
L7反思/中间推理记录多步任务的中间状态可压缩

这个排序不是随便定的。越靠上的层级,对模型行为的约束越强,越不能动;越靠下的层级,越是可以根据窗口余量灵活取舍。我见过不少项目把 few-shot 示例放在系统指令前面,结果模型把示例当成了任务本身,输出格式全乱。

2.2 优先级冲突时怎么取舍

实际跑起来,冲突几乎必然发生。用户输入很长、检索结果很多、历史对话又舍不得丢,窗口就那么大,怎么办?

我的处理原则是保 L1、L2,压缩 L5,精选 L4,动态加载 L3 和 L6。

具体操作上,历史对话不要全量保留,用滑动窗口 + 摘要的方式。比如保留最近 3 轮完整对话,更早的用一段 100 字以内的摘要代替。摘要不是简单截断,而是让模型自己总结“到目前为止用户的核心诉求和已达成的共识”。这一步多花一点 token,能省下后面大量的窗口空间。

检索结果这块,不要检索到几条就塞几条。我通常的做法是:检索返回 top-k(比如 8 条),然后用一个轻量的重排模型或规则打分,只把 top-3 塞进上下文,其余作为“备选”在需要时二次检索。上下文里塞 8 条半相关文档,效果往往不如塞 3 条高相关文档。

2.3 位置编排:首尾优先原则

前面提到“lost in the middle”,这个现象对上下文编排有直接的指导意义。

模型对上下文开头和结尾的信息注意力最集中,中间部分容易被忽略。所以关键信息要往两头放:

  • 开头放系统指令、角色设定、本轮任务的核心约束。
  • 结尾放当前用户的具体问题,或者最关键的检索结论。
  • 中间放支撑性材料、历史对话、示例。

一个常见的错误是把检索到的最重要文档放在中间,然后用户问题放在最前面。这样模型读完问题,翻过一堆材料,到结尾时已经“忘了”问题是什么。正确的做法是把用户问题在开头提一次(作为任务定义),在结尾再提一次(作为待回答项),中间夹材料。

提示:这个“首尾各放一次问题”的技巧,在长上下文场景下实测能明显提升回答的相关性,代价只是多几十个 token。

3. 上下文压缩与裁剪的实操方法

3.1 什么时候该压缩,什么时候不该

压缩不是无脑做。判断标准很简单:如果这段内容删掉后,模型回答的质量没有可感知的下降,那就该压缩或删除。

我一般分三种情况处理:

  • 冗余信息:直接删。比如工具返回的 JSON 里一堆模型用不上的字段,只保留关键字段。
  • 可摘要信息:用模型或规则压缩。比如长历史对话、长文档。
  • 不可压缩信息:保留原文。比如精确的数值、代码、法律条款,摘要会丢精度。

很多人一上来就搞“全量摘要”,把所有历史对话都摘要一遍,结果关键细节丢了,Agent 开始胡编。摘要适合处理“叙事性”内容,不适合处理“事实性”内容。

3.2 滑动窗口 + 摘要的具体实现

这是我用得最多的一套组合拳,代码逻辑大致如下:

def build_context(history, current_query, max_tokens=8000): # 保留最近 N 轮完整对话 recent = history[-3:] # 更早的对话做摘要 older = history[:-3] if older: summary = summarize(older) # 调用模型生成摘要 else: summary = "" # 组装 context = [] if summary: context.append({"role": "system", "content": f"历史对话摘要:{summary}"}) context.extend(recent) context.append({"role": "user", "content": current_query}) # 检查 token 数,超了就继续裁 while count_tokens(context) > max_tokens: if len(recent) > 1: recent.pop(0) # 从最旧的完整对话开始丢 else: break return context

这里有个细节:摘要的生成时机。不要每轮都重新摘要全部历史,那样成本高且不稳定。我的做法是维护一个“摘要缓冲区”,每积累 5 轮对话触发一次增量摘要,把新内容合并进已有摘要。这样既控制了成本,又保证了摘要的连贯性。

3.3 检索结果的裁剪策略

检索结果进上下文前,我通常过三道关:

第一道,相关性阈值过滤。检索返回的相似度分数低于某个阈值的直接丢。阈值设多少要看具体检索模型,我一般从 0.7 开始试,根据实际效果调。

第二道,去重与合并。同一个文档被切成多个 chunk 检索回来,内容高度重叠的合并成一条,避免重复占用窗口。

第三道,按需截断。单条文档太长时,不是简单截前 N 个字符,而是保留包含查询关键词的段落及其上下文。这个可以用简单的关键词定位实现,也可以用模型做段落级摘要。

注意:裁剪检索结果时,一定要保留来源标识(文档名、段落号)。Agent 在回答时如果能引用来源,可信度会高很多,也方便后续排查。

4. 工具调用场景下的上下文管理

4.1 工具返回结果怎么进上下文

工具调用是 Agent 区别于普通聊天机器人的核心,也是上下文管理最容易出问题的地方。

工具返回的结果,格式五花八门。有的返回一大坨 JSON,有的返回自然语言,有的返回表格。直接原样塞进上下文,轻则浪费 token,重则干扰模型判断。

我的处理原则是结构化提取 + 自然语言包装。举个例子,一个查询天气的工具返回:

{"code": 200, "data": {"city": "北京", "temp": 25, "humidity": 60, "wind": "3级", "forecast": [...]}}

不要把这个 JSON 直接塞进去,而是转成:

工具调用结果(天气查询): - 城市:北京 - 当前温度:25摄氏度 - 湿度:60% - 风力:3级

这样模型读起来更顺,也更容易在后续推理中引用。转换这一步可以用规则做,也可以用一个小模型做,成本很低但收益明显。

4.2 多轮工具调用的上下文累积问题

一个复杂任务,Agent 可能连续调用五六个工具。每轮调用的结果都往上下文里塞,很快就爆了。

我的做法是工具结果分级保留:

  • 最近一次工具调用的完整结果:保留。
  • 中间工具调用的结果:只保留关键结论,比如“查询到用户ID为12345”。
  • 早期工具调用的结果:如果后续不再需要,直接丢弃;如果需要,压缩成一行摘要。

判断“后续是否还需要”,可以在系统指令里让模型自己标注哪些结果是“已消费”的。比如让模型在每次工具调用后输出一个consumed: true/false标记,标记为 true 的结果在下一轮就可以压缩。

4.3 工具定义本身的上下文开销

很多人忽略了一点:工具定义本身也占上下文。一个 Agent 挂 20 个工具,每个工具的定义(名称、描述、参数 schema)加起来可能就两三千 token。这些 token 每轮都要传,成本不低。

优化手段有两个:

一是动态加载工具。根据当前任务类型,只加载相关的工具定义。比如用户问的是订单问题,就只加载订单相关的 5 个工具,其余 15 个不加载。这个可以用简单的意图分类实现。

二是精简工具描述。工具描述不是越详细越好,把模型真正需要知道的写清楚就行。参数说明能省则省,默认值不用写,模型不关心的字段不用列。

5. 上下文工程的评估与迭代

5.1 怎么判断上下文设计得好不好

不能只看最终回答对不对,要拆开看。我通常从四个维度评估:

维度评估方法合格标准
信息完整性检查关键信息是否都在上下文里回答所需事实无遗漏
信息密度计算有效 token 占比有效信息占比 > 60%
位置合理性关键信息是否在首尾核心约束在开头,问题在结尾
噪声水平统计无关内容占比无关内容占比 < 20%

“有效 token”怎么定义?我的土办法是:把上下文里的内容逐段删掉,看回答质量是否下降。下降的就是有效信息,不下降的就是噪声。这个方法费时但准,适合在调优阶段做几轮。

5.2 一个可复用的评估流程

我一般按这个流程走:

  1. 构造测试集:收集 20-50 个真实场景的输入,覆盖简单问答、多轮对话、工具调用、长文档检索等类型。
  2. 跑基线:用当前上下文策略跑一遍,记录每个 case 的回答质量和 token 消耗。
  3. 逐项改动:每次只改一个变量(比如调整检索结果数量、改变历史对话保留轮数),重跑测试集。
  4. 对比分析:看改动后哪些 case 变好、哪些变差,找出规律。
  5. 固化策略:把验证有效的改动合并进主流程。

这个流程的关键是一次只改一个变量。我见过团队同时改五六个参数,结果效果变好了也不知道是哪个起的作用,变差了也找不到原因。

5.3 常见的效果退化模式

上下文工程做久了,会发现一些反复出现的退化模式:

模式一:上下文膨胀导致注意力稀释。表现是回答越来越泛、越来越不聚焦。原因是上下文里塞了太多东西,模型抓不住重点。解法是狠心裁剪,宁可少而精。

模式二:历史对话污染。表现是 Agent 把之前轮次的错误结论当成事实继续用。原因是历史对话里包含了未修正的错误信息。解法是在历史摘要里显式标注“以下结论已被修正”。

模式三:工具结果格式不一致。表现是模型对工具返回的理解时好时坏。原因是不同工具返回格式差异大,模型需要额外精力去解析。解法是统一工具返回的包装格式。

模式四:系统指令被淹没。表现是 Agent 不遵守输出格式要求。原因是系统指令太长或位置太靠前,被后续内容冲淡。解法是精简系统指令,并在结尾处重复关键约束。

6. 我踩过的坑和几条硬经验

6.1 不要迷信“越长越好”

刚做 Agent 那会儿,我的直觉是上下文给得越全,模型回答越准。实测下来完全不是。有一次做合同审查 Agent,把整份合同全文塞进去,模型反而抓不住关键条款,回答泛泛而谈。后来改成先检索相关条款,只把 3-5 条最相关的塞进去,准确率反而上去了。

上下文的价值不在于“全”,而在于“准”。给模型一堆它不需要的信息,等于给它增加了解析负担。

6.2 分隔符和格式标记比想象中重要

上下文里不同来源的内容,一定要用清晰的分隔符隔开。我习惯用这种格式:

=== 系统指令 === ... === 检索文档 1(来源:xxx)=== ... === 用户问题 === ...

别小看这些标记,它们帮模型快速定位“哪段是哪段”。我做过对比测试,加了明确分隔符的版本,在长上下文场景下回答准确率能高出 10 个百分点以上。

6.3 动态调整比固定配置更靠谱

没有一套上下文配置能适配所有场景。简单问答不需要检索,复杂任务需要多轮工具调用,长文档分析需要大窗口。我的做法是根据任务类型动态切换上下文策略:

  • 简单问答:系统指令 + 用户问题,不检索,不加载历史。
  • 多轮对话:系统指令 + 历史摘要 + 最近对话 + 用户问题。
  • 工具调用任务:系统指令 + 工具定义 + 工具结果 + 用户问题。
  • 文档分析:系统指令 + 检索结果 + 用户问题 + 格式约束。

这个切换逻辑用一个轻量的意图分类器就能实现,成本很低,但效果提升明显。

6.4 留出“思考空间”

上下文不要塞满。我一般会预留 20% 的窗口空间给模型的推理过程。如果上下文占满了窗口,模型没有空间做中间推理,回答质量会明显下降。这个预留比例可以根据任务复杂度调整,复杂任务留 30%,简单任务留 10% 就够。

6.5 监控上下文长度分布

上线后一定要监控上下文长度的分布。如果发现 P95 长度接近窗口上限,说明上下文管理有问题,迟早会出截断事故。我一般设两个告警:P95 超过窗口 80% 告警,出现截断告警。

这套东西说起来都是细节,但正是这些细节决定了 Agent 是“能用”还是“好用”。上下文工程没有银弹,靠的是一轮轮实测、一点点抠。我自己的体会是,把上下文管理做扎实,比换一个更强的模型带来的提升更明显,而且成本更低。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询