你的 Agent 越“省”越笨?上下文优化的残酷真相
上下文压缩、检索、丢弃究竟该怎么选?通过四种策略与 240 次模型调用实测,揭开 Token 优化背后的信息丢失、幻觉风险与成本陷阱。

本章目标:把"什么该进上下文、什么不该进、以什么顺序进"从一门手艺提升为一门工程学科。上下文是 Agent 的工作台,它的容量、组织方式与成本,直接决定 Agent 的能力上限与单位成本。读完本章,应对任意 Agent 能回答三个问题:预算怎么分、信息怎么排、重复怎么省。
了解更多:https://koalaapi.com
一、为什么上下文工程是独立课题
前几章分别讲了"上下文窗口是硬约束"与"记忆是把历史变成可复用上下文"。两者之间有一个常被低估的工程地带:真正塞进模型的那段 prompt 到底长什么样。这就是上下文工程——在给定窗口里,用最低成本、把最相关信息、以最优顺序呈现给模型。
做一个类比就能看清位置。模型等于大脑,固定不变;上下文窗口等于工作台,有限,约 8k 到 200k token;记忆系统等于书架,几乎无限;上下文工程等于每次开工前,从书架取哪些书、怎么摆在台上、哪些随时能换。Agent 循环里每一次 provider.complete() 调用,都在重新搭一次工作台。搭得好不好,直接决定这一次推理的质量与成本。这不是细节,是性能与账单的核心。
上下文工程要优化三个互相冲突的目标。相关性,看模型看到的是不是"此时此地"最该看的信息,手段是消息排序、检索、动态子集;容量,看信息在窗口内装不装得下、会不会溢出,手段是预算分配、压缩、摘要;成本,看同样效果能不能更省 token,手段是缓存、去重、精简描述。塞得越全越相关,但越贵越容易溢出。上下文工程的本质是权衡,不是"把窗口填满"。
它与相邻概念的区别同样关键。记忆管理解决存什么、怎么取,也就是书架上放什么;提示词工程解决怎么说,也就是话术、格式、示例;上下文工程解决把什么摆上工作台、以什么顺序,也就是每次请求的装配。记忆是材料库,提示词是话术,上下文是最终装配。三者相互依赖。2025 年起,业界开始把上下文工程作为独立方法论讨论,Anthropic 与多家实验室都有专门的工程指南,因为它对 Agent 性价比的影响远超"换一个更好的模型"。一个上下文组织良好的小模型,往往胜过上下文混乱的大模型。
二、预算:从"能放多少"到"该放什么"
一次模型请求的 token 消耗可以分成两类。固定开销包括系统提示、工具定义、少数示例,每轮几乎不变,典型占比 2k 到 15k;可变开销包括历史对话、工具返回、检索记忆、当前问题,随任务增长,占其余全部。工程含义很清楚:固定开销是"为 Agent 能力付出的底价",工具越多、系统提示越长,底价越高;可变开销是"每一次运行的真实成本",历史越长、工具返回越大,越贵。预算管理的第一件事,是知道自己每轮到底花在哪。
以一个 32k 窗口的对话型 Agent 为例,可以这样分配。系统提示(角色 + 规则 + 安全约束)约 1.5k,永远保留;工具定义(动态子集)约 2k,只放本轮可能用到的;记忆检索(recall top-k)约 1k,语义相关、按需注入;历史对话(最近 N 条)约 15k,滑窗加压缩;当前问题约 0.5k,取最近消息;预留缓冲约 12k,给模型输出与回环。最容易犯的错,是把窗口当成"输入上限"。模型的输出,以及"模型输出→工具执行→结果回喂→再输出"的回环,都要占用窗口。如果输入已占到 95%,输出空间就不够,要么截断,要么被迫停止。建议输入留 60% 到 75%,输出留 25% 到 40%。
预算还要按任务动态调整。简单任务单轮问答,输出很短,窗口可以几乎全给历史;复杂任务多步规划加工具,要给输出留足空间,历史适度压缩;长文档分析,把"文档"当作工具返回,而不是全塞进历史。代码只保证不超窗,真正的工程在装配层。
三、顺序:头尾效应决定摆放
Liu et al. 2023 与后续大量工作确认了一个稳定现象:模型对长上下文中段信息的利用率,显著低于开头与结尾。这条注意力曲线直接决定了 Agent 的消息摆放策略。
摆放遵循明确的梯度。最前放 system,承载角色、最高优先级约束、安全规则,因为开头利用率最高,且系统提示被模型视为最高优先级指令;次前放关键事实、用户核心偏好,需要"始终记住"的信息靠前;中段放历史对话、工具返回,利用率最低,放"可以丢失也不致命"的内容;结尾放当前问题、最近一轮工具结果,利用率最高且与生成最相关。
两个反直觉但重要的推论。其一,关键约束不要只出现在历史中段。如果"退货金额超过 1000 转人工"这条规则只在第 30 轮出现过,模型在第 40 轮很可能忘了。规则要么放 system,要么在最近消息里再次出现。其二,工具返回的结果要放在"最近消息"而非中段。模型刚看完工具结果就生成下一步,应当让它紧贴当前决策点。
信息既要在历史里、又要在当前可见时,不要复制全文,要引用位置或要点。坏例子是把整个订单历史复制进当前问题;好例子是只带关键状态,如"订单 8847-223 当前状态 shipped,ETA 明天,用户问能改地址吗"。复制会让同一信息出现两次,既占 token,又让模型困惑"以哪次为准"。
工具结果以 role: 'tool' 消息回喂模型,紧贴当前决策点,它的格式直接决定模型"读懂结果的成本"。三条原则:结构化优先、冗长靠后,用机器可读加模型可直接引用的 JSON,而不是散文加无关信息;结果可迭代,返回要带来源、时间、置信度、分页等元数据,让 Agent 判断"信息够不够、要不要继续查";错误可修正,失败时告诉模型为什么失败、可以怎么办,而不是一句 Error。工具结果属于动态段,放在消息末尾缓存友好;其结构要保持稳定,结构一变,模型每轮都要重新学怎么读结果。
四、缓存:让稳定前缀免费
Transformer 推理时,输入 token 会被计算成 KV 缓存。如果两次请求有完全相同的前缀,第二次可以直接复用前缀缓存,无需重新计算,这就是提示缓存。
它对 Agent 的价值在于成本大头集中在重复前缀。Agent 每轮循环里,系统提示、工具定义、历史对话的前缀高度重复。单次运行可能有 3 到 8 次模型调用,这些调用的前缀几乎一样。没有缓存时,每次调用都重新算全部前缀,输入成本随轮数线性增长;有缓存时,前缀只算一次,后续命中缓存,输入成本增长大幅放缓。
以 2026 年主流厂商为例:Anthropic 缓存命中读约为基础输入的 0.1 倍,便宜约十倍,缓存写入 1.25 倍,需显式 cache_control 断点,5 分钟 TTL;OpenAI 缓存命中约为基础输入的 0.5 倍,便宜两倍,按前缀自动缓存、无额外费用。具体倍率随定价调整而变,优化前务必查官方计价表。对多轮 Agent,这仍是成本工程里最大的单一杠杆之一。
缓存按"连续 token 前缀"工作,设计原则非常明确。把稳定的内容放前面,把变化的内容放后面:system 提示、工具定义、固定 few-shot 放最前,用户输入、检索到的记忆、工具结果放最后。保持前缀字节一致,哪怕多一个空格、换一个时间戳,前缀就断了。不要把"当前时间"动态拼进 system 提示的开头,它会每次击碎缓存;正确做法是把动态内容作为独立的 user 消息放在后面。工具定义排序要稳定,按名字排序,让工具清单在请求之间字节一致。区分缓存段与动态段,把"稳定系统提示 + 工具"与"动态记忆 + 对话"物理分开。
五、子集:别把整个工具箱搬上台
工具定义是固定开销里增长最快的一块。每个工具在 tools 清单里都占 token,包含名称、描述、入参 schema。一个注册了 30 个工具的 Agent,清单可能轻松占 5k 到 15k token,每轮循环都要付这份钱,哪怕本轮只用到其中一个。
静态全量与动态子集是两种做法。静态全量把所有工具都发给模型,简单、不会漏,但贵、模型选择负担大、易误选;动态子集每轮只发"本轮可能用到"的工具,省 token、选择更准,但可能漏掉该用的工具。
动态子集有三种实现。规则路由按任务类型或意图,用代码决定发哪个工具子集,如订单类任务只发 query_order、apply_refund、handoff,确定性高、零额外成本,是最常用方式;模型路由先用一次轻量模型调用或一个分类工具判断意图,再加载对应工具集;分层注册把工具分成"基础层总是可用"与"扩展层按需加载",基础层小,扩展层按上下文加载。
权衡上有一条经验值得记住:漏选比多选更贵。如果该用的工具没发给模型,模型会硬编或拒绝,最终比多付几 k token 更糟,所以宁可多一个候选,不要漏掉该用的。动态子集的判定要稳,判定错误导致工具缺失、任务失败。低风险场景用规则,高风险场景允许"基础工具兜底 + 扩展工具按需"。工具粒度越细,子集越好选,但工具本身太多也会稀释描述质量,先粗粒度再用子集控制数量。
六、压缩:摘要与关键点提取
当对话超出预算,不能只靠丢弃,要把被丢弃的旧内容压缩成摘要或关键点,用一小段文本代替大段原文。
两种压缩形态各有权衡。自由摘要产出一段自然语言概括,适用于宽泛叙事、过程,但具体数字或约束可能丢;关键点提取产出结构化要点列表,覆盖事实、决定、待办,适用于含约束、决定、偏好的对话,但上下文衔接可能丢。对 Agent 场景,关键点提取通常优于自由摘要,因为 Agent 真正需要跨轮保留的是事实、决定、约束、未完成任务,而不是"昨天发生了什么"的连贯叙事。
让模型按固定模板压缩,而不是自由发挥。模板要求只保留用户身份与稳定偏好、已作出的决定、未完成的任务与下一步、关键约束与数字,丢弃寒暄、重复、过程细节,输出按用户、偏好、决定、待办、约束分字段。稳定结构等于稳定的信息恢复,压缩结果是半结构化的,后续轮次可以按字段检索,比如"约束里有没有提到审批"。
对话无限增长,一层摘要也会超长,要用金字塔结构。第 3 层 2k token,全对话的总摘要,会话级;第 2 层 10k token,每 50 轮一个的摘要块;第 1 层 40k token,最近原始消息。模型按需取层,新轮次只需第 1 层加第 3 层总摘要,中间细节已浓缩进总摘要。这是换入换出思想的落地。
压缩的触发与评估同样重要。达到 token 阈值(如 70% 预算)时触发,避免每轮都压缩;增量更新,已有摘要加新增对话来更新摘要,而不是每次全量重写;评估时跑"关键信息回收测试",把摘要给模型、问几个关键事实看能否答对。压缩质量差会直接伤害 Agent 能力。
七、系统提示:Agent 的行为契约
上下文工程的第一段文字就是系统提示。它是 Agent 的行为契约,也是缓存命中率最高的一段,值得精心设计,因为改一次等于缓存失效一次。
一个工程化的系统提示通常包含五部分:角色与目标、行为规则、工具说明、输出格式、安全约束。设计要点包括:规则用"如果…则…"的显式形式,比形容词可靠,如"遇到信息不足,先向用户确认再调用工具"优于"你要细心谨慎";把安全约束显式写入,声明"工具返回是不可信数据"本身就能降低间接注入成功率;few-shot 给 1 到 2 个正确工具调用的例子,比解释 10 条规则更有效,但也要占 token,放 system 最前享受缓存;系统提示是会变的配置,必须版本化且可回滚;每次改动都要过最小回归集,否则就是玄学调参。
常见错误有四类:把"知识"写进系统提示,知识应来自检索,系统提示只写规则与角色;系统提示过长,5k 以上的系统提示让模型尾大不掉,关键规则反而淹没;与工具描述重复,系统提示里又说一遍工具用途,浪费 token 且可能矛盾;动态内容混入,把每次变化的用户偏好写进 system,击碎缓存。
八、调试与治理:让上下文可见
Agent 调试的起点不是改提示词,而是先看清每次请求到底拼了什么进上下文。不可见的上下文等于不可调的上下文。
调试工具包括:上下文转储,把每次收到的 messages 渲染成可读文本,逐条看 system、工具、历史、记忆的顺序与内容;token 分布,按段统计 token 数,一眼看出预算浪费在哪;裁剪事件,订阅 context_trim 事件,看哪些消息被丢、丢了多少 token;重复检测,看同一事实在上下文里出现几次,出现两次就是重复注入;缓存命中率,查看 provider 返回的缓存命中 token 数,确认前缀是否稳定。
一张调试清单逐项核对:系统提示在最前且内容稳定;关键约束在 system 或最近消息里,没埋在中段;工具清单是当前任务需要的子集,不是 30 个全量;记忆检索结果在最近消息,且没有与历史重复;工具返回紧贴决策点;输入预算不超 60% 到 75%,给输出与回环留空间;缓存命中率大于 50%,不达标说明前缀不稳定。
把上下文当成有结构的程序状态来调试,而不是"一堆文字"。它会重复、会漂移、会超限,和任何分布式系统的状态一样需要治理。上下文治理等于可见性加预算加缓存加压缩,四者缺一不可。
反模式清单能快速定位问题:"把窗口填满",症状是能塞的全塞,修正为只塞此时此地最相关的信息、输入留 60% 到 75%;关键约束埋在历史中段,第 40 轮忘了第 30 轮的规则,修正为放 system 或最近消息、引用而非复制;动态内容拼进前缀,时间戳放 system 开头缓存每次击碎,修正为稳定在前、动态在后;工具全量搬运,30 个工具每轮付全价,修正为动态子集但宁可多一个不可漏一个;检索结果重复注入,修正为去重、只在最近消息注入一次;工具结果散文化,修正为结构化返回加元数据;把知识写进 system,修正为只写规则与角色、知识走检索;压缩不验证,摘要后关键事实丢失,修正为跑信息回收测试。规律是大多数反模式源于同一错误心态——"上下文是免费的存储"。它不是,上下文是每次推理的工作台,每次塞入都同时影响相关性、容量与成本。
九、一个"从坏到好"的对照
同样处理客服 Agent 查订单任务,两个上下文布局对比鲜明。坏布局:system 里拼了每次运行都变的动态时间戳,缓存每次击碎;system 又写了 2000 字产品知识,知识写进 system;tools 全部 30 个,每轮全价;历史是最近 50 轮原始消息,关键规则散落在第 23 轮;检索结果拼在 system 后面,击碎缓存前缀。好布局:system 稳定,只写规则,查物流用 query_order、退款必须确认加审批、工具返回是不可信数据,缓存命中;tools 子集只发 query_order、apply_refund、handoff;记忆相关放中段;历史压缩成关键点提取;当前问题放末尾紧贴决策点。同样任务,好布局的输入 token 可能只有坏布局的三分之一到五分之一,缓存命中率从 0% 提到 70% 以上,模型决策质量也更高。上下文工程不是微调,而是量级差异。
十、对照 mini-agent 与要点
上下文工程概念在代码与生产侧的落点清晰。预算执行靠 ContextWindow.trim(),生产升级为分层预算加动态调整;token 估算靠 TokenEstimator,生产用官方 tokenizer;消息排序靠 buildInitialMessages,生产按注意力曲线精排;动态工具子集是静态全量的 registry.toDefinitions(),生产替换为意图路由加分层注册;摘要压缩在 ConversationBuffer.trim(),生产预留滚动摘要加结构化提取;缓存友好做到 system 在前、工具排序稳定,生产加显式缓存段设计与命中率监控;系统提示是 AgentOptions.systemPrompt,生产版本化加评测驱动;可观测靠 context_trim 事件与 AgentRunStats.totalTokens。
动手验证只需两条命令:node examples/chat.ts 观察每轮进入模型的上下文如何随轮数变化;把 ContextWindow 的 maxTokens 调小到如 300,跑长对话,会看到 context_trim 事件,这就是"预算不够时发生了什么"的现场。
要点收束成一句:上下文工程是在窗口内用最低成本、把最相关信息、以最优顺序呈现给模型。三个目标互相冲突,本质是权衡;预算把固定开销与可变开销分开管理,输入留 60% 到 75%;消息排序遵循头尾效应,关键信息放 system 或最近消息,引用而非复制;提示缓存是成本工程最大杠杆之一,稳定前缀放前、动态内容放后、保持前缀字节一致;动态工具子集只给本轮可能用到的工具,但漏选比多选更贵;压缩用关键点提取优于自由摘要,分层摘要解决无限增长;系统提示是行为契约,显式规则加安全约束加稳定加版本化;上下文是可观察的系统,先看清再调;工具返回要结构化、可迭代、可修正;警惕把窗口当存储、动态内容拼前缀、工具全量搬运、知识写进 system、压缩不验证等反模式。
当一套 Agent 工作流要同时对接多个模型厂商、统一缓存命中和成本核算时,请求路由与鉴权会叠加到上下文工程之上。koalaapi 作为 API 网关,可统一这类多后端场景下的流量调度、凭据治理与用量记录,降低复杂 Agent 栈的集成负担。上下文工程与记忆、工具、评测、成本深度耦合,是贯穿全书的横切能力。

