当你的 AI 应用从 Demo 走向生产,你会发现一个残酷的事实:Prompt Engineering 只是冰山一角,真正决定输出质量的是 Context Engineering(上下文工程)。Anthropic 联合创始人在 2025 年指出,80% 的 AI 应用失败不是因为模型不够好,而是喂给模型的上下文质量太差。本文将从工程实践角度,拆解如何系统性地构建高质量上下文窗口。
🎯 一、什么是 Context Engineering?为什么它比 Prompt Engineering 更重要?
从 Prompt 到 Context 的思维转变
Prompt Engineering 关注的是"怎么问"——措辞、格式、few-shot 示例。而 Context Engineering 关注的是"问什么"——在有限的上下文窗口里,放哪些信息、以什么顺序、用什么格式。
📌 记住: LLM 的上下文窗口就像你大脑的工作记忆——容量有限,每一条信息都在竞争有限的注意力资源。Context Engineering 就是决定哪些信息能进入这个"工作记忆"的工程学科。
举个例子:你有一个客服 AI 应用,上下文窗口是 128K tokens。你需要在每次请求中塞入:
- 系统提示词(System Prompt):角色定义、行为约束
- 知识库检索结果(RAG):产品文档、FAQ
- 对话历史(Chat History):用户之前的多轮对话
- 用户画像(User Context):订单信息、偏好设置
- 工具定义(Tool Definitions):可调用的 API 描述
- 当前用户输入(User Query)
这些内容加起来很容易超过 128K,而且信息的排列顺序、压缩方式、筛选策略直接影响输出质量。这就是 Context Engineering 要解决的问题。
上下文窗口的注意力衰减效应
研究表明,LLM 对上下文中不同位置的信息注意力分布极不均匀:
注意力分布示意(非精确数据,仅表示趋势):
位置 注意力权重
─────────────────────────
系统提示词 ████████████ 高
前几轮对话 █████████ 中高
中间部分 ████ 低("Lost in the Middle" 问题)
最近几轮对话 ████████████ 高
用户最新输入 █████████████ 最高
⚠️ 警告: 千万不要把关键信息放在上下文的中间位置——LLM 对中间位置的信息关注度最低,这就是著名的 “Lost in the Middle” 问题。重要信息要么放开头,要么放末尾。
🔧 二、上下文构建的四大核心策略
策略一:上下文压缩(Context Compression)
上下文压缩是最基本也最有效的优化手段。核心思路:用更少的 token 传达同样多的信息。
方法 1:对话历史摘要
// ❌ 错误做法:保留完整对话历史
const fullHistory = messages.map(m => ({
role: m.role,
content: m.content // 原样保留,可能已经累积了 50 轮对话
}));
// ✅ 正确做法:对旧对话做摘要压缩
async function compressHistory(messages, maxRecentMessages = 6) {
if (messages.length <= maxRecentMessages) {
return messages; // 消息少,不压缩
}
// 保留最近 N 轮完整对话
const recentMessages = messages.slice(-maxRecentMessages);
// 对更早的对话做摘要
const oldMessages = messages.slice(0, -maxRecentMessages);
const summaryPrompt = `请将以下对话压缩为关键要点摘要,保留所有重要决策、用户偏好和未完成的任务:
${oldMessages.map(m => `${m.role}: ${m.content}`).join('\n')}`;
const summary = await llm.call({
messages: [{ role: 'user', content: summaryPrompt }],
max_tokens: 500
});
return [
{ role: 'system', content: `[对话摘要]\n${summary}` },
...recentMessages
];
}
方法 2:检索结果重排序与去重
# RAG 检索结果去重与精简
def deduplicate_and_trim(chunks: list[dict], max_tokens: int = 4000) -> list[dict]:
"""去重并控制总 token 数"""
seen_hashes = set()
result = []
total_tokens = 0
# 按相关性分数降序排列
sorted_chunks = sorted(chunks, key=lambda x: x['score'], reverse=True)
for chunk in sorted_chunks:
content_hash = hash(chunk['content'][:200]) # 前 200 字符做指纹
if content_hash in seen_hashes:
continue
chunk_tokens = count_tokens(chunk['content'])
if total_tokens + chunk_tokens > max_tokens:
break
seen_hashes.add(content_hash)
result.append(chunk)
total_tokens += chunk_tokens
return result
💡 提示: 实测表明,对 50 轮对话做摘要压缩后,token 消耗可降低 70%,而回答质量仅下降约 5%。这是性价比最高的优化手段。
策略二:动态上下文注入(Dynamic Context Injection)
不要把所有上下文一股脑塞进去——根据用户意图动态选择注入哪些上下文。
// 根据用户意图动态构建上下文
async function buildDynamicContext(userQuery, userId) {
const contextParts = [];
// 第一步:意图识别
const intent = await classifyIntent(userQuery);
// intent: 'order_query' | 'product_question' | 'complaint' | 'general'
// 第二步:根据意图注入不同上下文
if (intent === 'order_query') {
const orders = await getUserOrders(userId, limit = 3);
contextParts.push({
role: 'system',
content: `[用户订单信息]\n${formatOrders(orders)}`
});
}
if (intent === 'product_question') {
const docs = await searchKnowledgeBase(userQuery, topK = 3);
contextParts.push({
role: 'system',
content: `[相关产品文档]\n${docs.map(d => d.content).join('\n\n')}`
});
}
if (intent === 'complaint') {
const history = await getComplaintHistory(userId);
const sentiment = await analyzeSentiment(userQuery);
contextParts.push({
role: 'system',
content: `[用户情绪: ${sentiment}]\n[历史投诉记录]\n${history}`
});
}
return contextParts;
}
策略三:上下文缓存(Context Caching)
这是降低 API 成本的关键技术。以 Anthropic 的 Prompt Caching 为例:
# 使用 Anthropic API 的缓存标记
# 被标记为 cache_control 的内容会被缓存,后续请求命中缓存时
# 输入 token 成本降低 90%,且首 token 延迟大幅降低
// Anthropic API 缓存示例
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
system: [
{
type: 'text',
text: longSystemPrompt, // 很长的系统提示词(如 5000 tokens)
cache_control: { type: 'ephemeral' } // 标记为可缓存
}
],
messages: messages
});
// 缓存命中后的成本对比:
// 未缓存:$3.00 / 1M input tokens
// 缓存命中:$0.30 / 1M input tokens(节省 90%)
// 缓存写入:$3.75 / 1M input tokens(首次略贵)
策略四:结构化上下文模板
用 XML 或 Markdown 明确分隔不同类型的上下文信息,让模型更容易理解和区分:
# 系统提示词结构化模板
<role>
你是一个专业的技术客服助手,负责解答产品使用问题。
</role>
<constraints>
- 只回答与产品相关的问题
- 不确定时说"我不确定,建议联系人工客服"
- 回答要简洁,控制在 200 字以内
</constraints>
<context type="knowledge_base">
[检索到的相关文档内容]
</context>
<context type="user_profile">
[用户信息和历史行为]
</context>
<context type="conversation_summary">
[之前的对话摘要]
</context>
<context type="current_conversation">
[最近几轮完整对话]
</context>
💰 三、成本与质量的平衡实战
各策略的成本影响对比
| 策略 | Token 节省 | 实现复杂度 | 质量影响 | 推荐场景 |
|---|---|---|---|---|
| 对话摘要压缩 | 60-70% | ⭐⭐ | 轻微下降 | 长对话场景 |
| 意图分类后注入 | 40-50% | ⭐⭐⭐ | 无影响甚至提升 | 多功能 AI 应用 |
| Prompt Caching | 90%(缓存部分) | ⭐ | 无影响 | 长系统提示词 |
| 结构化模板 | 0%(甚至略增) | ⭐ | 显著提升 | 所有场景 |
| RAG 结果重排序 | 20-30% | ⭐⭐ | 提升 | 知识库问答 |
⚡ 关键结论: Prompt Caching 是成本优化的第一优先级,实现最简单且不影响质量。对话摘要是长对话的必备方案。结构化模板是最容易被忽视但效果最显著的优化。
实战:组合策略的完整实现
// 完整的上下文构建 Pipeline
class ContextPipeline {
constructor(config) {
this.maxTokens = config.maxContextTokens || 16000;
this.recentMessagesCount = config.recentMessages || 8;
this.ragTopK = config.ragTopK || 5;
this.ragMaxTokens = config.ragMaxTokens || 3000;
}
async build(userQuery, conversationHistory, userId) {
const budget = new TokenBudget(this.maxTokens);
// 1. 系统提示词(固定开销,最高优先级)
const systemPrompt = this.buildSystemPrompt();
budget.allocate('system', countTokens(systemPrompt));
// 2. 意图识别 + 动态上下文注入
const intent = await this.classifyIntent(userQuery);
const dynamicContext = await this.getDynamicContext(intent, userId);
budget.allocate('dynamic', countTokens(dynamicContext));
// 3. RAG 检索(受剩余预算约束)
const remainingForRag = budget.remaining() * 0.4; // 40% 给 RAG
const ragResults = await this.retrieveAndRank(userQuery, remainingForRag);
// 4. 对话历史(受剩余预算约束)
const remainingForHistory = budget.remaining();
const compressedHistory = await this.compressHistory(
conversationHistory,
remainingForHistory
);
// 5. 组装最终上下文
return this.assembleContext({
system: systemPrompt,
dynamic: dynamicContext,
rag: ragResults,
history: compressedHistory,
query: userQuery
});
}
}
⚠️ 四、常见陷阱与避坑指南
陷阱 1:上下文污染
当你把多个文档检索结果塞进上下文时,如果其中有互相矛盾的信息,模型会"精神分裂"——回答前后矛盾。
⚠️ 警告: RAG 检索结果必须做冲突检测。如果两个文档对同一问题给出不同答案,要么丢弃低置信度的那个,要么在提示词中明确告诉模型存在冲突并要求它说明。
陷阱 2:上下文膨胀(Context Bloat)
很多开发者习惯把"可能用到的信息"都塞进上下文,导致关键信息被稀释。
❌ 错误做法:塞入所有相关文档(8000 tokens)
→ 模型注意力被分散,回答变得泛泛而谈
✅ 正确做法:只塞入最相关的 3 条结果(2000 tokens)
→ 模型注意力集中,回答精准且有深度
陷阱 3:忽视上下文的顺序效应
同样的信息,放在不同位置效果差异巨大。经过大量实验验证的最佳顺序是:
- 系统角色定义(固定)
- 关键约束和规则(固定)
- RAG 检索结果(动态,按相关性排序)
- 用户画像/工具信息(动态)
- 对话历史摘要(如果有)
- 最近几轮对话(固定保留)
- 用户当前输入(固定在最后)
陷阱 4:没有做上下文预算管理
// ❌ 错误做法:没有预算管理
const context = [
...systemPrompt, // 2000 tokens
...ragResults, // 8000 tokens
...userProfile, // 500 tokens
...chatHistory, // 15000 tokens ← 爆了!
userQuery
];
// 总计 25500 tokens,可能超过模型限制
// ✅ 正确做法:Token 预算管理
class TokenBudget {
constructor(total) {
this.total = total;
this.allocated = 0;
this.breakdown = {};
}
allocate(category, tokens) {
if (this.allocated + tokens > this.total) {
throw new Error(`预算溢出: ${category} 需要 ${tokens},剩余 ${this.remaining()}`);
}
this.allocated += tokens;
this.breakdown[category] = tokens;
}
remaining() {
return this.total - this.allocated;
}
}
✅ 总结与建议
Context Engineering 不是一个单独的技术,而是一套系统性的工程方法论。以下是按优先级排列的实践建议:
- ✅ 立即实施:结构化上下文模板 — 零成本,效果立竿见影
- ✅ 尽快实施:Prompt Caching — 成本直接降 90%(缓存部分)
- ✅ 重要优化:对话历史摘要 — 长对话场景必备
- ✅ 架构层面:动态上下文注入 — 按需加载,避免信息过载
- ✅ 工程规范:Token 预算管理 — 防止上下文溢出的保底方案
Context Engineering 是 AI 应用从"能用"到"好用"的关键分水岭。模型能力在快速提升,但如果喂给模型的上下文质量不行,再强的模型也发挥不出应有的水平。把 80% 的工程精力花在上下文质量上,而不是提示词措辞上——这是我的核心建议。
推荐工具
- 🔧 LangChain / LlamaIndex:提供完整的 RAG 和上下文管理 Pipeline
- 🔧 Anthropic Prompt Caching:官方支持的上下文缓存方案
- 🔧 tiktoken:OpenAI 的 Token 计数工具,用于预算管理
- 🔧 jsjson.com JSON 格式化工具:调试 API 响应和上下文结构时的利器