下面是一个报销审批 Agent 的系统提示:
Level 1:把「规则清单」改写到正确高度按这套规则,出差住宿的发票没有任何一条能接住,会一律落到第 7 条被退回;工作日晚上的餐饮发票既不满足第 3 条也不满足第 4 条,同样落进兜底。请:(1)指出这段提示属于哪个失败极端,并说明这类症状为什么必然出现;(2)把它改写到正确高度,改写稿要能覆盖上面两种「没写到」的情况。
学习目标:
- 能拆解一次 LLM 请求的上下文组成(系统提示、工具定义、示例、消息历史),并解释它们为什么在花同一份注意力预算
- 能判断一段系统提示的「高度」问题——过度硬编码还是过度空泛——并把它改写到正确高度
- 能从上下文成本的视角审计工具定义与示例:合并重叠功能、精简返回、用少数典范示例替代边角案例清单
第 1 课我们借用了 Anthropic 的定义:上下文工程是「在 LLM 推理过程中,筛选并维护最优 token 集合的一整套策略」1。听起来还是有点抽象——「最优 token 集合」到底指哪些 token?本课就干一件事:把引擎盖打开,看看每次请求发出去时,窗口里究竟装了什么。
你在本系列第 7 门课手写过 harness 循环,对请求的形状应该不陌生。把它按内容归类,一次请求的上下文大致是这四块:
关键在于:这四块不是四个互相隔离的口袋,而是同一个窗口里的邻居。模型在解析大量上下文时,依赖的是同一份「注意力预算」,每个新进来的 token 都会把这份预算消耗掉一点1。系统提示多一段废话,留给消息历史的注意力就少一点;工具清单里躺着十个用不上的工具,示例能分到的注意力也被摊薄。而且第 1 课说过,token 堆得越多,模型从上下文里准确召回信息的能力会下降,这个退化是渐变而不是断崖1——所以每一块的浪费都不会立刻报错,它只是安静地让整体变笨一点,等你察觉时往往已经找不到「是哪一行搞的鬼」。
还要注意四块的增长速度不一样。系统提示、工具定义、示例基本是静态的,你写多长就是多长;消息历史却在循环里持续膨胀——跑在循环里的 Agent 会不断产生「下一轮推理可能用得上」的数据1。你在本系列第 7 门课的 harness 里,每轮把工具结果 push 进 messages 数组的那行代码,就是膨胀的现场。历史怎么治,本课不展开,第 4 课《压缩与笔记》专门讲;本课先管好三块静态的——它们是每一轮都要付的固定成本。
把「上下文是边际收益递减的有限资源」1这句话落到实处,就是对这几块逐一问同一个问题:这一块的每个 token,换回了多少行为上的改善?下面逐块来看。
Anthropic 的工程文章用「高度」(altitude)来描述系统提示的抽象层级,并点名了两个失败极端:一端是把复杂、脆弱的逻辑硬编码进提示;另一端是空泛的高层指导,给不出具体信号1。我们用同一个客服 Agent 写三版系统提示,把这两个坑和正确写法摆在一起看。
低空飞行版——把逻辑写死:
每一条规则只覆盖写出来的那个情况。用户说「箱子被压扁了」而不是「破损」呢?没有一条规则接得住,只能落到第 20 条兜底装傻。更麻烦的是规则之间开始打架,于是你补了第 19 条来调解——这时你已经在用自然语言写一个 if-else 解释器了:每加一条,提示更长、更脆,注意力预算烧得更多,而没写到的情况永远比写到的多。
高空飘着版——只给口号:
token 是省下来了,但模型拿不到任何具体信号:退款的边界在哪?补偿权限有多大?什么情况必须转人工?全靠模型自己猜。「让用户满意」在极端情况下可能意味着把不该退的钱退了——这不是模型不听话,是你什么都没告诉它。
正确高度版——按 Anthropic 的说法,好的系统提示要「具体到足以有效引导行为,又灵活到能给模型留下强启发式」1:
注意这版的结构:原则负责泛化,红线负责合规。「箱子被压扁了」没有被逐字写进任何规则,但它自然落进「破损属我方责任」这条原则;而真正不能商量的事(金额上限、法律纠纷)以少数几条硬边界写死。字数比 20 条版少了一大半,覆盖面反而更大。
判断高度有个快捷问题:**遇到一个没写到的情况,这段提示能给模型一个判断方向吗?**低空版给不了(只能兜底),高空版给了一个空方向(「满意」),正确高度版给的是可迁移的原则。
系统提示不只是你手写的那段字符串。很多 harness 会把项目级配置常驻在系统层,Claude Code 的 CLAUDE.md 就是典型:官方文档说它是「每次对话开始时 Claude 都会读取的特殊文件」2。「每次都加载」意味着它的每一行都在每个会话里花注意力预算,所以官方文档给它立的纪律很硬。
一条纪律:只放普遍适用的内容2。只有某个子目录才用得到的构建命令、只有某类任务才需要的规范,不配住在常驻层。
另一条纪律:逐行做删除测试——对每一行问「删掉它会不会导致 Claude 出错?」2答案是否,就删。这不是洁癖:文档明确警告,臃肿的 CLAUDE.md 会让你真正的指令被忽略2。这正是注意力预算的具象版本——塞进去的每行可有可无的内容,都在稀释那几行真正重要的规则。同一份文档甚至直接说「上下文窗口是最重要的待管理资源」,它「填满得很快,而且越满性能越差」2。
那些不常用、但偶尔确实需要的内容放哪?Claude Code 的做法是技能(skills):按需加载,「不会让每个会话都变臃肿」2。「常驻最小化,其余按需取用」这个组合,正是第 3 课的主题,这里先记个账。
顺带一提:你自己项目里如果有 AGENTS.md、system_prompt.txt 之类的常驻文件,同样的删除测试照做。常驻层的膨胀最隐蔽——它不出现在任何一轮对话里,却在每一轮里收税。
工具在上下文里出现两次:定义跟着每次请求走,返回结果进入消息历史。两头都在花预算。
本系列第 4 门课讲过工具设计的功能面——参数怎么定、错误怎么处理。本课换一个视角:每个工具定义都是一段要被模型读懂的 token。Anthropic 对工具的要求是自包含、对错误健壮、用途极其清晰1,并且工具之间的功能重叠要最小1。看一对反例(schema 为了可读性做了简写):
两个工具都能查订单,描述又都含糊,它们之间的边界连写工具的人都没说清楚。「重叠最小」1这条要求的道理就在这:边界模糊的工具堆在一起,等于把「该用哪个」这道题在每一轮都重新抛给模型。合并成一个、写清楚用途和行为,问题就消失了:
这个描述不只说了「干什么」,还写明了返回形态和空结果时的行为——这是「对错误健壮」1的直接体现:模型不用猜「查不到会怎样」,也就不容易在查不到时做出奇怪的补救动作。
再看返回端。Anthropic 要求工具返回的信息做到 token 高效1。一个把订单的 40 多个内部字段加完整操作日志全部 dump 回来的工具,每调用一次就往消息历史里灌一大桶低价值 token——而且这些 token 会留在历史里,被后面的每一轮反复收税。上面例子里「默认精简、detail=true 按需拿全量」就是针对这件事的常用形态。
最后,工具清单本身也要做减法:上下文里躺着十个从来不被调用的工具,它们的定义照样每一轮全额计费。审计工具清单和审计 CLAUDE.md 是同一个动作——删掉它会出错吗?不会就删。
示例(few-shot)是第三块静态成本。它最常见的腐化路径是这样的:线上每出一个 badcase,就往提示里加一个对应的例子,半年之后你有了一份 30 条的边角案例清单——而 Anthropic 的说法很直接:别把一堆边角案例(laundry list)塞进 prompt,应该精心挑选一组多样、典范(canonical)的例子来刻画期望行为1。
「典范」的意思是:一个例子代表一类行为,而不是一个具体情况。还拿客服 Agent 说,三个例子就能把行为空间的骨架撑起来:
「多样」则是指这三个例子分别覆盖判断的不同分支,而不是同一类行为的三个变体。
那边角案例去哪了?大多数应该升回系统提示的原则层:「用户辱骂怎么办」不需要一个 300 token 的完整对话示例,一条原则「遇到情绪激烈的用户,保持克制并聚焦解决问题本身」就够了。例子教的是「期望行为长什么样」,原则教的是「遇到新情况往哪个方向想」。你可能已经发现了:往示例清单里加边角案例,和往系统提示里加分支,是同一枚硬币的两面——都是在低空打补丁,而只有升到原则层才封得住口子。
把本课收拢成一张可执行的清单。往上下文里加任何东西之前,先过一遍对应的问题:
表格之外还有一条总原则值得带走。Anthropic 在谈构建 Agent 系统时给过一个分寸建议:只有当复杂度确实能改善结果时,才考虑增加复杂度3。它原本说的是系统架构,但用在上下文的每一块上同样成立——多一个工具、多一条规则、多一个示例都是复杂度,先证明它换得回行为改善,再让它上车。
到这里,三块静态上下文的配法都过了一遍。但还有一个更进一步的问题:有些信息根本就不该预先装进窗口——与其猜模型会用到什么,不如让 Agent 在运行时自己去找。这就是下一课的主题。
Effective context engineering for AI agents — Anthropic Engineering — https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21
Best practices for Claude Code — Claude Code Docs — https://code.claude.com/docs/en/best-practices ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
Building Effective AI Agents — Anthropic Engineering — https://www.anthropic.com/engineering/building-effective-agents ↩ ↩2
按这套规则,出差住宿的发票没有任何一条能接住,会一律落到第 7 条被退回;工作日晚上的餐饮发票既不满足第 3 条也不满足第 4 条,同样落进兜底。请:(1)指出这段提示属于哪个失败极端,并说明这类症状为什么必然出现;(2)把它改写到正确高度,改写稿要能覆盖上面两种「没写到」的情况。
请做一次上下文成本审计:(1)指出工具清单里的功能重叠并给出合并方案;(2)说明合并后的工具如何让返回做到 token 高效、同时保留必要信息可取;(3)给出示例清单的改造方案:应该留下什么样的例子,边角情况交给谁处理。
记下想法、痛点、没懂的地方。只写进这门课的附录,正课文件不动。
一次请求的上下文├── 系统提示(角色、规则、背景知识)├── 工具定义(每个工具的名字、描述、参数 schema)├── 示例(few-shot:刻画期望行为的输入输出样本)└── 消息历史(用户消息、模型回复、每一轮的工具调用与结果)你是订单客服。严格按以下规则处理:1. 用户说「没收到货」,回复模板 A。2. 用户说「收到但破损」,回复模板 B,并发放 10 元优惠券。3. 订单号以 TB 开头的先查物流,以 JD 开头的先查库存。4. 用户消息里出现「投诉」二字,转人工。……(省略 14 条)19. 用户同时符合第 2 条和第 4 条时,第 4 条优先。20. 以上都不匹配时,回复「抱歉,我不太明白您的意思」。你是订单客服。请表现得专业、友好,灵活处理用户的问题,让用户满意。你是订单客服,目标是在一次对话内解决用户的售后问题。
判断原则:- 未发货的订单,用户要求退款可直接操作;已发货的先引导拒收或走退货流程。- 破损、错发、漏发属于我方责任,主动提出补偿方案,不等用户开口。- 责任归属拿不准时,先向用户确认关键事实(订单号、照片),再下判断。
硬边界(必须遵守):- 单笔补偿不超过 50 元,超出转人工。- 涉及人身伤害或法律纠纷的对话,立即转人工并标记。[
{
"name": "get_order_info",
"description": "查订单。",
"parameters": { "order_id": "string" }
},
{
"name": "search_order",
"description": "也可以查订单,支持关键词。",
"parameters": { "keyword": "string" }
}
]
[
{
"name": "search_orders",
"description": "按订单号精确查询,或按关键词模糊检索订单。默认返回至多 5 条,每条只含订单号、状态、金额、下单时间四个字段;需要完整字段时,对单个订单号加 detail=true 再查。查无结果返回空列表,不报错。",
"parameters": {
"query": "订单号或关键词",
"detail": "布尔值,是否返回完整字段,默认 false"
}
}
]
你是报销审批助手。按以下规则审批:1. 打车发票金额 < 100 元,直接通过。2. 打车发票金额 >= 100 元,要求补充行程说明。3. 餐饮发票在工作日 12:00-14:00 之间的,直接通过。4. 餐饮发票在周末的,退回。5. 办公用品类目为「电子设备」的,转主管审批。6. 发票抬头与公司名不符的,退回。7. 以上都不匹配时,退回并备注「请联系行政」。[
{
"name": "query_order",
"description": "查询订单。",
"parameters": { "order_id": "string" }
},
{
"name": "search_order_by_text",
"description": "查询订单,支持模糊搜索。",
"parameters": { "text": "string" }
},
{
"name": "get_order_full_dump",
"description": "返回订单全部字段,含 40 多个内部字段和完整操作日志。",
"parameters": { "order_id": "string" }
}
]