本页目录
上下文工程
模型是无状态的——它"知道"的一切都在每次请求的上下文窗口里。把窗口当预算经营:缓存稳定前缀、压缩旧历史、按需加载工具,而不是无节制地往里塞。窗口管理是 agent 性能的第一变量。
概述
大语言模型(LLM)在一次请求里是无状态的:它不记得上一次对话,也没有隐藏的"记忆"。它对世界的全部认知,就是这一次请求里塞给它的那段文本——上下文窗口(context window)。所谓"上下文工程"(context engineering),就是在这个有限、昂贵、且对位置敏感的窗口里,决定放什么、放在哪、怎么复用,以换取更准、更快、更便宜的输出。
这件事之所以重要,是因为大家对 LLM 的直觉常常错位:以为"把更多资料喂进去"总会让答案更好。实际上窗口是有上限的(Claude Opus 4.8 是 1M token),注意力计算随长度呈 ~O(n²) 增长,而且模型对窗口中间的信息会"看不清"。把窗口当成一种预算来经营,而不是一个无底的垃圾桶,是用好任何现代模型的第一原则。
历史上这是从"prompt engineering"(怎么措辞)演进来的:单轮短 prompt 时代,措辞是全部;进入长上下文、工具调用、多轮 agent 时代后,窗口里有什么、按什么顺序排、哪些能被缓存才是主导因素。Anthropic 在 2024–2026 年陆续把 prompt caching、compaction、context editing、adaptive thinking 等做成 API 原语,本质都是上下文工程的工具。
模型一轮里"知道"的只有上下文窗口
每一次 /v1/messages 请求,模型看到的是这样一段被拼接起来的文本(渲染顺序固定为 tools → system → messages):
关键结论:模型"记得"上一轮,只是因为调用方把整段历史又发了一遍。API 是无状态的;"记忆"是调用方维护的。这也解释了为什么长对话会越来越贵——每一轮都要重发全部历史。
token 与注意力:为什么越长越贵越慢
文本先被分词器(tokenizer)切成 token(Claude 的 token ≠ OpenAI 的 token,别用 tiktoken 估 Claude,会少算 15–20%;要准就调 count_tokens)。计费、限流、窗口上限,单位都是 token。
长度带来两重成本:
- 算力 ~O(n²):自注意力(self-attention)让每个 token 关注所有其它 token,序列翻倍、计算约四倍。这是"越长越慢"的根。
- KV cache 显存:为避免每生成一个 token 都重算历史,推理引擎把历史每层的 Key/Value 缓存下来,显存随上下文长度线性增长。本地部署尤其吃紧——参见 本地 LLM 部署:64k 上下文的 f16 KV cache 要 ~5.37 GB,塞不下 24GB 卡,必须量化到 Q8_0 压到 ~2.85 GB。云端 API 看不到这块显存,但它就是"长上下文更贵"的物理原因。
KV cache(推理引擎为加速而缓存的张量)和下文的 prompt caching(API 为省钱而缓存的前缀)是两个不同层次的"缓存",别混。前者是单次生成内部的加速,后者是跨请求复用前缀。
上下文预算:谁在抢这个窗口
把窗口当预算分配。同一个窗口被这些东西瓜分,挤掉谁都会影响效果:
| 占用方 | 典型体量 | 经营手段 |
|---|---|---|
| 工具定义 tools | 几十~几千 token/工具 | 工具多了用 tool search 按需加载;别一次性全塞 |
| 系统提示 system | 固定 | 冻结它(见 caching);别在里头插时间戳 |
| 历史 messages | 随对话线性增长 | compaction / context editing |
| 检索内容 RAG | 可控 | 检索优于硬塞:只放命中片段,别塞整库 |
| 输出 output | max_tokens 预留 | 流式 + 合理 max_tokens |
| thinking | 由 effort 决定 | 用 effort 调,而非硬塞 |
一个常见误区是把整个知识库塞进 system 提示。正确做法是 RAG:先检索,只把相关片段放进窗口。检索的精度比上下文的长度更决定答案质量——这条线在 RAG 与检索增强 里展开。
prompt caching:把稳定前缀缓存下来
多轮对话和 agent 循环里,每次请求的前缀(工具定义 + 系统提示 + 大段历史)高度重复。prompt caching 把这段稳定前缀缓存在服务端,后续命中只按 ~0.1x 价格计费(写入 5 分钟 TTL 是 1.25x、1 小时 TTL 是 2x)。
它的全部行为都从一条不变量推出:
Prompt caching 是前缀匹配(prefix match)。前缀里任何一个字节变了,从该位置往后的缓存全部失效。
渲染顺序是 tools → system → messages,所以缓存的边界要卡在"稳定/易变"的交界:
实操要点:
- 稳定的放前面,易变的放后面。 系统提示里别插
now()、UUID、用户 ID、随机 ID——它们在前缀里,会让后面全部失效。动态信息放到 messages 靠后的位置。 - 断点:在稳定前缀的最后一个 block 上打
cache_control: {type: "ephemeral"}(或用顶层自动缓存)。每请求最多 4 个断点。 - 最小可缓存前缀与模型相关:Opus 4.8 是 4096 token,Sonnet 4.6 / Fable 5 是 2048 token——前缀太短会静默不缓存,不报错。
- 验证命中:看响应
usage.cache_read_input_tokens。若多次同前缀请求它一直是 0,说明有"静默失效器"(系统提示里的时间戳、未排序的 JSON、变动的工具集),逐字节 diff 两次请求的前缀去找。
| 改了什么 | 工具缓存 | 系统缓存 | 消息缓存 |
|---|---|---|---|
| 工具定义(增删/重排)、换模型 | ❌ | ❌ | ❌ |
| 系统提示内容 | ✅保留 | ❌ | ❌ |
消息内容、tool_choice、开关 thinking | ✅ | ✅ | ❌ |
(✅=该层缓存仍有效)。结论:别中途改工具集或换模型——那会从头作废整段缓存。
当对话超过窗口:compaction vs context editing
长 agent 跑久了,历史会逼近甚至超过窗口。两种 API 原语,方向相反,常配合使用:
| compaction(压缩) | context editing(裁剪) | |
|---|---|---|
| 做什么 | 把早期历史摘要成一个 compaction block | 把旧的 tool_result / thinking 清除 |
| 信息 | 概括保留 | 直接删掉 |
| 何时用 | 逼近窗口上限、需要继续对话 | 旧工具输出已无关,想瘦身 |
| 关键坑 | 必须把 response.content(含 compaction block)整块回传,只回传 text 会丢状态 | 只删 tool_use/thinking,不动对话结构 |
| Beta header | compact-2026-01-12 | context-management-2025-06-27 |
记住二者的差别:compaction 是"概括",context editing 是"删除"。跨会话的持久记忆是第三件事(把要点写进文件/记忆库),不在单次窗口内解决——展开见 记忆与状态。
thinking / effort / task_budget:让模型自己经营预算
现代 Claude(Opus 4.6+)用 adaptive thinking(thinking: {type: "adaptive"})——模型自行决定何时、想多深,不再手填 budget_tokens(在 Opus 4.7/4.8/Fable 5 上手填会 400)。想深浅的总闸是 effort:
output_config: { effort: "low" | "medium" | "high" | "xhigh" | "max" }
effort 越低,工具调用更少更聚合、前言更短;high/xhigh 适合 coding 与 agent。它直接决定一轮里 thinking + 工具 + 输出花多少 token——这是上下文预算最直接的旋钮。
还有 task budget(beta task-budgets-2026-03-13):给整个 agent 循环一个 token 预算(最小 20000),模型能看到倒计时并自我收尾。它和 max_tokens 不同:max_tokens 是单次响应的硬上限、模型看不到;task_budget 是模型可感知的"花销建议"。
"lost in the middle" 与 context rot:多 ≠ 好
模型对窗口的注意力不均匀:开头和结尾记得清,中间容易"看不见"(研究界称 "lost in the middle")。再加上塞进太多弱相关内容会稀释信号——更长的上下文反而降低准确率,这就是 "context rot"。
两条可落地的对策:
- 把最重要的指令放在模型注意力强的位置(靠前的 system,或靠后的 user),并用清晰分隔符 / XML 标签框住关键块。
- 宁可检索,不要硬塞。 与其把 50 页文档全贴进去赌模型自己找,不如先 RAG 命中再放 2 页(见 RAG 与检索增强)。
最佳实践
- 把窗口当预算经营,不是垃圾桶。 决定放什么、放哪、怎么复用,而非无脑塞满——这是用好任何现代模型的第一原则。
- 稳定前缀焊死,易变内容后置。 冻结 system、固定工具顺序、动态信息(时间戳/ID/检索片段)一律放靠后——这几乎就是缓存命中的全部秘诀。
- effort 起步
high,按 eval 扫档。 别反射式拉xhigh/max;关系非单调,高 effort 在 agent 上常更省。 - 检索优于硬塞。 RAG 命中 2 页胜过硬贴 50 页(见 RAG 与检索增强)。
- 关键指令放强位置 + 分隔符框住。 靠前 system 或靠后 user,用 XML 标签隔开,别埋中段。
- 长对话主动管理,分清三件事。 compaction 概括、context editing 删除、记忆 跨会话持久——别用一个手段干三件事。
权衡与失败模式
- 往窗口里硬塞:以为越多越准,实则触发 context rot、注意力被稀释,准确率反降 → 检索优于硬塞,只放命中片段。
- 缓存静默失效:system 里插了
now()/UUID/未排序 JSON,cache_read_input_tokens长期为 0 → 冻结前缀、动态信息后置,逐字节 diff 两次请求排查。 - 一轮内改工具集或换模型:整段前缀缓存作废、成本翻几倍 → 工具集与模型在同一循环里保持不变。
- 混淆两种"缓存":KV cache 是单次生成内的显存加速,prompt caching 是跨请求的前缀复用 → 分清层次再优化。
- 反射式拉满 effort:简单任务高 effort 空转烧 token → 起步
high,按 eval 扫档(见 推理与 thinking)。
前沿:上下文即"世界"
把上下文窗口推到极致,会通向一个有意思的方向:world model。阿里 Qwen 团队的 Qwen-AgentWorld-35B-A3B 是个 256K 上下文的 MoE(35B 总参 / 3B 激活),它不是用来聊天,而是在上下文里模拟 agent 所处的环境(终端、Web、OS……)。这类模型把"上下文"从"塞资料的地方"变成"承载一个可交互世界的状态"——上下文工程的尽头,是把窗口当成一台状态机来经营。这条线在 Agent 循环与工具使用 的前沿小节展开。
参考
- Anthropic 官方文档: Prompt Caching、Compaction、Context Editing、Adaptive Thinking & Effort(platform.claude.com,实现前以官方为准)
- 本地实战: 本地 LLM 部署(KV cache 量化、显存博弈)
- 研究: "Lost in the Middle: How Language Models Use Long Contexts" (Liu et al., 2023)
- 下游: Agent 循环与工具使用、MCP 与 Skills
Keywords: context window, token, tokenizer, KV cache, attention O(n²), prompt caching, prefix match, cache_control, TTL, compaction, context editing, adaptive thinking, effort, task budget, lost in the middle, context rot, RAG, world model