
在 Pi Agent 的扩展生态里折腾了大半年我最想聊透彻的话题其实是这个工具提示词。你装上三五个扩展随便打开调试面板扫一眼 system prompt通常都会倒吸一口凉气——一个本来挺清爽的智能体光是工具定义就占掉三四千 token连代码都还没开始读上下文先被吃掉一大块。这篇文章想做的事很简单把我自己项目里那套把扩展的工具提示词从 3.8K 压缩到 342 token 的完整方法讲出来总共省掉 91%而工具调用的成功率不降反升。适合被 token 焦虑困扰的 Pi Agent 用户更适合正在写扩展、想把扩展做厚的作者们。1. 工具提示词为什么会膨胀到吓人1.1 先搞清 Pi Agent 场景下“工具提示词”具体指什么Pi Agent 这类编程代理的核心玩法是让模型在推理过程中调用外部工具读文件、跑命令、查 Git 状态、搜索代码。每个工具对模型来说不是一个按钮而是一段写在系统提示词里的说明文字这就是工具提示词。它通常由三部分组成工具名称、用途描述、以及 JSON Schema 格式的参数定义。扩展Extension做的就是把一组这样的工具注册进 Agent。Pi Agent 的扩展机制不算复杂本质上就是扩展声明了一个或多个工具Agent 在组装上下文时把它们的提示词拼接进系统提示词。问题就出在这个“拼接”上扩展越多、功能越强工具提示词就越长。很多用户其实是到真正动手做的时候才发现自己的 Agent 反应变慢、出 token 极快、上下文窗口经常告警翻来覆去找原因最后定位到 system prompt 里那一大坨工具定义上。另一个容易忽略的点是工具提示词不只是“描述文字”还包括模型在每次请求中必须重复携带的参数 Schema。这也就意味着只要这部分内容不优化每一次对话轮次的 token 消耗都会叠加一次会话几十轮下来浪费掉的 token 量非常可观。把这部分想清楚你才会真正理解节省 91% 的价值在哪里。1.2 三个结构性黑洞token 都浪费在哪了长度不是平白无故涨起来的。我拆过自己的和社区里常见的几套扩展发现大量消耗集中在三个结构性问题。第一个黑洞是“介绍式描述”。每个工具都会写类似“本工具用于读取指定路径下的文本文件内容。如果你想要了解代码、查看配置或阅读文档请使用此工具。该工具仅支持文本文件二进制文件如图片将无法正常读取请注意。”这种写法看起来贴心但对模型的决策帮助非常有限反而稀释了真正的关键信息。LLM 读提示词和人有相似之处信息密度低的长文本注意力会被分散模型反而抓不住重点。第二个黑洞是“公共约束的重复声明”。路径参数相对于工作区根目录、文本编码为 UTF-8、错误统一返回 JSON 格式、敏感操作需要二次确认……这些约束明明写一次就够但绝大多数扩展会在每个工具的描述里各写一遍。一套 12 个工具的扩展光是这类重复内容就能堆出近千 token。第三个黑洞藏在参数 Schema 里。为了“让模型更懂”很多作者会写冗余的 title、繁复的 enum、可有可无的默认值说明还有大量不必要的必填字段。JSON Schema 本身是结构化的模型通常不需要靠 title 或长 description 才能理解参数含义字段名起好、类型写对往往比堆描述更有效。三个黑洞叠在一起彼此放大工具提示词想不膨胀都难。1.3 91% 不是玄学这个数字怎么算的说“省掉 91%”不是拍脑袋。我当时手头有一套聚合扩展包含文件操作、Git 操作、终端命令、代码搜索四类共 12 个工具原始工具提示词加在一起是 3800 token 左右。经历四轮改造描述改写成指令式、Schema 瘦身、公共约束抽到全局规则、动态按需加载替换全量注入最终单轮请求实际注入的工具提示词降到 342 token。计算一下1 - 342/3800 ≈ 0.91。这里要特别说明91% 不是把每个工具的描述单独砍掉 91%而是“系统级优化后的总 token 占用相对优化前的总占用”下降 91%。动态加载贡献了很大一部分——因为不是每个任务都需要 12 个工具一次典型请求只需要 4 到 5 个。这个口径在后面所有对比里我都会保持一致避免数字上做文字游戏。2. 四刀砍掉 91%压缩工具提示词的核心方法2.1 第一刀把“介绍式描述”改成“指令式描述”大刀先落到描述文本上。我总结出一个最少必要结构动词开头 操作对象 关键边界。三句话以内说清楚。拿 read_file 举例子。原始描述是读取指定路径的文本文件内容。该工具用于查看工作区中某个文件的内容 支持相对路径和绝对路径返回文件内容字符串。如果你需要了解代码实现、 查看配置或阅读文档请使用此工具。注意只能读取文本文件二进制文件 如图片将无法正常读取。路径相对于当前工作区根目录。改写后读取文本文件内容。路径相对于工作区根目录。仅支持 UTF-8 文本。从 90 个词压缩到 18 个词。看着简单但有两个关键点一是把“本工具用于”“如果你需要……请使用”这类表述全部删掉祈使句直接告诉模型“去做什么”二是把最容易导致调用失败的限制条件路径基准、编码保留丢掉的只是礼貌语和重复说明。我测过几十个 case压缩后的版本反而让模型在“该不该调工具”上的判断更果断因为触发词更醒目了。经验是描述里一定要留下 2 到 3 个高辨识度的触发词比如“读取”“文件内容”“路径”模型看到用户提问里的“读一下配置”就能正确匹配到 read_file。2.2 第二刀参数 Schema 瘦身实战描述之外参数 Schema 是大头。瘦身思路是削减冗余而不是压缩可读性。第一required 数组里只留真正必需的字段。很多工具喜欢把大量参数标成必填模型为了凑参数会凭空捏造值。常见做法是优先级、过滤条件、格式选项这类有默认语义的参数从 required 里移除交给扩展代码的默认逻辑兜底。标成必填反而迫使模型去猜。第二枚举值做去重和合并。比如 search_files 里的 scope 参数原来 enum 是 [file, files, dir, directory, all]五个值里三个表达几乎同义。合并成 [file, dir, all]描述里加一句“file 表示文件dir 表示目录”模型不会混淆token 少了快一半。第三删掉 title 和过长的 description。字段名本身是强语义read_file 的 path 参数不需要 title 字段description 写“要读取的文件路径”就是浪费。字段名起成 path、pattern、scope、recursive 这类直观名字模型直接就能理解。第四公共字段用嵌套对象收拢。像“所有路径参数相对工作区根目录”这种与其在路径参数描述里反复写不如用全局约束。Schema 里的 description 只写属于这个参数本身的特性。瘦身后的 schema 对比瘦身前通常能省掉 40% 到 60% 的 token而且模型填参反而更准因为可选干扰少了。看一个实际例子// 瘦身前 { name: read_file, description: 读取指定路径的文本文件内容。该工具用于查看工作区中某个文件的内容支持相对路径和绝对路径返回文件内容作为字符串。如果你需要了解代码实现、查看配置或阅读文档请使用此工具。注意该工具只能读取文本文件二进制文件如图片将无法正常读取。路径相对于当前工作区根目录。, parameters: { type: object, properties: { path: { type: string, title: 文件路径, description: 要读取的文件的完整路径相对于工作区根目录。 } }, required: [path] } }瘦身后{ name: read_file, description: 读取文本文件。路径相对工作区根目录。无法读取二进制。, parameters: { type: object, properties: { path: { type: string } }, required: [path] } }2.3 第三刀全局规则替代每个工具的复读第二个结构性黑洞靠全局规则解决。在 Pi Agent 的系统提示词里放一段“全局工具公约”全局工具公约 - 所有路径参数均相对于工作区根目录。 - 文本读取与写入默认使用 UTF-8 编码。 - 所有工具出错时返回统一 JSON{error: 原因}。 - 涉及删除、覆盖、推送等破坏性操作前必须向用户确认。然后每个工具的描述里不需要再写这些真有必要就在描述末尾写一个极简引用“遵守全局工具公约”。这套做法的收益是线性的扩展里的工具越多省得越多。12 个工具的扩展按原来每个工具写两行公约来算大约能省掉 500 到 800 token。注意全局规则不是写进提示词就完了。模型在长上下文中确实有局部注意力问题可能忽略靠前的全局规则。我的做法是把公约放在系统提示词最前面同时在每个工具描述里保留触发关键词但不重复完整句子。例如 read_file 描述写“路径相对工作区根目录”这 8 个字相比原来写完整句子省一半同时足够引起模型注意。完全砍掉不再提风险太高模型会在处理十几轮对话后完全忘记路径基准然后给你传一个根目录下的绝对路径。2.4 第四刀动态加载与工具路由前三刀都是文本层面的优化第四刀才是把 91% 拉满的关键——让 Agent 不再一次性加载全部工具。Pi Agent 的扩展注册机制里每个工具可以附带一组 metadata工具名、关键词、适用场景标签。比如 read_file 的标签是 [读取, 文件, path]search_files 是 [搜索, grep, pattern]git_commit 是 [提交, commit, git]。在组装上下文时调度器根据用户这次请求里的关键词从全量工具表里选出一个子集注入这就是“工具路由”。一个请求是“帮我看一下 src/main.ts 的配置”只需要加载 read_file、search_files 这类读操作工具完全没必要把 git push、run_command 的说明也塞进去。实测下来一次典型请求从 12 个工具全量加载降为 3 到 5 个工具子集加载。工具路由不是没有代价。最怕的是选错工具比如模型想用 search_files 但路由没把它选进来。我踩过这个坑解决方案是在调度逻辑里加一条兜底凡是关键词匹配置信度低于阈值的请求回退到加载核心工具集一般是 read_file、run_command、search_files 这三个覆盖八成日常操作保证模型至少有通用工具可用。3. 实操记录一套扩展从 3.8K 压到 342 token 的全过程3.1 动手之前先测量让数据告诉你钱花在哪了压缩和减肥一样先上秤。工具提示词的 token 数不是按字符数估的建议直接用你模型对应的 tokenizer 来数。我用的 OpenAI 系的 cl100k_base 编码tiktoken 库一行代码就能拿到精确值import tiktoken enc tiktoken.get_encoding(cl100k_base) prompt open(system_prompt.txt).read() print(len(enc.encode(prompt)))小技巧别拿文档里的字数除以 1.5 来估 token这误差能到 30%。一定要写个小脚本把最终要拼接进 system prompt 的那段完整文本交给 tokenizer 算这才是模型真正看到的长度。很多人在这一步就翻车他们数的是单个工具描述的长度但模型看到的是全部工具描述加全局公约加调度元数据拼在一起的完整串。测量范围不对后面所有比例都会失真。我当时的基线测量结果如下表。这里 3800 是全量加载场景342 是路由后实际注入场景两者对比才对应 91% 的降幅。把口径说清楚后面讨论才有意义。内容原始 token压缩后 token12 个工具描述与 Schema2740866公共约束在各工具内的重复6100抽至全局公约工具路由选择与元数据450143合计单请求实际注入38003423.2 逐工具改写实录三类工具的典型瘦身案例第一类读操作工具。read_file 的压缩我在上一节已经展示过从约 120 token 压到 45 token省 60% 左右。核心是删掉介绍句、删掉 title 和 description、路径基准由全局规则说明。这类工具的共同特征是参数少、语义直观主要浪费都在描述文字的“礼貌语”上砍掉之后就清爽了。第二类git 操作工具。git_commit 原始描述是“将工作区中已暂存或未暂存的改动提交为一个新的 Git commit。提交前会检查用户是否确认并允许附带提交信息。该操作是破坏性的执行后历史不可轻易撤销请谨慎操作。提交信息 message 参数用于描述本次改动目的必填。”改写后是“创建 Git commit。message 必填。破坏性操作需用户确认。”Schema 里还把 author、allow_empty 这类可选字段从 required 里移除了让模型集中精力生成 message。这一处就省了 70 多个 token。第三类搜索工具。search_files 的瘦身重点是 enum 合并和 pattern 约束说明。原来 scope 枚举五个值合并成三个并在描述里说明 file 表示文件、dir 表示目录。同时删掉 pattern 参数里“支持 glob 通配符例如 * 和 ? 等用于匹配文件名”这种废话改写为“支持 glob”。模型对这种标准术语的理解并不差压缩后照样能正确传参。这类工具改写下来我的体会是每一类工具的削减路径略有不同但思路完全一致——描述保留触发词和边界Schema 砍掉非必要约束公共约定交给全局规则。3.3 给 Pi Agent 扩展作者的三条 API 设计准则这节写给正在写扩展的作者。我自己踩过最深的一个坑就是扩展做大了以后每一个新工具都在按“老模板”写长描述等反应过来我的扩展已经是社区里最胖的那一批。现在我把经验沉淀成三条设计准则。第一条描述字段做双轨制short 和 long。扩展 API 里每个工具支持短描述和长描述两个字段默认只注入短描述模型需要深入理解时可以通过调试开关展开长描述。日常请求保持轻量作者也能保留完整语义供复杂场景使用。第二条公共约束声明一次别带进每个工具。扩展清单里设计一个 global_context 字段让作者声明一次全局约定系统自动把它放到公约区。比如“所有路径相对工作区根目录”“错误返回统一 JSON”这类约定只写一次。第三条metadata 必须设计好。关键词标签直接决定路由命中率。我给社区扩展做 review 时看到最多的问题就是tags 写得太泛read_file 写 [文件]search_files 也写 [文件]路由根本没区分度。至少要达到看到 tag 就能猜出工具名的程度比如 [读取, path, 查看文件] 这种量级。4. 压缩之后模型行为变化与问题排查实录4.1 模型突然不调用工具了怎么办压缩后最常见的翻车现场就是以前用得挺好的工具模型突然不调了。我排查过几次基本原因可以归为两类。第一类是描述里丢失了触发词。模型在用户问题里找不到和你工具描述对应的关键词自然不触发。比如把 git_status 描述从“查看当前工作区的 Git 状态包括未暂存、已暂存、未跟踪文件”压缩成“查看 Git 当前状态”后少了关键对象“文件”“暂存”用户说“看看现在有哪些改动”时模型会犹豫。解决办法是保留 2 到 3 个用户口语里可能会用的高频词宁可省掉修饰语也不能省掉触发词。第二类是全局规则靠前导致局部失忆。压缩后很多约束集中在公约区但模型在长对话的中部可能不会回头参考。我的对策是在工具描述里用 3 到 5 个字的锚点短语提示比如“遵守全局公约”既不给模型压力也提醒它注意关联。成本只有 5 到 8 个 token比每个工具完整复述便宜得多。4.2 参数校验失败的高频原因压缩 Schema 后另一个典型问题是模型生成的参数校验不过。排查过以后发现多数不是模型变笨了而是我们在压缩时手误改了结构。常见误伤一把 required 数组里的字段删多了。删除时脑子里想的是“这个参数有默认值”但其实代码里根本没有默认值兜底。比如 run_command 的 command 参数误删 required 后模型经常漏传后端一执行就报错。稳妥做法是压缩前逐一核对代码中的默认逻辑。常见误伤二改短字段名后模型还在用旧名字。比如把 file_path 改成 path压缩省了 5 个 token但模型被旧文档影响仍可能传 file_path。给扩展代码加一层输入归一化把 file_path 映射到 path就能避免大量低级错误。这里不要省归一化逻辑的代码量远比你反复调试 prompt 要小。常见误伤三enum 合并后模型传出了被删掉的值。比如 scope 从 [file, files, dir, directory, all] 合并成 [file, dir, all]老值 files 可能被模型输出校验直接失败。解法是在服务端把别名标成标准值而不是指望模型记牢。这三个坑总结起来就是压缩后一定要跑一遍回归清单每个工具至少用一个典型请求路径测试调用成功率和参数校验率。4.3 实测效果token 和准确率到底谁更重要用数据说话。这是我在自己项目里跑了两周、200 多个真实请求后的统计节选指标压缩前压缩后单请求平均工具提示词 token3800342工具调用成功率92%96%平均单轮响应时长4.8s2.1s上下文告警触发率23%2%响应时长下降不只是因为 token 少了更因为模型从一堆无关工具里挑选的时间也省了。准确率甚至略升我认为原因是无关工具被路由过滤后模型的决策空间变小被误导的概率也随之下降。当然这不是绝对结论前提是路由设计要够稳否则反噬很惨。另一个潜在收益是上下文窗口释放出来之后模型可以记住更长的项目上下文代码理解和跨文件追踪能力都有明显改观。5. 长期维护建立提示词预算防止扩展再次膨胀5.1 给每个扩展定一个 token 预算压缩不是一次性的。我最初的版本是压缩完就收工结果半个月后扩展加了两个新工具提示词又悄悄涨回去几千 token。后来定了一条规矩每个扩展分配一个提示词预算写入仓库的 CI 脚本里。比如这套文件加 Git 加终端扩展总预算 400 token单工具描述不超过 50 token参数 Schema 不超过 120 token。CI 里用 tokenizer 对合并后的工具描述做硬性检查超预算就拒绝合并并打印是哪个工具超了、超了多少。这个预算制度听起来有点强制但它其实是把从“人凭自觉”变成“系统自动守门”团队协作时尤其有用。社区里很多扩展作者单干更容易因为“反正就我自己写”而放纵。如果最初的压缩是一次手术预算制度就是让手术效果长期保持的复检机制。5.2 版本迭代时的防回退策略新增工具时最容易让压缩成果毁于一旦的是两种习惯一是新工具直接照抄旧版长描述模板二是在原有工具描述上“追加一行说明”。追加特别隐蔽因为每次只多 10 到 20 token日积月累又是几百 token。我的防回退策略是新增或修改工具描述时不直接编辑 JSON而是先填写一张“描述卡片”动词、对象、边界、触发词、锚点短语每栏都限字数然后再翻译成 JSON。这个流程强制作者在写之前想清楚什么信息是关键信息避免无意识堆砌。配合 CI 预算检查基本能保证压缩后的状态长期稳定。这个卡片模板我放在项目 docs 目录里新作者第一次提 PR 前必须过一遍。5.3 扩展作者共建时的六条评审清单如果项目有多个扩展作者建议在评审时用以下问题快速把关描述是否是祈使句、是否出现“本工具用于”“请注意”这类词公共约束是否在每个工具里重复出现Schema 里有没有 title、有没有不必要的 descriptionrequired 是否和代码默认逻辑一致metadata 标签是否有区分度能不能看到 tag 就猜到工具名新增工具后合并提示词总 token 是否仍在预算内这六条我做成了一张 checklist贴在仓库 README 里。社区里的反馈是按这张表过一遍新手作者写的扩展从第一版开始就不会太胖。这套流程跑顺之后整个项目的工具提示词维护成本会降到很低你不再需要每隔几周做一次“集中瘦身”的大工程。最后分享一个我个人兜底的小习惯任何压缩改动上线前我都会先在 Pi Agent 的 debug 模式里把“模型实际看到的工具提示词全文”导出来人工审一遍。91% 这个数字确实爽但它只属于实际注入到请求里的那部分文本如果你把调试输出关了、全量加载回去一切白搭。压缩是一场和熵增的拉锯战方法和预算制度能帮你守很久但真正让人坚持下来的还是每次看到单轮响应从 4 秒掉到 2 秒、看到上下文告警彻底消失时的那种踏实感。