---
title: 上下文工程
url: https://doc.liz6.com/ai/02-context-and-interfaces/02-context-engineering
locale: zh
area: ai
tags:
- 大模型与 Agent
- 单次请求与证据
date: 2026-06-30
modified: 2026-09-10
description: 输出契约明确后，下一步是决定本轮给模型哪些材料。上下文工程管理当前可见的信息：保留约束与必要证据，给输出留空间，复用稳定前缀，并把长历史整理为能回读原始产物的摘要。它与持久化、检索和模型权重各有边界。
---

# 上下文工程

输出契约明确后，下一步是决定本轮给模型哪些材料。上下文工程管理当前可见的信息：保留约束与必要证据，给输出留空间，复用稳定前缀，并把长历史整理为能回读原始产物的摘要。它与持久化、检索和模型权重各有边界。

## 知识与状态的三个层次

模型的语言能力和大量知识来自训练后的权重；上下文则提供当前问题、约束和可用证据。不能把“请求之间不自动记住聊天内容”解释成“模型全部知识都在这次输入里”。另一个独立层次是应用维护的会话历史、文件和数据库：它们可以长期存在，但存在并不等于已经进入当前推理。

<svg viewBox="0 0 760 341.6076354980469" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="知识、持久状态与本轮上下文是三个层次" style="max-width:100%;height:auto" font-family="Source Han Sans CN,Microsoft YaHei,sans-serif">
<defs><marker id="context-state-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 Z" fill="#64748b"></path></marker></defs>
<rect width="760" height="341.6076354980469" rx="12" fill="#f8fafc"></rect>


<g transform="translate(0 0)"><rect x="30" y="75" width="210" height="70" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="135.0" y="107.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">模型权重</text><text x="135.0" y="129.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">训练形成的参数与能力</text><rect x="30" y="190" width="210" height="70" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="135.0" y="222.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">应用持久状态</text><text x="135.0" y="244.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">文件、数据库、历史记录</text><rect x="295" y="190" width="190" height="70" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="390.0" y="222.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">选择与组装</text><text x="390.0" y="244.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">检索、读取、摘要</text><rect x="535" y="75" width="195" height="70" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="632.5" y="107.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">本轮生成</text><text x="632.5" y="129.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">权重与上下文共同参与</text><rect x="535" y="190" width="195" height="70" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="632.5" y="222.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">本轮上下文</text><text x="632.5" y="244.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">当前实际可访问的材料</text><line x1="240" y1="110" x2="530" y2="110" stroke="#64748b" stroke-width="1.8" marker-end="url(#context-state-arrow)"></line><line x1="240" y1="225" x2="290" y2="225" stroke="#64748b" stroke-width="1.8" marker-end="url(#context-state-arrow)"></line><line x1="485" y1="225" x2="530" y2="225" stroke="#64748b" stroke-width="1.8" marker-end="url(#context-state-arrow)"></line><line x1="632" y1="190" x2="632" y2="150" stroke="#64748b" stroke-width="1.8" marker-end="url(#context-state-arrow)"></line><text x="28" y="316" font-size="13" fill="#475569" text-anchor="start" font-weight="400">存在数据库里的记录，只有被读取并提供给本轮推理后，才能作为当前证据。</text></g><text x="24" y="29" font-size="19" fill="#0f172a" text-anchor="start" font-weight="700"><tspan x="24" dy="0">知识、持久状态与本轮上下文是三个层次</tspan></text>
</svg>

例如，仓库里有一份最新测试报告，模型权重中不可能包含刚刚生成的报告内容。应用必须读取报告，把相关结果交给模型；如果仅传一个路径，模型通常需要调用工具才能获取正文。文件可以帮助跨会话恢复，却不会自动扩大单次上下文。

“API 无状态”也要限定到接口。显式传历史的消息接口通常由调用方组织续话；托管会话可能在服务端保存和补入历史，推理服务还可能复用前缀状态。对应用更有用的问题是：**本次推理实际接收了什么，谁负责补回历史，哪里保存可恢复的真实状态？** Claude 的窗口文档区分了训练知识与当前工作上下文，并说明输入、输出及 thinking 的具体计数规则。[Claude 上下文窗口](https://platform.claude.com/docs/en/build-with-claude/context-windows)

## 窗口预算与输出预留

### 先给下一步留空间

先确认目标模型的输入、输出限制及计数规则，再分配材料。以下是一个**假设输入与输出合计上限为 128,000 token** 的规划例子，K 在本表表示 1,000；不代表某个模型的固定配置。

| 项目 | 预算 | 保留理由 |
|---|---:|---|
| 系统约束与工具定义 | 8K | 决定任务边界与可用操作 |
| 近期历史与任务摘要 | 32K | 保留当前方案和关键决定 |
| 检索证据 | 24K | 支持本轮判断，带来源位置 |
| 当前问题 | 1K | 用户本轮新增要求 |
| 生成预留 | 16K | 依接口规则包含回答、推理和工具调用输出 |
| 安全余量 | 8K | 给计数误差与下一步材料留下空间 |
| 合计 | 89K | 距离上限还有 39K |

输入部分是 65K。若下一次工具返回 50K 日志，计划总量会变成 139K，超出 11K。此时应先过滤日志或读取关键区间，而不是等请求报错再随意删除最早的历史。流式输出改变交付方式，不会增加窗口容量。

可以用下面的独立算例检查预算账。它只验证算术，实际请求仍要用目标模型的计数工具处理消息模板、工具定义和多模态内容。

```python
limit = 128_000
parts = {"rules_tools": 8_000, "history": 32_000,
         "evidence": 24_000, "question": 1_000}
reserve, margin = 16_000, 8_000
planned = sum(parts.values()) + reserve + margin
assert planned == 89_000
assert limit - planned == 39_000
incoming = 50_000
print("需要至少削减", max(0, planned + incoming - limit), "token")
# 需要至少削减 11000 token
```

不同分词器不能用固定倍率互换。即使用供应商计数端点，也应把结果视为请求前估计，并用响应 usage 校准；Claude 官方明确指出两者可能存在少量差异。[Token counting](https://platform.claude.com/docs/en/build-with-claude/token-counting)

### 材料放得下，下一步还放得下吗？

预算图里的固定 49K 包含规则、历史、问题和安全余量；输出预留与证据独立调整。超限是算术结果，删掉哪部分则是证据选择问题。不能为了让条形图缩短，就删除会改变结论的限制条件。

**材料放得下，下一步还放得下吗？**

输入、工具结果、输出预留与余量共同占用规划预算；缓存命中不删除逻辑上下文。


## 按证据需求选择材料

上下文选择首先是证据覆盖问题。问“某函数在哪里定义”，通常只需定位符号及少量邻近代码；问“迁移方案是否遗漏兼容性要求”，则可能需要同时读取多个完整章节。检索命中两段却漏掉否定条件，会比完整读取更危险。

| 任务 | 材料选择 | 必须验证的边界 |
|---|---|---|
| 定位事实或符号 | 检索片段加邻近上下文 | 版本、同名对象、限定条件 |
| 比较少量完整文档 | 文档整体或章节整体 | 是否截断附录、例外与定义 |
| 跨大量资料综合 | 分阶段检索、归并证据再生成 | 多跳关联是否覆盖、结论来源是否可追溯 |
| 持续修改工程 | 当前文件、需求、差异、验证结果 | 摘要是否仍对应最新工作区 |

以“这个配置是否适用于生产环境”为例，检索可能命中示例中的 `enabled: true`，但限制说明在前一段：“仅限单节点测试”。证据单元应包含配置及其适用条件，而不只是包含关键词的行。每段材料附文件或文档标识、版本、位置；冲突证据并列保留，让模型识别冲突，必要时再读取原文。

检索内容是待分析的数据，不会因为出现在上下文里就获得执行指令的权限。网页中出现“忽略先前要求”时，应保留来源身份并按资料内容处理；任务权限由应用的可信配置与用户授权决定。相关执行边界见 [安全与防护](/ai/03-agent-systems/05-authority-and-tool-boundaries.md)。

研究中观察到关键证据放在长输入的不同位置会影响表现，部分任务在中部更易退化。这支持做位置敏感性测试，并不证明所有模型都“看不见中间”，也不保证把结论复制到末尾就正确。[Lost in the Middle](https://arxiv.org/abs/2307.03172)

## 缓存的复用边界

KV cache 是推理中复用注意力历史状态的机制；prompt caching 是服务对重复提示前缀提供的复用能力，可能在底层使用这类状态。两者处于不同抽象层，并非完全无关。API 缓存命中也不表示模型“记住了答案”：变化的后缀仍需生成。

<svg viewBox="0 0 760 306.6076965332031" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="相同前缀可复用；变化发生后，后续状态需要重新计算" style="max-width:100%;height:auto" font-family="Source Han Sans CN,Microsoft YaHei,sans-serif">
<defs><marker id="context-prefix-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 Z" fill="#64748b"></path></marker></defs>
<rect width="760" height="306.6076965332031" rx="12" fill="#f8fafc"></rect>


<g transform="translate(0 0)"><text x="28" y="110" font-size="14" fill="#334155" text-anchor="start" font-weight="600">请求 A</text><rect x="120" y="80" width="160" height="55" rx="8" fill="#ccfbf1" stroke="#c7d2fe"></rect><text x="200.0" y="112.5" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">工具定义</text><line x1="280" y1="107" x2="298" y2="107" stroke="#64748b" stroke-width="1.8" marker-end="url(#context-prefix-arrow)"></line><rect x="300" y="80" width="180" height="55" rx="8" fill="#ccfbf1" stroke="#c7d2fe"></rect><text x="390.0" y="112.5" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">系统与稳定材料</text><line x1="480" y1="107" x2="498" y2="107" stroke="#64748b" stroke-width="1.8" marker-end="url(#context-prefix-arrow)"></line><rect x="500" y="80" width="225" height="55" rx="8" fill="#fef3c7" stroke="#c7d2fe"></rect><text x="612.5" y="112.5" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">用户问题 A</text><text x="28" y="200" font-size="14" fill="#334155" text-anchor="start" font-weight="600">请求 B</text><rect x="120" y="170" width="160" height="55" rx="8" fill="#ccfbf1" stroke="#c7d2fe"></rect><text x="200.0" y="202.5" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">工具定义</text><line x1="280" y1="197" x2="298" y2="197" stroke="#64748b" stroke-width="1.8" marker-end="url(#context-prefix-arrow)"></line><rect x="300" y="170" width="180" height="55" rx="8" fill="#ccfbf1" stroke="#c7d2fe"></rect><text x="390.0" y="202.5" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">系统与稳定材料</text><line x1="480" y1="197" x2="498" y2="197" stroke="#64748b" stroke-width="1.8" marker-end="url(#context-prefix-arrow)"></line><rect x="500" y="170" width="225" height="55" rx="8" fill="#fef3c7" stroke="#c7d2fe"></rect><text x="612.5" y="202.5" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">用户问题 B</text><text x="300" y="154" font-size="13" fill="#047857" text-anchor="middle" font-weight="400">前缀内容及缓存条件相同</text><text x="28" y="281" font-size="13" fill="#475569" text-anchor="start" font-weight="400">缓存命中节省前缀计算或费用；这段输入仍然占用模型的上下文窗口。</text></g><text x="24" y="29" font-size="19" fill="#0f172a" text-anchor="start" font-weight="700"><tspan x="24" dy="0">相同前缀可复用；变化发生后，后续状态需要重新计算</tspan></text>
</svg>

在前缀缓存中，工具定义和稳定指令适合放在动态消息之前。若每次在最前面插入随机 ID，后续本来相同的材料也可能无法复用；若只修改最后的问题，之前的共同前缀仍有复用机会。这里的边界还受缓存粒度、有效期、模型和服务配置影响，不能只比较可见正文。

以 Claude 为例，缓存涉及 tools、system、messages 的层次，命中还受最小长度、缓存断点及有效期约束；可从 `cache_read_input_tokens` 等 usage 字段核验。费用和限制会随模型、平台变化，接入时查对应页面，不把某个价格倍率当成缓存机制本身。[Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)

### 改动发生在哪里，复用就止于哪里？

把动态 ID 放在最前面，与只修改末尾问题做对照。前缀的依赖关系说明了稳定材料放置的价值；真实命中还必须满足服务的版本、计数、有效期与配置条件。

**改动发生在哪里，复用就止于哪里？**

后面的相同文本不能越过前面的变化继续当作共同前缀；复用边界与文本总相似度不同。


## 把历史压缩成可恢复状态

摘要的目标是让下一轮能继续做事。单纯写“已经讨论并修复了若干问题”几乎没有恢复价值，因为它没有说清修了哪里、验证过什么、下一步依赖什么。

假设任务正在修复导入器：旧方案已被否决，当前补丁已落盘，但仅运行了单元测试。一个有用的摘要应明确区分事实、未完成项和约束：

```json
{
  "goal": "修复导入器重复提交问题",
  "constraints": ["保持现有文件格式", "先不改公共接口"],
  "current_artifacts": ["src/importer.py", "tests/test_importer.py"],
  "decisions": ["使用输入批次 ID 去重；不使用进程内集合"],
  "verified": ["单元测试通过；详细结果在 artifacts/unit-test.txt"],
  "pending": ["验证进程重启后的重复提交"],
  "next_action": "读取当前补丁与测试报告，补做恢复场景验证"
}
```

这是应用自定义状态格式，不是供应商的 compaction 协议。恢复时先核对文件和报告是否仍存在、是否对应当前修改；摘要中的“测试通过”不能替代测试产物，更不能自动代表后续修改也通过了测试。

| 操作 | 保留什么 | 主要损失或要求 |
|---|---|---|
| 裁剪旧输出 | 当前相关片段与原文定位 | 被删除细节需重新读取 |
| 压缩历史 | 目标、约束、决定、证据索引和下一步 | 摘要可能遗漏或误写，关键状态需核验 |
| 持久记忆 | 跨会话确实有用的稳定信息 | 要处理版本、过期与权限 |

若使用服务端 compaction，必须按该接口保留和回传其结构化状态，不能把任意摘要 JSON 塞进协议块。具体兼容范围和流程见 [Claude Compaction](https://platform.claude.com/docs/en/build-with-claude/compaction)；跨会话设计见 [记忆与状态](/ai/03-agent-systems/04-memory-and-recovery.md)。

## 如何验证一次上下文改动有效

固定一组任务与答案依据，比较原始历史、压缩历史、检索材料三种输入。记录任务正确率、必要证据召回、输入量、首个可见输出延迟及总成本；再做一次“压缩后恢复”，检查是否重复已经失败的方案、遗漏用户约束或宣称未完成的步骤已完成。

如果输入量降低而恢复错误增多，说明删掉了关键状态；如果输入量升高但正确率不变，检查是否重复保留了工具输出；如果事实回答正确但无法给出支持位置，检查证据索引。这样才能判断上下文选择改善了任务，而不只是让 prompt 看起来更整齐。后续评估方法见 [评估与可观测](/ai/04-evaluation-and-production/01-evaluation-and-observability.md)。

继续阅读：[检索、RAG 与证据链](/ai/02-context-and-interfaces/03-retrieval-and-evidence)。
