从 Prompt Engineering 到 Context Engineering 的范式跃迁
一、为什么上下文管理是 Agent 构建的"卡脖子"问题
2023 年人人都在谈 Prompt Engineering,2024 年大家开始关注 Agent 框架,而到了 2026 年,所有深度落地 Agent 的团队都会告诉你同一件事:上下文管理才是真正的分水岭。
如果你的 Agent 只是一个跑几次 Demo 的原型,你大概率感受不到这个问题。但当你的 Agent 在生产环境运行:
用户聊了 2 小时,上下文膨胀到 40K tokens,成本是第一条消息的 100 倍
响应质量在长对话中持续下降,模型开始"胡言乱语"
明明有正确的工具和知识,Agent 就是选不对、用不对
每次迭代都要改 System Prompt,改完这处坏那处
这些问题,根源都在一个地方:上下文没管好。
1.1 从 Prompt Engineering 到 Context Engineering
两者有本质区别:
Andrej Karpathy 在 2025 年有过一个非常精辟的比喻:LLM 是 CPU,上下文窗口是 RAM,而你的工作就是做操作系统——在每次调用时为当前任务加载正确的代码和数据。
1.2 上下文窗口不是"记忆"
很多人会把大模型上下文窗口理解成"模型记住的东西",这个说法不准确。
上下文窗口更像是模型在当前推理时能看到的工作台。放在工作台上的,模型可以参考;没放进去的,模型就不知道。窗口再大,也不是无限记忆。
更重要的是,更大的窗口不等于更好的效果:
Context Rot(上下文腐烂):Chroma 2025 年的报告测试了 18 个 LLM,结论是随着输入长度增长,模型行为的可靠性下降
Lost in the Middle(迷失在中间):Liu et al. (2024) 揭示的 U 形性能曲线——相关信息在开头或结尾时准确率最高,埋在中间时下降超 30%
注意力预算稀释:每个 token 需要与其他所有 token 建立关系(O(n²) 的复杂度),上下文越长,注意力越分散
所以,上下文工程的黄金准则是:找到最小的高信噪比 Token 集,最大化期望结果的可能性。
二、System Prompt 的结构化设计
System Prompt 是上下文工程的第一道防线。它不是一段"你是一个有用的助手"就能打发的文本,而是一套精心设计的运行时配置。
2.1 五层架构模型
生产级 System Prompt 推荐采用五层架构:

Claude Code 就是这种模式的典型实践——它的 System Prompt 在运行时由 110+ 个条件片段动态组装,基础权重约 2,900 tokens,远少于一次性全量加载。
2.2 指令密度与位置效应
研究发现,指令的位置和密度直接影响执行效果:
前 500 词权重最高:埋在 2,000 词之后的指令受到的影响力明显下降
核心约束必须靠前放置:最重要的规则放在 System Prompt 的最前面
三种指令类型各有最佳场景:
禁止性指令("绝不暴露 API 密钥")—— 适合硬性安全边界
条件性指令("如果用户问计费,引导到...")—— 处理情境逻辑
解释性指令("避免 Markdown 表格因为渲染器会丢失格式")—— 给出理由,模型遵守率更高
研究表明,混合使用三种指令类型的效果优于单一类型。
2.3 Goldilocks 原则:不要太具体,不要太抽象
System Prompt 是一门需要找到"恰到好处"的艺术:
过于具体:在提示中硬编码复杂的 if-else 逻辑,系统变得脆弱、难以维护
过于抽象:给出过于高级别的指导,无法为模型提供具体的行为信号
最佳实践:指令既要足够具体以有效引导行为,又要足够灵活以赋予模型强大的启发式能力
建议使用 Markdown 强制分区,如 ## 角色、## 约束、## 执行流、## 输出格式,让结构一目了然。
三、通过提示词控制上下文的实战策略
这是本文的核心——如何在提示词层面设计上下文管理策略。
3.1 策略一:最少有效上下文(Minimum Viable Context)
这是上下文工程的第一性原理:模型在当前步骤收到恰好所需的信息——不多,不少。
# ❌ 坏做法:把所有信息都塞进去
context = entire_history + all_retrieved_docs + all_tool_results + full_user_profile
response = llm.complete(context + user_input)
# ✅ 好做法:只给当前步骤必要的信息
context = extract_relevant_history(current_step)
context += retrieve_top_k(user_input, k=3) # 只取最相关的3篇
context += summarize_tool_results() # 工具结果先摘要
context += compact_user_profile(current_intent) # 只加载与当前意图相关的画像
response = llm.complete(context + user_input)
核心检查清单:
当前步骤真正需要系统 Prompt 中的哪些部分?
对话历史哪些轮次与当前任务相关?
检索结果的前 K 个是否真的都被需要?
工具定义是否全量加载,还是按需激活?
3.2 策略二:上下文预算管理
将上下文窗口视为一个有限的预算,合理分配给不同类型的上下文。每一条上下文都应该有明确的 ROI(投资回报率)。
以下是一个 200K token 窗口的预算分配示例(仅供参考,实际因任务而异):
动态调整原则:
代码生成型 Agent → 提高检索上下文的比例
多轮对话助手 → 优先保证历史记录的预算
任务执行型 Agent → 工具定义和系统提示更关键
3.3 策略三:通过 System Prompt 控制"知道什么、不知道什么"
可以在 System Prompt 中明确告诉模型它的知识边界和信息源:
## 知识边界
- 你的知识截止于 2025 年 6 月
- 对于实时信息,你依赖于工具调用获取
- 如果工具返回空结果,告诉用户"我暂时无法获取该信息",不要编造
## 上下文使用规则
- 优先使用用户最新提供的信息
- 如果历史对话中存在矛盾信息,以最新确认为准
- 不要假设未被提供的信息,直接询问用户
这种方式相当于给模型一个"元指令",让它在使用上下文时遵循一套规则,而不是不加区别地依赖所有已加载的信息。
3.4 策略四:提示词中的信息优先级标注
在 Prompt 中显式标注信息的优先级,引导模型注意力的分配:
## 高优先级(必须遵守)
1. 安全规则:绝不要输出 API 密钥
2. 本次用户指令的核心诉求
## 中优先级(作为参考)
1. 用户的历史偏好
2. 相关的过往对话
## 低优先级(仅在需要时参考)
1. 工具调用的中间结果
2. 检索到的背景资料
研究表明,指令的前置排序对模型行为有直接可测的影响。
3.5 策略五:条件指令与动态片段
不是所有规则在所有场景下都需要加载。利用条件指令实现"按需加载":
## 条件规则
{if intent == "billing"}
查询用户的套餐信息,不要假设用户使用哪个版本
{/if}
{if user_role == "admin"}
可以访问高级配置面板
{/if}
{if context_length > 10000}
优先使用历史摘要而非完整历史
{/if}
这种模式在 Claude Code 中已经成熟实践——运行时从 110+ 条件片段动态组装 System Prompt,基础权重仅为 2,900 tokens。
四、Agent 上下文管理的六大核心模式
LangChain 将上下文工程形式化为四类核心操作:Write / Select / Compress / Isolate。我们可以在此基础上扩展为六大工程模式。
模式 1:写入(Write)——把信息外置到窗口之外
核心思路:不要把所有信息都塞进上下文,把可以外置的信息持久化存储,需要时再读取。
写入位置:
草稿纸(Scratchpad):模型在步骤间写下的中间思考、计划与状态
长期记忆:关于用户画像、历史决策的事实,持久化到数据库
任务状态:不要让模型自己记账,用外部状态管理
# ✅ 好做法:任务状态外置
task_state = {
"current_step": "data_validation",
"completed_steps": ["data_extraction", "format_conversion"],
"artifacts": {"extracted_file": "/tmp/data.csv"},
"errors": []
}
# 只在当前步骤传入 task_state.current_step,而非整个对象
模式 2:选取(Select)——按需拉取到上下文中
核心思路:在每一步,从记忆、工具、检索结果中拉取当前真正需要的内容。
常用技术:
RAG 检索:从向量库中检索与当前查询语义最相似的文档
重排序(Reranking):用第二遍模型对检索结果进行精排,而不是无差别塞入
工具门控(Tool Gating):根据当前意图,只激活可能用到的工具定义
# ✅ 好做法:只检索真正相关的文档
query_embedding = embed(user_query)
raw_results = vector_db.search(query_embedding, top_k=20)
reranked = reranker.rerank(raw_results, user_query) # 精排
relevant = reranked[:3] # 只取最相关的 3 篇
模式 3:压缩(Compress)——用摘要替代原始信息
核心思路:用摘要替代原始历史,保留语义,丢弃冗余。
压缩策略:
# 对话摘要压缩实现
def compress_conversation(history, max_turns=10):
if len(history) <= max_turns:
return history
older = history[:-max_turns]
recent = history[-max_turns:]
summary = llm.summarize(older, instruction="""
将以下对话压缩为一段摘要(不超过 200 词),
保留:用户的最终需求和已确认的关键信息,
丢弃:寒暄、重复确认、中间尝试的记录。
""")
return [{"role": "system", "content": f"历史摘要:{summary}"}] + recent
Factory 的评估数据很有说服力——在 36,000 条真实工程会话消息的测试中,将新摘要合并到持久状态文档的方式,在准确性、完整性和连续性上均优于原始历史保存和每次重新生成摘要。
模式 4:隔离(Isolate)——切分到独立的子上下文
核心思路:把上下文拆分到子 Agent 或沙箱中,各自保持干净的窗口。
适用场景:
子任务隔离:阅读长文档交给专门的阅读 Agent,不要在规划 Agent 的窗口中塞入整篇文档
工具隔离:重度工具调用由专用子 Agent 处理,主 Agent 只接收结果
多租户隔离:不同用户的数据上下文互不干扰
主 Agent(规划与协调)
├── 文档阅读 Agent(解决"迷失在中间")
├── 代码扫描 Agent(工具定义不污染主上下文)
└── 知识检索 Agent(RAG 结果精简后返回)
模式 5:滑动窗口(Sliding Window)——最近的才是最重要的
核心思路:只保留最近 N 轮对话的完整内容,旧的丢弃。
class SlidingWindowManager:
def __init__(self, max_tokens=8000, reserve_for_output=2000):
self.max_tokens = max_tokens
self.reserve_for_output = reserve_for_output
self.effective_limit = max_tokens - reserve_for_output
def build_context(self, system_prompt, history, new_input):
# 系统提示始终保留
context_tokens = count_tokens(system_prompt)
selected = [{"role": "system", "content": system_prompt}]
# 从最新消息开始往回取
for msg in reversed(history):
msg_tokens = count_tokens(msg)
if context_tokens + msg_tokens > self.effective_limit:
break
selected.insert(1, msg) # 插在系统提示后面
context_tokens += msg_tokens
selected.append(new_input)
return selected
最佳场景:客服对话、编码会话(最近的工作区最重要) 缺点:早期关键信息会丢失
模式 6:分层上下文(Hierarchical Context)——不同粒度的记忆并存
核心思路:同时维护不同粒度的上下文信息——顶层摘要、中层概述、底层细节。这是当前生产级别最推荐的模式。
层级 0(始终在上下文):系统提示 + 用户核心目标 + 关键约束
层级 1(摘要层):过去对话的压缩摘要,约 200-500 tokens
层级 2(按需层):根据当前需求检索的历史片段
层级 3(完整层):完整历史或文档,存储在外部队列中,极少数情况才加载
这种模式类似于操作系统的虚拟内存管理——工作内存保持活跃,冷数据分页到磁盘,按需换入。
五、工具定义的上下文优化
工具定义是 Agent 上下文管理中最容易被低估的部分。每个工具注入到上下文中的 schema——其名称、描述、参数名、类型——都会影响 Agent 的决策。
5.1 臃肿工具集问题
一个企业 Agent 平台可能有 50 个工具。如果全量注册进上下文:
- search_tool: 1,200 tokens
- database_tool: 800 tokens
- email_tool: 600 tokens
- calendar_tool: 700 tokens
- code_executor: 1,500 tokens
- file_manager: 900 tokens
- ... 共 50 个工具
- 总计: 约 30,000 tokens
30K tokens 的纯工具定义,留给业务内容的窗口所剩无几。
5.2 优化策略
策略一:按意图激活工具
不在 System Prompt 中注册所有工具,而是根据用户意图动态注入最可能需要的工具定义:
intent_to_tools = {
"query_data": ["search_database", "query_api"],
"send_email": ["compose_email", "search_contacts"],
"code_task": ["read_file", "write_file", "run_code"],
"customer_service": ["search_kb", "lookup_order", "create_ticket"],
}
tools = intent_to_tools.get(predicted_intent, default_tools)
策略二:精简工具描述
研究表明,工具描述的简洁性直接影响 Agent 调用工具的准确性:
名称自解释:
search_knowledge_base好于kb_search_v2描述精确:不说"This tool searches for things",而说"Searches the company knowledge base for product documentation. Use when user asks about product features, pricing, or troubleshooting."
参数最少化:只暴露必要的参数,减少模型的选择困难
策略三:工具分组
把同类工具分组,用"路由器"工具先选组、再选具体工具,减少单一上下文中工具定义的数量。
5.3 工具结果的上下文管理
工具返回的结果也需要管理,不能无差别塞入:
# ❌ 坏做法:工具结果原样塞入
def call_search(query):
results = search_engine.search(query) # 可能返回 50 条结果
return f"搜索结果:{results}" # 50K tokens
# ✅ 好做法:工具结果先处理再返回
def call_search(query):
results = search_engine.search(query)
top_k = results[:3] # 只保留最相关的前 3 条
compressed = [{"title": r.title,
"summary": llm_summarize(r.content, max_words=50),
"url": r.url} for r in top_k]
return json.dumps(compressed) # 压缩后约 1K tokens
六、Agent 长对话的上下文生命周期管理
6.1 对话的阶段式上下文策略

6.2 对话摘要的持续更新
摘要不是一次性生成的,而是需要持续更新的:
def update_summary(old_summary, new_turns):
"""将新对话并入现有摘要"""
if token_count(old_summary) + token_count(new_turns) > THRESHOLD:
return llm.complete(f"""
现有摘要:{old_summary}
新增对话:{new_turns}
请将新信息合并到现有摘要中,保持摘要不超过 {MAX_SUMMARY_TOKENS} tokens。
保留:用户最终需求、已确认的关键信息、重要的用户画像
丢弃:寒暄话、重复信息、中间尝试和失败记录
""")
return old_summary + new_turns
6.3 达到上下文上限的降级策略
当上下文即将溢出时,按优先级顺序采取以下降级策略:
裁剪工具结果:移除可选的中间工具输出
压缩对话历史:用摘要替换旧对话
裁剪检索文档:减少 top_k 或缩短文档片段
请求用户缩小范围:在必要时提示用户"当前对话较长,您可以让我聚焦于某个具体问题继续"
分阶段处理:将复杂任务拆分为多个独立会话
切换长上下文模型:临时切换到更大窗口的模型
七、常见错误与避坑指南
错误一:把上下文窗口当数据库
# ❌ 错误的认知:上下文窗口 = 永久记忆
def long_session():
history = []
while True:
response = llm.invoke(history) # 从不外置
history.append(response)
正确做法:上下文窗口是工作台,数据库才适合长期存储。关键信息外置写入,按需选回。
错误二:检索结果全量塞入
# ❌ top_k=20,不管相关性
docs = retriever.search(query, top_k=20)
context = "\n\n".join([d.content for d in docs])
正确做法:检索到的文档里真正相关的可能只有 3 篇,其余 17 篇是噪音。先重排,再精选。
错误三:System Prompt 无序增长
3 个月后,一版从 200 tokens 增长到 8,000+ tokens 的 System Prompt,真正每次都相关的内容可能只有 500 tokens。
正确做法:
定期审查 System Prompt,移除不再需要的规则
将条件性规则设计为按需注入,而不是全量加载
每添加一条规则,先问:"这次不写这条规则,模型会犯错吗?"
错误四:不预留输出空间
# ❌ 把上下文塞到只剩 100 tokens,模型连完整回复都写不出
context = "..." * 199000 # 对于 200K 窗口
response = llm.complete(context) # 模型只能输出不到 1000 tokens
正确做法:至少预留 10% 的上下文窗口空间给模型输出。
错误五:忽略"迷失在中间"效应
最重要的证据往往不是最后追加的内容,而是藏在上下文中间的政策段落、客户例外或关键工具定义。
正确做法:
将最高优先级的指令和证据放在最前面
关键内容不要埋在上下文中段
在多个输入长度下测试模型表现,而非仅在短提示下测试
八、上下文管理核验清单
在将 Agent 推向生产之前,用这个清单做一次全面检查:
上下文构建
系统提示是否遵循了 Goldilocks 原则(既不过于具体,也不过于抽象)?
核心约束是否放在了系统提示的前 500 词以内?
是否实现了条件指令的按需注入?
指令风格是否混合使用了禁止型、条件型和解释型?
历史处理
是否设定了上下文预算(每类内容的 max token)?
是否在达到 80% 窗口时触发了压缩或截断?
是否预留了足够输出空间(至少 10%)?
摘要是否持续更新而非一次性生成?
记忆系统
是否有分层记忆架构(工作记忆/情景记忆/语义记忆/程序记忆)?
长期记忆是否仅加载与当前任务相关的子集?
是否避免将完整历史无差别加载到上下文中?
工具调用
工具定义是否按意图按需激活,而非全量加载?
工具描述是否简洁、精确、自解释?
工具结果是否经过裁剪或摘要后再注入上下文?
是否避免了工具定义的臃肿和功能重叠?
检索增强
检索结果是否经过重排序(Reranking)?
是否限制了最终注入上下文的数据量(如 top_k=3)?
是否设置了相关性阈值,低于阈值的文档不注入?
监控与度量
是否追踪了每次调用的上下文 token 数(P50/P95)?
是否有上下文变化的版本管理和回归测试?
是否有异常的上下文膨胀告警?
九、总结与展望
上下文工程不是"更好的 Prompt",而是一套完整的信息供给系统设计方法。
从更高的维度看,Prompt Engineering 和 Context Engineering 之间的关系正在发生范式转移:
2023-2024:Prompt Engineering 的黄金时代,大家都在摸索"怎么问才能让模型答得好"
2025-2026:Context Engineering 成为主流,大家都发现"喂什么"比"怎么说"重要得多
2027+:展望未来,当 Agent 的上下文管理成为"操作系统级别"的基础设施,Agent 的可靠性将从"看运气"变成"可预期"
到最后,所有的 Agent 系统都面临同一个问题:如何在有限的注意力预算内,让模型看到最值得看到的信息?
这既是系统工程问题,也是设计哲学问题。它要求我们放下"给模型更多信息"的本能冲动,学会做减法——而学会做减法,往往比做加法难得多。
本文基于 2023-2026 年业界在 Agent 上下文工程领域的最新实践和研究整理,参考了 Anthropic、LangChain、Chroma、Factory、Zylos Research 等团队的公开研究与报告。