从零开始搭过几个 Agent 之后,我才意识到上下文工程才是决定 Agent 智商上限的关键。
标题里写了“深度解析”,但其实我更想聊的是实操。很多朋友上来就关心模型选型、Agent 框架、工具调用,结果调来调去,Agent 该犯傻还是犯傻。后来我复盘了一下,问题几乎都出在同一个地方:上下文没管好。
上下文工程不是什么新词,但在 AI Agent 这个体系里,它比单纯写 Prompt 要复杂得多。因为 Agent 是多轮交互、多工具协同、多任务并行的系统,它的上下文是动态的、会被污染的、会超出窗口的。这篇文章不搞理论堆砌,我直接把实际踩过的坑、用过的方案、调过的参数都摊开讲,希望帮你少走几周弯路。
1. 上下文工程到底是什么
1.1 先搞清楚 Agent 的工作方式
要理解上下文工程,得先清楚 Agent 到底怎么干活。你扔给它一个任务,它不会像普通聊天那样一句话回答完事,而是会拆解目标、调用工具、观察结果、调整策略,可能来来回回好几轮。
举个例子,你让 Agent 帮你“整理一份竞品分析报告”。它先要搜索资料,然后读取网页内容,再做对比表格,最后生成 Markdown 文件。每一步之间都需要依赖一个东西:对话历史。前一步的搜索结果、读取的数据、中间结论,都得放进后续的上下文里,Agent 才能继续判断“下一步该干什么”。
这个对话历史,就是上下文的物理载体。上下文工程,简单说就是怎么组织、筛选、压缩、编排这些历史信息,让模型在每一步都能拿到最需要的、且不超出窗口限制的上下文。
我刚上手时犯过一个很典型的错误:把所有中间结果一股脑全塞进上下文。结果就是上下文窗口很快被撑爆,Agent 开始丢前面的关键信息,甚至把上一次搜索的旧数据当成当前结果来用。那时候我还没意识到,这不是模型不行,是我没做上下文管理。
1.2 它和 Prompt Engineering 的区别
很多教程喜欢把上下文工程和 Prompt Engineering 混在一起讲,这其实会误导人。
Prompt Engineering 关注的是“你给模型的那段初始指令怎么写”,比如角色设定、任务目标、少样本示例,这些是静态的、一次性的。而上下文工程关注的是“整个任务生命周期中,每轮该喂什么模型”,这包括用户输入、模型输出、工具结果、记忆摘要、检索片段,这些是动态的、不断演化的。
用个生活化的类比:Prompt 是给新员工的第一天培训手册,上下文工程是之后每一天的工作日报、会议记录、项目文档管理。手册写得再好,如果每天的信息流乱七八糟,员工照样干不好活。
在 Agent 场景下,上下文工程的重要性远超 Prompt Engineering。因为 Agent 根本不是一个静态任务,它是动态流程。你甚至可以把上下文工程理解成给 Agent 做记忆管理——哪些该记住,哪些该忘记,哪些该重点强调,都是有讲究的。
1.3 上下文工程的四个核心维度
我把实际工作中涉及的上下文工程拆成四个维度,后面每一章都会围绕它们展开:
- 上下文构建:初始系统提示词怎么写,用户需求怎么结构化,工具定义如何注入;
- 上下文管理:多轮对话如何截断、摘要、压缩,不让历史无限膨胀;
- 上下文检索:需要外部知识时,如何把最相关的片段找出来放进上下文;
- 上下文编排:多个工具、多个任务并行时,如何组织每轮实际发送的上下文内容。
这四个维度不是孤立的,它们共同决定了一个 Agent 的有效上下文利用率。我见过不少项目,模型用的顶级款,工具也齐全,但 Agent 表现就是不如预期,原因就是这四个维度里至少有一个是瘸腿的。
2. 上下文构建:第一步就决定成败
2.1 系统提示词的结构化设计
很多人写系统提示词就是一段大作文,从“你是一个 AI 助手”写到“请务必注意”,中间夹杂各种要求。我建议你改用结构化写法,把系统提示词当成一个配置文档来写。
我目前用得比较顺的模板大概长这样:
[角色定义] 你是XX领域的资深专家,擅长把复杂问题拆解为可执行步骤。 [任务目标] - 接收用户的原始需求 - 判断是否需要调用工具 - 输出结构化结果 [工具使用规则] - 只有需要实时数据时才调用搜索工具 - 工具返回结果必须先总结再使用 - 若工具调用失败,如实告知用户,不编造结果 [输出格式] - 结论先行 - 使用列表和表格组织复杂信息 - 每个结论附带简要依据 [限制条件] - 不要重复用户指令 - 不要输出与任务无关的内容 - 遇到模糊需求时,先提问澄清再执行这么写的好处有三个:一是模型更容易“理解自己的职责边界”,不会做着做着跑偏去闲聊;二是后续想调整某一部分,不用重写整段,改一个区块就行;三是方便程序化拼接,比如动态注入用户名、时间、场景标识等信息。
2.2 用户需求的结构化预处理
用户输入往往是含糊的。比如“帮我看看最近的 AI Agent 趋势”,这句话扔给 Agent,它能搜出来一堆东西,但大概率不是你想要的——你想要的可能是技术趋势、框架热度、应用案例,甚至某些特定平台的数据趋势。
所以我在实际项目中会在用户输入进入上下文之前,先做一次意图识别与需求补全。这一步可以交给模型,也可以靠规则模板:
原始需求: {用户输入} 请将上述需求解析为结构化任务: 1. 任务目标:一句话说明用户想得到什么 2. 关键约束:有哪些限定条件(时间、地域、领域、数量等) 3. 需要的信息源:是否需要搜索、读取URL、查询数据库等 4. 输出要求:用户期望的呈现方式(报告、表格、代码、摘要等)这么做过之后,Agent 后续的每一步都有了明确的依据。相当于先把用户的模糊指令,翻译成内部可理解的任务清单,再塞进上下文。这一步看起来多消耗了一些 token,但对后续避免瞎跑非常有帮助。
2.3 工具定义要写清楚“触发条件”
Agent 的核心能力是调用工具,但模型什么时候该调用工具,完全取决于工具的描述信息。我在最初搭建 Agent 时,工具描述写得极其简单,比如“搜索工具,输入关键词返回搜索结果”,结果模型把所有问题都交给搜索工具,连数学计算都去搜。
后来我把工具描述改成了带触发条件的版本:
工具名称: web_search 功能: 搜索引擎查询 适用场景: - 用户需要实时信息、最新新闻、特定数据 - 任务涉及的事实需要外部验证 - 当前知识库中无明确答案 不适用场景: - 纯数学计算 - 代码调试(应使用代码执行工具) - 用户只是寻求建议而非事实查询这个改变非常明显。工具调用的准确率提升了,Agent 不再为了调用而调用。工具描述本质上也是上下文的一部分,而且是指导模型行为的关键上下文,值得多花一点功夫写清楚。
2.4 动态上下文注入
除了固定的系统提示词,很多场景需要动态注入一些信息。比如用户的地理位置、当前时间、历史偏好、会话目标。这些信息如果放在系统提示词里写死,那每个会话都得重新整一个提示词,成本高还不灵活。
我的做法是把动态信息放在系统提示词后面的一个独立区块,每次会话开始前用代码拼进去:
[会话动态信息] - 当前时间: 2025-XX-XX XX:XX - 用户偏好: 喜欢简洁回答、重视数据来源 - 本会话目标: 完成竞品分析报告 - 已完成的步骤: 资料收集、竞品初筛这样模型在每个轮次都能知道“自己进行到哪一步了”,即使中间插入新问题,也不会完全丢失主任务。动态上下文注入是构建阶段最容易被忽视的一环,但却是后续管理的基础。
3. 上下文管理:窗口不够用,压缩来凑
3.1 Token 计算的现实问题
上下文窗口是有限的。GPT-4 级别的模型常见的有 32k 或 128k 窗口,听起来很大,但实际用起来消耗极快。
我拿一个真实的 Agent 任务来估算一下:
- 系统提示词:约 500 token
- 用户需求结构化解析:约 300 token
- 第一次搜索关键词及结果摘要:约 800 token
- 第一次工具读取页面正文:约 4000 token
- 中间推理过程:约 500 token
- 第二次工具调用及结果:约 1500 token
- ……
三轮工具下来,很可能就吃掉了 15k 以上的 token。如果是长任务,比如“写一篇一万字的行业分析”,光中间过程可能就干到 50k。窗口一满,模型就开始遗忘早期内容,表现为前后矛盾、参考漏掉、逻辑断裂。
所以上下文管理的第一课是:别把窗口当无限用,要把它当有限资源来计划。
3.2 截断策略:最基础的保底手段
最原始的上下文管理方法就是截断——只保留最近的 N 条消息,更早的直接丢掉。这种策略实现简单,适合任务短、上下文不太重要的场景。
但它的缺点也很明显:丢掉的内容里面可能包含任务的核心目标、用户关键约束、早期的重要数据。一旦被截断,Agent 就会“失忆”。
我建议的截断策略不是按消息条数截断,而是按层级截断:
- 第一优先级保留:系统提示词、用户最终目标、当前子任务指令;
- 第二优先级保留:最近两轮的工具结果和模型推理;
- 第三优先级保留:早期但可能仍需要的背景信息(可压缩后保留);
- 可丢弃:重复的中间输出、已总结过的原始内容、调试日志。
按这种优先级做截断,比单纯取最后 N 条消息要可靠得多。
3.3 摘要压缩:让历史信息保留“骨架”
截断是被动丢弃,摘要则是主动提炼。当上下文接近上限时,把早先的对话历史交给模型,让它生成一段精炼摘要,替代原文进入上下文。
我常用的摘要指令是这样的:
请将以下对话历史压缩为摘要,保留: 1. 用户的核心目标与所有明确约束 2. 已经完成的关键步骤和重要结论 3. 尚未完成的任务与下一步计划 4. 任何用户强调过的格式或风格要求 历史内容: {历史消息列表}这里有个细节很关键:摘要不是一次性做好的,而是要分级维护。我建议把历史分成几个块,每块单独摘要,再对摘要做一次更上层的摘要,形成树状结构。需要某段细节时,再回溯到下一层摘要甚至原始内容。这种分级摘要方案,比每次全量压缩要灵活得多。
从成本上看,每做一次摘要会多花一些 token,但对比整个任务跑到一半因为上下文溢出而失败,这点成本完全可以接受。
3.4 结构化记忆:短期、长期、工作记忆分层
我的 Agent 里会把记忆分成三层,分别管理:
- 工作记忆:当前任务进行中的中间状态,比如“正在分析 A 公司,已完成财务数据收集”,这部分直接放进上下文中,每轮更新;
- 短期记忆:当前会话中产生的重要结论、用户偏好、关键数据,用摘要或结构化字段维护,跨轮次注入;
- 长期记忆:跨会话持久化的用户画像、项目背景、历史偏好,存在数据库里,需要时检索注入。
这三层对应不同的存储方式与注入策略。工作记忆每次直接追加;短期记忆在每轮开始时做一次归纳;长期记忆通过语义检索按需取用。
我踩过最大的坑是:把所有记忆塞进 Prompt。结果 Prompt 越来越长,关键信息反而被淹没。后来改用分层存储,才意识到记忆的价值不在量,而在能被准确定位和及时取出。
3.5 上下文窗口溢出的兜底方案
即使做了压缩和摘要,极端情况下还是会遇到窗口溢出。这时我准备了兜底方案:
- 任务拆解:如果任务本身就超过窗口承载能力,先把主任务拆成多个子任务,每个子任务单独执行,最后汇总结果;
- 结果固化:先将已产出的中间结果写入外部存储(文件或数据库),释放窗口空间,再继续后续步骤;
- 重新摘要:对当前整个上下文做一次强制压缩,只保留目标、约束、最近结果和下一步计划。
这套兜底方案保证我即使遇到超长任务,Agent 也不会因为窗口溢出而“崩溃”,最多是处理速度慢一些。
4. 上下文检索:让外部知识精准进来
4.1 什么时候需要外部知识注入
不是所有任务都需要检索外部知识。我习惯把任务分为三类:
- 纯生成型:写文案、总结、翻译、头脑风暴,这类不需要外部知识;
- 内部知识型:涉及项目资料、历史文档、个人偏好,这类需要局部检索;
- 实时信息型:最新新闻、市场数据、技术动态,这类需要搜索或读取 URL。
上下文检索的核心难点不在于“找到相关信息”,而在于“在有限的上下文窗口里,只放最相关的信息”。把整篇文档塞进去,虽然信息完整,但 token 消耗巨大,而且会稀释重点。
RAG 的思路这里完全适用:先检索,再重排,最后只把 Top-K 片段拼入上下文。
4.2 检索片段如何选择和排序
我在 RAG 流程里常用的是三步走:
- 向量检索:把用户当前的问题向量化,在知识库中找相似度最高的片段;
- 重排(Rerank):用重排模型对候选片段按真实相关性重新打分,这一步往往比单纯向量相似度准得多;
- 动态选择:根据当前上下文剩余空间,决定最终放入几个片段。
具体放几个片段,取决于剩余窗口和问题复杂度。我一般控制在 3 到 8 个片段之间。宁可少放几个高质量片段,也不要塞一堆边缘相关内容。
检索片段在上下文中的位置也有讲究。我发现把最关键的一个片段放在用户输入之前,效果往往比堆在后面更好。因为模型对开头和结尾内容的注意力相对更强,中间内容容易被稀释。
4.3 工具返回结果的即时压缩
搜索工具、网页读取工具返回的内容通常又臭又长。如果直接把整篇网页正文塞进上下文,什么都干不了。我建议所有工具的返回结果,在进入上下文之前,先经过一次即时压缩。
我的做法是在工具调用后接一个轻量提取流程:
原始内容: {工具返回的原文} 请提取与本任务直接相关的信息: - 关键事实和数字 - 引用来源 - 与当前任务目标的关联点 - 需要排除的无关内容 输出为结构化摘要,控制在200字以内。这个流程会让每个工具调用增加一次模型调用成本,但它换来的是后续每轮都能轻装上阵。实测下来,整个任务的成功率和稳定性大幅提升。如果为了省这一步的 token,后面上下文爆炸、推理混乱,代价反而更高。
4.4 引用溯源:防止模型“张冠李戴”
工具结果被压缩之后,有一个风险:模型记不清某个数据是从哪来的,然后混在一起乱输出。我开发了一个简单但有效的方法:给每个工具结果加来源标签。
[资料来源: 搜索结果-条目3] 标题: {标题} 链接: {URL} 摘要: {提取的关键信息}就算后续做了摘要压缩,我也会在摘要里保留每个关键结论对应的来源标签。这样一来,模型在生成结果时如果引用数据,我能追踪到具体来源,用户也能看到参考链接。Agent 的可信度一下子就上来了,不像某些黑盒输出,用户根本不知道数据是哪来的。
5. 上下文编排:多轮多工具下的秩序维护
5.1 消息结构的清晰分层
Agent 每轮实际发送给模型的上下文,绝不是简单的消息列表拼接。我把上下文内容分成几层:
第1层: 系统提示词(固定角色、规则) 第2层: 本会话目标(用户最新需求+长期目标) 第3层: 动态上下文(时间、用户状态、任务进度) 第4层: 工作记忆(当前子任务、中间结论) 第5层: 检索信息(外部知识片段,按需加入) 第6层: 最近的对话轮次(用户问题、模型回答、工具结果)这个顺序不是随便排的,它遵循了“全局信息在前、局部信息在后”的原则。模型在读上下文时,前面内容是理解的基础,后面内容是处理当前轮的直接依据。
我在调试中发现的规律是:如果第5层检索信息和第6层近期对话顺序调换,模型偶尔会把检索到的旧信息当成对话历史里的内容,导致时间线混乱。分层清晰之后,这类问题几乎消失。
5.2 工具结果与模型推理的轮流插入
多工具并行的场景下,上下文会快速膨胀。我采取的策略是每个工具结果之后,都接一轮模型摘要或判断,而不是让多个工具结果连续堆砌。
举例说明:
用户: 请对比A和B两款AI框架的优缺点 步骤1: 模型判断 -> 需要分别搜索A和B 步骤2: 调用搜索A -> 返回结果 步骤3: 模型摘要A -> 提炼A的核心信息 步骤4: 调用搜索B -> 返回结果 步骤5: 模型摘要B -> 提炼B的核心信息 步骤6: 模型综合分析 -> 输出对比结论如果步骤2和步骤4的搜索结果直接连着塞,模型在做第6步时,很可能分不清哪些数据属于 A、哪些属于 B。加了“模型摘要”这一步之后,每个框架的信息已经结构化,后续对比自然不容易混淆。
5.3 并行任务时的上下文隔离
有一次我让 Agent 同时做三件事:查天气、写邮件、安排日程。结果发现它把查天气的结果当成了写邮件的内容,输出了一封莫名其妙的邮件。原因很简单:三个子任务的上下文混在了一起。
从此之后,我在并行任务设计时严格做了上下文隔离:
- 每个独立子任务维护独立的上下文片段;
- 共享的长期目标信息单独存放;
- 子任务完成后,只将结论和关键产物汇入总上下文。
这就像你同时开三个浏览器标签页,每个标签页有自己的历史和状态,互不干扰。Agent 的并行能力不是靠死记所有内容,而是靠合理隔离和汇总。
5.4 编排中使用的结构化输出协议
为了让模型在每轮输出中能“帮我”完成上下文管理,我给模型设定了一套结构化输出协议。在系统提示词中明确要求,每一轮模型输出的末尾必须包含一个“上下文状态小结”:
[上下文状态] - 当前任务进度: 60% - 本轮使用工具: web_search - 关键结论: A框架性能更优 - 待办事项: 完成时间线对比 - 下一步动作: 调用 code_executor 绘制图表这个状态小结会在下一轮被重新注入到工作记忆中。相当于让模型自己给自己留笔记,防止中间因为上下文压缩而失忆。实测下来,这个做法让 Agent 在长任务中的稳定性提升了非常多。
6. 常见问题排查:上下文工程踩坑实录
6.1 上下文溢出后模型“失忆”
现象:任务执行到一半,模型忘记了最开始的用户目标,开始自由发挥。
原因:历史内容过多,早期关键信息被挤出有效窗口。
排查思路:查看每一轮实际发送的上下文 token 数,找出溢出点。很多时候不是一下就溢出的,而是多次累积后悄悄越过阈值。
解决:提前做摘要压缩,而不是等到溢出再补救;把用户目标放进每一轮上下文的固定位置。
这个情况我遇到至少十几次。印象最深的一次是让 Agent 跑一个 12 步的数据分析流程,到第 9 步时它突然开始分析一个完全不相关的指标,我一看上下文记录,初始任务目标已经被挤到非常靠后的位置了。后来我把“用户目标”强制提到第2层,并且每轮都重新写入,问题就再没出现过。
6.2 工具结果混乱,模型张冠李戴
现象:模型把 A 工具的结果说成 B 工具的结果,或者引用错误来源。
原因:多个工具的结果没有做来源标识,模型在长上下文中丢失了来源信息。
排查思路:逐轮检查工具结果的格式,看看是否都有清晰的来源标签。
解决:工具结果统一带来源头,摘要阶段强制保留来源标签。
我特别想强调,标签要写清楚,但不要写得太复杂。以前我试过在每条工具结果前加超大段说明,结果模型把说明当正文了,反而忽略了真正的数据。现在统一用简短的[来源ID]开头,干净利落。
6.3 检索内容不相关,Agent 被带偏
现象:明明知识库里有答案,但 Agent 没找到,反而用了一堆无关信息作答。
原因:可能是检索的召回率不够,也可能是片段切分不合理,上下文窗口太小导致片段被截断。
排查思路:打印出每一轮注入的检索片段,人工检查是否相关。
解决:调整知识库的切分粒度,引入重排模型,控制注入片段数量。
检索不相关的问题,在前置的过滤机制上也要下功夫。我后来在知识库的每个片段前都人工添加了标签字段,比如“技术”“营销”“财务”,然后在检索时根据当前任务类型先做一次粗筛选,大幅提升了准确率。
6.4 token 消耗过快,成本飙升
现象:任务没跑几步,API 账单却涨得飞快。
原因:上下文没有压缩,工具结果全量进入,模型多次调用,每次都带着完整历史。
排查思路:统计每轮发送的 token 数,找出哪些轮次消耗异常。
解决:工具结果强制摘要、历史消息按需裁减、动态注入必要的动态信息。
token 消耗这件事,我在实际项目里总结了一个估算公式:单轮成本约等于固定上下文(系统+历史摘要)加本轮工具结果加模型输出。优化的重点永远是降低固定上下文的大小,而不是纠结于本轮输出的几个 token。
6.5 模型反复调用同一工具,死循环
现象:Agent 陷入无限循环,调用同一个工具好几遍,结果还是同一个。
原因:模型没有记录“已经调用过什么工具、拿到了什么结果”,所以重复执行。
排查思路:看上下文里是否有工具调用记录。
解决:在工作记忆中加入“工具调用历史”,每次调用后更新记录;如果重复调用,模型会发现自己已经拿到了该结果。
这个问题的根子在于上下文里缺少“状态意识”。我后来在编排层强制加了一个“步骤序号”字段,Agent 的每一步都有编号,模型在生成下一步时会先查看编号,有效避免重复动作。
7. 上下文工程的工具选型与落地经验
7.1 框架层面怎么选
目前主流的 Agent 框架都会内置一些上下文管理能力,但普遍做得比较简单,多数只是把历史消息传给模型,如果超了就截断。选框架时我会重点看几点:
- 是否支持自定义消息结构(能否在系统提示词之外动态插入内容);
- 是否有内置的摘要压缩策略;
- 工具结果是否可以在进入上下文前做钩子(Hook)处理;
- 是否支持记忆模块扩展。
对于轻量级项目,自己写上下文管理逻辑完全可控。项目规模大了之后,比如有多个知识库、多类工具、多用户场景,则建议基于主流框架做二次开发,而不是从零造轮子。
7.2 评测方法:怎么判断上下文工程改好了
上下文工程优化到底有没有效果,不能靠感觉,要开会话级的评测。我的做法是维护一组建好的评测用例,每个用例都包含多步任务、工具调用、长上下文场景。每次改动上下文策略后,都在这组用例上跑一遍,对比成功率、关键信息召回率、错误率。
评测指标方面建议关注:
- 任务完成率:最终有没有产出用户想要的结果;
- 关键信息保留率:早期信息在后期是否仍被引用;
- 工具调用正确率:该调的时候调,不该调的时候不乱调;
- 平均耗时与 token 成本:优化后成本有没有下降。
我一般在改完上下文工程配置后,会连续跑一周的线上日志,抽一部分会话做人工标注,重点看模型是否记住了该记住的信息,是否漏了该用的数据。
7.3 上下文工程的迭代节奏
最后想分享一个迭代节奏上的经验:不要追求一次性把上下文工程做到完美,先跑通,再优化。第一版直接用最基础的截断策略,能完成任务就算成功。第二版加上工具结果摘要和来源标签。第三版再做分层记忆和动态注入。每一步都有稳定的效果增量,而且出问题时也容易定位是哪个环节引入的。
我在实际工作中见过不少团队,一上来就整巨复杂的记忆系统,结果上下文结构臃肿,模型反而表现更差。上下文工程的目标是让模型在每一步都轻松拿到正确信息,而不是把系统做成信息堆积场。
7.4 一个重要原则:留白
上下文工程非常讲究“留白”。不要想着把每个知识碎片都塞进去,也不要为了用工具而用工具。上下文本身就是一笔预算,花在刀刃上才有价值。
我记得有次调一个客户项目,对方要求把几十页产品手册全部塞进上下文,让 Agent“无所不知”。结果 Agent 反而变得保守、回复冗长、抓不住重点。后来我只保留产品卖点、FAQ 和竞品对比三个核心片段,Agent 的应答质量瞬间回到正常水准。上下文宁可少而精,也不要多而杂。
最后说点实在的
上下文工程不是一条固定公式,它更像是一门和模型窗口、任务类型、工具设计紧密耦合的工程手艺。同一个方案,在对话型任务里很稳,在深度研究型任务里可能撑不住。我自己也在持续迭代,每次跑长任务或者碰到 Agent 行为异常,都会先从上下文入手排查:是不是该保留的丢了,是不是该压缩的没压,是不是检索进来的内容是噪音。
如果你现在正被 Agent 的“不稳定”“傻里傻气”“上下文超限”折磨,我建议你从今天开始,把上下文当成一等公民来对待。先给每轮实际发送的内容打日志,看看模型到底看到了什么;再逐步引入摘要压缩、来源标签、状态小结。这几步做完,你会明显感觉到 Agent “变聪明了”——其实不是模型变了,是你把它的工作台整理干净了。