NVIDIA SoL-Pi 开源:Agent 重复 Token 开销的结构化复用方案
Agent 跑一个任务Token 账单里有一大半花在了重复读同样的东西上——这不是模型的问题是架构的问题。NVIDIA 开源的 SoL-Pi 就是冲着这块冗余去的它把 Agent 执行过程中反复出现的上下文、工具调用描述、历史轨迹做了结构化复用让同一份信息不必在每一轮推理里被重新编码一遍。如果你正在做 Agent 开发、被 Token 用量压得喘不过气或者单纯好奇省 Token这件事到底能省到什么程度这篇内容会把 SoL-Pi 的思路、Harness 的角色、以及实际能落地的优化手段拆开讲清楚。1. SoL-Pi 到底在解决 Agent 的哪一块浪费1.1 先搞清楚 Agent 的 Token 都花在哪了很多人第一次看 Agent 的账单会懵明明用户只问了一句话为什么消耗了几万 Token拆开看就明白了。一个典型的 Agent 循环里每一轮推理都要把系统提示词、工具定义、历史对话、中间观察结果全部重新塞进上下文。假设系统提示词加工具定义固定占 3000 Token任务跑了 15 轮光这部分固定开销就是 45000 Token而真正新产生的信息可能只有几千 Token。这就是所谓的重复开销redundant overhead。它有几个典型来源固定前缀重复系统提示、工具 schema、few-shot 示例每轮都在重复编码。历史轨迹重复前面几轮的思考和工具返回结果被原封不动地带着往后传。工具描述重复同一个工具的能力说明在多轮里被反复读取。观察结果冗余工具返回的大段原始数据比如一整页 HTML其实只有几行有用。SoL-Pi 的切入点就是把这些每轮都要重新过一遍的东西从每次重新计算变成一次构建、多次复用。1.2 SoL-Pi 的核心思路把重复的部分固化下来SoL-Pi 这个名字里的 SoL 可以理解为一种分层/结构化的组织方式Pi 则指向策略或规划层面。它做的事情本质上是给 Agent 的执行过程建立一套可复用的中间表示。打个比方。传统 Agent 每轮推理就像每次做饭都从去菜市场买菜开始哪怕你昨天已经买好了同样的菜。SoL-Pi 的做法是把已经确定不变的食材提前备好、贴上标签、放进冰箱下一轮直接取用只处理这一轮真正新增的那道菜。具体到技术层面它大致做了这几件事上下文分层把上下文拆成稳定层系统提示、工具定义和动态层本轮新增的观察、思考稳定层只编码一次。轨迹压缩对历史轨迹做摘要化处理保留决策关键点丢弃冗余的中间态。工具调用缓存相同参数的重复工具调用直接命中缓存结果不再走一遍完整推理。结构化复用把工具返回结果解析成结构化字段只把相关字段喂回模型。注意SoL-Pi 不是魔法压缩它省的是结构性冗余不是把有用信息也砍掉。理解这一点很关键否则你会在调参时把关键上下文一起压没了导致 Agent 变傻。1.3 为什么这件事值得 NVIDIA 专门开源NVIDIA 做这件事的动机其实很直接Agent 是当前算力消耗的大头之一而 Token 消耗直接对应推理算力。如果能把重复开销砍掉 30% 到 50%意味着同样的硬件能跑更多 Agent 任务或者同样的任务成本直接下降。从工程角度看SoL-Pi 的价值不在于某个单点技巧而在于它把省 Token这件事系统化了。市面上很多优化是零散的——有人手动精简 prompt有人写个缓存层有人做结果截断。SoL-Pi 提供的是一个相对完整的框架让你不用自己从零拼装。对开发者来说最实际的意义是你可以拿它当参考架构看看一个成熟的 Agent 执行框架是怎么组织上下文、怎么管理轨迹、怎么处理工具调用的。哪怕你不用它的代码这套思路也能直接迁移到自己的项目里。2. Harness 在 Agent 里扮演什么角色和 SoL-Pi 什么关系2.1 Harness 不是 Agent是 Agent 的跑道热词里反复出现 harness 和 harness 和 agent 区别说明很多人对这两个概念是混的。用一句话说清楚Agent 是跑的人Harness 是跑道和裁判。Harness 负责的是 Agent 运行所需的那套外围设施执行循环管理什么时候调用模型、什么时候执行工具、什么时候结束。上下文组装把系统提示、历史、工具结果拼成模型能吃的输入。工具调度解析模型输出的工具调用意图实际去执行再把结果塞回去。状态与错误处理超时、重试、异常捕获、循环终止条件。可观测性记录每一步的输入输出方便调试和计费。Agent 本身更偏向决策逻辑——它决定下一步做什么。Harness 则是把决策落地的那套工程骨架。2.2 SoL-Pi 和 Harness 的配合关系SoL-Pi 的优化点绝大部分落在 Harness 这一层。因为重复开销恰恰是 Harness 在组装上下文时产生的。所以两者的关系是维度Harness 的职责SoL-Pi 的介入点上下文组装拼接系统提示、历史、工具结果分层缓存稳定部分只拼动态部分工具调用执行工具并回填结果缓存重复调用结构化解析结果轨迹管理保存完整执行历史压缩历史保留决策关键点循环控制判断是否继续基于压缩后的状态做判断减少无效轮次理解这层关系后你会发现想省 Token改 Harness 比改模型有效得多。模型你换不了但 Harness 是你自己写的每一处上下文拼接都是优化机会。2.3 一个容易踩的坑Harness 缓存和语义正确性我在实际项目里踩过一个坑值得单独说。当时为了省 Token给工具调用加了一层相同参数直接返回缓存的逻辑。结果遇到一个查询类工具参数一样但底层数据变了比如查库存参数是商品 ID但库存实时变动缓存直接返回了旧值Agent 基于错误数据做了决策。教训是缓存要区分幂等和非幂等。纯计算类、静态查询类工具可以缓存涉及实时状态、有副作用的工具绝对不能盲目缓存。SoL-Pi 这类框架在设计缓存策略时通常会让开发者显式标注工具的可缓存性而不是一刀切。提示给工具打标签时至少分三类——可缓存纯读、静态、条件缓存读但有时效、不可缓存写、有副作用。这个分类决定了你的 Harness 能省多少、以及会不会省出 bug。3. 把重复开销真正省下来的几个实操手段3.1 上下文分层稳定层只编码一次这是最直接、收益最明显的一招。做法是把上下文明确切成两层稳定层系统提示词、工具定义、固定的 few-shot 示例、角色设定。这些在整个会话里几乎不变。动态层用户输入、本轮工具返回、模型新产生的思考。关键在于如果你的推理后端支持prompt caching很多主流推理服务都支持前缀缓存那么把稳定层放在最前面、且保持字节级不变就能命中缓存这部分 Token 的计费会大幅降低甚至免费。实操要点稳定层内容一个字都不要变。哪怕多一个空格、换一个标点缓存就失效了。动态内容永远追加在稳定层之后不要插到中间。工具定义按固定顺序排列不要每轮重新排序。我实测过一个客服 Agent光是把工具定义顺序固定、系统提示去掉动态时间戳缓存命中率从 40% 出头提到了 85% 以上Token 成本直接砍半。3.2 轨迹压缩别把整本日记都带着走Agent 跑长任务时历史轨迹会越来越长。很多人图省事直接把所有历史原样往后传结果第 20 轮的时候光历史就占了几万 Token。正确的做法是分级压缩最近 N 轮保留完整内容因为模型需要细节做决策。较早的轮次压缩成摘要只保留做了什么决策、得到什么结论。更早的轮次进一步压缩成一句话甚至只保留关键实体和状态。这里有个经验值最近 3 到 5 轮保留原文5 到 15 轮做摘要15 轮以上只留结论。具体阈值要看任务复杂度但核心原则是——越久远的信息越应该被压缩成结论而非过程。注意压缩不是简单截断。截断会丢掉关键上下文导致 Agent 重复劳动。摘要要保留决策依据比如用户已确认地址是 X库存查询显示 Y 缺货这些是后续决策的锚点。3.3 工具结果结构化只喂有用的字段工具返回的原始数据往往很胖。比如一个网页抓取工具返回整页 HTML一个数据库查询返回 50 个字段但 Agent 真正需要的可能只有 3 个。SoL-Pi 思路里很重要的一点就是在工具返回和模型输入之间加一层结构化解析# 反面例子直接把原始结果塞回去 raw_result fetch_page(url) # 返回 200KB HTML context.append(raw_result) # 200KB 全进上下文 # 正确做法解析出关键字段 parsed { title: extract_title(raw_result), price: extract_price(raw_result), stock: extract_stock(raw_result), } context.append(json.dumps(parsed)) # 只进几百字节这一层解析可以用规则、正则也可以用一个小模型做抽取。收益非常可观——我见过一个电商比价 Agent加了这层解析后单次任务的 Token 从 8 万降到 2 万出头。3.4 重复工具调用去重同一件事别做两遍Agent 有个常见毛病在推理过程中反复调用同一个工具、传同样的参数。比如反复查同一个订单状态反复读同一个文件。Harness 层可以加一个调用指纹机制对每次工具调用生成指纹工具名 规范化参数。在会话内维护一个指纹到结果的映射。命中指纹时直接返回缓存结果并明确告诉模型这是缓存结果。这里要小心前面提到的幂等性问题。我的做法是给每个工具配一个cacheable标志只有标记为可缓存的工具才走指纹去重。4. 省 Token 的边界哪些能省哪些省了会出事4.1 省 Token 不等于无脑压缩优化做到一定程度一定会遇到再压就变傻的临界点。这个临界点因任务而异但有几个信号可以帮你判断Agent 开始重复问已经回答过的问题——说明历史压缩过头丢了关键状态。Agent做出和之前矛盾的决策——说明决策依据被压没了。工具调用参数明显错误——说明工具定义或上下文被破坏。遇到这些信号说明你压过头了要往回退。我的经验是先压最没信息量的部分原始 HTML、冗余日志、重复的工具描述最后才动历史轨迹和决策依据。4.2 不同任务类型的省 Token 空间差异很大不是所有 Agent 都能省出同样的比例。大致可以分几类任务类型重复开销占比可优化空间主要手段短对话问答低小前缀缓存多轮工具调用高大轨迹压缩 结果结构化长文档处理中中分块 摘要代码生成/修改中高大上下文分层 增量传递实时数据查询中小受幂等限制谨慎缓存看这张表你会发现多轮工具调用类 Agent 是省 Token 的主战场因为它的重复开销天然最高。如果你的 Agent 属于这一类投入优化 Harness 的回报会非常明显。4.3 一个反直觉的结论有时候多花 Token 反而更省这话听起来矛盾但实际项目中经常遇到。比如为了省 Token 把工具结果压得太狠导致 Agent 判断失误多跑了好几轮才纠正总消耗反而更高。为了省 Token 不做充分的 few-shot导致模型输出格式错误反复重试。所以优化的目标不是单轮 Token 最少而是**完成任务的总 Token 最少**。这个视角的转换很重要很多新手会陷进每轮都要压到极致的误区。提示衡量优化效果一定要看端到端指标——任务成功率、平均总 Token、平均轮次。只看单轮 Token 会误导你。5. 从 SoL-Pi 能学到什么可迁移的工程经验5.1 把省做成架构而不是补丁SoL-Pi 最值得学的地方是它把 Token 优化当成架构设计的一部分而不是事后打的补丁。很多团队的做法是先写个能跑的 Agent发现账单太高再到处加缓存、加截断结果代码里全是零散的优化逻辑维护起来一团糟。更好的做法是从一开始就在 Harness 里设计好分层上下文组装器负责分层和缓存轨迹管理器负责压缩和摘要工具调度器负责去重和结构化可观测层负责计量和调试每个模块职责清晰优化逻辑内聚后续调整也方便。5.2 可观测性先行不知道钱花哪了就没法省我强烈建议在做任何优化之前先把计量和追踪做起来。具体要能回答每一轮推理消耗了多少 Token其中稳定层、历史、工具结果各占多少哪些工具调用最频繁、返回最大哪些轮次是无效轮次没产生新信息没有这些数据你的优化就是盲猜。我见过团队花两周做上下文压缩结果发现瓶颈根本不在历史而在某个工具返回了巨大的 JSON。数据先行能帮你把力气花在刀刃上。5.3 缓存失效是最大的隐形坑最后再强调一次缓存。缓存能省 Token但缓存失效处理不好会引入极难排查的 bug。几个实操建议缓存 key 要包含所有影响结果的输入别漏字段。给缓存设 TTL尤其是涉及外部数据的。缓存命中时在上下文里明确标注这是缓存结果可能不是最新让模型自己判断要不要重新查。上线前专门测一遍数据变更后缓存是否正确失效。我在一个项目里就因为缓存 key 漏了一个筛选参数导致不同用户看到了彼此的数据排查了大半天。这种坑一次就够记一辈子。Agent 的 Token 优化这件事说到底是在信息完整性和计算成本之间找平衡。SoL-Pi 给的是一套结构化的思路Harness 是你落地这套思路的地方而真正省下多少取决于你对任务本身的理解有多深——哪些信息是决策必需的哪些只是过程噪音。把这个问题想清楚了省 Token 就是水到渠成的事。