上下文工程 Context Engineering
所属:01-fundamentals / 04-agent-architecture
面试权重:★★★
来源:通用领域知识(B站「AI Agent 八股」合集高频);未做一手考证
相关项目:Writing Copilot —— 检索内容的筛选链与压缩;Race Bot —— schema 召回与 few-shot 注入 → 见02-interview/projects/writing-copilot.md
一句话#
上下文工程是有意识地决定「每一轮往上下文窗口里放什么、放多少、按什么顺序放」——它是 prompt engineering 的超集:prompt 管的是「怎么措辞」,context 管的是「窗口里到底有什么」。
为什么重要(面试角度)#
这是 2025–2026 年 agent 领域最热的话题之一,而且它比大多数 topic 更能区分「做过 demo」和「做过生产」:
- 你会不会把上下文当预算来管理(有账、有配额、有取舍),而不是「能塞多少塞多少」
- 你能不能解释长上下文为什么退化(有名义窗口 ≠ 有有效窗口)
- 你知不知道指令放在哪里会影响结果(首尾效应、lost in the middle)
- 你会不会做压缩和摘要,以及知道压缩的代价必须可评测
- 你理解子 agent 的上下文隔离是上下文工程的一种手段(把污染性的工作挡在主上下文之外)
Writing Copilot 里的检索筛选链(粗筛 → 精排 → 压缩 → 注入)就是一次完整的上下文工程实践,这题必须结合它答。
核心内容#
上下文预算:把它当账来算#
一个 agent 每轮的上下文窗口被这些部分瓜分:
| 组成 | 性质 | 归属 |
|---|---|---|
| 系统提示 / 指令 | 常驻 | 固定成本,每轮都付费 |
| 工具定义(name + description + schema) | 常驻 | 随工具数线性增长,常被低估 |
| 记忆(用户画像、长期事实) | 常驻或按需 | 见 memory.md |
| 检索内容 | 按需 | 通常是最大且最不可控的一块 |
| 对话历史 | 累积 | 随时间线性增长 |
| 当前任务输入 | 按需 | — |
| 输出预留 | — | 必须留,否则会被截断 |
| 推理余量 | — | 上下文太满会挤压「思考空间」 |
核心动作是显式分配预算:例如给检索内容设上限(不超过窗口的 X%),超出就压缩或丢弃;给工具定义设上限,超了就做工具检索或拆子 agent。
最常被忽略的两项:一是工具定义,几十个工具就是每轮上万 token 的固定开销;二是输出预留,忘记预留会导致长回答被截断。
另一个工程约束:稳定的前缀有利于 prompt caching(缓存公共前缀的 KV,降低重复系统提示的成本与首 token 延迟)。这意味着上下文的顺序应该稳定 —— 把不变的东西放前面(系统提示、工具定义),变化的东西放后面。把动态内容插到前面会反复击穿缓存,成本会明显上升。
放进去 vs 去检索#
这是上下文工程最基本的决策:
| 放进去(常驻) | 去检索(按需) |
|---|---|
| 内容少、稳定、每轮都要用 | 内容多、稀疏相关、只有部分场景用 |
| 例:输出格式约束、核心业务规则、语气要求 | 例:知识库文档、历史工单、用户过往记录 |
| 成本可预测,收益是模型「一直记得」 | 成本随用量变化,风险是该召回的没召回 |
判据:如果某信息「不用会导致错误」,且它小而稳定 —— 放进去;如果它大、且只有一部分请求需要 —— 去检索。介于两者之间(中等大小、高频需要)的,可以放摘要版本常驻、细节按需检索。
反模式:把整个知识库塞进系统提示(成本爆炸、注意力稀释);或者反过来,把核心格式约束也做成检索项(该命中时没命中,模型就自由发挥了)。
指令位置与「lost in the middle」#
模型对上下文的注意力不是均匀的:
- 首尾效应:开头和结尾的信息更容易被利用,中间部分容易被忽略。已有研究(常被引用的是 "Lost in the Middle" 这一类发现)观察到:把关键信息放在长上下文中间时,模型检索到并使用它的比例明显低于放在开头或结尾。
- 实践含义:
- 关键约束放开头(系统提示),并在任务末尾复述一次
- 长文档做检索/问答时,把最相关的片段放在靠近问题的位置,而不是按原始文档顺序堆
- 不要指望「提醒一句很重要」能起作用 —— 位置比措辞更重要
注意这条结论的边界:具体退化幅度依模型、长度与任务类型而变,且新模型不断在改善长上下文能力。所以面试时应该说「这是一个被反复观察到的现象,方向和机制清楚,具体幅度依模型而定」,而不是当成固定规律引用。
长上下文的退化#
名义窗口 ≠ 有效窗口。即使模型声称支持很长的窗口:
- 注意力稀释:内容越多,单个信息分到的注意力越少,关键细节被淹没
- 指令遗忘:早期设定的约束在后文被稀释,模型开始「不守规矩」
- 干扰:不相关内容会主动误导模型(尤其是检索回来的看似相关但实际无关的片段)
- 性能与成本:上下文越长,首 token 延迟越高、显存占用越大(KV cache 线性增长,见
01-llm-core/kv-cache.md)
业内有「context rot」这类说法描述「上下文越长、模型对其中信息的利用越不可靠」的现象 —— 它是一个经验性、被广泛观察到的工程共识,而非严格的定量定律。衰减发生的具体长度与阈值依模型和任务而变,不要抛具体数字。
结论:上下文工程的目标不是「塞得下」,而是「塞得准」。 更少但更相关的上下文,通常优于更多但杂乱的上下文。
压缩与摘要#
当内容超出预算时:
| 手段 | 做法 | 代价 |
|---|---|---|
| 截断 | 丢弃最旧的历史 / 只保留最近 N 轮 | 简单粗暴,早期约束彻底丢失 |
| 滑动窗口 | 保留最近 N 轮 + 常驻系统提示 | 同上,但对闲聊类任务够用 |
| 滚动摘要 | 定期把历史压缩成一段摘要,替换掉原始轮次 | 有信息损失,且摘要会累积误差 |
| 结构化摘要 | 摘要时保留决策、约束、未完成事项,丢弃过程性内容 | 效果好,但需要设计摘要的 schema |
| 工具输出裁剪 | 在工具层截断/分页/只取字段 | 见 tool-use.md,这是性价比最高的一环 |
| 检索式历史 | 不常驻历史,按需检索相关轮次 | 多一次检索开销,且可能漏 |
三个必须说清的点:
- 摘要是有损的,而且损失不可逆。原文一旦被替换成摘要,信息就再也回不来了。
- 摘要本身会引入错误。用一个模型去压缩,压缩过程可能把「不确定」压成「确定」,把细节压错。
- 压缩策略必须可评测。要能验证「压缩之后,回答所需的信息是否还在上下文里」—— Writing Copilot 的做法就是先粗排砍掉大量低分块,再对保留的块做细粒度抽取,并配专门的评测验证压缩后答案质量没有退化。不能凭感觉调压缩率。
子 agent 的上下文隔离#
把「上下文污染性」最强的工作放到子 agent 里做,只把结论带回主上下文:
主 agent 的上下文:任务 + 计划 + 各子任务的结论(干净、可控)
↑ 只回传结论
子 agent 的上下文:检索原始内容 / 试错过程 / 大量工具输出(脏、但被隔离)
↓ 用完全部丢弃这是多智能体的核心收益(见 multi-agent.md),也是上下文工程最有力的手段之一 —— 它把「如何压缩」变成了「根本不让它进来」。代价是信息损失不可控:子 agent 决定回传什么,就等于替你做了压缩决策,所以回传内容要有明确 schema(结论 + 依据 + 置信度)。
上下文的评测#
上下文策略是必须 A/B 评测的工程参数,不能靠直觉:
- 压缩率调高调低,端到端质量怎么变?(画一条权衡曲线)
- 关键信息是否还在注入的上下文里?(对「答案所需信息的存在性」做专门验证)
- 不同上下文组织方式(顺序、位置)对同一批问题的效果差异
- 成本与延迟的联动变化(token 数、TTFT)
面试里这一点很加分:它说明你把上下文当成可度量的工程变量,而不是「prompt 玄学」。
常见面试问法#
Q:什么是上下文工程?和 prompt engineering 什么关系? A:prompt engineering 关注措辞与指令写法;上下文工程关注窗口里放什么、放多少、什么顺序 —— 检索内容、工具定义、记忆、历史、压缩策略、子 agent 隔离都算。它是 prompt engineering 的超集:措辞再好,如果关键信息没进上下文,模型也无从作答。
Q:长上下文模型是不是就不用做上下文工程了? A:不是。名义窗口不等于有效窗口:内容越多,注意力越稀释,关键信息越容易被淹没,早期指令容易被遗忘,且不相关内容会主动干扰。同时长上下文带来延迟与成本上升(KV cache 随长度线性增长)。所以窗口变大降低的是「放不下」的焦虑,不解决「放得准」的问题。目标是更少但更相关。
Q:指令应该放在哪里? A:放在开头(系统提示)并在任务末尾复述一次 —— 这是利用首尾效应。长上下文中间的信息更容易被忽略,这是被反复观察到的现象("lost in the middle" 一类发现),所以关键约束不要埋在中间。注意具体退化幅度依模型与任务而变,不是一个固定阈值。
Q:上下文满了怎么办? A:分层处理:先把工具输出裁剪或分页(性价比最高);再对历史做滚动摘要,摘要时保留决策、约束与未完成事项,丢弃过程性内容(结构化摘要优于自由摘要);再考虑把污染性强的工作放到子 agent 里做隔离,只回传结论。三条原则:摘要是有损且不可逆的、摘要本身会引入错误、以及压缩策略必须配评测 —— 不能凭感觉调压缩率。
Q:你在 Writing Copilot 里怎么做上下文控制? A:一条筛选链:爬取页面 → 分块 → 相关性打分 → 重排序 → 细粒度内容筛选与压缩 → 才注入 LLM。核心是先粗筛再精排再压缩,逐级降低候选量,最后只把高质量片段喂给模型 —— 推理成本、延迟、噪声同时下降。关键点是压缩必须可评测,否则调压缩率时没有依据。
易错点#
- ❌ 把上下文工程等同于「prompt 写得好一点」。
- ❌ 认为窗口大就不需要管理上下文 ——「塞得下」不等于「用得上」。
- ❌ 忘记工具定义是常驻成本,几十个工具就是每轮上万 token。
- ❌ 忘记给输出预留空间,导致回答被截断。
- ❌ 把动态内容放在上下文最前面,破坏 prompt caching 的稳定前缀。
- ❌ 认为摘要无损,或者不评测就调压缩率。
- ❌ 抛「第 N 个 token 之后开始遗忘」这类精确阈值 —— 现象方向明确,幅度依模型而变。
- ❌ 把关键约束埋在长上下文的中间位置,然后抱怨模型「不听指令」。
自测#
1. 「名义上下文窗口」和「有效上下文窗口」有什么区别?为什么这个区别重要?
答案
名义窗口是模型技术上能接受的最大 token 数;有效窗口是模型能可靠利用的信息量。两者不等:随着上下文变长,注意力被稀释,关键信息更容易被淹没,早期设定的指令容易被忽略,不相关内容会主动干扰,模型对中间位置信息的利用率明显低于首尾("lost in the middle" 一类被反复观察到的现象)。重要性在于:它意味着「放得下」不等于「用得上」,所以上下文工程的目标是更少但更相关,窗口变大并没有取消管理的必要性。同时长上下文还会带来延迟与成本上升(KV cache 随长度线性增长)。注意具体退化幅度依模型和任务而变,不宜当作固定阈值使用。
2. 一个信息,你怎么决定是放进系统提示(常驻)还是做成检索项(按需)?
答案
看两个维度:规模与必要性。如果信息小巧稳定、且每轮都需要(例如输出格式约束、核心业务规则、语气要求),放进去 —— 成本可预测,且保证模型一直记得;如果信息量大且只有部分请求需要(知识库文档、历史工单、用户过往记录),去检索 —— 否则成本爆炸且注意力被稀释。中间地带(中等大小、高频需要)的常见做法是:常驻一个摘要版本,细节按需检索。反模式有两个:把整个知识库塞进系统提示,或者把核心格式约束也做成检索项(该命中时没命中,模型就自由发挥了)。
3. 上下文超预算了,你会按什么顺序做压缩?每一步的代价是什么?
答案
顺序建议:① 裁剪工具输出(在工具层截断、分页、只保留需要的字段)—— 代价最低,因为工具返回里通常大量是噪声;② 滚动摘要对话历史 —— 保留决策、约束、未完成事项,丢弃过程性内容;代价是有损且不可逆,摘要本身还可能引入错误;③ 子 agent 隔离 —— 把检索、试错这类污染性工作放到子 agent,只把结论带回主上下文;代价是子 agent 替你做了压缩决策,回传内容要有 schema 才可控;④ 最后才是截断/滑动窗口 —— 简单但会彻底丢失早期信息。无论哪一步,压缩策略都必须配评测(验证「回答所需信息是否仍在上下文里」),不能凭感觉调压缩率。
4. 为什么说「子 agent 上下文隔离」是上下文工程的手段?它的代价是什么?
答案
因为它把「如何压缩」这个问题变成了「根本不让它进来」:让子 agent 在它自己的上下文里消耗大量检索原文与试错过程,用完之后整体丢弃,只把提炼后的结论回传主上下文。主上下文因此始终干净,指令不被稀释,工具集也被隔离。代价有两层:一是信息损失不可控 —— 回传什么由子 agent 决定,等于它替你做了压缩决策,它丢掉的可能正是下游需要的;二是子 agent 自己也有成本(重复的系统提示与工具定义)。缓解办法是让回传走结构化 schema(结论 + 依据 + 置信度 + 缺失项),并在关键节点做校验。
5. 为什么上下文的「顺序」在工程上很重要?至少给两个理由。
答案
① 注意力分布不均:开头和结尾的信息更容易被模型利用,中间部分容易被忽略,所以关键约束应放在系统提示并在任务末尾复述,检索到的最相关片段应靠近问题而不是按原文档顺序堆放。② prompt caching:缓存以公共前缀为单位,把稳定内容(系统提示、工具定义)放前面、动态内容放后面,才能让缓存命中;把动态内容插到前面会反复击穿缓存,成本和首 token 延迟都会明显上升。此外稳定的顺序也让调试更容易 —— 上下文布局变化本身就是一个需要控制的变量。