| title | 上下文工程实战指南:让 Agent 少犯蠢的工程方法论 | |||||||
|---|---|---|---|---|---|---|---|---|
| description | 深入解析 Context Engineering 核心概念,涵盖静态规则编排、动态信息挂载、Token 预算降级、按需加载策略及长任务上下文持久化,帮助开发者构建高信噪比的 Agent 上下文供给系统。 | |||||||
| category | AI 应用开发 | |||||||
| head |
|
同样的模型,同样的 Agent 框架,为什么有的人跑起来很稳,你的一跑就开始迷路?
它会重复调用工具,查了一堆没用的信息,最后还输出一段看起来很像结论、实际根本跑不通的东西。
很多时候,问题不在模型,而是出在上下文。Agent 每次调用 LLM 前,窗口里到底塞了什么,塞得干不干净,顺序对不对,工具描述够不够清楚,都会直接影响最后表现。
这篇文章聊 Context Engineering。说白了,就是怎么给 Agent 准备一套高质量的上下文供给系统。
文章比较长,接近 6000 字。看完你大概能搞清楚几件事:
- 为什么上下文会决定 Agent 表现
- Context Engineering 和 Prompt Engineering 到底差在哪
- 静态规则、动态信息、Token 预算、按需加载怎么落地
- Compaction、结构化笔记、Sub-agent 怎么解决长任务上下文问题
先看一个很常见的电商售后场景。
用户发来一句话:
“我上周买的耳机右耳没声音了,怎么处理?”
如果 Agent 拿到的上下文很少,它大概率会这么回:
User: 我上周买的耳机右耳没声音了,怎么处理?
Model: 抱歉给您带来不便。请问您购买的是哪款耳机?订单号是多少?能否描述一下具体故障表现?
这段回复不能说错,但它像一个刚上岗的客服新人,只会照着流程追问。代码逻辑没问题,LLM 调用也没问题,就是没有主动整合信息。
换一个上下文充足的版本。
在调用 LLM 之前,系统先把该查的信息查出来:
- 查订单系统,定位到上周购买记录:索尼 WH-1000XM5,3 月 25 日下单
- 查保修状态,发现还在 7 天无理由退换期内
- 查历史工单,发现用户是老客户,之前没有售后纠纷
- 挂载
create_return_order和check_inventory工具
这时候 Agent 就可以这样回复:
“您好,查到您 3 月 25 日购买的索尼 WH-1000XM5,目前还在退换期内。我这边直接帮您发起换货申请,仓库显示同款有库存,预计 2-3 天寄出新品。需要我操作吗?”
差距一下就出来了:前一个 Agent 在要信息,后一个 Agent 在解决问题。
Agent 的很多失败,根子都在上下文。 上下文不够,模型再强也只能猜;上下文给对了,中等水平的模型也能把任务做下去。
Tobi Lutke 对 Context Engineering 有个说法:
the art of providing all the context for the task to be plausibly solvable by the LLM
意思是:给 LLM 提供足够上下文,让这个任务在模型能力范围内“有可能被解决”。
这里的关键词是 plausibly——它不是说上下文给够了模型就一定能解决,而是强调如果没有这些上下文,任务压根就不具备可解条件。
很多文章会把 Context Engineering 和 Prompt Engineering 混着讲,但这两个东西关注点不一样。
Prompt Engineering 更关心指令怎么写,比如措辞、顺序、格式、语气。
Context Engineering 关心的是这轮调用前,模型窗口里应该放哪些信息,以什么结构放,什么时候放,什么时候撤掉。
下面这张图来自 Anthropic 官方博客,对比很直观:
如果把 Prompt Engineering 比成“告诉厨师这道菜怎么做”,那 Context Engineering 更像是给厨师准备厨房:食材在哪、刀具在哪、调料怎么分类、火候参考在哪里。
我更喜欢另一个类比:Context Engineering 是 LLM 的内存管理。
LLM 的上下文窗口就是一块有限内存。Context Engineering 管的是这块内存里装什么、换出什么、什么时候读、什么时候写。
窗口满了,就要淘汰内容。这和操作系统里的页面置换有点像,比如 LRU、优先级策略。后面讲 Token 降级时,也是在处理这个问题。
拆开看,Context Engineering 至少管六块。
System Prompt
这是静态规则,比如 .cursorrules、.claude/rules、AGENTS.md 这类文件。里面一般会放角色设定、目标、约束、执行流、输出格式。
这些内容决定了 Agent 做任务时的基本边界。
User Prompt
用户输入的业务数据和指令。
这部分看起来简单,但真实项目里经常会混着自然语言、业务字段、历史状态、附件内容,处理不好就会污染上下文。
Memory
记忆系统分短期和长期。短期记忆一般是 Session 内的滑动窗口,长期记忆通常是核心事实提取后写入向量数据库,后续按需检索。
RAG & Tools
RAG 负责检索外部文档,把相关内容塞进上下文;Tools 负责把可调用工具的描述、参数格式、调用结果挂载进去。
RAG 可以看作 Context Engineering 的一种实现。它回答的是:检索什么、怎么检索、结果怎么放进上下文。
Structured Output
结构化输出也属于上下文的一部分,比如 JSON Schema、function call 的返回结构。
它会影响下游系统怎么解析,也会影响后续 Agent 链路怎么衔接。很多人写 Agent 时会忽略这块,最后解析阶段一堆脏活。
Token 优化
摘要压缩、历史剔除、Context Caching 都属于这里,目标很简单:保留信息完整度,同时控制 Token 消耗。
静态规则可以理解成 Agent 的“出厂设置”。
现在比较常见的做法,是用结构化 Markdown 写系统提示词。不要把所有东西揉成一大段,而是拆成角色、目标、约束、执行流、输出格式。
比如一个故障排查 Agent,可以这样写:
## 角色
你是一个后端服务故障排查专家,擅长通过日志和监控数据定位问题根因。
## 约束
- 只调用必要的工具,不重复调用相同逻辑的工具
- 发现关键信息时立即停止搜索,输出结论
- 优先使用实时数据而非历史推断
## 执行流
1. 查监控指标(CPU/内存/网络)
2. 查对应时间范围的日志
3. 如发现异常调用链,追踪上下游依赖
4. 输出结构化报告:问题描述 → 根因 → 建议修复方案
## 输出格式
使用 JSON,包含字段:incident_summary, root_cause, evidence, recommendation这些规则可以固化到 .cursorrules 或 AGENTS.md 文件里。
现在模型越来越强,对 Prompt 细节没以前那么敏感了。但结构化规则依然值得做。它的价值不只是提升模型表现,还方便团队维护。
一个团队里,如果每个人都靠口头经验写 Agent 规则,后面一定会乱。
上下文窗口不是垃圾桶,很多 Agent 失败不是信息不够,而是塞了太多无关信息。
动态挂载主要看两块:第一块是工具懒加载,也就是 Tool Retrieval。
当 Agent 面对大量 MCP 工具时,把所有工具描述一次性塞进去,既浪费 Token,也会增加误调用概率。
更合理的做法是:先通过向量检索找出当前任务最相关的 Top-5 工具定义,再挂载进去。
这和人查手册差不多。你不会把整本手册背下来,而是先翻到相关章节。
不过这里也有个现实限制。Anthropic 更强调在设计阶段就精简工具集,别把工具集合做得过度膨胀。工具太多,后面再做检索也只是补救。
第二块是动态记忆和 RAG。
短期记忆可以用滑动窗口管理,长期事实通过向量数据库检索。API 报错日志、工具返回结果这类 Observation,最好先让 LLM 做一次摘要,只把关键信息写回上下文。原始日志洪流直接塞进去,很容易把模型淹没。
长任务跑到后面,窗口一定会紧张,这时候不能靠感觉删内容,得有优先级。
| 优先级 | 内容 | 处理方式 |
|---|---|---|
| 低优先级(可折叠) | 早期对话历史 | AI 摘要压缩 |
| 中优先级(可精简) | RAG 检索的背景资料 | 二次裁剪,保留核心段落 |
| 高优先级(绝对保护) | System Constraints、当前核心工具描述 | 永不丢失,确保逻辑一致性 |
低优先级内容可以折叠,比如早期对话历史不一定要保留原文,压缩成摘要就行。中优先级内容可以精简,比如 RAG 检索出来的资料没必要整段保留,可以二次裁剪,只留和当前任务直接相关的片段。高优先级内容不能丢,System Constraints、当前核心工具描述、关键任务目标这些一旦丢了,Agent 很容易开始乱跑。
大规模并发场景里,还可以配合 Context Caching。相同的 System Prompt 不用每次重复加载,可以降低首 Token 延迟和推理成本。
很多人直觉上会觉得:窗口越大,塞的信息越多,模型应该表现越好。实际不是这样——上下文存在边际收益递减,塞过头之后效果还可能变差。
原因和 Attention 机制有关。Transformer 里,每个 Token 都要和上下文里的其他 Token 计算注意力关系。n 个 Token 会产生 n² 量级的注意力计算。
当上下文从 1K 扩展到 100K Token,问题不只是“信息被稀释”这么简单。
真正麻烦的是,模型要在更多 Token 之间判断哪些相关、哪些不相关。上下文越长,噪声越多,信号越难被挑出来。
这就是 Context Rot,也就是上下文腐化。
随着上下文 Token 总量增加,模型整体的信息回忆能力会下降。和它相关的,还有 Lost in the Middle 问题:模型对上下文中间位置的信息记忆更弱,对开头和结尾更敏感,整体呈 U 型分布。
这两个现象都说明一件事:上下文不是越长越好。还有一个训练层面的原因。
模型的 Attention 模式主要是在相对短的文本序列上学出来的。互联网文本的平均长度远低于现在一些模型支持的上下文窗口。
这意味着模型处理超长依赖时,学习经验本来就不足。位置编码外推能力也有限。虽然有 Position Encoding Interpolation,比如基于 RoPE 的 YaRN、NTK-aware Interpolation,用来缓解长序列外推问题,但精度损失不会完全消失。
工程上别迷信窗口大小,不同模型的衰减曲线不一样,有些退化平缓,有些退化很陡,具体阈值要靠实测。但有一点可以确定:上下文必须当作有限资源来管,真正要找的是高信噪比平衡点,而不是把窗口塞满。
System Prompt 常见两个问题。
第一个是过度设计。有些工程师会把大量 if-else 逻辑硬塞进 Prompt,试图精确控制 Agent 的每一步,结果是 Prompt 又长又脆弱,像纸片房——维护成本很高,遇到没见过的边缘情况,模型照样会跑偏。
第二个是过度抽象。只写一句“你要做一个有帮助的助手”,模型拿不到足够决策依据,要么不停追问用户,要么输出和业务预期偏得很远。
比较好的状态是:具体到能引导行为,抽象到能覆盖常见变化。
Anthropic 工程博客里提到过一个词,叫 Goldilocks zone,也就是刚刚好的区域。
实操上可以这么做:先用最小 Prompt 测基线表现,再根据 failure case 一条一条补规则,不要第一天就试图穷举所有情况。Anthropic 把这件事叫 Calibrating the system prompt——System Prompt 应该像一个持续调校的参数,不应该是一份写完就不再动的配置文档。每发现一个 failure case,就补一条清楚的规则,然后重新测试。
工具定义写得好不好,直接决定 Agent 会不会选错工具。
一个好的工具描述要回答两个问题:什么时候该调用?什么时候不该调用?如果一个工具描述连人类工程师都看不出该不该用,Agent 也一定会犯错。
最常见的坑,是做一个“大而全”的工具。比如 manage_database,里面同时包含建表、查数据、删数据、备份、导出五个能力。Agent 选择工具时会犹豫,填参数时也容易被一堆无关字段干扰。
很多人觉得工具描述越详细越好,其实重点不是面面俱到,而是边界清楚。做到两条就行:一个工具只做一件事,参数描述里给格式示例。这两条做到,误调用率通常会明显下降。
Few-shot prompting 很有用,但很多人用法不对。典型错误是往 Prompt 里塞几十个 edge case,试图覆盖所有规则,结果模型可能过度拟合示例表面的写法,反而忽略真正该学的处理逻辑。
更稳的做法是选 3-5 个多样化的典型示例,也就是 canonical examples。“Canonical” 的意思不是把所有边缘情况列全,而是每个示例能代表一类标准场景。对模型来说,示例像一张图——它展示的是“什么情况该用什么策略”,不是“这个输入必须对应这个输出”。
传统 AI 应用常用预检索,也就是在调用 LLM 之前,先通过 Embedding 相似度找出最相关的上下文,然后一次性塞进 Prompt。
简单问答场景里,这套机制还挺好用,但到了复杂 Agent 任务里,它会暴露问题。
预检索拿到的是“调用前看起来相关”的信息,但 Agent 执行过程中会不断发现新线索,而这些线索在预检索时根本还不存在。
Just-in-Time 的思路是:不要一开始就装载所有可能相关的信息。
Agent 运行时先维护轻量级引用,比如文件路径、数据库查询、Web 链接。真正需要时,再通过工具动态拉取数据。
Claude Code 就是很典型的例子。它分析大型代码库时,不会把所有文件都塞进上下文,而是先通过目录结构、文件名、搜索命令定位目标,再用 head、tail、grep 这类方式逐步读取。
Agent 像人一样靠文件名和目录结构理解信息位置,靠文件大小和时间戳判断优先级,而不是上来就把全部内容吞进去。
这里有个很容易被忽略的点:元数据本身也是信息。
tests/test_utils.py 和 src/core_logic/test_utils.py 语义就不一样。光看路径,Agent 就能判断它们大概率服务于不同目的。
Anthropic 把这种方式叫 Progressive Disclosure,也就是渐进式披露。
Agent 不是一次性拿到所有上下文,而是通过一轮轮探索逐渐理解任务。文件大小暗示复杂度,时间戳暗示相关性,目录结构传递语义。
但按需加载也有代价:它比预检索慢,而且需要工程师提供好用的导航工具,比如 glob、grep、tree。
如果导航工具不好用,或者导航启发式规则写得差,Agent 很容易追进死胡同,浪费上下文和调用次数。
所以 Just-in-Time 不是“不预处理”,恰恰相反,它对工具集和导航策略要求更高。
更现实的方案通常是混合策略:确定性高的静态知识可以预检索,运行中动态发现的信息再按需拉取。
Claude Code 也是这个思路:CLAUDE.md 文件可以预加载,但具体文件内容靠 Agent 运行时探索。
不同场景的选择也有规律。代码库分析、信息检索这种探索空间大、动态内容多的任务,更适合以 Just-in-Time 为主;法律文书审阅、财务报表分析这种上下文稳定、动态内容少的任务,更适合预检索加少量运行时补充。
Agent 如果要连续跑几个小时,处理很多轮迭代,只靠普通上下文管理是不够的,它需要跨窗口持久化。
Compaction 就是常见做法:当上下文快满时,把历史内容交给 LLM 总结,然后用摘要开启一个新的上下文窗口继续跑。
Claude Code 的思路是:把历史消息交给模型做摘要,保留架构决策、未解决 Bug、关键实现细节,丢掉冗余工具调用结果。然后 Agent 拿着压缩后的上下文,再加上最近访问的 5 个文件,继续工作。
难点在取舍:保留太多压缩没意义,保留太少关键上下文丢了。
比较实际的做法是:拿复杂 Agent 轨迹反复调压缩 Prompt。先保证重要信息别漏,再逐步删掉冗余内容。
这不是一次能写准的。还有一个更轻量的压缩方法:清理工具结果。
工具已经调用过,结果也被消化了,后面就没必要保留完整原始输出。Anthropic 的 Developer Platform 已经把这个做成了原生功能。
Structured Note-taking 是另一种长任务处理方式。让 Agent 把关键进展写到外部文件里,比如 NOTES.md。上下文重置后,再读取这些笔记继续工作。
这和人类工程师写 to-do list、技术备忘很像。Claude Code 在长任务里会自动维护 to-do list。自定义 Agent 也可以在项目根目录维护 NOTES.md,里面记录当前进度、已知问题、下一步计划。
一个很有意思的例子是 Claude 玩 Pokemon。在数千轮游戏步骤里,Agent 自己维护了数值追踪,比如“过去 1234 步我在 1 号道路训练皮卡丘,已升 8 级,距离目标还差 2 级”。
它还自发建立了地图、成就清单、战斗策略笔记。上下文重置后,这些笔记还能被重新读取,所以它才能跨几个小时持续推进游戏。
Anthropic 在 Sonnet 4.5 发布时,也推出了 Memory Tool 公开测试版,用文件系统持久化的方式让 Agent 建立跨会话知识库。
Sub-agent 架构的思路很直接:别让一个 Agent 扛完整项目状态。具体来说,就是把专门任务拆给专业化子 Agent,主 Agent 负责分配任务和汇总结果。
每个子 Agent 可以自己探索大量上下文,可能是几万个 Token。但返回给主 Agent 的,只是一段 1000-2000 Token 的高密度摘要。
这样主 Agent 的上下文会干净很多——详细搜索过程被隔离在子 Agent 里,主 Agent 只处理分析和决策。
Anthropic 在《How we built our multi-agent research system》里讲过这个模式。相比单 Agent,它在复杂研究任务上有明显质量提升。
三种方式可以这么选:
| 技术 | 适用场景 |
|---|---|
| Compaction | 需要持续对话的长流程,重点是保持上下文连贯 |
| Note-taking | 迭代式开发、有清晰里程碑、多步推进的任务 |
| Sub-agents | 复杂研究、需要并行探索、最终要汇总结果的任务 |
方法讲完,工程工具也顺一下。
- 编排框架:LangChain、LangGraph 这类框架,主要负责 Agent 的控制流、状态管理和循环调度
- 数据框架:LlamaIndex 更偏 RAG,负责数据摄取、索引构建和检索优化
- 向量数据库:Pinecone、Weaviate、Chroma、Qdrant 这类工具,负责 Embedding 存储和语义搜索
- 通信协议:MCP(Model Context Protocol)解决的是工具怎么标准化接入宿主程序的问题,经常被类比成 AI 应用里的 USB-C。Anthropic 发布的 MCP 基于 JSON-RPC 2.0,定义了 Tools(可执行函数)、Resources(只读数据)、Prompts(可复用模板)三类标准原语
- Memory 产品:Mem0、LETTA(原 MemGPT)、ZEP 这类产品,主要做 Agent 记忆层,通常在向量库之上封装记忆写入、检索、遗忘这些生命周期管理能力
Context Engineering 最重要的判断其实很简单:Agent 的大多数失败,不在模型智商,而在上下文精度。
过去大家更关心“这句 Prompt 怎么写”,现在更该关心的是:什么信息,以什么格式,在什么时机进入窗口。
模型能力还会继续变强,但注意力有限这个约束不会消失——窗口再大,塞一堆噪声进去,模型一样会变笨。
上下文是系统输出,不是静态配置。
每次 LLM 调用前,你都在组装一个动态上下文,这个组装逻辑本身就是工程重点。
改一个检索策略,换一种摘要方式,调整工具 Schema 的挂载顺序,效果差别可能比换模型还大。
高信噪比比高信息量更重要。
上下文长度不决定效果,Dex Horthy 的 40% 阈值实验也说明塞满窗口不如只放必要信息。真正要找的是让模型做出正确决策所需的最小高密度信息集。
长任务里,上下文一定会腐化。
Compaction、结构化笔记、Sub-agent 分层,要组合起来用,才能让上下文在长时间运行里不变质。
先从最简单的方案跑通。
Anthropic 反复强调过一句话:do the simplest thing that works。过度设计的上下文系统,和上下文不足一样危险。
Guide 见过不少团队,连基线都没跑通,就开始做记忆分层、复杂检索、长期状态管理,最后调试成本比收益还高。先跑通,再加复杂度。
上下文给对了,中等模型也能做出复杂任务。上下文给烂了,再贵的模型也会输出一坨看起来很像答案的噪声。


