上下文工程
上下文工程(Context Engineering)是提示工程的高阶延伸——不再只关注”怎么写 Prompt”,而是系统性地设计、组装和管理 LLM 上下文窗口里的全部信息。它是 LLM 应用生态概览中的核心工程能力,直接决定了 RAG、Agent、多轮对话等场景的成败。前置阅读:提示工程。
上下文工程 = 给 LLM 准备一份完美的”开卷考试资料包”。
LLM 的上下文窗口(context window,即模型单次能处理的最大 token 数量)就像一张有限大小的书桌(比如 128K token 的桌面)。你往桌上放的每一样东西——系统指令、用户问题、检索到的文档、历史对话、工具调用结果——都在争夺这块有限的空间。放对了,LLM 表现神勇;放错了,LLM 要么遗漏关键信息,要么被噪声淹没。
关键洞察:
- 位置很重要:LLM 对上下文开头和结尾的信息注意力更高,中间部分容易被忽略——这就是”迷失在中间”(Lost in the Middle)现象。重要的信息要放在开头或结尾。
- 顺序很重要:先给上下文再问问题,比先问问题再给上下文效果好——因为 LLM 是自回归模型(autoregressive,即从左到右逐个 token 生成,后面的 token 生成时能看到前面的全部信息),后面的 token 生成时能看到前面的信息。
- 密度很重要:无关信息会稀释关键信息的注意力。与其塞 10 篇相关度一般的文档,不如精选 3 篇最相关的。
- 结构很重要:用清晰的分隔符(XML 标签、Markdown 标题)把不同来源的信息分区,LLM 更容易定位。
2025 年趋势:随着 Gemini 支持 2M token 上下文窗口、Claude 支持 200K、GPT-4o 支持 128K,“能装”的问题大幅缓解。但”能用”的问题依然存在——研究反复表明,即使模型标注支持超长上下文,在 32K 以上就开始出现信息遗漏。2025 年的上下文工程重点从”怎么塞进更多”转向了”怎么让模型高效利用已有信息”,包括 Prompt Caching(上下文缓存)、Context Compression(上下文压缩)和智能检索策略。
注意力机制与上下文
Section titled “注意力机制与上下文”LLM 基于 Transformer 的自注意力机制(详见 Transformer 架构),理论上每个 token 都能”看到”上下文中的所有其他 token。自注意力(Self-Attention)的核心操作是:对序列中每个位置的 token,计算它与所有其他 token 的相关性分数(attention weight),然后加权求和得到该位置的上下文表示。但实际运行中注意力是不均匀的:
- 每一层、每个注意力头(attention head)都有有限的”注意力预算”。
- 当上下文很长时,注意力被稀释——关键信息如果混在大段无关文本中间,分配到的注意力就少了。
- 这就是为什么即使模型支持 128K 上下文,实际有效利用的信息可能远少于 128K。
KV Cache(Key-Value Cache) 是 Transformer 推理时的关键优化——把之前计算的 Key 和 Value 矩阵缓存起来,避免重复计算。上下文工程中的很多策略(如 Prompt Caching)本质上就是利用 KV Cache 来减少计算量和成本。详见 LLM 推理优化。
Lost in the Middle 效应
Section titled “Lost in the Middle 效应”研究(Liu et al. 2023)发现了一个普遍现象:当关键信息放在长上下文的中间位置时,模型的表现显著下降。具体表现:
- 信息在开头:表现最好(模型首先处理的信息,注意力分配最多)。
- 信息在结尾:表现也很好(最近的上下文,距离生成位置最近,注意力天然高)。
- 信息在中间:表现明显最差(被前面和后面的信息”夹击”,注意力分配最少)。
为什么会有这个效应? 这与 Transformer 的注意力分布有关——在长序列中,开头和结尾的 token 因为位置效应( positional bias,即注意力机制天然偏向序列边界的位置)获得更多注意力权重,而中间的 token 注意力被稀释。这个效应在大多数 Transformer 模型中普遍存在。
实践建议:把最重要的信息(任务指令、关键文档)放在上下文的开头或结尾,避免埋没在中间。
上下文组装的六大要素
Section titled “上下文组装的六大要素”一个生产级 LLM 应用的上下文通常包含:
- 系统提示词(System Prompt):定义角色、能力边界、输出格式。通常放在最前面,固定不变。这是整个上下文的”宪法”。
- 检索内容(Retrieved Context):来自 RAG(Retrieval-Augmented Generation,检索增强生成)的文档片段。详见 RAG 检索增强生成。
- 工具定义(Tool Definitions):可用工具的 JSON Schema 描述——告诉 LLM 有哪些函数可以调用、参数格式是什么。详见 MCP 协议与工具调用。
- 少样本示例(Few-shot Examples):引导输出格式和风格的输入-输出范例。详见提示工程。
- 对话历史(Conversation History):多轮对话的历史消息。
- 用户输入(User Query):当前轮次的实际问题。
这六类信息需要按特定的优先级和顺序排列,而不是随便堆在一起。
上下文压缩与管理策略
Section titled “上下文压缩与管理策略”当信息总量超过窗口限制(或为了控制成本和延迟)时,需要压缩和管理策略:
- 对话历史摘要:将早期对话用 LLM 摘要压缩成短文本,只保留近几轮完整对话。这是 ChatGPT 长对话的核心策略。例如,把前 20 轮对话压缩成 200 token 的摘要,加上最近 3 轮的完整对话。
- 检索后重排:用重排序模型(Reranker,一个专门训练来判断文档与查询相关性的模型)对检索结果按相关性重排,只保留 top-K。详见重排序模型。
- 滑动窗口:只保留最近 N 轮对话,更早的直接丢弃。最简单但最粗暴。
- 选择性保留:保留对话中的关键决策点和用户偏好,丢弃寒暄和中间过程。比滑动窗口更智能但需要额外的判断逻辑。
- 文档分块优化:控制每块的大小和重叠度(overlap,即相邻块之间共享的内容量),确保检索回来的内容密度高。详见向量数据库。
- 上下文压缩模型:2025 年出现了专门用于上下文压缩的技术——如 LLMLingua 系列,用一个小的 LLM 压缩长 prompt 中的冗余 token,可以在几乎不损失性能的情况下减少 50-80% 的 token 数量。
上下文缓存(Prompt Caching)
Section titled “上下文缓存(Prompt Caching)”部分推理引擎支持 Prompt Caching(上下文缓存):如果多次请求的上下文前缀完全相同(如很长的系统提示词 + 固定文档),引擎会缓存这部分中间计算结果(KV Cache,即 Transformer 推理时缓存的 Key-Value 矩阵,避免重复计算之前 token 的注意力),后续请求只需计算新增部分。
- Anthropic Claude 支持 Prompt Caching,可节省最高 90% 的输入 token 成本和 85% 的延迟(TTL 默认 5 分钟,支持 1 小时)。
- OpenAI 自动对重复前缀(1024 token 以上)进行缓存折扣,输入价格减半。
- Google Gemini 支持显式的 context caching,可以预缓存大量文档。
- 效果取决于上下文前缀的稳定性——把固定内容放前面,变化内容放后面。
2025 进展:Prompt Caching 已成为生产级 LLM 应用的标配。对于 RAG 系统、Agent 等需要大量固定上下文的场景,缓存可以将成本降低一个数量级。关键原则是稳定前缀 + 变化后缀——系统提示、工具定义、知识库文档等不变的内容放最前面,用户输入放最后。
长上下文模型的能力边界
Section titled “长上下文模型的能力边界”2025 年的顶级模型上下文窗口已经非常惊人:
| 模型 | 上下文窗口 | 约等于 |
|---|---|---|
| GPT-4o | 128K token | ~100K 英文单词 / ~一本小说 |
| Claude 3.5/4 Sonnet | 200K token | ~150K 英文单词 / ~一本半小说 |
| Gemini 1.5/2.0 Pro | 2M token | ~1500K 英文单词 / ~一部《战争与和平》 |
但研究表明,窗口大 ≠ 利用率高。在长上下文检索任务(Needle-in-a-Haystack,即在长文本中找到一个特定事实)中:
- 开头和结尾:几乎所有模型表现优秀(找到率 >95%)。
- 中间位置:多数模型在 64K 以上开始出现明显下降。
- 多事实检索:当需要在长上下文中同时找多个事实时,性能下降更显著。
最佳实践:即使有百万级窗口,也应该配合重排序和摘要策略——把最相关的信息放在开头和结尾,而不是盲目堆砌。
上下文窗口的信息布局
Section titled “上下文窗口的信息布局”上下文压缩策略对比
Section titled “上下文压缩策略对比”Lost in the Middle 效应
Section titled “Lost in the Middle 效应”上下文组装与 Token 管理
Section titled “上下文组装与 Token 管理”from anthropic import Anthropic
client = Anthropic()
# 上下文工程的实践:系统性地组装每一部分system_prompt = """你是一个技术文档助手。规则:1. 只基于提供的文档回答2. 找不到答案时说"文档中未提及"3. 回答末尾标注引用来源编号 [1] [2]格式: Markdown,代码用代码块包裹"""
# 检索到的文档(实际从向量数据库获取)retrieved_docs = [ {"id": 1, "text": "Transformer 使用自注意力机制处理序列..."}, {"id": 2, "text": "梯度下降是模型训练的核心优化算法..."},]
# 将文档用 XML 标签清晰分区(Claude 训练时见过大量 XML,分区效果好)context_block = "\n".join( f'<doc id="{d["id"]}">\n{d["text"]}\n</doc>' for d in retrieved_docs)
user_query = "Transformer 的核心机制是什么?"
# 组装完整消息(固定内容在前,变化内容在后——利于 Prompt Caching)messages = [{ "role": "user", "content": f"参考文档:\n{context_block}\n\n问题: {user_query}"}]
response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=system_prompt, messages=messages,)print(response.content[0].text)对话历史压缩
Section titled “对话历史压缩”import openai
client = openai.OpenAI()
def chat_with_compression(messages, max_history=6): """当对话过长时自动压缩早期历史""" if len(messages) <= max_history: return messages
# 把早期消息交给 LLM 生成摘要 old_msgs = messages[:-max_history] recent_msgs = messages[-max_history:]
summary_prompt = "请用 3-5 句话总结以下对话的关键信息:\n\n" for m in old_msgs: summary_prompt += f"{m['role']}: {m['content'][:200]}\n"
resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": summary_prompt}], max_tokens=300, ) summary = resp.choices[0].message.content
# 用摘要替代早期历史 return [ {"role": "system", "content": f"对话历史摘要: {summary}"} ] + recent_msgs
# 使用示例history = [ {"role": "user", "content": f"问题 {i}"}, {"role": "assistant", "content": f"回答 {i}"}, for i in range(10)]compressed = chat_with_compression(history)print(f"压缩前 {len(history)} 条 → 压缩后 {len(compressed)} 条消息")Prompt Caching 的最佳实践
Section titled “Prompt Caching 的最佳实践”from anthropic import Anthropic
client = Anthropic()
# 大量固定内容放在 system prompt 中,并启用缓存# cache_control: ephemeral 表示标记缓存边界system_prompt = [ { "type": "text", "text": """你是一个专业技术文档助手。规则:1. 只基于提供的文档回答2. 找不到答案时说"文档中未提及"3. 回答末尾标注引用来源编号 [1] [2]""", }, { "type": "text", "text": f"<knowledge_base>\n{load_large_kb()}\n</knowledge_base>", # 标记此处为缓存边界——前面的内容都会被缓存 "cache_control": {"type": "ephemeral"} }]
# 第一次调用:计算并缓存 system prompt 的 KV Cacheresponse1 = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=system_prompt, messages=[{"role": "user", "content": "什么是 Transformer?"}],)
# 第二次调用:system prompt 命中缓存,输入成本降低 90%response2 = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=system_prompt, # 完全相同的前缀 messages=[{"role": "user", "content": "什么是梯度下降?"}],)# response2 的 usage 中会显示 cache_read_input_tokens > 0Token 预算管理
Section titled “Token 预算管理”import tiktoken
def count_tokens(text: str, model: str = "gpt-4o") -> int: """精确计算文本的 token 数量""" enc = tiktoken.encoding_for_model(model) return len(enc.encode(text))
def budget_check(system_prompt, retrieved_docs, history, user_query, max_context=8000, output_budget=2000): """检查上下文是否超出预算,并给出分配建议""" available = max_context - output_budget parts = { "system_prompt": count_tokens(system_prompt), "retrieved_docs": sum(count_tokens(d) for d in retrieved_docs), "history": sum(count_tokens(m) for m in history), "user_query": count_tokens(user_query), } total = sum(parts.values()) print(f"Token 预算分配:") for name, count in parts.items(): pct = count / available * 100 bar = "█" * int(pct / 2) print(f" {name:20s} {count:6d} ({pct:5.1f}%) {bar}") print(f" {'TOTAL':20s} {total:6d} / {available}") if total > available: print(f" ⚠️ 超出预算 {total - available} token!需要压缩。") return total <= available
# budget_check(system_prompt, docs, history, query)- 分层放置:把最稳定的固定内容(系统提示词、工具定义)放在最前面,把变化的用户输入放在最后面。这不仅有利于 Lost-in-the-Middle 效应,还能最大化 Prompt Caching 的收益。
- 用分隔符分区:使用 XML 标签(
<doc>...</doc>)或清晰的 Markdown 标题来分隔不同来源的内容。Claude 官方推荐 XML 格式,因为它训练时见过大量 XML 结构化文档;GPT 系列对 Markdown 标题和 XML 都支持良好。 - 少即是多:不要一股脑把检索结果全塞进上下文。重排序后只取 top-3 到 top-5 最相关的片段,比塞入 20 篇文档效果好得多——信息密度比信息量更重要。
- 显式引用而非隐式记忆:不要期望 LLM”记住”前面说过的规则。关键约束在最后再提醒一次(“再次强调:不要编造,只基于文档回答”),效果远好于只在开头说一次。
- Token 预算管理:用 Tokenizer(分词器,将文本拆分为模型能处理的 token 序列的工具)精确计算各部分 token 占用。一个常见的预算分配:系统提示词 500 token,检索内容 2000-4000 token,历史 1000-2000 token,留足输出空间。详见 Tokenizer 分词器详解。
- 长上下文 ≠ 无限有效:即使模型标注支持 128K/200K/2M 上下文,实际在 32K 以上就开始出现信息遗漏。长上下文要配合重排序和摘要策略,不能无脑堆砌。
- 利用 Prompt Caching:对于 RAG 系统、Agent 等需要大量固定上下文的场景,Prompt Caching 可以将成本降低一个数量级。关键是保持前缀稳定——把不变的 system prompt、工具定义、知识库放在最前面。
- Agent 场景要主动清理:Agent 的思考链、工具调用结果、观察记录会快速膨胀上下文。每隔几步做一次摘要压缩,只保留关键决策和最新状态。详见 Agent 设计模式。
- 多轮对话系统:ChatGPT、Claude 等的长对话都依赖历史摘要 + 滑动窗口来管理上下文。ChatGPT 在对话过长时会自动提示”之前的对话已被压缩”。详见提示工程。
- RAG 系统:检索内容如何组装进上下文直接决定回答质量。2025 年的先进 RAG 系统不仅做向量检索,还做重排序、去重、上下文压缩,确保进入上下文的每一条信息都是高密度的。详见 RAG 检索增强生成。
- AI Agent:Agent 的思考链、工具调用结果、观察记录都需要上下文管理策略,否则几轮循环后上下文就满了。Agent 框架(如 LangGraph)提供了状态管理(state management)和消息裁剪(message trimming)功能。详见 AI Agent 与多智能体。
- AI 编程助手:代码文件、函数定义、项目约定等信息如何高效塞入上下文。Cursor 的 Codebase Indexing 本质上是上下文工程在编程场景的应用。详见 AI 编程助手。
- AI 搜索引擎:多步检索的结果如何压缩和重组。AI 搜索需要在有限上下文里容纳多个网页的提取内容,压缩和去重至关重要。详见 AI 搜索引擎。
典型类库与工具
Section titled “典型类库与工具”| 类库 | 语言 | 说明 |
|---|---|---|
| tiktoken | Python | OpenAI 的分词器(tokenizer),用于精确计算各部分 token 占用 |
| langchain ConversationSummaryMemory | Python | LangChain 内置的对话历史摘要管理组件 |
| llama-index Context | Python | LlamaIndex 的上下文管理模块,支持节点压缩和重排 |
| anthropic Prompt Caching | API | Claude API 的上下文缓存功能,大幅降低重复前缀的成本 |
| LLMLingua | Python | 微软的上下文压缩工具,用小模型压缩长 prompt 中的冗余 token |
| lettuceDetect / trim_messages | Python | 开源工具,自动检测和裁剪超出窗口限制的历史消息 |
| Mem0 / Letta | Python | 开源记忆层框架,提供跨会话持久化记忆和上下文管理 |
| 术语 | 英文 | 解释 |
|---|---|---|
| 上下文窗口 | Context Window | LLM 单次能处理的最大 token 数量,如 GPT-4o 为 128K,Gemini 2.0 为 2M |
| 上下文工程 | Context Engineering | 系统性设计和管理上下文窗口内容的工程方法论 |
| 迷失在中间 | Lost in the Middle | LLM 对长上下文中间位置信息利用率显著下降的现象 |
| 注意力稀释 | Attention Dilution | 上下文越长,每个 token 分配到的注意力越少 |
| 对话历史摘要 | Conversation Summary | 将早期对话压缩为短文本摘要以节省上下文空间 |
| 滑动窗口 | Sliding Window | 只保留最近 N 轮对话,丢弃更早的历史 |
| 提示词缓存 | Prompt Caching | 缓存重复上下文前缀的 KV Cache 以加速推理和降低成本 |
| 上下文压缩 | Context Compression | 通过摘要、过滤或压缩模型等手段减少上下文中的冗余信息 |
| KV 缓存 | KV Cache | Transformer 推理时缓存已计算的 Key/Value 矩阵以避免重复计算 |
| 自回归 | Autoregressive | 模型从左到右逐个 token 生成,每个 token 依赖之前所有的 token |
| Token | Token | LLM 处理文本的最小单位,大致 1 个英文单词 ≈ 1-2 个 token |
| 重排序 | Reranking | 用专门的模型对检索结果按与查询的相关性重新排序 |
- Liu et al.,「Lost in the Middle: How Language Models Use Long Contexts」(2023):经典论文,系统性地揭示了 LLM 在长上下文中”迷失在中间”的现象,是上下文工程的理论基础。
- Anthropic,「Prompt Caching」官方文档:Claude 上下文缓存功能的官方指南,详细说明缓存机制和最佳实践。
- Jiang et al.,「LongLLMLingua: Accelerating and Enhancing LLMs in Long Context Scenarios via Prompt Compression」(2024):微软的上下文压缩技术,在几乎不损失性能的情况下大幅减少 token 使用。
- OpenAI,「Practices for growing LLM applications」:OpenAI 工程博客,讨论如何管理多轮对话上下文和缓存策略。
- LangChain Memory 文档:LangChain 对话记忆模块文档,包含滑动窗口、摘要、向量存储等多种上下文管理策略的代码示例。
- Peng et al.,「Yarn: Efficient Context Window Extension」(2023):讨论如何扩展 LLM 的上下文窗口长度,以及长上下文下注意力分布的变化。
- Google,「Gemini 1.5: Unlocking Million-Token Context」技术报告 (2024):Google 展示 Gemini 百万 token 上下文能力的技术报告,包含 Needle-in-a-Haystack 评测结果。