上下文是最稀缺的资源:窗口、注意力衰减与成本,从提示词工程走到上下文工程
先把一次会话的上下文拆成系统提示、工具定义、历史、工具结果四块并算出各自占比,再看清窗口变长为什么反而更容易出错,以及成本、延迟与正确率这三笔账。
今日目标
- 能说出一次模型请求的上下文由哪四块构成,并算出各自的 token 占比
- 能用自己的话解释注意力预算与上下文腐烂,并说出它们带来的三种代价
- 能判断一个问题该用提示词工程解决,还是必须升级成上下文工程
这门课只解决一件事:每一次调用模型的时候,到底该往请求里装什么。 今天先学会看清楚现在装了什么——不看清楚,后面四天所有的裁剪、压缩、隔离都是在拍脑袋。读完回到页面顶部把三条目标勾掉。
小白版讲解
出差前收拾登机箱
登机箱的容量是固定的。你要出差五天,衣服、电脑、充电器、合同、伞、给客户的样品,全都想带。真开始收拾就会发现,这件事的难点从来不是「箱子够不够大」,而是三个问题:哪些必须现在就装进去;哪些可以到了当地再买或者临时借;哪些看着有用其实一次都不会打开。
聪明的做法不是买个更大的箱子。箱子越大,你越会往里塞,到了酒店翻找一支笔要倒腾五分钟。真正管用的是三件事:装之前先列清单,路上需要什么再取什么,中转的时候重新收拾一遍把用完的扔掉。
模型的上下文窗口(context window)就是这只箱子。它的容量以 token 计——一个 token 大致是一个汉字,或者三四个英文字母。每次调用模型,你都要把这只箱子重新装满一遍再递过去,因为模型本身没有记忆:它不记得上一轮说过什么,你不重新装,它就不知道。
于是「装什么」这件事从一个细节变成了一门工程。它有个名字叫上下文工程(context engineering),一句话定义是:在每一次推理之前,挑出那组能让模型做对事的、最小的高信号 token。 注意这句话里的两个词——「最小」和「高信号」。它们经常互相冲突,而怎么在冲突里做取舍,就是这五天要练的东西。
先记住一个和直觉相反的结论:装得多不等于效果好。 后面第三节会告诉你为什么。
一次请求里到底装了什么
先把箱子打开看看。一次 Agent 请求发出去的上下文,几乎总是由四块组成:
Mermaid 源码
flowchart LR
A[系统提示] --> E[一次请求的上下文]
B[工具定义] --> E
C[对话历史] --> E
D[工具结果] --> E- 系统提示:角色、流程、硬约束。每一轮都原样重发,长度基本不变。
- 工具定义:每个工具的名字、描述、参数结构。每一轮也都重发,长度随工具数量增长。
- 对话历史:用户说的话和模型说的话。每轮增加几十个 token,线性增长。
- 工具结果:工具吐回来的数据。一次就能加两千个 token,阶梯式增长。
这四块的增长方式完全不同,所以必须分开治。把它们混成一个「总 token 数」去看,你永远不知道该动哪里。
增长方式的差别还带来一个不那么明显的后果:改哪一块的收益完全不一样。 系统提示每一轮都原样重发,把它砍掉一半,整场会话每一轮都受益,这是复利最高的一块;工具结果是一次性加进来然后一直躺在那里,裁掉一次只省这一次,但它的单次体量最大,所以绝对收益反而最高;对话历史涨得最慢,多数时候是四块里最小的一块。这三种性质决定了后面四天的顺序,第五天会用一张真实的账单表把它算给你看。
一个最容易搞错的地方:工具结果虽然写在 role 为 user 的消息里,看起来像是对话历史的一部分,但它不是用户说的话。它是数据,不是对话。 混进历史里算,这张表就废了。拆的时候要按内容块的类型走,不要按消息的角色走:
// 按内容块类型拆四块:工具结果单独一桶,不要混进对话历史
export function splitContext(system, tools, messages) {
const history = []
const toolResults = []
for (const msg of messages) {
const kept = []
for (const block of msg.content) {
if (block.type === 'tool_result') {
toolResults.push({ id: block.tool_use_id, content: block.content })
} else {
kept.push(block)
}
}
// 挑空之后没剩下内容块的消息不要再放回去,空消息发出去会被接口拒绝
if (kept.length > 0) history.push({ role: msg.role, content: kept })
}
return { system, tools, history, toolResults }
}# 按内容块类型拆四块:工具结果单独一桶,不要混进对话历史
def split_context(system, tools, messages):
history = []
tool_results = []
for msg in messages:
kept = []
for block in msg["content"]:
if block["type"] == "tool_result":
tool_results.append({"id": block["tool_use_id"], "content": block["content"]})
else:
kept.append(block)
# 挑空之后没剩下内容块的消息不要再放回去,空消息发出去会被接口拒绝
if kept:
history.append({"role": msg["role"], "content": kept})
return {"system": system, "tools": tools, "history": history, "tool_results": tool_results}今天的实验会把这段代码跑在一个虚构的电商客服会话上。剧透一下结果:系统提示 280、工具定义 308、对话历史 218、工具结果 2191——工具结果一块就占了 72.9%。 这个比例在真实项目里非常典型,而绝大多数人第一次量之前都以为大头是对话历史。
注意力预算与上下文腐烂
「那我换个窗口更大的模型不就行了?」
这是每个人的第一反应,也是这门课要拆掉的第一个直觉。窗口变大确实解决了「装不下」的问题,但它解决不了另一个问题:装得越多,模型越容易看漏。
这个现象有个名字叫上下文腐烂(context rot):随着上下文变长,模型准确回忆其中信息的能力会下降。它的来源在架构里——Transformer 里每个 token 都要和其它每个 token 建立关系,n 个 token 就有 n 的平方级别的两两关系。上下文越长,同样一份注意力就被摊得越薄。这不是一道悬崖,是一条缓坡:没有哪个长度会突然崩掉,但每多塞一千个不相干的 token,正确率就低一点点。
还有一个更现实的原因:模型训练时见到的大多是较短的序列,专门用来处理超长距离依赖的参数本来就少。所以「窗口标称 20 万」和「在 20 万 token 上表现稳定」是两件事,标称值是容量,不是保证。
所以正确的心态是把注意力当成预算,而不是把窗口当成容量。容量思维问的是「还装得下吗」,预算思维问的是「这一千个 token 值不值得花」。后者才是这门课的思维方式。
一个可以立刻用上的判断:如果你往上下文里加一段内容,说不出它会改变模型哪一个具体决定,那它就不该加。 这条判据在第二天精简系统提示、第三天裁剪工具结果时都会反复用到。
三笔账:钱、延迟、正确率
上下文变长,你要付三笔账,而大多数人只算了第一笔。
第一笔是钱。 输入 token 按量计费,而且模型没有记忆意味着每一轮都要把前面的全部重发一遍。一次 20 轮的会话,第 1 轮发 3000 token,第 20 轮可能要发 11000 token,整场下来的总输入量是一个累加值,不是最后那次的值。今天的实验会让你亲手把这个累加值算出来。
把这件事算成数字更有冲击力。假设一次会话 20 轮,第 1 轮发 3000 token,之后每轮因为历史和工具结果累积再加一些,到第 20 轮变成 11000。你要付的不是 11000,而是这 20 次的总和——按线性增长粗算大约十四万 token。很多人在做成本估算时报的是那个 11000,于是把整场会话的成本低估了一个数量级。第五天会用今天实验的真实数据把这张账单完整算一遍,包括缓存会怎么把它改写。
第二笔是延迟。 输入越长,首字返回越慢。用户感受到的不是「贵了三成」,是「转圈转了四秒」。而且这笔账在 Agent 场景里会被放大:一次任务里模型要被调用很多轮,每一轮都慢一点,累积起来就是用户能明确感知到的「这东西很卡」。
第三笔是正确率,也是最容易被忽略的一笔。上一节已经说了,无关内容会稀释注意力。这笔账的可怕之处在于它不会报错:模型不会告诉你「因为上下文太乱我这次没看见那条约束」,它只会给出一个看起来合理、实际上违反了系统提示的回答。等你发现的时候,通常已经是线上事故了。
提示词工程与上下文工程的分界
提示词工程(prompt engineering)关心的是怎么把一段话写好:角色怎么定、任务怎么说、格式怎么约束。上下文工程关心的是这一整只箱子怎么装:那段话只是箱子里的一件行李,旁边还有工具定义、历史和工具结果,而且它们每一轮都在变。
分界线其实很清楚。问自己一个问题:我这次的问题,是单次调用里写得不够清楚,还是多轮下来管得不够干净?
- 同一个问题问一次就答错,换个说法就对了——这是提示词工程。
- 前五轮好好的,第二十轮开始违反最初的约束——这是上下文工程。
- 明明查到了数据,模型却说「我没有这个信息」——这也是上下文工程,信息在窗口里,只是被淹没了。
再给一条更硬的判据:提示词工程处理的是内容,上下文工程处理的是预算分配。 如果你的改动是「把这句话改得更准确」,那是前者;如果是「这块该占多少、什么时候该扔」,那是后者。
这门课默认你已经会写提示词。如果这一块还不熟,可以先去看 5 天提示词工程零基础;写 Agent 循环本身的基础在 30 天课的第 5 天,那一天讲工具调用的完整回路,正是本课「工具定义」和「工具结果」两块的来源。
五天分别解决哪一块
四块里,能动手的其实只有三处,五天的编排就是按它们的性价比排的:
- 今天(D1)先量:把四块拆开算出占比,找出你的大头在哪。没有这张表,后面全是猜。
- D2 治系统提示:它每轮都重发,是复利最高的一块。用高度、分层、渐进披露三个手段把它砍到一半。
- D3 治工具定义与工具结果:这两块通常是大头。给出裁剪工具清单的判据,并写一个能量化前后差距的裁剪器。
- D4 治对话历史:任务跑久了历史一定会满。压缩、外部笔记、子代理隔离三条路各有各的适用场景。
- D5 收口:把前四天的手段变成可度量的循环,算清账单、盯住两个利用率指标、对照四种失败模式排查。
注意顺序:先量再改,先治重发的再治增长的。 很多人上来就去做压缩,因为它听起来最高级;但压缩治的是历史,而历史往往是四块里最小的一块——治了半天,账单纹丝不动。
源码导读
动手实验
这个实验没有 key 也能做完前四条,第五条才需要真实接口。做之前先想清楚一件事:你猜哪一块最大? 把你的猜测写下来再跑,猜错的那一刻才是这个实验真正的价值。
- 跑通 solution 的
MOCK=1 pnpm start,看清一次会话被拆成四块之后的占比表,和你的猜测对一下。 - 回到 starter 补全练习 1 的分块函数,跑一次确认工具结果那一行不再是 0,且四块之和等于合计。
- 补全练习 2 的离线估算器,把「按字符数算」换成按字符类别算,看合计从七千多掉回三千左右。
- 补全练习 3 的差分计数与练习 4 的占比计算,然后用
TOOL_COUNT=8再跑一遍,观察哪一块的占比先失控。 - 配好
ANTHROPIC_API_KEY去掉MOCK=1跑一次,把两次的合计相减,记下估算器的误差方向和幅度。
面试题
今天 3 道题在下方题库区,侧重上下文窗口的构成、注意力预算与上下文腐烂、以及上下文工程与提示词工程的边界。展开后先看「分析过程」再看要点——照着推导练,比背要点管用。标注「国内高频 / 海外高频」方便按目标市场取舍。
检查清单与明日预告
- 能说出一次模型请求的上下文由哪四块构成,并算出各自的 token 占比
- 能用自己的话解释注意力预算与上下文腐烂,并说出它们带来的三种代价
- 能判断一个问题该用提示词工程解决,还是必须升级成上下文工程
- 能解释为什么工具结果必须和对话历史分开算,而不是都算成消息
- 实验的 5 条验收标准全部通过,且记下了估算器与真实计数的误差
- 3 道面试题不看要点也能答出至少 2 道
明天(D2)我们从占比最高的那一块开始动手:系统提示。它每一轮都原样重发,改一次省的是整场会话的钱,是四块里复利最高的。我们会讲怎么判断一段系统提示是写太细还是写太粗,怎么把它分层,以及哪些内容根本就不该待在里面。先治重发的、再治增长的——这个顺序不是随口排的,是按每改一次能省下多少来排的。
面试题库
一次 Agent 请求的上下文里都有什么?哪一块最容易失控,为什么?What actually goes into the context of a single agent request, and which part is most likely to blow up?
国内高频海外高频基础#context-window#token-budget分析过程 · 先想清楚再作答
- 这题在考你有没有真的量过。能背出「系统提示、工具定义、历史、工具结果」四块的人很多,能说出各自增长方式的人很少,区分度全在后半句。
- 怎么拆:按「每一轮会怎么变」给四块归类。系统提示和工具定义是每轮原样重发、长度基本不变;对话历史是线性增长,每轮加几十个 token;工具结果是阶梯增长,一次调用就能加两千。
- 结论:最容易失控的是工具结果,因为它的单次增量比其它三块大一到两个数量级,而且完全由外部系统决定,你写代码的时候看不到它会有多大。工具定义排第二,它随工具数量线性增长,而工具是最容易被顺手加上去的东西。
- 补一个容易被忽略的点:工具结果虽然写在 user 角色的消息里,但它是数据不是对话。按消息角色统计会把它算进历史,那张表就废了——要按内容块的类型拆。
- 可预期的追问:那你实际量出来是多少?给一个具体数字最有说服力,比如一次电商客服会话里工具结果占 72.9%,而大多数人事先都猜的是对话历史。
How to reason about it · think before answering
- This question separates people who have measured from people who have read. Naming the four parts is easy; describing how each one grows is where the signal is.
- Classify the four by growth pattern: system prompt and tool definitions are resent verbatim every turn at roughly constant size; conversation history grows linearly by tens of tokens per turn; tool results grow in steps, often thousands of tokens per call.
- Conclusion: tool results are the most likely to blow up, because a single increment is one to two orders of magnitude larger than the others and its size is decided by an external system you do not control. Tool definitions come second since they scale with a tool count that only ever goes up.
- Add the subtlety: tool results live inside user-role messages but they are data, not dialogue. Bucketing by message role folds them into history and ruins the breakdown, so bucket by content block type instead.
- Expect the follow-up: what numbers did you actually see? A concrete figure lands best, for example tool results at 72.9 percent of a customer-support session while most people had guessed history.
答题要点
- 四块:系统提示、工具定义、对话历史、工具结果。
- 按增长方式分:前两块每轮重发且基本恒定,历史线性增长,工具结果阶梯增长。
- 最容易失控的是工具结果,单次增量最大且由外部系统决定;其次是工具定义,随工具数量增长。
- 统计时要按内容块类型拆,不能按消息角色拆,否则工具结果会被算进对话历史。
Key points
- Four parts: system prompt, tool definitions, conversation history, tool results.
- Group them by growth: the first two are resent every turn at near-constant size, history grows linearly, tool results grow in steps.
- Tool results blow up first because a single call can add thousands of tokens and its size is set externally; tool definitions are second, scaling with tool count.
- Bucket by content block type, not by message role, or tool results get miscounted as history.
窗口越来越大了,为什么不能把所有可能有用的资料都塞进去?Context windows keep growing. Why not just put everything potentially relevant into the prompt?
国内高频海外高频进阶#context-rot#attention-budget#cost分析过程 · 先想清楚再作答
- 这题的题眼是「你知不知道窗口是容量、注意力是预算」。只回答「太贵了」的人会被判成没做过工程,因为成本是三笔账里最容易想到、也最不致命的一笔。
- 怎么拆:分成能力和代价两条线。能力这条线要点出上下文腐烂——随着上下文变长,模型准确回忆其中信息的能力会下降,根源在于 Transformer 里 n 个 token 有 n 平方级别的两两关系,注意力被摊薄;而且训练语料里长序列本来就少,处理长距离依赖的参数不够多。
- 关键是要强调它是一条缓坡不是一道悬崖:没有哪个长度会突然崩掉,每多塞一千个不相干的 token,正确率就低一点点。这个措辞能立刻区分读过一手材料的人。
- 代价这条线给三笔账:钱(模型无状态,每轮全量重发,总输入是累加值不是最后那次的值)、延迟(首字返回变慢)、正确率(无关内容稀释注意力)。第三笔最贵,因为它不会报错,只会给出看起来合理但违反了约束的回答。
- 可预期的追问:那你怎么判断某段内容该不该加?给一条可执行的判据——说不出它会改变模型哪一个具体决定,就不该加。
How to reason about it · think before answering
- The hinge is whether you treat the window as capacity or attention as a budget. Answering only with cost reads as inexperience, since cost is the easiest and least dangerous of the three bills.
- Split into capability and cost. On capability, name context rot: recall accuracy degrades as context grows, rooted in the n-squared pairwise relationships a transformer maintains over n tokens, plus the fact that long-range parameters are underrepresented in training.
- Stress that this is a gradient, not a cliff. No specific length breaks; every thousand irrelevant tokens shaves a little accuracy. That phrasing distinguishes people who read primary sources.
- On cost, give three bills: money (the model is stateless, so every turn resends everything and the total is cumulative, not the last call), latency (slower time to first token), and accuracy (irrelevant content dilutes attention). The third is worst because it never raises an error, it just returns a plausible answer that violates a stated constraint.
- Expect the follow-up: how do you decide whether a given chunk earns its place? Give an operational test: if you cannot name the specific decision it changes, it does not go in.
答题要点
- 窗口是容量,注意力是预算;容量够不代表模型用得好。
- 上下文腐烂:上下文越长,准确回忆的能力越差,是渐进的性能梯度而不是一道悬崖。
- 三笔账:钱(每轮全量重发,成本是累加值)、延迟、正确率。
- 正确率那一笔最危险,因为它不报错,只会给出看似合理却违反约束的回答。
- 判据:说不出这段内容会改变哪一个具体决定,就不该放进去。
Key points
- The window is capacity; attention is the budget. Fitting is not the same as being used well.
- Context rot: recall degrades as context grows, as a gradient rather than a hard cliff.
- Three bills: money (stateless models resend everything each turn, so cost is cumulative), latency, and accuracy.
- Accuracy is the dangerous one because it fails silently with plausible answers that break stated constraints.
- Test: if you cannot name the specific decision a chunk changes, leave it out.
提示词工程和上下文工程的分界在哪?什么时候该从前者切换到后者?Where is the line between prompt engineering and context engineering, and when do you switch?
国内高频海外高频进阶#prompt-engineering#context-engineering#scoping分析过程 · 先想清楚再作答
- 这题最容易答成「上下文工程是提示词工程的升级版」,那是营销话术。面试官想听的是一条能当场用来分类问题的判据。
- 怎么拆:先给对象的差别。提示词工程处理的是内容——一段话怎么写才准确;上下文工程处理的是预算分配——整只箱子里各块占多少、什么时候该扔。前者是单次调用内的优化,后者是跨多轮的状态管理。
- 再给一条现场可用的分类法,用症状反推:同一个问题问一次答错、换个说法就对,是提示词问题;前五轮正常、第二十轮开始违反最初约束,是上下文问题;明明查到了数据模型却说没有,也是上下文问题——信息在窗口里,只是被淹没了。
- 结论:切换的时机不是「提示词写得够好了」,而是「问题的成因从单次表达变成了多轮累积」。加了工具、加了检索、开始多轮长跑,这三件事任何一件发生,都意味着该切换了。
- 可预期的追问:那上下文工程包含提示词工程吗?答:系统提示是上下文四块里的一块,所以提示词工程是上下文工程的一个子问题,但它解决不了另外三块——工具定义、历史和工具结果都不是靠把话写好能管住的。
How to reason about it · think before answering
- The trap is answering that context engineering is just prompt engineering leveled up. Interviewers want a rule they can apply to classify a live problem.
- Start with the object of each. Prompt engineering shapes content: how to phrase one instruction precisely. Context engineering allocates budget: how much of the window each part gets and when to drop things. One optimizes inside a single call, the other manages state across turns.
- Then give a symptom-based test. Wrong once but right after rephrasing means a prompt problem. Fine for five turns and violating the original constraints by turn twenty means a context problem. Retrieving the data and then claiming it does not exist is also a context problem: the information is in the window but buried.
- Conclusion: you switch not when the prompt is good enough, but when the cause moves from single-turn phrasing to multi-turn accumulation. Adding tools, adding retrieval, or running long sessions each trigger the switch.
- Expect the follow-up: does context engineering subsume prompt engineering? The system prompt is one of the four parts, so prompt engineering is a subproblem, but it cannot touch tool definitions, history, or tool results.
答题要点
- 提示词工程处理内容,上下文工程处理预算分配;一个在单次调用内,一个跨多轮。
- 症状分类法:换个说法就对是提示词问题;跑久了开始违反约束是上下文问题;查到了却说没有也是上下文问题。
- 切换时机是问题成因从单次表达变成多轮累积,通常发生在加工具、加检索、开始长跑之后。
- 系统提示只是上下文四块之一,所以写好提示词管不住另外三块。
Key points
- Prompt engineering shapes content; context engineering allocates budget across turns.
- Classify by symptom: fixed by rephrasing is a prompt issue; drifting after many turns is a context issue; retrieved but reported missing is also a context issue.
- Switch when the cause moves from single-turn phrasing to multi-turn accumulation, typically after adding tools, retrieval, or long-running sessions.
- The system prompt is one of four parts, so good phrasing alone cannot control the other three.
评论
登录后即可参与讨论
还没有评论,来说第一句。