20万token预算下的Agent槽位分配:从静态配置到动态调度 1. 20万 token 预算下的 Agent 困境为什么同样的钱花出了两种效果同一个模型、同一个 20 万 token 的预算有人跑出来的 Agent 又快又准有人跑出来的又贵又笨。这个差距往往不在模型本身而在一个大多数人没认真对待的环节——槽位分配。先把概念说清楚。所谓槽位指的是上下文窗口里被划分出来的功能区块。一个典型的 Agent 上下文大致包含这几块系统提示词System Prompt、工具定义Tool Definitions、对话历史Conversation History、检索到的外部知识Retrieved Context、当前任务指令Task Instruction、以及留给模型输出的生成空间。每一块占多少 token就是槽位分配问题。为什么这件事在 20 万 token 的预算下格外要命因为窗口越大越容易产生反正放得下的错觉。很多人一上来就把系统提示词写到三千字工具定义塞进二十个历史对话全量保留检索结果一股脑灌进去。结果就是真正决定当前任务成败的那几百 token 指令被淹没在几万 token 的噪声里。模型不是没看到而是注意力被稀释了。我见过最典型的翻车场景是这样的一个客服 Agent系统提示词写了 4000 token 的品牌调性和话术规范工具定义了 15 个其中 8 个几乎用不到历史对话保留最近 30 轮每轮还带着完整的工具调用返回结果。跑到第 10 轮的时候用户问帮我改一下收货地址模型居然开始复述品牌价值观。这不是模型笨是槽位分错了——关键指令的权重被稀释到了几乎为零。反过来一个分配得当的 Agent同样的 20 万预算系统提示词压到 800 token 以内工具定义按场景动态加载历史对话做摘要压缩检索结果只保留 top-3 且做重排。跑起来又快又准token 消耗还低。这里有个反直觉的结论20 万 token 的预算真正该用的可能只有 2 万。剩下的 18 万是安全垫不是让你挥霍的额度。窗口大不等于要填满就像你买了个 500 平的房子不会把每个房间都堆满杂物。下面我会把槽位分配这件事拆开讲每一块该占多少、为什么这么分、怎么动态调整、以及那些我踩过的坑。如果你正在用 LangChain、Dify、CrewAI 这类框架搭 Agent或者自己手写编排逻辑这篇内容应该能帮你省下不少冤枉钱。2. 拆解 Agent 上下文的六个槽位谁在吃你的 token2.1 系统提示词最容易被写肥的槽位系统提示词是 Agent 的人格底座也是最容易失控的槽位。我见过太多项目系统提示词从最初的 500 token 一路膨胀到 5000 token每次加需求就往后追加一段最后变成一锅粥。系统提示词膨胀的典型症状里面混杂了角色设定、输出格式要求、安全边界、业务规则、示例对话、工具使用说明。这些东西本该分层管理全塞进系统提示词的结果就是——模型每次都要重新读一遍所有规则而且规则之间还会互相干扰。我的做法是把系统提示词控制在 800 token 以内只保留三样东西角色定位一句话、核心行为准则不超过 5 条、输出格式约束。业务规则和示例对话挪到单独的槽位按需注入。这样做的理由是系统提示词是每轮都要重读的它的 token 成本要乘以对话轮数。一个 5000 token 的系统提示词跑 20 轮就是 10 万 token 的消耗直接吃掉一半预算。提示系统提示词里不要写你可以使用以下工具这种话工具定义有专门的槽位重复描述只会浪费 token 还容易让模型混淆。2.2 工具定义数量比长度更致命工具定义Tool Definitions是 token 消耗的隐形杀手。每个工具的 JSON Schema 描述包括名称、描述、参数定义平均在 150 到 400 token 之间。20 个工具就是 3000 到 8000 token而且这部分是每轮都要带的。更麻烦的是工具数量多了之后模型的工具选择准确率会明显下降。我实测过一个数据工具数量从 5 个增加到 15 个时工具选择错误率从 3% 涨到了 18%。原因很简单模型在众多相似的工具描述里做选择本身就容易犯迷糊。所以工具定义槽位的核心策略是动态加载。不是所有工具每轮都需要按当前任务阶段只挂载相关的 3 到 5 个工具。比如一个订单处理 Agent在查询阶段只挂查询类工具进入修改阶段再切换到修改类工具。这样既省 token又提高选择准确率。2.3 对话历史全量保留是最贵的偷懒对话历史槽位是最容易失控的地方。很多人图省事直接把完整历史全量带上觉得反正窗口够大。但对话历史的 token 增长是线性的而且带着工具调用返回结果的历史单轮就可能上千 token。我做过一个测算一个带工具调用的 Agent每轮对话平均产生 800 token 的历史增量用户输入 模型输出 工具调用 工具返回。跑 30 轮就是 24000 token。如果工具返回的是长文本比如网页抓取结果单轮就能到 5000 token30 轮直接爆窗。对话历史槽位的正确做法是分层处理最近 3 到 5 轮保留原文更早的做摘要压缩工具返回的长文本只保留关键字段。摘要压缩不是简单截断而是让模型自己总结到目前为止发生了什么、达成了什么共识、还有什么待办。这样既保留了语义连续性又把 token 压到了原来的十分之一。2.4 检索上下文top-k 不是越大越好RAG 场景下检索上下文槽位经常被灌爆。很多人默认 top-k 设成 10 甚至 20觉得召回越多越好。但检索结果里真正相关的往往只有前 2 到 3 条后面的都是噪声不仅浪费 token还会干扰模型判断。我的经验是top-k 设成 5然后做重排rerank只保留重排后的前 3 条。如果单条内容超过 500 token做摘要或截取关键段落。这样检索上下文槽位能稳定控制在 1500 token 以内。2.5 当前任务指令最该被保护的槽位当前任务指令是决定这一轮成败的关键但它经常被放在上下文的中间位置被前后的大段内容淹没。模型对上下文中间部分的注意力天然较弱这是有实证研究的。所以任务指令要么放在最前面紧跟系统提示词要么放在最后面紧贴生成位置。我倾向于放在最后因为这样离生成位置最近注意力权重最高。同时任务指令要写得极其明确不要有歧义不要和系统提示词里的规则打架。2.6 生成空间别把预算榨干最后一个槽位是留给模型输出的生成空间。很多人分配预算时把前面几个槽位塞满只留一两千 token 给输出结果模型写到一半被截断或者被迫压缩输出质量。生成空间要按任务类型预留简单问答留 500 token结构化输出留 1500 token长文生成留 4000 token 以上。这部分预算不能省省了就是让模型话说到一半。把六个槽位的典型分配列个表方便对照槽位静态方案动态方案说明系统提示词3000800每轮重读成本乘以轮数工具定义60001500按阶段动态加载对话历史200004000近期原文 早期摘要检索上下文80001500top-5 重排取 top-3任务指令500500放末尾权重最高生成空间20003000按任务类型预留合计3950011300动态方案省 70%这张表里的数字不是标准答案但比例关系值得参考。静态方案下20 万预算跑 5 轮就见底动态方案下同样的预算能跑 17 轮以上。这就是一样的预算效果差一倍的根源。3. 槽位分配的底层逻辑注意力预算比 token 预算更重要3.1 模型不是均匀地读你的上下文很多人把上下文窗口想象成一个均匀的容器觉得放进去的内容模型都会平等对待。这是最大的误解。模型的注意力机制决定了它对不同位置、不同内容的关注度差异巨大。经验规律是这样的开头和结尾的内容注意力权重最高中间部分最低。这就是所谓的中间迷失Lost in the Middle现象。所以槽位分配不只是 token 数量问题更是注意力位置问题。关键指令要放在注意力高地噪声内容要尽量压缩或后置。系统提示词放开头任务指令放结尾中间放对话历史和检索上下文。这个顺序不是随便定的是顺着注意力分布来的。3.2 无效 token 的三种典型形态不是所有 token 都在干活。我总结了三类无效 token它们占了预算但不产生价值第一类是重复 token。系统提示词里说了你是客服助手工具定义里又写用于客服场景对话历史里模型又复述了一遍作为客服助手。同一个信息出现三次后两次都是浪费。第二类是过期 token。对话历史里那些已经完成的任务、已经确认的信息对当前轮次没有价值但还在占着位置。比如用户三轮前说我要退货现在已经进入填写退货原因阶段那个我要退货的原始表述就没必要全量保留了。第三类是干扰 token。检索结果里那些相关性不高的文档、工具返回里那些用不上的字段、系统提示词里那些永远不会触发的边界规则。它们不仅浪费 token还会主动干扰模型判断。识别并清理这三类 token是槽位优化的核心工作。我的做法是每轮对话前跑一个轻量的上下文体检统计各槽位 token 占比、标记重复内容、清理过期信息。这个体检本身消耗的 token 很少几百 token但能省下几千 token。3.3 为什么分错槽位比预算不够更致命预算不够你还能通过压缩、摘要、动态加载来救。但槽位分错了是结构性问题压缩也救不回来。举个具体例子。假设你把业务规则写进了对话历史槽位比如通过用户之前提到过的方式注入那么这些规则会随着对话轮数增加被逐渐推到上下文中间注意力权重越来越低。跑到后面模型就忘了这些规则。这不是模型记性差是规则放错了位置。再比如你把工具定义和系统提示词混在一起写模型在生成时会分不清哪些是行为准则哪些是可调用工具容易出现该调工具时不调、不该调时乱调的情况。槽位分错的本质是让不同性质的信息互相污染。系统提示词是宪法工具定义是工具箱对话历史是会议记录检索上下文是参考资料任务指令是当前待办。这五样东西性质完全不同混在一起就是灾难。4. 动态槽位分配的实操方案从静态配置到运行时调度4.1 建立槽位预算表先量化再优化优化槽位的第一步是量化。你需要知道当前每个槽位实际占了多少 token。不同模型的 tokenizer 不一样中文和英文的 token 比例也不一样所以不能靠估算要实际统计。在 LangChain 里可以用get_num_tokens_from_messages来统计消息列表的 token 数。自己手写编排的话用对应模型的 tokenizer 库比如 tiktoken来算。关键是每轮对话后都记录一次各槽位的 token 占用形成时间序列。有了数据之后建一张预算表给每个槽位设上限。比如系统提示词 800、工具定义 1500、对话历史 4000、检索上下文 1500、任务指令 500、生成空间 3000。这个上限是硬约束超了就触发压缩逻辑。注意预算表要按任务类型分别设置。客服 Agent 和代码 Agent 的槽位比例完全不同代码 Agent 的生成空间要留大得多检索上下文反而可以小一些。4.2 工具定义的按需加载用路由层做前置判断工具定义动态加载的关键是加一个路由层。在正式调用模型之前先用一个轻量判断可以是小模型也可以是规则匹配决定这一轮需要哪些工具。路由层的输入是用户当前输入加上最近一轮对话输出是工具子集。比如用户说帮我查一下订单状态路由层判断需要订单查询工具就只挂载这一个工具。用户说我要退货并退款路由层判断需要退货工具和退款工具就挂载这两个。路由层本身可以用规则实现关键词匹配也可以用一个小模型做分类。规则实现成本低但覆盖有限小模型实现灵活但增加一次调用。我的建议是混合高频场景用规则长尾场景用小模型兜底。这样做的收益很明显工具定义槽位从固定的 6000 token 降到平均 1500 token同时工具选择准确率提升。因为模型每次只需要在 3 到 5 个工具里选而不是在 20 个里选。4.3 对话历史的分层压缩摘要 关键实体提取对话历史的压缩不能简单截断那样会丢失语义连续性。我的方案是三层结构第一层是最近 3 轮原文完整保留包括工具调用和返回。这保证了当前任务的上下文完整。第二层是更早对话的摘要由模型生成控制在 500 token 以内。摘要要包含已确认的关键信息订单号、用户偏好等、已达成的共识、未完成的待办。第三层是关键实体表用结构化格式存储比如{订单号: 12345, 收货地址: 已修改, 退款状态: 待处理}。这部分不占多少 token但能在需要时快速检索。三层加起来对话历史槽位能稳定控制在 4000 token 以内而全量保留可能要 20000 token。压缩比达到 5 倍而且关键信息不丢。摘要的生成时机也有讲究。不要每轮都重新生成摘要那样太贵。我的做法是每 5 轮生成一次或者当对话历史超过预算上限时触发。摘要生成本身用便宜的小模型就行不需要用主模型。4.4 检索结果的重排与截断把 top-k 用对检索上下文槽位的优化分两步重排和截断。重排是把初步召回的 top-10 用 rerank 模型重新排序取前 3。这一步能显著提升相关性因为向量检索的相似度和实际相关性经常不一致。rerank 模型可以用 Cohere Rerank 或者开源的 bge-reranker成本很低。截断是对单条结果做长度控制。如果一条结果超过 500 token就提取关键段落或做摘要。很多文档的核心信息就集中在几个段落全文灌进去大部分是浪费。这两步做完检索上下文槽位从 8000 token 降到 1500 token而且模型用起来更准。因为噪声少了模型不会被不相关的文档带偏。4.5 任务指令的位置与写法放末尾写具体任务指令要放在消息列表的最后一条紧贴生成位置。写法上要避免模糊表述直接说清楚这一轮要做什么、输出什么格式、有什么约束。反面例子请根据以上信息回答用户问题。这种指令太泛模型不知道重点在哪。正面例子用户当前询问订单 12345 的物流状态。请调用订单查询工具获取最新物流信息然后用一句话概括当前状态并给出预计送达时间。如果物流信息超过 24 小时未更新提示用户可能延迟。后者把任务、工具、输出格式、边界条件都说清楚了模型执行起来准确率高得多。任务指令槽位不需要长但需要精确。5. 那些让我交过学费的槽位分配坑5.1 系统提示词里的示例对话陷阱我早期特别喜欢在系统提示词里放示例对话few-shot examples觉得这样能提升输出质量。确实有效果但代价很大。一段三组示例对话轻松占掉 2000 token而且每轮都要重读。更麻烦的是示例对话会锚定模型的输出风格。如果示例是简短回答模型就倾向于简短如果示例是详细回答模型就倾向于啰嗦。当任务类型变化时固定的示例反而成了束缚。后来我改成动态示例只在需要特定格式输出时临时注入 1 到 2 个示例放在任务指令附近。这样既保留了示例的引导作用又避免了常驻 token 消耗。5.2 工具返回结果的全量回灌工具调用返回的结果很多人直接全量塞回对话历史。比如调用一个网页抓取工具返回 5000 token 的 HTML就原样放进去。这是巨大的浪费。工具返回结果应该做字段提取。比如订单查询工具返回一个 JSON你只需要status、estimated_delivery、last_update三个字段其他都不要。这一步在工具层做不要留给模型做因为模型做字段提取本身也要消耗 token。我见过一个项目工具返回全量回灌单轮对话历史涨到 8000 token跑 10 轮就爆窗。改成字段提取后单轮降到 800 token效果还更好因为模型不用在无关字段里找信息了。5.3 多 Agent 协作时的槽位重复用 CrewAI 或类似框架做多 Agent 协作时容易出现槽位重复。每个 Agent 都有自己的系统提示词如果这些提示词里都写了相同的背景信息那就是 N 倍浪费。多 Agent 场景下共享的背景信息应该抽出来只注入一次。每个 Agent 的系统提示词只保留自己特有的角色定位和职责。这样能把总 token 消耗降下来。另外Agent 之间的消息传递也要控制。不要把一个 Agent 的完整输出直接传给下一个 Agent而是传递结构化摘要。比如研究员 Agent 输出 3000 token 的报告传给写作 Agent 时应该压缩成 500 token 的要点。5.4 Prompt Cache 用不对省的钱又吐回去Prompt Cache 是个好东西能把重复的前缀缓存起来后续调用只算增量 token。但用不对的话反而会增加成本。关键点是缓存的前缀必须稳定。如果你每轮都改系统提示词或者把动态内容放在前面缓存就失效了。正确的做法是把稳定内容系统提示词、固定工具定义放前面动态内容对话历史、任务指令放后面。这样前缀能命中缓存动态部分才计费。我实测过一个场景把系统提示词和工具定义固定在前对话历史放后Prompt Cache 命中率能到 80% 以上整体 token 成本降了 60%。但如果顺序反了命中率掉到 20%省的钱又吐回去了。提示不同平台的 Prompt Cache 机制不一样有的自动缓存有的需要显式标记。用之前先看清楚文档别想当然。6. 一套可复用的槽位分配检查清单6.1 上线前的六项自检每次 Agent 上线前我会跑一遍这个检查清单第一系统提示词是否超过 1000 token超了就拆把业务规则挪出去。第二工具定义是否超过 8 个超了就做动态加载按场景分组。第三对话历史是否有压缩策略没有就加上至少做摘要。第四检索结果是否做了重排没做就加上 rerank。第五任务指令是否在末尾不在就调整位置。第六生成空间是否按任务类型预留没预留就补上。这六项检查下来基本能避免 80% 的槽位分配问题。6.2 运行时的监控指标上线后要持续监控几个指标各槽位 token 占比哪个槽位在膨胀一眼能看出来。缓存命中率低于 50% 就要检查前缀是否稳定。工具选择准确率低于 90% 就要检查工具定义是否过多或描述是否模糊。任务完成率低于预期就要检查任务指令是否清晰。单轮平均 token 消耗突然升高就要排查是否有异常输入。这些指标不需要很复杂的监控系统在日志里打点统计就行。关键是要持续看不能上线就不管了。6.3 不同场景的槽位配比参考最后给几个不同场景的槽位配比参考都是我在实际项目里验证过的场景系统提示词工具定义对话历史检索上下文生成空间客服问答8001000300015001000代码助手10002000500020004000数据分析8001500400030003000内容创作1200500200020005000多 Agent 协作600/Agent1000/Agent200010002000这些数字不是死的要根据实际任务调整。但比例关系可以参考生成空间在代码和创作场景要留大检索上下文在数据场景要留大工具定义在客服场景要控制。槽位分配这件事说到底是个把钱花在刀刃上的问题。20 万 token 的预算分对了能跑出 40 万的效果分错了 20 万只能当 5 万花。我踩过的那些坑希望你能绕过去。