写扩展的时候,最让我上头的不是逻辑本身,而是那一大坨工具提示词。明明功能就那点东西,为了告诉模型“这个工具是干嘛的、参数什么意思”,得堆上几百个字的描述。扩展开多了以后,每次对话光刷提示词就刷掉一堆token,Agent 的理解准确率还没见涨。最近在 Pi Agent 上折腾完一套精简方案,实测把工具提示词的开销砍掉了约 91%,扩展描述不再逐条塞进上下文,模型反而更不容易选错工具。这篇文章就是把整套操作思路完整拆开,Pi Agent 用户照着配置就能立省,扩展作者看完能直接按新规范改造自己的工具。
Pi Agent 的工具体系跟主流 Agent 有一点很不一样:它的扩展以代码内注册为主,工具描述和参数 schema 都写在扩展源码里。这个设计天然适合做“提示词瘦身”,因为它不像纯提示词 Agent 那样非要靠自然语言描述来驱动模型,而是可以在运行时把大部分信息转化为结构化元数据,只在模型真正需要决策的时候,才把一小段精准的描述喂进去。省掉 91% 的工具提示词,不是指每个工具的文案都要大幅精简,而是指在完整运行链路里,真正进入上下文的提示词总量被压到了原来的 9% 左右。下面拆开讲清楚每个环节。
1. 工具提示词为什么越写越膨胀
1.1 传统做法里提示词都进上下文了
先回顾一下原来的机制。常规 Agent 框架里,每个工具一旦被注册,它的描述、参数说明、枚举值、注意事项几乎都会拼成一段长文本,放进系统提示词或者工具列表里,随每一轮请求发送给模型。假设你写了 5 个扩展,每个扩展 3 个工具,每个工具的描述 300 字,加上参数细节,一轮请求光是工具提示词就有几千 token。
Pi Agent 刚诞生时也有很多扩展沿用了这套习惯,大家从别的框架迁移过来,复制粘贴的痕迹很重。于是出现一个典型场景:Agent 要调用一个“天气查询”工具,模型在提示词里看到的不只是天气查询,还要顺带读完同组的文件解析、时间转换、单位换算工具描述。上下文塞得多,模型反而容易被无关信息干扰,工具选择的准确率下降,这跟直觉相反。
那么问题到底卡在哪?本质上是因为工具提示词被当成了“一次性发给模型的全量知识库”,而模型的注意力有限,塞进去不代表会被正确使用。真正有效的做法,是把工具的静态说明和动态调用解耦。
1.2 91% 这个数字是怎么算出来的
先给出一个可量化的前后对比。改造前,一个带有 12 个工具、每个工具平均约 260 字描述的扩展包,每轮请求贡献的工具提示词大约 3120 字。改造后,运行时只向模型暴露一个“轻量工具注册表”,每个工具只保留名称、一句话摘要、关键参数名列表,压缩到约 280 字。即便如此,这 280 字也不是每次都原样塞进上下文,而是在加载扩展时生成一次并缓存,模型对话期间的固定开销降到极低。
按 token 占比算,改造后工具提示词在整个请求中的占比从原来的 28% 降到 2.5% 左右,约等于省掉 91%。这不是玄学,是结构优化,后面会讲具体怎么实现的。你可能觉得这不就是把长描述改成短描述吗?实际不止,真正的核心在于“何时把哪段信息交给模型”这个时机的设计。
2. 核心设计思路:把静态描述从上下文中拆出去
2.1 扩展注册中心与工具索引
第一件事,是在 Pi Agent 里建立一个“扩展注册中心”,替代原来直接拼接提示词的逻辑。每个扩展安装后,注册中心会扫描它的工具清单,生成一份紧凑的工具索引。这份索引包含工具的唯一标识、名称、一句话摘要、入参名称列表,不含完整的参数 schema、枚举说明、示例代码、注意事项。
索引的生成发生在扩展加载阶段,之后所有对话请求共用同一份缓存。模型拿到的是“目录”,不是“全书”。比如原来一个工具的完整描述是:
{ "name": "parse_log", "description": "解析日志文件,支持多种格式,包括 JSON、XML、纯文本。可以指定起始行、结束行、过滤关键字、时间范围等参数。如果指定了 time_range,则会忽略 start_line 和 end_line。返回值包含解析后的日志条目列表,每条包含时间戳、级别、消息字段。对于超过 100MB 的大文件请使用 stream 模式,否则可能超时。", "parameters": { "file_path": { "type": "string", "description": "日志文件路径,支持相对路径和绝对路径" }, "time_range": { "type": "string", "description": "可选,ISO 8601 格式,如 2025-01-01T00:00:00Z/2025-01-02T00:00:00Z" } } }索引化之后,模型看到的内容只有一行:parse_log(file_path, time_range),后面附一句“解析日志文件,格式与参数见工具详情”。需要在哪一步看到完整 schema?在模型已经明确决定调用这个工具、Agent 运行时准备执行参数校验的时候,系统才会把对应的完整定义从注册中心拉取出来。
这个设计的直接收益是,上下文里飘着一堆无关工具描述的问题消失了,每个工具只留下最短的“可识别签名”。而参数怎么校验、默认值是什么、有没有约束条件,全部由运行时确保,不需要模型用自然语言来推理。
2.2 何时加载完整提示词:按需拉取与延迟注入
你可能会问,模型都不知道某个工具的完整参数,它能正确填参吗?答案是模型不需要正确填参,它只需要表达“我要调用哪个工具”,参数由运行时按 schema 约束来组装和补全。举个具体场景:用户说“查一下昨天下午三点的日志里有没有 ERROR”,模型会先看到索引里有个 parse_log 工具,它只需要决定“调用 parse_log,时间范围取昨天 15:00 前后”,然后生成一个意图对象。
Pi Agent 运行时拿到这个意图以后,才加载 parse_log 的完整 schema,把用户语句里的“昨天下午三点”转成 ISO 8601 格式,填入 time_range 参数,再真正执行扩展代码。这个“两段式”流程的好处是,模型在绝大多数上下文里只需要扫描短签名,扩展的完整描述只在调用发生时短暂出现,用完即弃。
这跟我们平时查文档很像。你先在目录里找到章节,翻到对应页再精读,而不是把整本书揣在兜里一页页背下来。91% 的节省,本质上省掉的就是“整本书兜售”的成本。
2.3 语义保持:一句话摘要怎么写才有用
索引化以后,一句话摘要成了模型判断工具用途的关键。写太泛会失去指引作用,写太细则回到冗长状态。我建议摘要统一采用“动作 + 对象 + 产出”的结构。
- 反例:“日志工具” —— 模型无法判断它跟其他日志扩展有什么区别
- 反例:“解析日志文件,支持 JSON、XML、纯文本,支持时间范围、行号、关键字过滤、流式读取……” —— 又变成了小作文
- 正例:“解析指定日志文件,返回结构化日志条目列表” —— 动作是解析,对象是指定日志文件,产出是结构化日志条目列表
参数列表只需要在索引里列出名称,不展开类型说明。参数类型信息交给运行时,模型在需要时能看到完整 schema。只要摘要句子干净,模型的工具选择准确率反而会比读长篇描述时更高,实测下来这一点非常明显。
3. 用户端实操:从安装到验证,三步走就能省下 91%
3.1 第一步:检查扩展声明的元数据结构
用户侧玩转这套方案不需要碰代码,但得会看扩展的元数据结构。Pi Agent 扩展的 manifest.json 里一般会声明工具列表,每个工具包含 name、summary、parameters 等字段。如果你想享受“省 91% 工具提示词”的收益,最快的方式是确认自己使用的扩展是否符合新的“索引化声明”规范。
打开 Pi Agent 的扩展管理页面,随便选一个已安装扩展,查看它的 manifest。如果 summary 字段一行写得完、parameters 只声明名称和必填性、没有大段自然语言说明,那这个扩展就已经是轻量化的了。如果看到大段的 description 和逐参数详解,说明它还在老模式下运行,你可以通过升级扩展或者手动改写步骤来优化。
好消息是,Pi Agent 新版本运行时兼容两种模式。旧的完整描述扩展也能跑,只是享受不到提示词压缩红利。对于已经安装了一堆老扩展的用户,比较干净的过渡办法是:卸载不需要的扩展,保留核心工具,然后逐个检查 manifest,把 description 改写成精简摘要。
3.2 第二步:调整 Agent 运行时配置
运行时的配置叫compact_tool_registry,默认是在启用状态。但如果你从旧版本升级上来,配置文件里可能还留着verbose_tool_descriptions: true这个开关,它会强制所有扩展输出完整描述到上下文。需要把它翻成 false。
找到 Pi Agent 的 config 文件,通常是~/.piagent/config.toml,修改以下两项:
[agent] compact_tool_registry = true verbose_tool_descriptions = false改完重启 Pi Agent,让注册中心重新生成缓存。再到 Agent 的调试面板里看一次完整请求的 token 明细,你会看到工具提示词的 token 数从上千直接降到几百甚至更低。这个数字就是 91% 节省的直接证据。
如果你用的是多会话并行模式,还要注意缓存共享。注册中心生成一次工具索引以后,所有会话共用,这个设计意味着它的成本是固定的、不随会话数量线性增长。这是这套方案能在多会话场景下放大优势的原因。
3.3 第三步:量化验证,拆掉“感觉省了”的模糊
别停留在“感觉更快了”的层面,直接量化。Pi Agent 的每次请求响应日志里,会打印request_token_breakdown字段,其中包含tool_registry_tokens。取改造前后各 20 条不同类型请求做对比,算平均数。
我实测的一组数字是这样的:改造前工具注册表 token 均值 1173,改造后均值 104,节省率 91.1%。而工具调用成功率从 81% 微升到 89%。这个提升不是模型变聪明了,而是干扰信息变少了。这项验证非常重要,因为只有数字能帮你判断到底是“省了但误事了”还是“省了且更准了”。
如果你的场景里 Agent 频繁在多个工具间犹豫,那你大概率会遇到工具选择准确率不升反降的情况,这事有解决办法,后面常问题记里详细讲。
4. 扩展作者视角:写“无需提示词”的扩展,比你想的更简单
4.1 工具签名设计:让参数名自己说话
扩展作者是这套方案最大的受益者,也是最大责任方。你在 manifest 里写的内容,决定用户能省多少。有几个原则值得反复打磨。
参数名本身就是文档。start_time、end_time这类参数即使没有任何描述,模型和用户也能一眼理解。相反t1、t2这种缩写就依赖额外描述。索引模式下,参数描述被大幅压缩,名称的可读性变得空前重要。尽量使用全称,避免缩写。我在自己的扩展里把use_llm改成了enable_llm_rerank之后,同样的功能,用户反馈误用率降了一半。
返回值要有结构感。不要返回一个裸字符串,而是返回固定 JSON 结构体,字段名也要有自解释性。模型阅读工具调用结果时,好名字依然会减少它的误解。设计签名时把“我的参数名能不能单独拿出来被人看懂”作为及格线。
4.2 annotation 该短则短,该藏则藏
在老模式下,写扩展时流行的做法是尽量写全:枚举值要写到 description 里,边界情况要写到描述里,甚至给模型看的“使用教程”也塞进去。而在索引化模式下,这些内容应该被放到三个不同的层级:
- 摘要层:一句话,描述工具整体功能,这是唯一固定进入上下文的内容
- 详情层:完整参数 schema、枚举说明、示例,只在工具被调用时按需加载
- 实现层:边界条件的处理逻辑,留在代码里,模型永远不需要看
我见过很多优秀的扩展把原本 800 字的描述压缩成了一句话 + 一个详情的组合。然后原本描述中“如果参数 A 设置了,则忽略参数 B”这种依赖关系,直接搬进代码里做参数归一化。模型提交原始参数,运行时帮它消化规则,这对准确率的影响是正向的。
对于枚举值,详情层里照样写全,不用刻意删。反正它是按需加载的,不进入日常上下文。但要注意,详情层的数据要注册到独立的 schema 存储中,不要再用“把整段 JSON 塞进 description”的老办法。
4.3 错误信息设计:这其实也是给模型的提示词
很多扩展作者忽略了错误信息对模型决策的影响。当工具调用失败时,模型会看到错误文本,然后决定下一步做什么。错误信息写得含糊,模型可能反复重试同一个错误动作。这不属于传统的“工具提示词”,但对整体 token 消耗和任务成功率影响极大。
设计错误输出时遵循三点:错误代码、具体原因、可操作的修复提示。例如:
{ "error_code": "INVALID_TIME_RANGE", "message": "time_range 开始时间晚于结束时间", "suggestion": "调整 start_time 和 end_time 的顺序,或省略 end_time 只查询单点时间" }这个错误信息又清晰又简短,模型拿到它几乎不需要额外推理。实测中,这类错误提示能显著减少 Agent 的无效重试次数,间接拉低了 token 消耗。又是一层“省”,只不过它省的是重试链路上的钱。
4.4 升级存量扩展:一个低成本迁移模板
如果你手上已经有老扩展,不想全部推倒重写,那有个小模板可以直接套。把 manifest 里的 description 字段全部提炼为一行,把原描述存到详情 schema 里,其他字段不变。
{ "name": "file_search", "summary": "按关键字搜索目录内文本文件,返回文件路径与命中行", "detail_ref": "file_search.detail.json", "parameters": ["query", "directory", "case_sensitive", "max_results"] }detail_ref 指向的文件放完整 schema 即可。运行时最多增加一次文件读取,性能损耗远小于省下的 token 成本。我在三个存量扩展上做过度量,改造耗时平均一个多小时一个,收益是每个工具的输出 tokens 直接砍掉七成以上。这个投入产出比相当划算。
5. 常见问题与排查技巧实录
5.1 工具索引太短,模型反而选错工具了怎么办
这是我遇到最多的问题。摘要精简后,模型判断不准工具的用途,尤其是有两个名称相近的工具时。排查思路不是把摘要加长,而是检查两个工具的边界是否天然清晰。两个工具都叫“解析日志”,一个查 ERROR,一个是统计 PV,那问题出在功能边界上,不提示词描述上。
正确的做法是把工具的职责切得更锐利。统计 PV 的工具改名为count_log_events,职责限定为聚合计数;查错工具保留parse_log。命名切分后,摘要不变,选择准确率立刻上来了。如果两个工具确实无法用名字划清边界,说明它们应该合并成一个工具,内部通过操作类型区分。
另外还有一个隐藏因素:工具索引的展示顺序。Pi Agent 会按注册顺序排列工具,这会影响模型优先选哪个。把最常用的工具放在前面,冷门工具移到后面,准确率会有小幅提升。不需要改代码,调整扩展加载顺序即可。
5.2 参数由运行时补全后,扩展收到一堆“错误参数”怎么办
按需加载 schema 后,参数校验从模型自然语言理解变成了运行时严格校验。用户说“下午三点”,如果运行时转成了 15:00,没问题。但有些自然语言表达比较模糊,比如“最近几天”,运行时如果没有内置解析规则,就可能填默认值。扩展端就会收到一个跟用户意图不太一致的参数。
这个问题的解法是在扩展代码里加“必需参数确认”机制。当参数值是根据模糊表达推测出来的,给参数附加一个confidence字段,扩展端收到低置信度参数时,可以通过一次简短追问确认,而不是直接执行错误操作。这套机制增加的复杂度不大,但对任务成功率提升显著。
那种把用户原始语句直接塞给扩展的做法其实不可取。严格的参数解析应该发生在运行时统一处理,扩展代码里不要自己去猜用户意图。
5.3 开启压缩后,某些老扩展在调试日志里打不完整参数描述
这是兼容性排查里最常见的问题。老扩展的代码里可能直接读取了原始描述上下文来判断参数逻辑,而不是依赖运行时注入的 schema。压缩模式下,这部分扩展的逻辑就会缺失。
排查方式是看调试日志里有没有missing detail_ref或fallback to manual parse之类的警告。有的话说明扩展还带着老逻辑。当前阶段的建议是给老扩展写一个简单的适配层,在扩展初始化时把完整 schema 加载进自己的内存里,不对 Agent 上下文输出,但在扩展内部继续使用。
如果你的扩展是第三方下载的,等作者适配即可。如果自己维护,尽量按 4.4 节的迁移模板走一遍。这属于一次性改动,不复杂,但能彻底消除兼容隐患。
5.4 关于“省下来的 token 到底有多少价值”的一点判断
最后多聊一句。工具提示词省下来的 token,在长会话场景下不是小数目。拿典型的重型任务来说,单轮省 1000 token,50 轮对话就是 5 万 token,这还只是一个任务。如果是跑批处理或者多会话并行,总量差距会更大。
但有一点值得注意,别为了省 token 牺牲准确率。如果精简之后工具选错率上升,那省下的 token 价值就会打折,因为重试会消耗更多。所以每次调整完配置,都建议用真实业务任务跑一遍回归测试,别只看仪表盘上的 token 曲线。
这些经验都是我在 Pi Agent 上从老框架迁移时一点点踩出来的。现在维护的扩展包已经全部按索引化规范重写,日常跑任务时工具提示词几乎感觉不到存在,Agent 的选择反而更稳定。“少即是多”这句话在工具提示词这个领域,确实被验证得明明白白。
唯一还需要留个心眼的,就是新老扩展混装时的表现。如果你属于重度扩展用户,建议分批迁移,先改最常用的两三个,跑一两周验证稳定后再推进到全量。动手前记得备份 config,改坏了也能随时还原。