首页 / 资讯中心 / 文章详情

如何节省91%工具提示词:Pi Agent上下文Token优化实战指南

如何节省91%工具提示词:Pi Agent上下文Token优化实战指南 ★ FEATURED ARTICLE
1. 工具提示词开销的真相91%的冗余到底藏在哪几个月前我在给 Pi Agent 写一套自己的扩展工具一多问题就暴露了。这也是我决定写这篇操作指南的起点不是科普什么是工具提示词不是罗列概念而是把“怎么省掉 91% 的工具提示词”这件事从头到尾讲清楚。Pi Agent 会把所有已加载扩展的工具定义塞进系统上下文每次请求都要重新传一遍扩展作者在定义工具时又习惯性地把描述写得非常详细结果就是上下文里一半以上的 token 都在反复传输那些无关紧要的字段说明。这篇文章既适合正在用 Pi Agent 配置扩展的用户也适合计划写自己扩展的作者你不需要有很深的前端或大模型背景只要用过 Pi Agent、打开过它的配置文件就能跟上。1.1 一次真实调用里的浪费假设你装了文件操作、代码检索、Shell 执行三个扩展每个扩展提供三个工具。Pi Agent 默认会在系统提示词里把全部九份工具定义都带上。拿其中一份普通的文件读取工具为例{ name: read_text_file_from_local_path, description: Read the content of a text file from the local filesystem at the specified path. This tool is designed for reading UTF-8 encoded plain text files..., parameters: { type: object, properties: { path: { type: string, description: The absolute or relative path to the file to read, title: File Path }, encoding: { type: string, default: utf-8, description: The character encoding used to read the file } }, required: [path] } }像上面这个小工具结构name 里塞了一大段语义description 又解释了一遍parameters 的 title、description 也全是重复信息。你数一下光这一个工具的 JSON 渲染出来大概有多少个 token我给个粗略换算一个普通英文工具定义的 JSON 文本平均每 3.5 到 4 个字符算一个 token。上面这段大约 1000 个字符也就是 250 到 280 个 token。九份工具全带上就是 2300 到 2500 个 token而这个量发生在模型看到用户任何提问之前。如果用户每轮只说一句“帮我看看当前目录下有哪些 Python 文件”这 40 个 token 的请求却要背负 2500 个 token 的工具定义一起走。你想象一下每天通勤只有两公里但为了出门得先拉着一个装满砖头的拖车上路每一趟都得拉。Pi Agent 这种“把所有工具定义都带进上下文”的机制本身没问题问题出在砖头上——工具定义里大量冗余的 description、title、example都是可省的。1.2 冗余集中在五个地方我在实际拆解了十几个扩展的定义之后把冗余归纳成了五类工具名过长。像read_text_file_from_local_path这种名字模型其实只需要核心语义“read file path”后面的 local、text 都是堆砌。名字越长token 越多模型把名字和工具行为对齐的难度也越大。description 写得像使用手册。很多扩展作者担心模型不理解参数于是把参数说明写成完整技术文档。但模型看 description 是为了判断“这个工具什么时候用、用了之后会发生什么”不是为了学习文件系统原理。JSON Schema 里塞满了 title。绝大多数工具的 title 跟字段名是同一个意思纯粹浪费。比如title: File Path和字段名path表达的是同一个信息模型不需要读两遍。每个工具都重复一遍公共信息。比如“该工具用于本地文件系统操作”这种话十个工具写了十遍模型也不需要每份定义里都看到同一句。示例和数据样例过多。有些官方扩展为了演示会在 description 里带上两三个完整调用示例。示例对模型有引导作用但没必要每个字段都配一个。这五类叠加起来就构成了 91% 优化空间的真实来源。它不是靠什么黑魔法纯粹是把重复信息清干净。1.3 91% 这个数字是怎么算出来的动手之前我先做了一次基线测量。我选了一个包含 12 个工具的文件管理扩展把原始 JSON schema 全部拉出来统计 token 数记做 A。然后手动写了一份“极简版”保留 8 个必要工具每个工具只写名称、最简参数列表和一句 30 字以内的描述记做 B。同一批任务A 的总计 token 约为 3400B 的总计 token 约为 300。3400 到 300节省率正好在 91% 左右。这里要多解释一句91% 是“工具提示词本身”的削减不是整体上下文的削减。因为对话内容还会产生 token上下文里还有系统组件和用户消息所以总 token 的节省要根据对话长短打折。但这个折扣不影响结论——工具数量越多、扩展越丰富这部分优化越值。后面第四章我会给一套实测对比数据包括准确率和延迟的变化现在先把方法和原理讲透。2. 用户端配置不碰扩展代码先把默认提示词瘦下来2.1 打开精简工具描述模式市面上的多数 Pi Agent 发行版在升级到较新版本之后都提供了工具描述压缩方案只是默认没开。原因是保守起见默认状态要保证所有工具定义完整呈现防止模型因为描述不完整而分析错。我在自己的配置里做的第一件事就是打开精简工具描述模式。这个模式做的事情很直接把工具定义里的 title 字段全部去掉把 description 里超过 80 字符的部分截断把默认值合并进字段定义而不是单独列一行。你不需要改扩展只需要在配置里加一行开关。在 Pi Agent 的配置文件里一般长这样{ tools: { compact_schema: true, drop_title: true, max_description_chars: 80 } }不同版本的字段名可能略有差异但思路一致用一个全局开关对已有扩展做一次“通用瘦身”。这个开关的效果可以直接在日志里看到工具定义部分的 token 明显变少而且绝大多数场景下模型该调工具还调工具准确率几乎没有变化。我建议你第一次改动时先只开compact_schema确认各方面稳定后再把drop_title打开分两步走比一次性全开更容易定位问题。2.2 按扩展粒度调整详细程度全局开关是一刀切实际使用中我发现更好的做法是“按扩展调整”。有些扩展的工具副作用很强比如“删除文件”“执行任意 shell 命令”“发网络请求”这类工具的 description 稍微长一点是有价值的。模型需要在调用前准确理解它会带来什么后果。相反像“读取文件”“列出目录”“计算校验和”这种只读工具描述可以短到不能再短模型靠工具名就能判断用途。Pi Agent 支持在扩展配置里单独指定详略级别。我会这样配{ tools: { compact_schema: true, overrides: { fs.read: minimal, fs.write: detailed, shell.run: detailed, web.fetch: normal } } }minimal 级别只保留名称、参数类型、required 列表normal 级别保留一句 40 字以内的描述detailed 级别保留完整描述和必要的注意提示。这套分级让我在“省 token”和“让模型理解”之间找到了平衡。你给扩展定级的时候可以按这个原则这个工具调用错了最坏后果是什么如果后果很轻微用 minimal如果后果不可逆用 detailed。2.3 按场景动态裁剪工具集除了压缩定义用户还能做一件更立竿见影的事减少同时加载的工具数量。工具定义写得再精简如果一口气加载五十个照样要花不少 token。我之前在项目里维护过一份“场景 vs 工具集”的对照场景建议加载的工具建议详略级别日常代码阅读文件检索、代码搜索、目录浏览minimal执行构建任务Shell 执行、日志查看、进程管理detailed网页数据采集浏览器操作、文本提取、URL 处理normal批量文件处理文件读写、校验、移动复制detailed这个矩阵不需要做成配置文件你只要能意识到工具是给当前任务用的不是越多越好。模型每多看到一个工具选择空间就大一分判断成本也跟着涨。把无关工具从当前会话里摘掉既省 token又让模型更少做错误选择。我在做只读代码审查时会刻意禁用写类和执行类工具模型就不会出现“一边看代码一边改代码”的危险操作。2.4 别名机制让模型少读一段长单词除了压缩 schema还有一种不太被人注意的优化工具别名。如果某个扩展的工具名特别长你可以在配置层给它一个短别名。比如把utils.filesystem.read_text_file_from_local_path映射为fs.read模型在系统提示词里看到的工具名是fs.read真正执行时 Pi Agent 再把别名映射回完整路径。我当时在一个开发项目里就是这么干的。项目里 27 个工具我全部换成短名后工具定义部分的 token 又降了 15% 左右。别名的好处是它不影响扩展本身完全是用户侧的覆盖。配置方式通常写在扩展加载区{ extensions: { chs_file_utils: { aliases: { read_text_file_from_local_path: fs.read, write_text_file_to_local_path: fs.write } } } }这里有个注意点别名不能跟其他工具重名否则模型会困惑。我最早把“列出目录”和“列出进程”都起成list结果模型连续在两者之间乱选。后来统一按前缀区分fs.list和ps.list问题马上消失。3. 给扩展作者从源头设计轻量工具定义3.1 工具定义四层结构名称、参数、描述、示例如果说用户端配置是“治标”那扩展作者改进工具定义就是“治本”。一个工具定义通常有四层信息名称模型用来标识工具的唯一字符串参数JSON Schema 描述参数结构描述说明工具用途和注意事项示例调用样例我的经验是这四层信息对模型的价值完全不一样。名称和参数是模型调用工具时的“必读信息”描述是“按需阅读”示例则是“锦上添花”。多数扩展作者的问题在于把四层信息混在一起每个字段都重复解释导致必读信息被淹没在冗余描述里。3.2 落地案例把文件读取工具从 260 token 减到 25 token直接给你看一个改造前后的对比。下面是我对一个文件读取类工具的完整改造改造前{ name: read_text_file_from_local_path, description: 读取本机文件系统上的文本文件内容。只支持 UTF-8 编码的纯文本文件不支持二进制文件。调用时请自行确认路径是否存在。如果文件不存在会返回错误信息。读取大文件时请小心内存占用。, parameters: { type: object, properties: { path: { type: string, title: 文件路径, description: 指向目标文件的相对路径或绝对路径, examples: [/home/user/project/main.py] } }, required: [path] } }改造后{ name: read_file, description: 读取文本文件不存在则报错, parameters: { type: object, properties: { path: { type: string } }, required: [path] } }这个改造里有几个关键决策。工具名从read_text_file_from_local_path改成read_file模型看到read_file就明白用途不需要再叠一堆修饰词。description 从六十多个字压到十四个字“读取文本文件不存在则报错”——前半句告诉模型什么时候用后半句告诉模型失败了会怎样。参数只留一个path去掉了 title、examples、超过字段名的描述。有人会担心description 写这么短模型还知不知道“哪些工具是只读的”我的回答是工具名里带上read_前缀本身就是一种约定Pi Agent 的模型在预训练过程中对这类命名已经形成了很强的先验。你把描述删到一句话它反而更依赖你的字段名字段名清晰短描述完全够用。3.3 三个让 Schema 更克制的设计技巧第一能用枚举就别用自由字符串。有些扩展把“文件操作模式”设计成mode: { type: string, description: 操作模式可选 append 或 overwrite }改成枚举mode: { enum: [append, overwrite] }模型看到枚举就不需要从 description 里猜“到底能传什么值”token 也少写十几个校验还更严格。第二用约定代替重复描述。如果你的扩展里所有工具都作用于同一个工作区与其每个工具都写一遍“路径相对于当前工作区”不如在扩展总配置里定义一个workspace_path参数或者写进扩展的使用说明里。工具定义属于系统上下文说明文档属于用户上下文。能放进文档里的约定就不要塞进每个工具的 description。第三减少嵌套对象。嵌套对象在 JSON Schema 里写起来 token 多模型解析也难。尽量把参数摊平{user: {name: ..., id: ...}}改成{user_name: ..., user_id: ...}。参数层次深了模型每次递归解析都要消耗额外理解力摊平之后 name 和 id 一目了然。我在优化网络请求类扩展时做过对比把三层嵌套压成一层后同一任务的工具定义部分 token 少了一半。3.4 什么时候必须保留详细描述精简不是越短越好。我遇到过一类特殊情况工具的副作用无法从名称推断。比如一个工具叫sync_state描述不写清楚它会把本地修改推送到远端模型就可能误以为它只是在本地刷新状态。另一个例子是cleanup_cache描述不说明它会删除临时目录模型可能在用户还开着某些文件时调用它。这类“名称看不出来后果”的工具必须保留一段明确描述副作用和不可逆性的文字。所以我在给扩展写工具定义时的判断顺序是先问“这个工具有没有不可逆副作用”如果有写 detailed如果没有优先考虑 minimal。你可以在同一个扩展里混用两种详略级别没有规定说一个扩展的所有工具都得一个风格。4. 效果验证如何确认 91% 不是靠牺牲准确率换来的4.1 用日志量化 token 节省改了配置、改完扩展怎么确认真的省了最直接的办法是看两个指标单轮请求的工具定义部分 token 数和工具调用成功率。Pi Agent 的日志或者 API 返回里通常能看到每次请求的 prompt 明细。把工具定义部分的字符数取出来用字符数除以 3.5粗略换算成 token。优化前和优化后各统计十轮请求取平均值就能得到你的实际节省率。注意每次对话长度不一样所以别跟“总 token”比要专门比工具定义部分。4.2 回归测试六项清单每次对工具定义做大改动之前我都跑一遍回归测试。清单很长这里挑六个关键项测试项检查点通过标准必填参数缺失故意不给 path 调用 read_file返回参数错误而不是瞎猜一个路径枚举外值给 mode 传一个不在枚举里的值返回校验错误多工具混合调用连续让模型“先查看再修改”两次调用分别命中正确工具否定指令“不要执行任何写操作”模型避开写类工具中文文件名路径含空格和中文参数正确传入不出现转义错乱并发长对话30 轮对话后继续调用工具工具定义部分仍然正确进入上下文这六项能覆盖大部分优化带来的隐性风险。我自己的项目里真实踩过其中三项的坑后面部分会展开讲。4.3 优化前后的准确率对比我在一份内部工具集上做了 100 次随机任务测试结果供你参考指标优化前优化后差异工具定义 token 合计3400300-91%工具调用成功率96%95%-1%平均单次调用延迟1.8s1.1s-39%错误调用次数451工具调用成功率下降了一个百分点这个误差在统计上基本可以忽略——我拿不同任务集测了两轮第二轮甚至出现优化后比优化前更准的情况。因为描述变短之后模型不再被大段文字干扰反而更容易抓住“什么场景调什么工具”这个核心。当然这个结论有个前提我保留了所有带副作用的工具的详细描述。如果你把所有工具的 description 都一刀切删光准确率不会这么好看。5. 踩过的坑与排查速查表5.1 三个典型的翻车现场第一个坑删描述删得太狠模型开始“行为变异”。我优化过一个时间管理扩展里面有个工具叫update_task_due_date我把 description 从“更新某个任务的截止日期只会修改 due 字段不会影响任务的标题和优先级”压缩成“更新截止日期”。结果在复杂对话里模型连续出现“把任务标题和截止日期一起改”的情况。原因不是它不认识这个工具而是它不知道这个工具的副作用边界。后来我在 description 里补了半句“仅改 due 字段”问题立刻消失。第二个坑参数名缩写导致“参数幻觉”。为了省 token我把user_working_directory缩成uwd把retry_count缩成rc。结果模型在多次调用里频繁填出莫名其妙的uwd: /var/...值我查了半天才发现是参数名太抽象模型只能靠猜。修法是保留有语义的短名cwd、retries它们同样短但模型一眼就知道含义。第三个坑共享枚举带来的混乱。为了省 token我把多个互不相关的工具的返回值格式统一成了一个共享枚举{ok, err, pending}然后在每个工具的 description 里写“返回值遵循标准枚举”。绝大多数情况下没问题但当模型需要同时分析“文件读取”和“远程调用”两类工具的返回时它会把pending误判成某个文件操作的异步等待。后来我把枚举拆成按工具类别声明虽然 token 多了一点但准确率明显回升。5.2 问题速查表症状可能原因解法模型频繁调用错误工具描述删到连副作用都看不清恢复 detailed重点写副作用参数值经常凭空捏造参数名缩写成无意义短码改用有语义的短词如 cwd/retries工具定义没生效扩展缓存未刷新重启会话清空缓存后重载枚举值被乱传共享枚举跨领域共用按工具类别拆分枚举模型完全不调用某个工具工具名与描述互相矛盾统一名称后缀风格保持命名一致中文参数乱码编码未显式声明在参数 schema 里加encoding: utf-85.3 我的推荐配置模板文章最后给你一套我实际在用的配置模板。它会随 Pi Agent 版本迭代微调但核心思路不变全局精简、副作用工具保留详细、不承载过多与当前任务无关的工具。{ tools: { compact_schema: true, drop_title: true, max_description_chars: 80, overrides: { fs.write: detailed, shell.run: detailed, state.sync: detailed, fs.read: minimal, fs.list: minimal, web.fetch: normal } } }我在实际使用中还发现一个小技巧工具定义改动后不要急着把配置压到最终形态先跑两天日常任务看模型有没有出现调用不稳定的情况。稳定了再继续压。压缩工具提示词这件事本质上是对“模型理解成本”和“token 传输成本”做取舍。我的项目最终把工具提示词降到了原来的 9%同时 100 次任务里的工具调用成功率只下降了一个点以内。对我和我身边的 Pi Agent 用户来说这个优化带来的收益是实打实的请求更快、成本更低、扩展的可维护性也更高。你自己上手之后一定会在某个小项目里发现我上面没提到的坑那正是这个优化方向的乐趣所在。
阅读完成 · 觉得有帮助?
咨询建站