AI Agent 的上下文管理与控制

AI Agent 的上下文管理与控制

从 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

两者有本质区别:

维度

Prompt Engineering

Context Engineering

关心什么

怎么措辞、怎么排列指令

什么信息、以什么格式、在什么时机填入

本质

优化指令本身的表达

构建动态信息供给系统

比喻

教厨师的做菜口诀

配备齐全的厨房——食材、刀具、菜谱、计时器一步到位

效果上限

受限于模型本身

可以系统性地提升 Agent 能力

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 窗口的预算分配示例(仅供参考,实际因任务而异):

上下文类型

预算比例

参考 Token 数

系统提示(System Prompt)

10%

~20K

工具定义(Tool Definitions)

20%

~40K

检索上下文(Retrieved Context)

25%

~50K

历史记录(History)

30%

~60K

用户输入(User Input)

5%-10%

~10-20K

预留缓冲(Buffer)

5%

~10K

动态调整原则

  • 代码生成型 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)——用摘要替代原始信息

核心思路:用摘要替代原始历史,保留语义,丢弃冗余。

压缩策略

技术

压缩比

适用场景

风险

对话摘要

10:1

长对话历史

丢失细节

工具输出裁剪

5:1

工具返回长文本

可能丢失关键数据

实体抽取

20:1

只需关键信息

上下文缺失

树状摘要

51~201

大文档分析

逐层压缩累积误差

# 对话摘要压缩实现
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 达到上下文上限的降级策略

当上下文即将溢出时,按优先级顺序采取以下降级策略:

  1. 裁剪工具结果:移除可选的中间工具输出

  2. 压缩对话历史:用摘要替换旧对话

  3. 裁剪检索文档:减少 top_k 或缩短文档片段

  4. 请求用户缩小范围:在必要时提示用户"当前对话较长,您可以让我聚焦于某个具体问题继续"

  5. 分阶段处理:将复杂任务拆分为多个独立会话

  6. 切换长上下文模型:临时切换到更大窗口的模型


七、常见错误与避坑指南

错误一:把上下文窗口当数据库

# ❌ 错误的认知:上下文窗口 = 永久记忆
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 等团队的公开研究与报告。

🦞 OpenClaw 2026.7.1 更新 2026-07-13
AI Agent 记忆解析:从短期到长期的生产级记忆堆栈 2026-07-16

评论区