上下文工程入门:让大模型应用稳定工作的第一层架构

0阅读12分钟

从一次翻车说起

我在做一个内部知识库问答系统时,遇到过一件典型的事。第一版上线后,产品经理反馈:模型经常「答非所问」——用户问的是上个月的政策,它却引用了三年前的旧文件;用户要求只查技术文档,它把销售材料也翻出来了。

当时的第一反应是改 Prompt。我把系统提示词从 200 字扩到 800 字,加了各种约束:「你必须只使用最近三个月的文档」「你必须标注来源和时间」「如果不确定请回答不知道」。改完之后,某些场景好了,另一些场景又坏了。约束加得越多,模型越容易忽略其中某几条。

后来我停下来重新审视整个信息流,才发现问题不在 Prompt 文案上。模型看到的上下文本身就有结构性缺陷——检索结果没有时间过滤、没有来源标注、没有权限区分,历史对话和当前问题混在一起,系统规则和用户指令的边界模糊。Prompt 写得再好,也救不了信息组织层面的混乱。

这就是上下文工程要解决的问题。Anthropic 在 2025 年 9 月发表的工程博客里给出了一个精确的定义:上下文工程是「在 LLM 推理期间,策划和维护最优 token 集合的实践」[1]。它不是单次提示词技巧,而是关于信息进入模型之前的结构设计。

Prompt 工程与上下文工程的边界

2025 年中,「上下文工程」这个词开始在工程社区频繁出现。Andrej Karpathy 当时有一个广为流传的比喻:「LLM 是 CPU,上下文窗口是 RAM」[2]。这个比喻抓住了核心区别——Prompt 工程关注的是你对模型说了什么,上下文工程关注的是模型在回答时能「看到」什么。

维度Prompt 工程上下文工程
关注点单次输入的措辞和结构模型推理时看到的完整信息集合
作用范围一次调用多轮对话、多工具调用的全生命周期
核心问题怎么问模型知道什么、为什么应该关心
数据生命周期临时的、任务级的持久的、架构级的
失败表现输出质量不稳定输出「自信地错误」——格式完美但引用了过期或无关的信息
团队定位通常由写 Prompt 的人负责需要作为一等工程系统来建设

这两者不是替代关系。Prompt 工程是上下文工程的一个子集——你仍然需要写好指令,但好指令必须建立在好信息之上。unblocked 的工程博客有一个判断我觉得很准确:「Prompt 工程指挥演员的表演,上下文工程搭建表演的舞台」[3]。

为什么信息堆得越多,效果反而越差

很多人直觉上认为,模型能看到的上下文窗口越大越好。GPT-4.1 支持 100 万 token,Claude Sonnet 4 支持 20 万标准 token(Beta 可达 100 万),Gemini 1.5 Pro 甚至能处理 200 万 token。但这些数字容易造成错觉——能把 100 万 token 塞进去,不代表应该这么做。

注意力预算

Transformer 架构中,每个 token 都会和其他所有 token 建立注意力关系。n 个 token 意味着 n² 量级的关系计算。Redis 的工程博客引用了一项研究:当上下文长度达到 15,000 词时,推理延迟增加超过 7 倍 [4]。

更关键的是质量问题。Anthropic 用「注意力预算」来解释这个现象——模型的注意力是一种有限资源,随着上下文增长被逐步消耗 [1]。这就是「上下文腐化」(context rot):模型在简短基准上表现良好,但随着输入长度增加,在需要跨文档推理的复杂任务上性能持续下降。

中间丢失效应

2023 年斯坦福的论文揭示了一个至今仍然有效的问题:模型对上下文开头和结尾的信息关注度更高,中间部分容易被忽略 [5]。这意味着如果你把关键业务规则放在检索文档的第 7 条,而用户问题和系统提示占了上下文的前 30%,这条规则大概率会被模型跳过。

有效上下文长度

模型 advertised 的最大上下文长度和实际有效长度之间有明显差距。一项 2024 年的研究表明,许多模型在上下文接近上限时不仅性能递减,甚至会出现回退——回答准确率反而低于较短上下文的情况 [6]。一个经验法则是把上下文使用控制在最大 token 数的 75% 以内,给模型留出生成空间 [7]。

上下文分层的工程实践

理解了这些限制之后,回到工程层面。一个可维护的上下文结构需要分层管理,每层有不同的生命周期和来源。

五层模型

我参考 Anthropic 的建议和自身项目经验,把上下文分成五层:

层级内容生命周期典型来源
系统层角色定义、输出约束、安全边界、不可覆盖规则全局稳定,按版本发布配置文件、代码仓库
任务层当前用户目标、输入材料、输出格式单次任务用户输入、前端参数
数据层检索结果、业务对象、权限过滤后的记录按查询动态获取向量数据库、业务 API
状态层用户偏好、历史摘要、当前流程进度跨轮次持久化,定期压缩对话历史、用户画像
工具层函数调用结果、错误信息、可继续操作的选项单次调用,用完即弃工具执行引擎

这个分层的核心不是技术实现,而是回答一个问题:这条信息为什么在这里,它应该在什么时候消失?

设计上下文之前,可以先回答五个判断问题:

  1. 哪些规则是全局稳定的,哪些只对当前任务有效?
  2. 哪些用户信息可以长期保存,哪些只能在一次会话中使用?
  3. 检索材料是否有来源、时间和权限边界?
  4. 历史对话是否全部有用,是否需要摘要或裁剪?
  5. 工具返回的数据是否需要再加工,还是直接交给模型?

把这五个问题想清楚,分层结构就自然出来了。

上下文组装流程

下面的流程图展示了一个典型的上下文组装过程。用户请求到达后,系统并行获取不同层级的信息,经过裁剪和排序后组装成最终的上下文:

流程图画布 · 115%
Mermaid 流程图加载中...

这里有两个工程细节值得展开。

第一个是「即时加载」vs「预加载」。Anthropic 把前者称为 just-in-time context [1]——系统不预先把所有可能相关的文档塞进上下文,而是维护轻量级的索引标识(文件路径、查询语句、URL),在需要时通过工具动态拉取。Claude Code 就是这个思路:配置文件在启动时加载,但具体文件内容通过 grep、glob 等工具按需获取。这样做的代价是增加了运行时的探索步骤,但保持了工作记忆的聚焦。

第二个是位置策略。既然中间丢失效应是客观存在的,那上下文里各部分的排列顺序就需要刻意设计。我的做法是把系统规则和当前用户问题放在最前面(高注意力区域),检索到的参考数据放在中间(低注意力区域),输出格式要求和最新的工具调用结果放在最后(高注意力区域)。

三个实际案例

案例一:知识库问答的时间错位

回到开头那个知识库问答系统。用户问「最新的远程办公政策是什么」,模型引用了一份 2023 年的文件。原因是检索层只做了语义匹配,没有时间过滤。语义上,2023 年的文件和 2026 年的文件可能都很「相关」,但用户要的是最新的。

修复方案不是改 Prompt 里加一句「请使用最新文档」,而是在数据层加了两道处理:

# 修复前:只靠语义检索,没有时间过滤
results = vector_db.search(query, top_k=10)
 
# 修复后:语义检索 + 时间权重 + 来源过滤
results = vector_db.search(
    query,
    top_k=10,
    filters={
        "updated_at": {"$gte": "2026-01-01"},  # 时间硬过滤
        "department": user_department,           # 权限过滤
    },
    # 对检索结果按时间衰减加权
    recency_boost={
        "field": "updated_at",
        "decay_rate": 0.1,  # 每月衰减 10%
    }
)

同时,每条检索结果在传入上下文时附带结构化元数据:

# 修复前:只传文本内容
context = "\n".join([doc.text for doc in results])
 
# 修复后:附带来源、时间、权限标记
context = "\n\n".join([
    f"[来源: {doc.source} | 更新时间: {doc.updated_at} | 部门: {doc.department}]\n{doc.text}"
    for doc in results
])

这个改动的关键不在代码本身,而在于思路转变——把时间、来源、权限这些「元信息」从隐式变成显式,让模型有能力判断信息的时效性和适用范围。

案例二:多轮对话中的状态膨胀

一个客服对话场景。用户和模型聊了 20 轮之后,上下文里堆积了大量历史消息。问题不只是 token 数量增加带来的延迟和成本,更关键的是模型在多轮之后开始「忘记」早期的重要约束。比如用户在第 3 轮说过「我是企业版用户」,到了第 18 轮模型给出的方案却变成了个人版。

这是因为对话历史在上下文中占据了过多空间,把早期信息推到了注意力低谷区域。

# 修复前:全量历史传入
messages = conversation_history  # 20 轮全量
messages.append(current_user_message)
response = llm.chat(messages)
 
# 修复后:分层处理历史
def build_context(conversation, current_message):
    # 最近 3 轮保留原文
    recent = conversation[-3:]
 
    # 更早的对话生成结构化摘要
    older = conversation[:-3]
    summary = llm.summarize(
        older,
        instructions="保留:用户身份、核心需求、已确认的约束条件、未解决的问题。丢弃:寒暄、重复确认、已解决的细节讨论。"
    )
 
    return [
        {"role": "system", "content": system_prompt},
        {"role": "system", "content": f"历史摘要:{summary}"},
        *recent,
        current_message,
    ]

Anthropic 把这种策略称为 compaction——在上下文接近限制时,将对话压缩为高保真摘要,然后用压缩后的内容重新初始化上下文窗口 [1]。关键是在压缩时要保留架构决策、未解决的问题和关键约束,丢弃冗余的输出和重复的确认。

案例三:工具调用结果的噪声控制

一个数据分析 Agent,用户让它「分析上个月的销售趋势」。Agent 调用了数据库查询工具,返回了一个包含 500 行的原始结果集。这个结果集直接塞进上下文,占了 8000 个 token。模型花了大量注意力处理这些原始数据,最后给出的分析反而不如预期——因为关键的趋势信息被淹没在逐行数据里了。

# 修复前:工具结果原样传入
def analyze_sales(query):
    raw_data = db.query(query)  # 返回 500 行
    context = f"查询结果:\n{raw_data}"  # 8000 token
    return llm.generate(context + "\n请分析销售趋势")
 
# 修复后:工具层做预加工
def analyze_sales(query):
    raw_data = db.query(query)
    # 工具层聚合,而非模型层
    summary = {
        "total_rows": len(raw_data),
        "date_range": f"{raw_data.min_date} ~ {raw_data.max_date}",
        "daily_trend": raw_data.groupby("date").agg({
            "revenue": "sum",
            "orders": "count"
        }).to_dict(),
        "top_products": raw_data.nlargest(5, "revenue")[["product", "revenue"]].to_dict(),
        "anomalies": detect_anomalies(raw_data),
    }
    context = f"销售数据摘要(共 {summary['total_rows']} 条):\n{json.dumps(summary, ensure_ascii=False)}"
    # 约 1200 token,减少了 85%
    return llm.generate(context + "\n请分析销售趋势")

这个案例的原则是:工具返回的数据应该在工具层做加工和压缩,只把模型需要的信息传进去。模型擅长从摘要中发现规律,不擅长从 500 行原始数据里数数。

案例四:位置策略对规则遵循的影响

一个代码审查 Agent,系统提示里有 8 条审查规则。测试时发现模型总是遗漏第 5、6 条规则——恰好位于系统提示的中间位置。这不是规则写得不好,而是中间丢失效应在起作用。

# 修复前:规则按逻辑顺序平铺,中间规则被忽略
system_prompt = """你是一个代码审查 Agent。请遵循以下规则:
1. 检查安全漏洞
2. 检查性能问题
3. 检查类型安全
4. 检查错误处理
5. 检查日志规范        ← 模型经常跳过
6. 检查命名约定        ← 模型经常跳过
7. 检查测试覆盖
8. 检查文档更新
"""
 
# 修复后:按重要性分组,关键规则放在开头和结尾
system_prompt = """你是一个代码审查 Agent。
 
【最高优先级规则】
- 检查安全漏洞(必须每次执行)
- 检查性能问题(必须每次执行)
 
【审查规则】
- 检查类型安全
- 检查错误处理
- 检查日志规范
- 检查命名约定
- 检查测试覆盖
 
【输出要求】(必须遵守)
- 按严重程度排序输出发现的问题
- 每个问题必须包含文件位置和修复建议
- 文档更新检查作为最后一项确认
"""

把关键规则从「平铺在中间」变成「分组放在首尾」,第 5、6 条规则的遵循率从约 40% 提升到接近 100%。代码没变,模型没变,只是信息在上下文中的位置变了。

上下文工程 vs 传统数据管道的区别

对比维度传统数据管道上下文工程
数据消费者前端 UI、报表系统LLM 推理引擎
格式要求结构化 JSON/表格自然语言 + 结构化混合
质量判断标准数据完整性、一致性信息信号密度(每 token 对输出的贡献)
容量约束数据库存储、网络带宽上下文窗口 token 上限 + 注意力预算
失败模式数据缺失、类型错误信息过载导致注意力分散、中间信息丢失
优化方向减少查询延迟、提高吞吐在有限 token 内最大化信息价值

这张对比表想说明的是,上下文工程不是「把数据查出来扔给模型」这么简单。它要求你用一种新的视角看数据——每个 token 都有成本(延迟、费用、注意力消耗),你需要判断哪些 token 值得进入上下文。

不同场景下的策略选择

不是所有任务都需要复杂的上下文系统。下面按场景复杂度给出策略建议:

场景特征推荐策略上下文复杂度示例
输入短、规则少、输出可人工检查简单 Prompt 模板单轮翻译、文本分类
需要外部知识但规则简单Prompt + 单次 RAG中低产品FAQ问答
多轮对话、有用户状态Prompt + 历史管理 + RAG客服对话、代码助手
多工具调用、长任务链分层上下文 + compaction + 子代理代码 Agent、研究助手
跨会话、需要长期记忆分层上下文 + 持久化笔记 + 子代理很高个人 AI 助理、项目管理助手

Anthropic 总结了三种应对长任务的主要策略 [1]:

  • Compaction(压缩):适合需要大量来回对话的场景。在上下文接近限制时生成摘要,重新初始化窗口。
  • 结构化笔记(Agentic Memory):适合有明确里程碑的迭代任务。Agent 定期把关键信息写入外部持久存储,需要时读回。Claude playing Pokémon 的案例里,Agent 在没有显式记忆指令的情况下,自发维护了探索地图和战斗策略笔记 [1]。
  • 子代理架构(Sub-Agent):适合复杂研究和并行分析。专门的子代理用干净的上下文处理子任务,只向主代理返回压缩后的摘要。
策略最适合主要风险
Compaction对话密集型任务压缩过度导致丢失微妙但关键的信息
结构化笔记迭代开发、长期任务笔记更新不及时导致信息过期
子代理并行研究和复杂分析子代理之间信息同步开销

上下文工程实施检查清单

下面是一份可以直接用于项目审查的检查清单:

结构设计

  1. 上下文是否按系统层、任务层、数据层、状态层、工具层分层管理?
  2. 每一层的信息是否有明确的来源和生命周期?
  3. 系统层规则是否和用户输入做了明确边界隔离?

信息质量

  1. 检索结果是否附带来源、时间、权限等元数据?
  2. 工具返回的数据是否在工具层做了预加工,而非原样传入?
  3. 是否有多余的、重复的、过期的信息占据了上下文空间?

容量管理

  1. 上下文总 token 数是否控制在模型最大窗口的 75% 以内?
  2. 是否实现了对话历史的压缩或摘要机制?
  3. 当上下文接近预算上限时,是否有明确的裁剪优先级?

位置策略

  1. 最关键的规则和约束是否放在上下文的开头或结尾(高注意力区域)?
  2. 检索到的参考文档是否避免了冗长的中间堆放?

可观测性

  1. 是否跟踪了每次调用的 token 消耗分布(系统 / 检索 / 历史 / 工具各占多少)?
  2. 是否有 Trace 机制能追踪某条输出引用了哪一层、哪一条上下文信息?
  3. 是否对上下文质量做了定期评估(检索精度、回答忠实度、上下文相关性)?

成本控制

  1. 是否分析了 token 成本的主要来源,是否存在冗余上下文在每次调用中重复发送?
  2. 是否按查询复杂度做了条件化处理——简单问题不走全量上下文?

收束

上下文工程之所以重要,是因为它解决的是大模型应用的基础设施问题。Prompt 工程关注「怎么问」,但一个好问题如果缺少必要的背景信息,模型也只能给出空洞或错误的回答。上下文工程关注的是「模型在回答时知道什么」——这包括系统规则、用户意图、业务数据、历史状态和工具结果的完整组织。

从工程实践角度看,上下文工程的核心原则只有一条:把上下文当作有限的、有边际递减效应的资源来管理。不是塞得越多越好,而是找到最小的、高信号的 token 集合,最大化模型正确响应的概率。

这个领域还在快速演进。MCP(Model Context Protocol)正在成为上下文传递的事实标准,更多框架开始提供原生的上下文管理能力。但底层原则不会变——好信息比好措辞更重要,结构设计比单次优化更持久。


参考资料

[1] Anthropic. Effective Context Engineering for AI Agents. September 2025.

[2] Andrej Karpathy. 关于 LLM 上下文窗口的论述. 2025 年 6 月. 引自多篇技术社区的分析和讨论.

[3] Unblocked. Context Engineering: Why LLMs Need More Than Prompts and MCP. October 2025.

[4] Redis. Context Window Management for LLM Apps: Developer Guide. February 2026. 引用了 arXiv:2601.11564 关于上下文长度与延迟关系的研究.

[5] Liu, N. F., et al. Lost in the Middle: How Language Models Use Long Contexts. arXiv:2307.03172, 2023.

[6] arXiv. Effective Context Length of Language Models. arXiv:2411.03538, 2024.

[7] Mohd Mus99. Strategies and Techniques for Managing the Size of the Context Window. December 2024. 提出了 75% 上下文使用率经验法则.

评论 0

0 / 1000