Agent工具提示词瘦身指南:扩展按需加载省91% Token 做过 agent 开发的朋友都有同感提示词越长模型越容易“挑花眼”token 账单也越吓人。我在给 Pi Agent 优化工具体系时把原来塞在系统提示词里的全套工具定义改成了扩展extension按需加载工具相关的提示词直接砍掉了 91%而且实测工具调用的准确率不降反升。这篇指南写给 Pi Agent 用户也写给打算贡献扩展的作者我会先用账目说明那 91% 是从哪里省出来的接着拆解扩展机制的配置方法最后给出一套写扩展时避坑的操作清单。只要你手头有 Pi Agent 项目或者正在维护一个依赖工具调用的 agent里面的步骤都能直接抄。1. 先搞清楚91% 的工具提示词到底浪费在哪1.1 工具提示词是怎么膨胀起来的在 agent 类应用里“工具提示词”通常指系统提示词中用于描述可用工具的那一块一般以 function schema 或者 JSON Schema 的形式存在。每个工具至少包含函数名、描述、参数列表每个参数又有类型、含义、取值范围、默认值偶尔还带上一个调用示例。一套 100 个工具的工具库平均每个工具 200 到 500 token 非常常见加在一起就是 2 万到 5 万 token。问题在于单次任务通常只调用其中两三个工具剩下九成多描述纯属陪跑却每个请求都完整发送一遍既费钱又稀释了模型对任务本身的注意力。你可以把它想象成一张堆满工具的工作台桌面上摆着几百把工具每把旁边还贴了张 A4 纸的说明书。真正干活时你只需要一把螺丝刀但每次伸手都要从一片说明书里找到它找东西的时间比拧螺丝还长。提示词越长模型在“挑工具”这件事上花掉的注意力就越多反而挤占了真正理解用户意图的空间。只要你项目里的工具超过 30 个膨胀就已经开始了。我最早在 Pi Agent 上挂了一个全量工具文件50 多个工具定义塞进 system prompt一眼望去全是 JSON后来每次迭代加工具都变得很痛苦——不是代码难写而是提示词变长以后模型开始莫名其妙选错工具有些明显不该用的工具也被调用出来。1.2 冗余的三种典型来源我清理过几轮工具描述发现冗余主要来自三个地方。第一种是重复定义。很多工具都有 timezone、format、mode 这类公共参数当初每张 schema 都完整写一遍“取值范围、默认值、约束”几十个工具一叠加光重复参数就占了三四千 token。第二种是防御式说明。因为担心模型理解错作者把参数约束写成小说max_retries后面跟着“重试次数最大 10 次默认 3传 0 表示不重试不要传负数……”。其实 JSON Schema 里maximum、default、minimum已经把这些语义写清楚了描述里再写一遍就是双倍 token。第三种是示例过量给每个工具附带三个调用示例每个示例连带参数和返回结构大约 80 到 120 token。如果某个工具本来就语义清晰一个示例都不需要只有参数存在歧义的工具才配一个示例。这三类加在一起通常占掉总描述长度的 50% 以上。也就是说就算不引入扩展机制光靠清理描述也能把工具提示词砍掉一半。但光清理还不够因为全量注入的结构性问题仍然存在——你再怎么压缩单条描述100 个工具全部出现在上下文里总量还是压不低。真正彻底的办法是让大部分工具在多数情况下“根本不被模型看见”。1.3 省掉 91% 不是玄学是一笔具体账我直接算一笔账。假设你有一个 100 工具的工具库平均每个工具描述 220 token全量注入就是 100 × 220 22000 token。现在把工具按场景折成 12 个扩展每个扩展的索引描述大约 20 token先付 12 × 20 240 token 的“菜单费”。一次典型任务里路由层只需要加载 1 到 2 个扩展假设平均 1.5 个扩展、每个扩展包含 5 个工具那么完整注入的工具描述大约是 1.5 × 5 × 220 1650 token。加上菜单 240 token合计 1890 token。和 22000 token 相比省掉了 91.4%这就是标题里 91% 的来源。这个 91% 的构成里大头其实是“不再全量注入那 98 个用不到的工具”其次是“描述清理把平均长度压缩了”。如果你只做索引但保留完整描述也能省 85% 左右如果你只清理描述而不改加载方式最多省 50%。两者配合才有 91% 这个数字。需要提醒的是这里说的只是“工具提示词”这一块不包括任务指令、角色设定和其他固定内容那些是另一套优化题目。2. 扩展机制怎么用把“全量工具库”改成“按需工具餐”2.1 扩展是什么一个迷你工具包加一个清单Pi Agent 里的扩展不是插件程序也不是浏览器装的那种 crx 文件。它本质上是一组工具定义加上一份清单manifest清单告诉系统“我这个扩展是干嘛的包含哪些工具依赖谁”。加载之后工具就在当前会话里可用不加载模型根本不知道它的存在。目录结构一般长这样extensions/ ├── git_workflow/ │ ├── manifest.toml │ ├── tools/ │ │ ├── git_status.py │ │ ├── git_diff.py │ │ └── git_commit.py │ └── schema/ # 可选公共类型 ├── file_ops/ │ ├── manifest.toml │ └── tools/ │ ├── read_file.py │ ├── write_file.py │ └── list_dir.py └── ...把扩展和浏览器扩展类比会比较好懂浏览器插件市场里一个扩展解决一类需求启用才占内存。这里的 Pi Agent 扩展解决一类 agent 能力场景加载才进上下文。区别是这里没有“商店审核”每个项目可以自建扩展目录也可以引用别人发布的扩展包。这种自由度很好用但也意味着每个作者都要对自己扩展的“体积控制”负责没人会替你做提示词瘦身。2.2 清单文件里的四个关键字段manifest 是整个扩展机制的心脏以 TOML 为例一个最小可用的清单长这样name git_workflow version 1.2.0 description 代码提交与版本管理。用户提到 commit、push、merge、rebase、diff、分支切换、冲突解决、撤销提交时加载。 [tools] include [git_status, git_diff, git_commit, git_branch, git_merge] [depends_on] base_types 1.0.0四个字段里name 是机器识别用的扩展 id全局唯一不能跟内置扩展撞名version 是语义化版本号用户锁定版本和排查回归都靠它description 是路由描述也就是“什么时候该加载我”的说明书这一段直接决定路由模型选不选你比工具实现还重要tools 是扩展包含的工具清单也可以引用别的扩展里的工具组合出工作流depends_on 是可选的依赖声明两个扩展共用一套公共 schema 时把它们抽成 base_types 这种底层扩展再由依赖方声明避免重复定义。很多人会犯的错是把 description 写成自我介绍“本扩展由 xx 团队维护遵循 xx 规范包含若干工具函数”。路由阶段模型只关心“什么用户请求该触发你”介绍写得再漂亮也没用。我见过一个扩展description 里全是维护信息和设计理念结果用户怎么问都触发不了最后发现是路由模型根本不知道该把哪些请求和它对上号。2.3 路由层是省提示词的真正发动机省掉 91% 的核心不光是扩展这个文件格式而是 Pi Agent 在处理每一轮请求时先跑一个路由阶段。这个阶段拿着用户的问题跟扩展索引里的 description 做一次粗匹配选中 1 到 3 个候选扩展然后把它们的完整工具定义注入后续上下文其余扩展一概不注入。你可以把路由层看成餐厅的点菜流程先上菜单索引很薄顾客点单模型决策后厨才把对应食材工具 schema拿进厨房。如果一开始就把整个仓库的食材全堆在灶台上厨师翻找的时间远比做菜时间长。这里的工程取舍要解释一下为什么不直接让模型从全量工具里选因为“从 100 个工具里选 2 个”和“从 12 个扩展里选 1 到 2 个”难度差别很大。前者要模型在大量文本噪声里做精细匹配经常选错或幻觉出不存在的工具后者是先做一次低成本的意图判断再做一次小范围的精细选择。两层路由加在一起比单次大海捞针更稳。实测在我自己的项目里选错工具的比率从调整前的 6% 降到 1.5% 左右说明少一点噪声模型反而更容易做对决定。2.4 用户能干预的三组控制参数扩展机制不是黑盒用户可以调三组参数配置一般放在pi.toml或环境变量里。第一组是加载策略 auto_load_policyall表示还是全量加载core只加载核心扩展smart是默认的按需路由。升级到扩展机制之后建议先跑几天smart再用日志调优。第二组是单轮最大工具数 max_tools_loaded可以限制每次请求最多加载多少工具防止路由层抽风把两个大扩展同时加载出来。我一般设在 8 到 12太少了容易缺工具太多了省 token 效果打折。第三组是固定驻留 pin_extensions哪些扩展你要保证每轮都能用就让它们常驻不受路由影响。[agent] auto_load_policy smart max_tools_loaded 10 pin_extensions [core_workflow, git_workflow]注意 pin 是把双刃剑每 pin 一个扩展就相当于向所有请求无条件摊派它的 token。我见过有人一上来 pin 了 7 个扩展结果 token 只省下 40%然后骂扩展机制没用。pin 是特殊手段不是常规操作绝大多数场景里你只需要 pin 一个核心扩展就够了。3. 用户实操把 Pi Agent 调成“省 token”状态的四步3.1 清点先看你的扩展和工具有多大体积动手改配置之前先清点当前状态。在 Pi Agent 项目里执行pi extensions list --format table输出大概长这样扩展名工具数平均描述 token是否常驻core_workflow7180pingit_workflow5220否database_ops12260否api_calls30310否看到 database_ops 和 api_calls 这种大块头先别急着删算一下如果不 pin它们在普通任务里是否真的会被路由选中。如果某个扩展工具少但描述巨长那才是优先处理对象。我建议只把“平均工具描述超过 400 token 的扩展”列为重点排查项其余先不动。如果你连当前工具提示词占多少 token 都想量化可以用pi stats --detail直接看到 system prompt 里工具定义部分的 token 估算值以及历史请求的平均值。有了这个基线后面做任何优化才有对照物不然你很难说清 91% 到底是怎么来的。3.2 切换把默认策略改成按需加载清点完就可以切策略。默认配置通常还是全量需要把它改成smart同时设置一个 max_tools_loaded 上限[agent] auto_load_policy smart max_tools_loaded 10改完后挑一个平时经常跑的任务做冒烟测试看路由层能不能正确加载工具。我自己的经验是第一次切换最怕遇到“核心工具没被加载”。我刚开始把文件读写类工具放进 file_ops 扩展结果用户说“帮我看看项目里哪里用了这个函数”路由层却只加载了 search_workflow没加载 file_ops导致一步操作要绕两轮才完成。解决办法不是回退到all而是把最常用、任何任务都可能用到的几个工具挪进 core_workflow 并 pin 住。这个思路下一节会展开讲。3.3 pin 核心工作流但别 pin 成习惯pin 的正确姿势是只 pin 那些“无论用户说什么你几乎都需要”的工具。对多数开发任务这个集合大概是读文件、写文件、列表目录、执行命令再加一个 git 状态查看。这五个凑成 core_workflow 就够了不要贪多。把其他常用场景比如数据库操作、外部 API 调用留给路由层去判断这些场景和具体任务强相关强行常驻只会增加噪音。我用了一段时间后发现除了 core_workflow 之外几乎不需要第二个 pin。如果你发现自己想 pin 三个以上扩展说明扩展粒度拆得太粗把跨场景的东西搅在一个包里了应该回去拆分扩展而不是继续堆 pin。另外修改 pin 配置后记得跑一轮真实任务验证因为有些工具在 pin 之后会和路由加载的同名工具冲突这种事不实测是发现不了的。3.4 验证从 token 消耗和命中率两个维度看效果配置落地后跑一个稍复杂的真实任务再用统计命令看结果pi stats --recent 20。关注三个指标平均每轮工具提示词 token 数、路由命中率实际加载的扩展里有多少被真正使用、以及工具调用失败率。我调整前后的典型数据是工具提示词从 22400 token 降到 1600 左右节省约 93%路由命中率从 70% 提到 93%工具调用失败率基本持平。更直观的感受是响应首字时间变快了因为不用再先消化 2 万多 token 的提示词模型很快就能进入正题。成本这块按 token 计价的话工具部分的费用直接降到原来的十分之一不到。如果命中率偏低别急着怪模型。先看看是不是扩展描述写得太差或者某个场景同时挂在两个扩展里——这属于扩展设计问题和模型能力关系不大。路由命中率是描述质量的直接体检指标它低就是描述没有覆盖用户语言改描述比换模型更有效。4. 扩展作者指南怎么写扩展才能既灵巧又省提示词4.1 写好“路由描述”别写成“工具说明书”扩展作者最先写的应该是 description而不是工具实现。这段文字是给路由模型看的它决定了“这个扩展什么时候被选中”。很多人不重视它结果工具实现得很漂亮用户却根本调不到。我对比一下坏和好的写法坏的description 本扩展提供 git 相关操作包括 status、diff、commit、branch、merge、rebase、stash 等函数支持常见的 git 工作流并且对错误做了完整处理。好的description 代码提交与版本管理。用户提到 commit、push、merge、rebase、diff、分支切换、冲突解决、撤销提交时加载。差别在于坏的版本在“罗列函数清单”好的版本在“描述触发场景”。路由模型不是读文档理解你的工具接口而是做意图匹配——它需要知道用户在什么场景下会需要你。写 description 时你要试着用用户的原话去写而不是用函数名去写。用户不会说“我要调用 git_diff”用户会说“帮我看看改了哪些地方”。一个实操技巧收集 5 条你希望这个扩展被触发的真实用户请求然后从这些请求里提取高频词写进 description。用户喜欢说“看下改动”“提交代码”“把分支合进来”这些原话比术语列表管用得多。4.2 粒度按场景拆不按函数拆扩展的最佳粒度是“一个领域场景3 到 8 个工具”。我不建议把一个 API 的每个端点都做成扩展也不建议把文件读写、数据库、网络请求全塞进同一个巨型扩展。前者会让扩展数量失控索引本身膨胀路由从 12 个里做选择变成从 40 个里做选择难度又回去了后者会让单个扩展加载时带上大量不相关工具省 token 效果直接打折。一个让我印象很深的例子有人把“所有 python 操作”做成了一个扩展里面既有执行脚本、又有包管理、还有 lint、测试运行共 18 个工具。路由只要选中它一次就注入 18 个工具定义token 比原来只省了一半。拆成 python_exec、python_pkg、python_lint 三个扩展后同样任务只需要 5 到 7 个工具省下的量才达到目标。判断粒度是否合理的简单标准看一篇文章介绍你的扩展能不能用一句话说清它解决什么问题。说不清就是太杂。4.3 给工具描述本身做一次大扫除工具描述是扩展内部还有优化空间的最后一环。我通常按以下顺序清理删掉 schema 已经表达的约束maximum: 10之后不要再写“最大 10”示例只留给有歧义的参数比如 date 的格式、timezone 的取值其他删掉把描述动词前置、宾语明确“读取指定路径的文件内容支持文本和二进制”比“该函数用于获取文件系统上指定路径对应的文件内容”省了三分之一 token含义还更清楚值枚举用enum定义不在描述里重复列。工具名称也是提示词的一部分。read_file就比get_file_content_by_path好前者短且能猜出用途后者更像代码库内部函数名。在工具提示词语境里命名本身就是免费的路由提示一个好名字能省掉模型“读完全部描述才明白这是干什么”的成本。如果你手上有一批老工具命名很差我建议用一次性重命名迁移来解决别为了兼容旧调用而保留又长又模糊的名字——那是把成本留给每一轮请求。4.4 用依赖声明代替重复类型定义如果你的多个扩展都用同一个公共参数结构比如 timezone、pagination、format不要在每份 schema 里复制粘贴。把它们抽成一个公共扩展例如base_types然后在清单的 depends_on 里声明。这样每个扩展的描述体积都减少了而且公共类型只需要在自己被依赖的扩展里维护一份。有些人觉得多一层依赖麻烦宁愿复制在 5 个扩展以内复制确实无所谓但扩展一多公共类型的小改动会引发一堆不同步。我用这套结构后公共结构只改过两处全部扩展自动生效省下来的是长期维护成本。依赖关系本身写得像 Git 子模块一样可控性很强。需要注意版本锁定把依赖的版本号写死或写明兼容范围避免某天公共扩展大改版本结果一堆下游扩展集体罢工最后在运行时才爆出错误。4.5 上线前撞墙测试五条意图看命中扩展写完后最容易被忽略的是验证“路由能不能选中你”。我的做法是建一个测试脚本准备 5 条覆盖该扩展典型场景的用户意图跑一轮路由看每次是否选中目标扩展。命中 4 次以上才算合格否则就回去改 description。用户意图期望扩展实际路由结果是否命中帮我把这几处改动提交一下git_workflowgit_workflow是看一下当前分支和远端差多少git_workflowgit_workflow是拉取下远端最新代码git_workflowgit_workflow是帮我把上次提交撤销了git_workflowcore_workflow否这个 merge 冲突怎么解决git_workflowgit_workflow是第四行失败很典型因为 description 里没有“撤销”“回退”这两个词路由就没把 git 扩展当作候选。遇到这种情况把用户语言补进 description 就解决了。这套测试成本很低但能让扩展作者避免上线后用户骂“工具根本用不了”的尴尬。我甚至建议把它写进 CI每次改 description 自动跑一轮防止后续无心改动把路由描述改坏了。5. 常见问题与排查加载失败、选错扩展、省不下来怎么办5.1 扩展加载了工具却始终没被调用这是我把扩展机制分享出去之后收到最多的反馈。表象是路由阶段已经选中了扩展工具定义也在上下文里但模型就是不调用。查了一圈发现根因往往是工具描述里的措辞和用户问法对不上。比如工具叫list_files用户说的是“帮我看看目录下都有啥”而描述里只有 read_dir、directory 这些词模型想不到要用它。解决方法是把用户的常用说法作为一种别名写进工具描述或者写进扩展 intents。工具描述不必文艺但一定要口语化覆盖。我试过最有效的一招把工具当作用命令在描述开头直接写一句“列目录、查看文件夹下文件、ls”命中率立刻起来。这个问题的另一个隐蔽来源是路由阶段加载了扩展但模型认为“这个工具有点多余我直接算就行”。这其实是好事说明模型在对工具做判断而不是盲目调用。真正的坏事是模型完全没意识到工具存在那才是描述层面的问题。5.2 模型总是选错扩展根因多半是 intent 重叠选错扩展不是模型傻更多是你的描述有歧义。两个扩展的 description 都用到了“文件”“编辑”这些词路由就很难区分。比如 file_ops 是“编辑文件”doc_ops 是“编辑文档”用户说“把这份 markdown 改一下”时两个都可能被选中。排查方法是做一次全量 intent 撞车测试把每个扩展的描述放到一起看哪些句子有超过两个扩展能匹配。发现重叠后给每个扩展加上独占的场景词。我一般会给每个扩展保留一个“专属触发区”比如 git 扩展专属 commit/push/merge文本编辑专属 refactor/format/replace这样路由的区分度就出来了。不要把撞车完全消掉适当的模糊空间可以兜底但核心触发词一定要区隔开。这类问题的排查数据可以从路由日志里看每次没选中目标扩展时日志会记录候选扩展得分你能清楚看到第二名和第一名的分差有多近从而判断是描述问题还是模型抖动。5.3 配置完 token 却没降三个最可能的原因如果有人照着这篇配置完发现 token 只降了一点点我会让他依次查三件事。第一pin_extensions 是不是太多了。每个 pin 都在给所有请求摊派工具定义pin 五个扩展基本抵消按需加载的优势。第二索引本身是不是胖了。如果 12 个扩展每个 description 都写了两三百字那菜单本身就消耗了几千 token按需加载省下的又被菜单吃回去。给索引瘦身的标准是每个 description 控制在 30 到 50 个词内。第三是不是存在“平铺注册”的残留。有时候新机制只作用在部分扩展上老的 tools 文件还在被全量注入检查配置里有没有把旧工具列表清干净。这三个原因我全都实际踩过。尤其第二个我一度很困惑为什么改了加载方式 token 还是高后来把扩展索引按精简格式压缩到 20 到 40 token 每行问题立刻消失。还有一个容易被忽略的细节部分版本的 Pi Agent 会在每次请求里重复加载同一份扩展描述两次双份 token 消耗。这会体现在 stats 日志的重复计费上升级版本或检查 dedupe 选项就能解决。5.4 老项目怎么迁移到扩展机制如果你的项目已经很庞大几十个工具躺在全量文件里最稳妥的迁移路径不是一夜之间拆成 20 个扩展而是先做一次低成本分组。第一步把现有工具按“场景聚合”列一张表标出哪些工具通常一起出现。第二步为一组工具生成一份最小 manifestdescription 先用一句话概括场景。第三步从全量文件里移除这些工具让系统尝试路由。一次移一组每组验证一周出现选错再调整描述。这样迁移风险可控而且每迁移一组就能看到 token 下降。如果个别状态实在没法平滑迁移也可以采用类似“二进制扩展法”的思路做过渡把少数冷门工具的 schema 编码成紧凑格式在本地解码后再注入降低传输成本。但注意这种压缩对模型推理本身没帮助——模型消费的是解码后的可读文本不是压缩流所以它只能作为传输优化不能替代语义精简。对绝大多数项目文本清单已经够用不必为了压缩而压缩。5.5 一个容易混淆的常识Pi Agent 扩展不等于浏览器扩展经常有人用“扩展”这个词搜进来结果发现讲的是 chrome 扩展、HEVC 视频扩展装不上、扩展坞之类的东西。这里我专门说明一下Pi Agent 里的扩展是一种给 agent 用的“工具注册表和按需加载单元”不是浏览器插件也不是视频解码组件。它不涉及 crx 文件、不涉及 manifest_version、不会出现“该扩展程序未列在 Chrome 应用商店中”这种提示自然也不用担心浏览器禁止安装的问题。如果你在开发时看到“扩展错误”之类的报错先检查是不是 Pi Agent 的扩展清单格式或依赖版本出了问题别再往浏览器扩展的方向排查。把这两个概念分开能省掉不少无头苍蝇式的搜索时间——我被这类问题带偏过不止一次某次调了半天浏览器策略最后发现只是清单里的依赖版本号写错了。扩展体系之间看似都叫“扩展”内核完全不是一回事。5.6 扩展作者的三条避坑心得第一先写 description再写实现。我见过太多作者把工具写得很好但 description 是复制粘贴的接口文档结果上线后无人问津。路由模型读不懂你的接口文档它只认场景。第二一个扩展只解决一个场景。你可以在一个场景里放 8 个工具但不要把一个工具家族全塞进去。扩展的职责边界就是路由描述里那一句话的边界。第三版本号要当真。扩展机制下用户可能同时开启多个扩展冲突排查依赖版本信息。我在自己项目里吃过没写 version 的亏某次公共 schema 改了三个扩展表现不一致查了半天才发现其中一个还在用旧类型定义。还有一个小细节给扩展起名尽量用短单词用连字符而不是下划线分隔因为扩展名会出现在日志、统计和路由候选里名字越长噪音越大。git_workflow和git-workflow看起来只有一字之差但日志分析和 URL 引用时完全不一样保持一致能省掉后续很多麻烦。最后分享一个我的个人习惯每次新建扩展第一件事不是打开编辑器写工具函数而是先在文档里写 5 句“用户会说”的话。写顺了description 基本就有了写不顺说明这个扩展的场景没想清楚。这 5 句话后来成了我判断扩展是否值得做的门槛。省掉 91% 的工具提示词本质上是把“让模型读海量文档”改成“让模型看精炼菜单”真正值钱的不是压缩技巧而是对工具场景的重新梳理。你把这个结构做对了模型会替你把每一步都跑得更稳。