上下文管理是 Agent 系统中最容易被低估、却最决定成败的基础设施。本文从原理到实践,系统性地梳理上下文管理的核心概念、技术方案与工程陷阱。
目录
- 什么是上下文,为什么它如此重要
- 上下文窗口的物理边界
- 上下文组织:不只是"塞进去"
- 上下文压缩与摘要策略
- 结构化上下文:让模型"看懂"世界
- 工具调用中的上下文传递
- [多 Agent 场景的上下文路由](#多 Agent 场景的上下文路由 "#7-%E5%A4%9A-agent-%E5%9C%BA%E6%99%AF%E7%9A%84%E4%B8%8A%E4%B8%8B%E6%96%87%E8%B7%AF%E7%94%B1")
- 工程实践与踩坑指南
- 参考资料
1. 什么是上下文,为什么它如此重要
1.1 定义
在 LLM 系统中,上下文(Context) 是指模型在生成每个 token 时所能"看到"的全部信息。它决定了模型的知识边界、行为约束和推理路径。
对于 AI Agent 而言,上下文不仅仅是对话历史------它包括了:
| 上下文类型 | 示例 |
|---|---|
| 系统指令 | System Prompt、角色定义、安全规则 |
| 对话历史 | 用户消息、助手回复、多轮交互记录 |
| 工具调用结果 | API 返回、文件内容、搜索结果、错误信息 |
| 环境状态 | 当前工作目录、操作系统信息、可用工具列表 |
| 记忆注入 | 长期记忆检索到的相关片段 |
| 元指令 | 输出格式要求、执行策略、优先级规则 |
1.2 上下文决定 Agent 的能力天花板
一个 Agent 的"聪明程度"可以表达为:
scss
Agent 能力 = f(模型能力, 上下文质量, 工具丰富度)
上下文质量直接影响:
- 指令遵循度:规则是否被正确理解和执行
- 推理连贯性:多步推理是否逻辑自洽
- 工具使用准确性:参数提取和工具选择是否正确
- 信息保真度:长对话中是否遗忘关键信息
一个简单的思想实验:给 GPT-4 一个糟糕的 System Prompt,和给 GPT-3.5 一个精心设计的上下文,后者的表现可能远超前者。
2. 上下文窗口的物理边界
2.1 Token 预算模型
每个模型都有硬性的上下文窗口上限(Context Window)。以 GPT-4 Turbo 为例,128K tokens 听起来很大,但在 Agent 场景下消耗极快。
总预算 = 系统指令 + 对话历史 + 工具结果 + 生成预留
一个典型的 Agent 交互回合可能消耗:
makefile
用户消息: ~200 tokens
System Prompt: ~2,000-8,000 tokens
工具定义(10个): ~3,000 tokens
单次工具结果: ~500-5,000 tokens
历史对话(10轮): ~5,000-15,000 tokens
──────────────────────────────────
合计(保守估计): ~15,000-30,000 tokens / 交互
2.2 上下文预算管理实战
python
class ContextBudget:
"""上下文预算管理器"""
def __init__(self, max_tokens: int = 100_000):
self.max_tokens = max_tokens
@staticmethod
def estimate_tokens(text: str) -> int:
"""粗略估算 token 数(英文约 4 chars/token,中文约 1.5 chars/token)"""
return len(text) // 4 # 简化版本,生产环境使用 tiktoken
def allocate(self, components: dict[str, str]) -> dict[str, int]:
"""
为各组件分配 token 预算。
优先级: 系统指令 > 最新消息 > 工具结果 > 历史对话
"""
budgets = {}
remaining = self.max_tokens
# 1. 系统指令(最高优先级,固定分配)
system_tokens = self.estimate_tokens(components.get("system", ""))
budgets["system"] = system_tokens
remaining -= system_tokens
# 2. 当前轮次消息 + 工具定义
current = components.get("current_message", "") + components.get("tools", "")
current_tokens = self.estimate_tokens(current)
budgets["current"] = current_tokens
remaining -= current_tokens
# 3. 工具结果(截断策略)
tool_results = components.get("tool_results", "")
tool_tokens = self.estimate_tokens(tool_results)
if tool_tokens > remaining * 0.4:
# 如果工具结果太大,进行摘要压缩
budgets["tool_results"] = int(remaining * 0.4)
remaining -= budgets["tool_results"]
else:
budgets["tool_results"] = tool_tokens
remaining -= tool_tokens
# 4. 历史对话拿剩余预算
budgets["history"] = max(0, remaining)
return budgets
3. 上下文组织:不只是"塞进去"
3.1 上下文组织的几个层次
yaml
┌─────────────────────────────────────────┐
│ Layer 1: 固定层(System Prompt) │
│ - 角色定义、核心规则、安全约束 │
│ - 在会话生命周期内不变 │
├─────────────────────────────────────────┤
│ Layer 2: 半固定层(会话级元信息) │
│ - 环境信息、可用工具、当前工作目录 │
│ - 会话内不变,会话间变化 │
├─────────────────────────────────────────┤
│ Layer 3: 动态注入层(Memory / RAG) │
│ - 从长期记忆中检索的相关信息 │
│ - 根据当前查询动态变化 │
├─────────────────────────────────────────┤
│ Layer 4: 交互层(对话历史 + 工具结果) │
│ - 用户消息、助手回复、工具调用与结果 │
│ - 随对话推进持续增长 │
└─────────────────────────────────────────┘
3.2 上下文编排器的实现
python
from dataclasses import dataclass, field
from typing import Any
@dataclass
class ContextAssembler:
"""上下文编排器:将多层信息组装为最终 prompt"""
system_prompt: str
env_info: dict = field(default_factory=dict)
tool_definitions: list[dict] = field(default_factory=list)
max_history_turns: int = 20
def assemble(
self,
messages: list[dict],
memory_chunks: list[str],
current_tool_results: list[dict] | None = None,
) -> str:
"""组装最终上下文"""
parts = []
# Layer 1: 系统指令
parts.append(self._format_system())
# Layer 2: 环境与工具
parts.append(self._format_env_and_tools())
# Layer 3: 记忆注入(在对话历史之前,让模型先"知道"背景)
if memory_chunks:
parts.append(self._format_memory(memory_chunks))
# Layer 4: 对话历史 + 工具结果
parts.append(self._format_conversation(messages, current_tool_results))
return "\n\n---\n\n".join(parts)
def _format_system(self) -> str:
return f"<system>\n{self.system_prompt}\n</system>"
def _format_env_and_tools(self) -> str:
tools_xml = "\n".join(
f"<tool name=\"{t['name']}\">{t['description']}</tool>"
for t in self.tool_definitions
)
return f"<environment>\n{self.env_info}\n</environment>\n\n<tools>\n{tools_xml}\n</tools>"
def _format_memory(self, chunks: list[str]) -> str:
items = "\n".join(f"<memory_chunk>{c}</memory_chunk>" for c in chunks)
return f"<relevant_memories>\n{items}\n</relevant_memories>"
def _format_conversation(
self,
messages: list[dict],
tool_results: list[dict] | None = None,
) -> str:
"""格式化对话,自动截断超出窗口的历史"""
formatted = []
# 截断历史轮次
recent = messages[-self.max_history_turns * 2:] # 每轮 = user + assistant
for msg in recent:
role = msg["role"]
content = msg.get("content", "")
formatted.append(f"<{role}>\n{content}\n</{role}>")
# 追加当前工具结果
if tool_results:
for tr in tool_results:
formatted.append(
f"<tool_result name=\"{tr['tool']}\">\n{tr['result']}\n</tool_result>"
)
return "<conversation>\n" + "\n".join(formatted) + "\n</conversation>"
4. 上下文压缩与摘要策略
当对话超出窗口限制时,不能简单地丢弃旧消息------必须进行有损压缩,保留关键信息。
4.1 滑动窗口 + 摘要
最经典的策略:保留最近 N 轮完整对话,对更早的部分用 LLM 生成结构化摘要。
python
class SlidingWindowWithSummary:
"""滑动窗口 + 渐进式摘要"""
def __init__(self, window_size: int = 10, llm_call=None):
self.window_size = window_size
self.llm_call = llm_call
self.summary = "" # 累积摘要
def process(self, messages: list[dict]) -> list[dict]:
"""
处理消息列表,返回压缩后的上下文消息。
策略:
1. 最近 window_size 轮完整保留
2. 更早的消息合并到渐进式摘要中
"""
if len(messages) <= self.window_size * 2:
return messages
# 超出窗口的消息
overflow = messages[:-(self.window_size * 2)]
recent = messages[-(self.window_size * 2):]
# 增量更新摘要
self.summary = self._update_summary(self.summary, overflow)
# 构造压缩后的上下文
summary_msg = {
"role": "system",
"content": f"<conversation_summary>\n{self.summary}\n</conversation_summary>"
}
return [summary_msg] + recent
def _update_summary(self, existing: str, new_messages: list[dict]) -> str:
"""增量更新摘要"""
new_text = "\n".join(
f"[{m['role']}]: {m.get('content', '')[:500]}"
for m in new_messages
)
prompt = f"""现有对话摘要:
{existing}
新增对话片段:
{new_text}
请将新增信息合并到摘要中,保持以下结构:
- 关键决策与结论
- 用户偏好与约束
- 待处理的任务
- 重要的数据/事实
更新后的摘要(仅输出摘要内容):"""
return self.llm_call(prompt) if self.llm_call else new_text
4.2 关键信息保留策略
并非所有信息都同等重要。设计上下文压缩时,需要考虑信息的优先级权重:
| 优先级 | 信息类型 | 保留策略 |
|---|---|---|
| P0 | 安全约束、任务目标 | 始终保留,不可压缩 |
| P1 | 用户偏好、关键决策 | 摘要中显式标注 |
| P2 | 工具调用结果中的关键数据 | 结构化提取后保留 |
| P3 | 中间推理过程 | 可大幅压缩 |
| P4 | 已完成的子任务细节 | 仅保留结论 |
5. 结构化上下文:让模型"看懂"世界
5.1 为什么需要结构化
扁平的自然语言上下文在复杂场景下存在严重问题:
- 歧义性:模型可能混淆不同类型的指令
- 注意力稀释:重要信息淹没在大量文本中
- 跨引用困难:多层嵌套信息难以准确定位
5.2 XML 标签方案
一种被广泛验证有效的方案是使用 XML 标签对上下文进行结构分区:
xml
<context>
<system>
<role>你是一个代码审查助手</role>
<constraints>
<constraint priority="critical">永远不要执行 rm -rf 命令</constraint>
<constraint priority="high">修改文件前必须展示 diff</constraint>
</constraints>
</system>
<environment>
<os>Linux</os>
<workspace>/home/user/project</workspace>
<git_branch>feature/new-api</git_branch>
</environment>
<relevant_memories>
<memory id="mem_001" relevance="0.92">
用户偏好函数式编程风格,避免使用类继承
</memory>
<memory id="mem_002" relevance="0.85">
上次修改 auth.py 时引入了 JWT 过期 bug,已修复
</memory>
</relevant_memories>
<conversation>
<user_message id="1">
帮我审查 src/auth.py 的安全问题
</user_message>
<tool_call id="tc_1" name="read_file">
<parameters>{"path": "src/auth.py"}</parameters>
</tool_call>
<tool_result id="tr_1" for="tc_1">
<content><!-- 文件内容 --></content>
<metadata>
<lines>342</lines>
<language>python</language>
</metadata>
</tool_result>
</conversation>
</context>
5.3 结构化带来的好处
- 注意力引导:XML 标签作为"锚点",引导模型关注特定区域
- 可控截断:可以按标签块进行精确截断,而非粗暴按 token 数切割
- 可解析性:上下文可以被程序化解析、验证和调试
- 层级化优先级:通过标签嵌套表达信息的重要程度
6. 工具调用中的上下文传递
Agent 区别于普通 Chatbot 的核心在于工具调用,而工具调用的上下文传递是最容易出问题的环节。
6.1 工具结果的上下文污染
一个常见的反模式:
python
# ❌ 反模式:将所有工具结果全部塞入上下文
for tool_call in history:
context.append(tool_call.result) # 可能包含数万 tokens 的网页内容
更好的做法:
python
# ✅ 对工具结果进行分类处理
class ToolResultProcessor:
"""工具结果处理器:根据结果类型采用不同策略"""
TRUNCATION_RULES = {
"web_search": {"max_items": 5, "max_per_item": 300},
"read_file": {"max_lines": 500, "strategy": "head_tail"}, # 头尾保留
"shell_executor": {"max_chars": 2000, "strategy": "error_first"}, # 错误优先
"default": {"max_chars": 1000},
}
def process(self, tool_name: str, result: str) -> str:
rule = self.TRUNCATION_RULES.get(tool_name, self.TRUNCATION_RULES["default"])
if tool_name == "read_file" and rule["strategy"] == "head_tail":
return self._head_tail_truncate(result, rule["max_lines"])
elif len(result) > rule.get("max_chars", 1000):
return result[:rule["max_chars"]] + f"\n... [截断 {len(result) - rule['max_chars']} 字符]"
return result
def _head_tail_truncate(self, text: str, max_lines: int) -> str:
lines = text.split("\n")
if len(lines) <= max_lines:
return text
head = lines[:max_lines // 2]
tail = lines[-(max_lines // 2):]
return (
"\n".join(head) +
f"\n\n... [省略中间 {len(lines) - max_lines} 行] ...\n\n" +
"\n".join(tail)
)
6.2 工具调用链的上下文折叠
当 Agent 执行多步工具调用时,中间步骤的工具调用细节可以折叠:
python
def collapse_tool_chain(messages: list[dict]) -> list[dict]:
"""
折叠连续的 tool_call → tool_result 对,
只保留最后一次调用的结果和中间关键信息。
"""
collapsed = []
pending_tool_calls = []
for msg in messages:
if msg["role"] == "tool_call":
pending_tool_calls.append(msg)
elif msg["role"] == "tool_result" and pending_tool_calls:
# 只保留最后一个结果,前面的折叠为摘要
if len(pending_tool_calls) > 1:
summary = f"[已完成 {len(pending_tool_calls)} 步工具调用,关键发现:...]"
collapsed.append({"role": "system", "content": summary})
collapsed.append(msg)
pending_tool_calls = []
else:
collapsed.append(msg)
return collapsed
7. 多 Agent 场景的上下文路由
在多 Agent 系统中,上下文管理变得更加复杂------不是"一个上下文",而是"多个上下文的协同与隔离"。
7.1 上下文隔离与选择性继承
python
class MultiAgentContextManager:
"""
多 Agent 上下文管理器。
核心原则:
- 每个 Agent 拥有独立的上下文空间
- Agent 之间通过"交接协议"传递必要信息
- 避免全量上下文复制(会导致 token 爆炸)
"""
def __init__(self):
self.agent_contexts: dict[str, list[dict]] = {}
def create_context(self, agent_id: str, base_context: dict):
"""为新 Agent 创建独立上下文"""
self.agent_contexts[agent_id] = [base_context]
def handoff(
self,
from_agent: str,
to_agent: str,
handoff_info: dict,
):
"""
Agent 间交接:将必要信息注入目标 Agent 上下文。
不是全量传递,而是传递结构化的"交接摘要":
- 任务目标
- 已完成的工作
- 关键中间产物
- 当前阻塞点
"""
handoff_msg = {
"role": "system",
"content": f"""
<handoff from="{from_agent}">
<task>{handoff_info['task']}</task>
<progress>{handoff_info['progress']}</progress>
<artifacts>{handoff_info.get('artifacts', [])}</artifacts>
<blockers>{handoff_info.get('blockers', '无')}</blockers>
</handoff>
"""
}
if to_agent not in self.agent_contexts:
self.agent_contexts[to_agent] = []
self.agent_contexts[to_agent].append(handoff_msg)
def get_context(self, agent_id: str) -> list[dict]:
"""获取 Agent 的当前上下文"""
return self.agent_contexts.get(agent_id, [])
7.2 上下文继承 vs 上下文注入
在多 Agent 协作中,有两种传递信息的方式:
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 上下文继承 | 同一 Agent 的连续任务 | 完整保留执行记忆 | Token 消耗大 |
| 上下文注入 | 跨 Agent 交接 | 信息精炼,Token 高效 | 可能丢失细节 |
| Memory ID 引用 | 共享知识库查询 | 不占用上下文,按需检索 | 需要向量数据库支持 |
8. 工程实践与踩坑指南
8.1 常见陷阱
陷阱 1:System Prompt 膨胀
随着需求迭代,System Prompt 从 500 字膨胀到 5000 字,模型注意力被稀释。 解法:分层设计,将低频规则外置为按需注入的"技能卡片"。
陷阱 2:工具结果的全量透传
一次网页抓取返回 50K tokens,直接塞入上下文,后续推理质量断崖式下降。 解法:工具结果必须在进入上下文前经过"过滤-提取-截断"管道。
陷阱 3:对话历史的"幽灵状态"
用户 50 轮之前说过的偏好已被窗口丢弃,Agent 行为突变。 解法:关键信息应写入长期记忆,而非仅依赖对话历史。
陷阱 4:XML 标签的嵌套地狱
工具结果中包含 XML 标签,破坏了上下文结构,模型解析混乱。 解法:对工具结果进行 XML 转义或使用 CDATA 包裹。
8.2 调试上下文的最佳实践
python
def debug_context(messages: list[dict], output_path: str):
"""
将当前上下文导出为可读文件,便于调试。
输出:
- 各层级的 token 占比统计
- 截断/压缩发生的位置
- 记忆注入的来源与相关性分数
"""
report = []
total = 0
for i, msg in enumerate(messages):
tokens = len(msg.get("content", "")) // 4
total += tokens
report.append(
f"[{i}] role={msg['role']:10s} | ~{tokens:>6d} tokens | "
f"preview: {msg.get('content', '')[:80]}..."
)
report.append(f"\n{'='*60}")
report.append(f"Total estimated tokens: ~{total}")
with open(output_path, "w", encoding="utf-8") as f:
f.write("\n".join(report))
return total
8.3 上下文性能监控指标
在生产环境中,应持续监控以下指标:
| 指标 | 含义 | 告警阈值 |
|---|---|---|
| 上下文填充率 | 已用 tokens / 窗口上限 | > 80% |
| 工具结果压缩比 | 压缩后 / 原始大小 | 根据场景设定 |
| 记忆命中率 | 检索到的记忆中被实际引用的比例 | < 30% 需优化 |
| 上下文切换延迟 | 注入新上下文到模型开始生成的时间 | > 2s |
| 指令遵循率下降 | 长对话后期指令遵循率 vs 初期 | 下降 > 15% |
9. 参考资料
论文
-
Lost in the Middle: How Language Models Use Long Contexts (Liu et al., 2023)
- 揭示了 LLM 对长上下文中部信息的"注意力衰减"现象
- arXiv: 2307.03172
-
MemGPT: Towards LLMs as Operating Systems (Packer et al., 2023)
- 提出将 LLM 上下文视为虚拟内存,实现上下文的分页管理
- arXiv: 2310.08560
-
Let's Verify Step by Step (Lightman et al., 2023)
- 探讨过程监督对推理链上下文质量的影响
- arXiv: 2305.20050
-
ReAct: Synergizing Reasoning and Acting in Language Models (Yao et al., 2023)
- 开创性地将推理和行动交织在同一上下文中
- arXiv: 2210.03629
工程实践
-
Anthropic - Context Engineering
- Anthropic 关于上下文工程的系列博客和实践指南
- docs.anthropic.com
-
OpenAI - Prompt Engineering Guide
- 包含上下文管理和 token 优化的最佳实践
- platform.openai.com
-
LangChain - Memory and Context
- LangChain 框架中记忆和上下文管理的实现参考
- python.langchain.com
-
LlamaIndex - Advanced Context Management
- 上下文窗口优化、句子窗口检索等高级技术
- docs.llamaindex.ai
推荐阅读顺序
- 先读 Lost in the Middle 理解上下文的基础限制
- 再读 MemGPT 了解虚拟上下文管理的思路
- 然后参考 Anthropic 和 OpenAI 的工程实践
- 最后研究 LangChain/LlamaIndex 的开源实现
本文力求从理论到实践全面覆盖 AI Agent 上下文管理的核心知识点。上下文管理是一个快速演进的领域,建议持续关注前沿论文和工程实践。