版本:2026.03 | 面向:有实际落地需求的工程团队 | 聚焦:LLM 应用的上下文设计与管理
目录
- 核心认知:什么是上下文工程
- 上下文的五个来源
- 上下文窗口管理策略
- Prompt 工程的生产规范
- 多轮对话的上下文设计
- Agent 场景的上下文管理
- 上下文压缩与摘要技术
- 成本与延迟优化
- 评估与可观测性
- 常见生产问题与根本解法
1. 核心认知:什么是上下文工程
不是 Prompt 工程的升级,是范式转变
"Prompt 工程"关注的是如何写好一条指令。"上下文工程"关注的是如何向 LLM 的上下文窗口填入正确的信息——包括指令、事实、示例、历史、工具结果、外部记忆,以及关键的:决定哪些信息不放进去。
Andrej Karpathy 在 2025 年将上下文工程定义为:
"The delicate art and science of filling the context window with just the right information."
这个定义揭示了两个关键点:
- 信息密度:上下文窗口是稀缺资源,填什么、不填什么同样重要
- 动态性:正确的信息依赖于当前任务的状态,不是静态模板
为什么现在变得关键
模型能力已经足够强——大多数生产问题不是模型"不够聪明",而是模型没有获得完成任务所需的信息,或者被无关信息干扰了。上下文工程是当前 LLM 应用工程化的核心瓶颈。
上下文工程的核心张力
完整性 vs 简洁性
─────────────────────────────────
信息越完整,模型越准确
信息越冗余,模型越慢、越贵、越容易分心
没有普适最优解,只有针对具体任务的权衡决策。
2. 上下文的五个来源
理解上下文从哪里来,是设计上下文管理系统的基础。
2.1 写入上下文(Write to Context)
直接存在于当前对话请求中的内容:
| 类型 | 内容 | 位置 |
|---|---|---|
| System Prompt | 角色定义、行为规则、输出格式 | messages[0].role=system |
| User Message | 用户当前输入 | messages[-1].role=user |
| Few-shot 示例 | 输入输出示例对 | system 或 user 中 |
| 工具定义 | 可调用工具的 schema | tools 字段 |
2.2 检索到上下文(Retrieved into Context)
通过检索机制动态注入的内容:
- 向量检索:从知识库按语义相似度检索的文档片段(参见 RAG 落地指南)
- 关键词检索:BM25 精确匹配的文档段落
- 结构化查询:从数据库/API 实时拉取的结构化数据
- 工具调用结果:Agent 执行工具后返回的结果
2.3 外部记忆(External Memory)
不在上下文窗口内,但可以被检索进来的持久化信息:
短期记忆(Session Memory):当前会话的关键事实摘要
长期记忆(Long-term Memory):用户偏好、历史决策、领域知识
实体记忆(Entity Memory):对话中出现的人、物、概念的属性
2.4 内部记忆(In-weights Knowledge)
模型训练时编码进权重的知识。无法在推理时修改,只能通过微调更新。生产中的关键认知:不要依赖模型的内部知识处理私域数据,要通过上下文显式提供。
2.5 上下文内计算(In-context Computation)
让模型在生成最终答案之前先进行推理:
- Chain-of-Thought:让模型先写出思考过程
- 工具调用中间步骤:ReAct 模式的 Think→Act→Observe 循环
- 草稿区:让模型先生成草稿再润色
3. 上下文窗口管理策略
3.1 窗口容量规划
2026 年主流模型的上下文窗口:
| 模型 | 上下文窗口 | 实际可用建议 |
|---|---|---|
| Claude Sonnet/Opus 4.x | 200K tokens | ≤ 100K(避免"中间丢失"问题) |
| GPT-4o | 128K tokens | ≤ 80K |
| Gemini 2.x | 1M tokens | ≤ 500K(但成本高) |
"Lost in the Middle" 问题:研究证实,模型对上下文中间位置的信息注意力显著下降。关键信息应放在开头(system prompt)或结尾(最近的 user message),避免堆在中间。
3.2 上下文分区设计
推荐的上下文分区结构(按 token 预算分配):
┌─────────────────────────────────────┐
│ System Prompt (固定,~2K tokens) │
│ - 角色定义 │
│ - 核心行为规则 │
│ - 输出格式要求 │
├─────────────────────────────────────┤
│ 静态知识注入 (~5K tokens) │
│ - 领域术语表 │
│ - 固定业务规则 │
│ - 必要的 few-shot 示例 │
├─────────────────────────────────────┤
│ 动态检索内容 (~20K tokens) │
│ - 本次查询相关文档 │
│ - 工具调用结果 │
│ - 外部记忆检索结果 │
├─────────────────────────────────────┤
│ 对话历史 (~8K tokens) │
│ - 经压缩/摘要的历史对话 │
├─────────────────────────────────────┤
│ 当前用户输入 (~2K tokens) │
└─────────────────────────────────────┘
3.3 Token 预算硬性控制
永远在代码层做硬性 token 上限,不要依赖模型自己控制长度。
import tiktoken
def build_context(system: str, docs: list[str], history: list[dict],
user_input: str, max_tokens: int = 80000) -> list[dict]:
encoder = tiktoken.encoding_for_model("gpt-4o")
# 预留空间:system(固定) + user_input + 输出空间
system_tokens = len(encoder.encode(system))
user_tokens = len(encoder.encode(user_input))
output_reserve = 4000 # 为模型输出预留
available = max_tokens - system_tokens - user_tokens - output_reserve
# 历史对话:优先保留最近的,超出则截断
history_budget = min(available * 0.3, 8000)
trimmed_history = trim_history(history, history_budget, encoder)
# 检索文档:剩余 budget
docs_budget = available - count_tokens(trimmed_history, encoder)
selected_docs = select_docs_by_budget(docs, docs_budget, encoder)
return build_messages(system, selected_docs, trimmed_history, user_input)
4. Prompt 工程的生产规范
4.1 System Prompt 设计原则
原则一:角色定义要具体,不要泛化
# 差(泛化)
你是一个有帮助的 AI 助手。
# 好(具体)
你是 XX 公司的技术支持工程师,专门处理 [产品名] 的集成问题。
你的用户是有一定开发经验的工程师,回答应技术化、直接,避免过度解释基础概念。
原则二:约束用否定句,但要说明原因
# 差(无原因的禁止)
不要推荐竞品。
# 好(有原因,模型更倾向遵守)
不要推荐竞品产品,因为这会让用户产生混淆,且超出你的支持范围。
如果用户询问竞品对比,说明你专注于 [产品名] 的支持,建议他们查阅官方对比文档。
原则三:输出格式用示例,不用描述
# 差(描述格式)
以 JSON 格式输出,包含 answer 和 confidence 字段。
# 好(示例格式)
按以下格式输出:
{"answer": "你的回答内容", "confidence": "high|medium|low", "sources": ["来源1", "来源2"]}
4.2 Few-shot 示例选择策略
Few-shot 示例是上下文中最昂贵但最有效的内容。生产中的选择原则:
| 策略 | 适用场景 | 示例数量 |
|---|---|---|
| 静态示例 | 任务类型固定,分布稳定 | 3-5 个 |
| 动态检索示例 | 任务类型多样,有历史案例库 | 1-3 个(按相似度检索) |
| 零示例 | 任务描述足够清晰,模型能力足够强 | 0 个 |
动态检索示例的实现:将历史高质量的(输入, 输出)对存入向量数据库,在每次请求时检索与当前输入最相似的 1-3 个作为 few-shot。这比静态示例平均提升 10-25% 的准确率,且不浪费 token。
4.3 Prompt 版本管理
生产中 prompt 必须版本化管理,与代码同等对待:
prompts/
├── system/
│ ├── support_agent_v1.2.txt # 当前生产版本
│ ├── support_agent_v1.3-beta.txt # 测试中
│ └── changelog.md
├── templates/
│ ├── doc_retrieval.jinja2
│ └── summarization.jinja2
└── tests/
└── regression_cases.jsonl # 固定评估集
每次 prompt 变更必须:
- 更新版本号
- 在固定评估集上对比新旧版本指标
- 记录变更原因和效果数据
5. 多轮对话的上下文设计
5.1 对话历史的三种处理策略
策略一:全量保留(适合短会话)
简单直接,对话轮次 ≤ 10 轮时可行。超过后 token 成本线性增长,且出现"遗忘中间"问题。
策略二:滑动窗口(适合长会话)
只保留最近 N 轮对话,丢弃更早的内容。实现简单,但会真的"遗忘"早期重要信息。
def sliding_window_history(messages: list[dict], max_rounds: int = 10) -> list[dict]:
# 保留 system message + 最近 max_rounds 轮的 user/assistant 对
system = [m for m in messages if m["role"] == "system"]
conversation = [m for m in messages if m["role"] != "system"]
# 每轮 = user + assistant,所以取最后 max_rounds * 2 条
trimmed = conversation[-(max_rounds * 2):]
return system + trimmed
策略三:压缩摘要(适合生产级长会话)
当对话超过阈值时,将早期历史压缩为结构化摘要,保留摘要 + 最近 N 轮原始内容。
SUMMARY_TRIGGER = 20 # 超过 20 轮触发压缩
def get_context_with_compression(session: Session) -> list[dict]:
messages = session.messages
if len(messages) < SUMMARY_TRIGGER:
return messages # 短会话直接返回
# 取前一半进行压缩,保留后一半原始内容
split = len(messages) // 2
to_summarize = messages[:split]
to_keep = messages[split:]
# 检查是否已有该段的摘要缓存
summary = session.get_cached_summary(split)
if not summary:
summary = call_llm_for_summary(to_summarize)
session.cache_summary(split, summary)
summary_message = {
"role": "system",
"content": f"[早期对话摘要]\n{summary}"
}
return [messages[0]] + [summary_message] + to_keep
5.2 关键信息的显式提取与注入
不要依赖模型"记住"重要信息——在对话中显式提取并结构化存储:
# 每轮对话结束后,提取关键实体和事实
def extract_key_facts(user_msg: str, assistant_msg: str) -> dict:
extraction_prompt = """从以下对话中提取关键事实,JSON 格式:
{"entities": [], "decisions": [], "constraints": [], "open_questions": []}
对话:
用户:{user_msg}
助手:{assistant_msg}"""
return call_llm(extraction_prompt)
# 在后续对话的 system prompt 中注入
def build_system_with_facts(base_system: str, facts: dict) -> str:
if not facts:
return base_system
facts_section = f"""
[当前会话已知信息]
- 涉及实体:{', '.join(facts.get('entities', []))}
- 已确认决策:{'; '.join(facts.get('decisions', []))}
- 约束条件:{'; '.join(facts.get('constraints', []))}
"""
return base_system + facts_section
6. Agent 场景的上下文管理
6.1 Agent 上下文的特殊挑战
Agent 系统中,上下文窗口会随着工具调用不断增长:
初始请求 → 思考(1K) → 工具调用A → 结果A(5K) → 思考(1K)
→ 工具调用B → 结果B(10K) → 思考(1K) → ... → 最终回答
一个复杂任务执行 10 次工具调用,工具结果可能累积 50K+ tokens,严重挤压有效上下文空间。
6.2 工具结果的上下文管理
原则:工具结果要摘要,不要全量保留
class ToolResultManager:
MAX_RESULT_TOKENS = 2000 # 单次工具结果上限
def process_tool_result(self, tool_name: str, raw_result: str) -> str:
tokens = count_tokens(raw_result)
if tokens <= self.MAX_RESULT_TOKENS:
return raw_result
# 超出上限,根据工具类型选择压缩策略
if tool_name in ["web_search", "doc_retrieve"]:
return self.extract_key_points(raw_result)
elif tool_name in ["code_executor", "bash"]:
return self.extract_execution_summary(raw_result)
else:
return self.truncate_with_notice(raw_result, self.MAX_RESULT_TOKENS)
def extract_key_points(self, content: str) -> str:
return call_llm(f"提取以下内容的关键信息,不超过 500 字:\n{content[:8000]}")
6.3 长程 Agent 任务的检查点设计
对于执行时间超过 5 分钟或步骤超过 20 步的 Agent 任务,必须实现检查点机制:
@dataclass
class AgentCheckpoint:
task_id: str
step: int
completed_actions: list[dict] # 已完成的操作
accumulated_facts: dict # 从工具结果中提取的事实
current_plan: list[str] # 当前计划(可能已更新)
context_summary: str # 已处理内容的摘要
timestamp: str
class CheckpointedAgent:
CHECKPOINT_INTERVAL = 5 # 每 5 步保存一次
def run_step(self, step: int, context: list[dict]) -> AgentOutput:
result = self.llm.invoke(context)
if step % self.CHECKPOINT_INTERVAL == 0:
# 将已处理的工具结果压缩为摘要,释放 token 空间
checkpoint = self.create_checkpoint(step, context, result)
self.save_checkpoint(checkpoint)
# 用摘要替换详细历史,保持上下文窗口可控
context = self.rebuild_context_from_checkpoint(checkpoint)
return result
6.4 多 Agent 系统的上下文隔离
在 Orchestrator + Sub-agent 架构中:
┌──────────────────────────────────┐
│ Orchestrator Agent │
│ 上下文:任务规划 + 各子任务摘要 │
│ 不包含:子任务的详细执行过程 │
└──────────┬───────────────────────┘
│ 只传递必要的子任务指令
┌──────┴──────┐
▼ ▼
┌────────┐ ┌────────┐
│ Agent A│ │ Agent B│
│独立上下│ │独立上下│
│文窗口 │ │文窗口 │
└────────┘ └────────┘
│ │
└──────┬──────┘
│ 只返回结构化摘要结果
▼
Orchestrator 接收摘要,
不接收原始执行细节
关键设计:子 Agent 的执行细节不应全量传回给 Orchestrator,只传结构化结果摘要。Orchestrator 的上下文应保持精简,专注于任务协调而非执行细节。
7. 上下文压缩与摘要技术
7.1 递归摘要(Recursive Summarization)
适用于超长文档或超长对话的处理:
def recursive_summarize(text: str, target_tokens: int = 2000,
chunk_size: int = 4000) -> str:
"""将长文本递归压缩到目标 token 数"""
current_tokens = count_tokens(text)
if current_tokens <= target_tokens:
return text
# 分块摘要
chunks = split_by_tokens(text, chunk_size)
chunk_summaries = [
call_llm(f"摘要以下内容的核心信息(100字以内):\n{chunk}")
for chunk in chunks
]
combined = "\n".join(chunk_summaries)
# 如果摘要的摘要还是太长,递归处理
if count_tokens(combined) > target_tokens:
return recursive_summarize(combined, target_tokens, chunk_size)
return combined
7.2 结构化压缩(Structured Compression)
比自由文本摘要更可靠,强制提取特定维度信息:
COMPRESSION_SCHEMA = """
从以下内容中提取信息,严格按 JSON 格式输出:
{
"key_facts": [], // 关键事实,每条 ≤ 30 字
"decisions_made": [], // 已做决策
"pending_items": [], // 未解决事项
"critical_constraints": [] // 不可违反的约束
}
内容:{content}
"""
def structured_compress(content: str) -> dict:
result = call_llm(COMPRESSION_SCHEMA.format(content=content))
return json.loads(result)
def inject_structured_summary(system: str, summary: dict) -> str:
lines = ["[背景信息]"]
if summary.get("key_facts"):
lines.append("关键事实:" + " | ".join(summary["key_facts"]))
if summary.get("critical_constraints"):
lines.append("约束:" + " | ".join(summary["critical_constraints"]))
return system + "\n" + "\n".join(lines)
7.3 缓存策略(KV Cache 利用)
现代模型 API 对前缀相同的请求有 KV Cache 优化,可大幅降低成本:
最大化 Cache 命中率的设计原则:
- System Prompt 固定化:将所有静态内容集中在 system prompt 开头,保持不变
- 动态内容后置:用户输入、检索结果等变化内容放在 messages 末尾
- 避免随机化:时间戳、随机 ID 等每次不同的内容会破坏 cache
# 好:静态内容在前,动态内容在后
messages = [
{"role": "system", "content": STATIC_SYSTEM_PROMPT}, # 固定,可 cache
{"role": "user", "content": f"[检索结果]\n{retrieved_docs}\n\n用户问题:{query}"}
]
# 差:将时间戳混入 system prompt
messages = [
{"role": "system", "content": f"{STATIC_SYSTEM_PROMPT}\n当前时间:{datetime.now()}"}, # 每次不同,无法 cache
]
Claude 的 Prompt Caching 可节省 90% 的输入 token 成本(缓存命中时),需要在 system content 中使用 cache_control 标记。
8. 成本与延迟优化
8.1 上下文成本分析
在大规模生产中,上下文 token 成本是主要开销:
典型 RAG 应用的 token 构成(每次请求):
System Prompt: 2,000 tokens ( 5%)
检索文档: 15,000 tokens (37%)
对话历史: 8,000 tokens (20%)
用户输入: 1,500 tokens ( 4%)
模型输出: 14,000 tokens (34%) ← 输出 token 约 3-5x 输入价格
────────────────────────────────────
总计: 40,500 tokens/请求
优化优先级:输出 token > 检索文档 token > 历史 token > System Prompt token
8.2 分级上下文策略
不是所有请求都需要完整上下文,按请求复杂度分级:
| 级别 | 判断条件 | 上下文策略 | 典型延迟 |
|---|---|---|---|
| L1 简单 | 闲聊/明确指令/FAQ | 最小上下文(仅 system + user) | < 1s |
| L2 标准 | 需要少量知识 | system + 3-5 文档 + 短历史 | 1-3s |
| L3 复杂 | 多跳推理/分析 | 完整上下文 + 压缩历史 | 3-8s |
| L4 Agent | 需要工具调用 | 动态管理,检查点压缩 | 8-30s |
def route_request(query: str, session: Session) -> ContextLevel:
"""请求复杂度路由"""
# 简单规则判断(低成本)
if is_faq(query) or is_chitchat(query):
return ContextLevel.L1
# 检查是否需要工具
if requires_tool_use(query):
return ContextLevel.L4
# 通过轻量分类模型判断
complexity = classify_complexity(query) # 小模型,低成本
return complexity
8.3 并行上下文构建
上下文的不同部分可以并行获取:
import asyncio
async def build_context_parallel(query: str, session: Session) -> list[dict]:
# 并行执行:向量检索 + 历史摘要 + 实体提取
retrieved_docs, history_summary, entities = await asyncio.gather(
retrieve_documents(query), # 向量检索
get_compressed_history(session), # 历史压缩
extract_query_entities(query), # 实体提取
)
return assemble_context(retrieved_docs, history_summary, entities, query)
9. 评估与可观测性
9.1 上下文质量指标
上下文工程的核心评估维度:
| 指标 | 含义 | 测量方式 |
|---|---|---|
| 相关性召回率 | 关键信息是否被注入上下文 | 对黄金答案做逆向检验 |
| 信息噪声比 | 上下文中无关信息的比例 | 人工标注抽样 |
| Token 效率 | 每有效 token 带来的质量提升 | 消融实验 |
| 位置敏感性 | 信息位置对答案质量的影响 | 随机打乱位置测试 |
| 压缩损失率 | 摘要后关键信息的丢失比例 | 信息保留率评估 |
9.2 上下文追踪设计
生产中每次请求的上下文应完整记录:
@dataclass
class ContextTrace:
request_id: str
timestamp: str
# 输入
system_prompt_hash: str # 不存全文,存哈希
retrieved_doc_ids: list[str] # 文档 ID,不存全文
history_rounds: int # 历史轮数
user_input_tokens: int
# 上下文构建
total_input_tokens: int
cache_hit: bool
context_build_ms: int # 构建耗时
# 输出
output_tokens: int
latency_ms: int
# 质量信号
user_feedback: Optional[str] # 用户反馈
auto_eval_score: Optional[float] # 自动评估分数
def log_context_trace(trace: ContextTrace):
# 写入可查询的存储(ClickHouse / BigQuery)
analytics_client.write(trace)
9.3 上下文变更的 A/B 测试框架
Prompt 和上下文策略变更必须通过 A/B 测试验证:
class ContextABTest:
def __init__(self, control_config: ContextConfig,
treatment_config: ContextConfig,
traffic_split: float = 0.1): # 10% 流量走新策略
self.control = control_config
self.treatment = treatment_config
self.split = traffic_split
def get_config(self, user_id: str) -> tuple[ContextConfig, str]:
# 用 user_id 做一致性哈希,同一用户始终走同一分支
bucket = hash(user_id) % 100
if bucket < self.split * 100:
return self.treatment, "treatment"
return self.control, "control"
def should_stop_early(self) -> bool:
"""统计显著性检验,防止持续暴露用户于更差的体验"""
stats = self.get_current_stats()
return stats.p_value < 0.05 and stats.effect_size < 0
10. 常见生产问题与根本解法
问题一:模型"忽略"了上下文中的关键信息
症状:上下文中明确提供了信息,但模型给出了错误或默认答案。
根本原因:
- 关键信息被埋在上下文中间(Lost in the Middle)
- 上下文太长,注意力被稀释
- 信息表述方式与模型期望不匹配
解法:
- 将关键信息移至 system prompt 开头或 user message 结尾
- 减少上下文总长度(删除不相关文档)
- 在 user message 中显式引用:"根据上面提供的[信息类型],请..."
- 使用结构化标记区分不同类型信息(XML 标签效果优于 Markdown)
# 效果更好的关键信息标记方式
<critical_constraint>
用户账户类型为"免费版",不能推荐付费功能。
</critical_constraint>
问题二:多轮对话中模型"忘记"了早期信息
症状:用户在前几轮说的重要条件,模型在后续轮次中没有考虑。
根本原因:早期对话被截断或摘要不完整。
解法:
- 实现关键信息的显式提取和注入(见第 5.2 节)
- 对用户在对话开始时设定的"约束"类信息,持久化到 system prompt 中
- 不要只做对话历史截断,要做结构化摘要
问题三:上下文冲突导致模型输出不稳定
症状:注入了多个文档,文档之间存在矛盾,模型输出随机在两种答案之间波动。
根本原因:模型没有足够指令来处理信息冲突。
解法:
- 在 system prompt 中添加冲突处理规则
- 对文档添加时间戳或权威性标注,让模型知道优先采信哪个
- 在 retrieval 阶段做去重和冲突检测,不将冲突文档一起注入
# system prompt 中的冲突处理规则
当提供的文档之间存在矛盾时:
1. 优先采信标注为"官方文档"的内容
2. 优先采信更新日期较近的内容
3. 如果无法判断,明确告知用户存在不一致,并列出两种说法
问题四:长文档 Agent 任务执行到中途上下文溢出
症状:Agent 任务失败,错误信息为 context length exceeded 或输出质量急剧下降。
根本原因:没有对工具结果做 token 控制,上下文随任务进行线性增长。
解法:
- 对所有工具结果设置 token 上限(见第 6.2 节)
- 实现检查点压缩机制(见第 6.3 节)
- 在任务开始前预估上下文增长,超过阈值时主动触发压缩
- 将大任务拆分为有明确边界的子任务,每个子任务独立维护上下文
问题五:Prompt 变更导致线上效果下降
症状:更新了 system prompt 后,部分用户反馈质量下降,但无法快速定位问题。
根本原因:没有基线评估集,无法量化变更影响。
解法:
- 维护覆盖主要场景的固定评估集(至少 100 条,含黄金答案)
- 每次 prompt 变更前后必须跑评估集对比
- 使用 LLM-as-Judge 做自动评分(但要对评分器本身做校验)
- 灰度发布:先对 5% 流量生效,监控关键指标 24 小时后再全量
附录:2026 年上下文工程技术栈参考
上下文管理库
| 工具 | 用途 | 成熟度 |
|---|---|---|
| LangChain Memory | 对话历史管理 | 生产可用 |
| LlamaIndex | 文档上下文管理 | 生产可用 |
| MemoryOS / Mem0 | 长期记忆管理 | 生产可用 |
| Letta (MemGPT) | 持久化 Agent 记忆 | 早期生产 |
Token 计算
| 工具 | 适用模型 |
|---|---|
| tiktoken | OpenAI 系列 |
| anthropic.count_tokens() | Claude 系列 |
| HuggingFace tokenizers | 开源模型 |
可观测性
| 工具 | 用途 |
|---|---|
| LangSmith | Prompt 追踪、评估、版本管理 |
| Langfuse | 开源替代,自托管友好 |
| Braintrust | 评估实验管理 |
| Weave (W&B) | 实验追踪 |
核心原则总结:上下文工程的本质是信息管理。在有限的 token 预算内,放入最相关的信息,排除噪声,并确保关键信息在模型注意力最强的位置。技术手段(压缩、检索、分区)都是服务于这个目标的工具。