省掉91%工具提示词:Pi Agent自动描述机制与扩展编写指南
省掉 91% 的工具提示词给 Pi Agent 用户和扩展作者的操作指南最近我在给 Pi Agent 写扩展的时候算了一笔账如果按传统方式每个工具动辄要写八十到一百行提示词从 JSON Schema 到自然语言描述再到 few-shot 示例一套下来累得够呛。而换了 Pi Agent 的自动描述机制之后单个工具的平均提示词从 3000 多字符降到了 270 字符左右算下来正好省掉 91% 的内容。这篇文章就把这套怎么写工具提示词才能省力的方法论整理出来重点面向两类人一是 Pi Agent 的重度用户日常想少打几句话就让 agent 干活二是打算写扩展的作者想让自己的工具被用户开箱即用而不是附赠一篇万字说明书。先说结论Pi Agent 会从函数的签名、类型注解、docstring 里自动提取工具描述不需要作者额外维护一大段提示词。但前提是代码得自带描述能力命名、注解、文档字符串三者配合到位否则自动提取照样翻车。下文会给出可复现的写法、迁移步骤和避坑清单你照着改就能把扩展的提示词负担降下来。1. 工具提示词膨胀为什么扩展作者总在无声加班1.1 一个三万字符的教训我手写过最大的工具描述上个月我给 Pi Agent 写了一个视频信息查询扩展功能不复杂输入文件路径返回分辨率、编码格式、时长、音轨信息。按我过去的习惯会在扩展的 manifest 里手写工具定义差不多是这个画风{ name: get_video_info, description: 获取视频文件的元数据信息。当用户想知道视频的分辨率、时长、编码格式、帧率或音轨情况时使用此工具。如果传入路径不存在返回错误码 1001如果文件不是视频格式返回错误码 1002。注意本工具只读取元数据不进行转码。对于远程 URL 不生效需要先下载到本地。, parameters: { type: object, properties: { path: { type: string, description: 视频文件的绝对路径或相对当前工作目录的路径。目录路径无效。 }, detail_level: { type: string, enum: [basic, full], description: basic 只返回分辨率、时长、编码full 额外返回音轨语言、比特率、创建时间。默认 basic。 } }, required: [path] } }单看这一个工具不觉得多但我那个扩展一共挂了 12 个工具。为了每个工具的边界情况、返回格式示例manifest 文件一路膨胀到了接近 3 万字符。更难受的是维护成本我改过一次参数结构JSON Schema 忘同步结果 agent 按旧参数调用连着报了好几天错才被用户发现。1.2 膨胀从哪来一段正常提示词的构成拆解传统工具提示词之所以厚主要不是名字本身而是五类附加内容的叠加自然语言功能描述为了让模型理解什么时候该用这个工具通常要写几十到几百字的场景说明。参数的逐字段说明每个字段的类型、取值范围、默认值、与其它字段的约束关系都要展开写。边界条件和错误码文件不存在怎么办、权限不足怎么办、格式不支持怎么办模型需要提前知道。few-shot 示例有时候还得塞一两个完整调用示例教模型怎么组合参数这一塞又是几百字符。冗余安全话术防止模型误用比如不要在用户没有要求时自动调用这类话来回来去写。这几部分合计下来一个中等复杂度的工具没有两千字符打不住。你要是去翻一些成熟 agent 项目的工具定义文件动辄上万字符的 manifest 真不少见。但问题在于工具的实现本身就在代码里提示词里的描述不过是对代码的二道翻译翻译错了、漏了、过期了全是新的坑。1.3 91% 到底是怎么算出来的一个可复现的对比口径我按自己的扩展做了一个对照实验。同一个视频信息读取工具传统手写法写满自然语言描述和 JSON Schema一共 3120 字符Pi Agent 模式下我只需要写函数定义、类型注解和两行 docstringfrom pi_agent import tool from typing import Literal tool def get_video_info( path: str, detail_level: Literal[basic, full] basic ) - dict: 读取视频文件的元数据返回分辨率、编码、时长等核心信息。 ...函数签名部分 147 字符注解部分 58 字符docstring 65 字符加起来 270 字符。3120 到 270减少了约 91.3%。这中间省掉的部分不是偷工减料而是 Pi Agent 自己根据类型注解和命名推导出来的Literal[basic, full]自动变成枚举path: str自动映射为字符串参数函数名里的get_video_info配合 docstring 的第一句话会自动生成场景描述。所以省掉 91%不是玄学是换了一套描述生成协议。下面两节我会把机制拆开讲。2. 自动描述生成的内幕函数签名如何变成模型读得懂的工具说明2.1 从类型注解到工具参数模型的映射关系Pi Agent 的扩展系统有一套内置的类型映射协议简单说就是把你写在函数签名里的 Python 类型直接转成模型侧使用的 JSON Schema。协议对应关系大致如下Python 类型生成的 JSON Schema 片段备注str{type: string}最常用int{type: integer}float{type: number}bool{type: boolean}Literal[a, b]{type: string, enum: [a, b]}自动生成枚举list[str]{type: array, items: {type: string}}只支持单一泛型dict{type: object}无内部结构时只声明对象TypedDict{type: object, properties: {...}}适合结构化入参None / NoneType参数可空或返回空用于可选返回值Path{type: string, format: path}路径特化处理不需要手动写parameters: {...}这些全部由函数签名自动映射。使用这一层机制扩展作者要维护的内容就变成了函数签名本身提示词和代码实现天然同步不会出现改了参数忘了改描述的问题。2.2 它到底读懂了什么命名、参数名和 docstring 的配合逻辑类型映射只是骨架真正让工具描述像人话的是三个元素的配合。第一个是函数名。Pi Agent 的提取器会把函数名按 snake_case 拆词get_video_info拆成get / video / info再在生成的工具描述里合成一个简短的功能头获取视频信息。这意味着你给函数起名时得用强动词 明确宾语别用process_data这种谁都看不懂的名字。第二个是参数名。path、detail_level这种自解释的名字会被原样保留进描述如果你写p、dl提取器生成的参数描述就只剩下类型信息模型就只能靠猜。参数默认值也会被读取detail_levelbasic会被解析成该参数选填默认值为 basic模型调用时就能省掉这个字段。第三个是 docstring。第一行会被当作工具的简短描述紧跟函数名合成的头部共同组成完整的场景说明。所以 docstring 千万别写这个函数用来干嘛的这种废话直接把什么时候该用、什么时候不该用压缩成一句话效果远比三行空话好。2.3 为什么这是对扩展作者解放而非压缩的设计很多人以为省提示词只是省存储空间其实真正的价值是解除了维护负担。传统模式下工具描述和工具实现是两个独立事实来源改一个必须同步另一个否则就会出现说明书与实物不符的翻车现场。Pi Agent 的自动描述机制把实现本身变成了唯一事实来源描述不过是实现的一个投影视图。对照一下两种模式的心智负担维护动作传统手写 JSON 描述Pi Agent 自动描述新增一个参数改函数 改 JSON Schema 改自然语言描述改函数签名即可调整参数取值范围改枚举 改描述文本改Literal注解更新工具行为改描述里的边界条件说明改函数实现 docstring 第一句排查 agent 误用对比描述与实现是否一致直接看函数签名没有二义性一句话总结自动描述把写提示词变成了把代码写清楚。代码本身就是文档文档就是代码的一部分。对单打独斗的扩展作者来说维护成本能降一个量级。3. 扩展作者实操写出零提示词负担的工具函数3.1 一个完整示例同样的视频读取工具两种写法先看最终的 Pi Agent 扩展代码长什么样from pi_agent import tool from typing import Literal, TypedDict class VideoMeta(TypedDict): width: int height: int codec: str duration_seconds: float audio_tracks: list[str] tool def get_video_info( path: str, detail_level: Literal[basic, full] basic ) - VideoMeta: 读取视频文件的元数据用于回答分辨率、编码格式、时长、音轨相关问题。 # 实际实现可以调用 ffprobe 或自研解析器 ...这段代码里没有任何提示词但 Pi Agent 会自动生成下面这份等价描述{ tool_name: get_video_info, description: 读取视频文件的元数据用于回答分辨率、编码格式、时长、音轨相关问题。, parameters: { type: object, properties: { path: { type: string, format: path, description: 参数 path 的类型为 string表示文件路径。 }, detail_level: { type: string, enum: [basic, full], default: basic, description: 参数 detail_level 的类型为 string可选值为 basic 或 full默认 basic。 } }, required: [path] }, returns: { type: object, properties: { width: {type: integer}, height: {type: integer}, codec: {type: string}, duration_seconds: {type: number}, audio_tracks: {type: array, items: {type: string}} } } }对比第一节的手写 JSON这里没有一个字是在额外维护全部来源于代码本身。你只需要保证代码正确工具描述就正确。3.2 三个技术要点命名、类型注解与 docstring 的最佳实践想让自动描述发挥最大效果得把代码写成提取器友好的形态。我总结了三条硬规则。规则一函数名要能望文生义。get_video_info没问题但handle_event、do_task、process这类名字就是在浪费自动提取的机会。好的函数名由强动词 明确宾语构成list_workspace_files、search_by_keyword、convert_encoding。不要用缩写到无法读懂的短名get_vid_meta这种名字虽然省了几个字符但会直接影响模型判断工具用途。规则二参数一定要写类型注解能用Literal绝不用裸str。裸str意味着模型不知道参数的可选范围它会可着劲猜。Literal[basic, full]一写枚举直接生成模型就只在范围内取值。结构化参数用TypedDict定义比dict这种笼统类型安全得多。另外可选参数记得给默认值默认值会被解析进描述模型调用时就不会非传不可。规则三docstring 第一句写何时使用而非如何使用。拿上面的例子说docstring 写读取视频文件的元数据用于回答分辨率、编码格式、时长、音轨相关问题重点在什么场景下用户会需要这个工具。如果写成传入路径参数返回元数据字典等于把类型注解已经表达的信息再重复一遍白白浪费提取器留给你的宝贵描述空间。3.3 常见反模式这些写法会让自动提取器直接摆烂光知道怎么对还不够还得知道哪些写法会让工具描述变得不可用。我踩过的坑按严重程度排了个序没有任何类型注解的参数。提取器对无注解参数只能写一个笼统的type: string模型把目录当文件传、把数组当字符串拼都是这么来的。**kwargs兜底接一切。提取器压根不知道你接受哪些字段结果就是模型传什么你都收错误要到运行时才炸。函数名和实际行为不匹配。比如函数叫get_video_info实际还顺带做了转码docstring 也没写清楚模型就会误以为它具备转换能力。docstring 写成小作文。Pi Agent 只取第一句作为工具描述后面全是冗余。把边界条件、错误码、示例都堆进 docstring提取器只会截断不会帮你拆结构。返回类型写dict而不是TypedDict。返回结构不明确模型就不知道工具返回后该怎么解读结果尤其容易把{width: 1920}错当字符串处理。可以用下面这个对照表快速自查你的工具函数维度推荐写法容易翻车的写法函数名list_workspace_filesdo_thing/process参数类型path: strdef f(path)枚举取值Literal[basic, full]裸字符串加注释结构化入参TypedDict类**kwargsdocstring一句话讲场景三段式流水账返回类型TypedDict/list[T]Any/dict4. 用户侧玩法不写一行扩展代码日常也能省掉九成提示词4.1 内置工具本来就免描述你的提示词里根本不用写工具细节作为 Pi Agent 的普通用户你可能永远不会写扩展但同样能享受到提示词减少的红利。因为 Pi Agent 内置的文件读写、代码搜索、终端执行、网络请求等工具全部走自动描述协议。这意味着你在对话里不需要教它应该用哪个工具、参数怎么传、返回结果怎么看它自己就能完成工具匹配。举个实际例子。以前用别的 agent想让它统计项目里的代码行数我得说请先用 list_files 列出 src 目录下所有文件然后对每个文件计算行数注意排除 node_modules再汇总。这种话本质上是在手动替 agent 做工具选择。换成 Pi Agent 的自动描述工具我只需要说统计 src 目录下所有 Python 文件的总行数排除测试文件。剩下的路径怎么传、用哪几个工具自动描述机制会替模型完成选择。4.2 使用官方与第三方扩展包装完即用对话只需提需求Pi Agent 的扩展市场里已经有不少第三方扩展比如各类媒体处理、数据分析、图表生成工具。这些扩展如果按自动描述协议写好用户装完之后不需要学任何新话术——直接在对话里提需求就行。安装一个视频元数据扩展后你可以直接问帮我看看 downloads 目录里那个 demo.mp4 编码是什么、有没有多音轨扩展里的get_video_info工具会自动匹配上参数path会被填成绝对路径detail_level会默认取 basic。你全程没有提请调用工具这类元指令因为工具描述已经从函数签名里生成了模型看到的是当前环境有一个能读取视频元数据的工具接受路径参数它自然会去用。我自己测过很多次这种玩法下对话里真正需要用户表达的只有要做什么而不是怎么做。提示词省下来的不光是字符数更是思考负担。我想让 Pi Agent 干活时不再像在写 API 文档。4.3 用户与扩展作者协作时的注意点更新、缓存与版本自动描述机制也不是说完全不用维护。扩展作者更新了函数签名之后用户的本地描述缓存如果不刷新你暂时还是会看到旧的工具描述。按我目前的经验Pi Agent 会在扩展加载时校验函数签名哈希但是缓存策略比较保守高频使用的扩展未必每次都触发重载。遇到这种情况一个靠谱的排查方式是查看当前会话实际生效的工具描述。Pi Agent 的会话里可以直接询问你现在有哪些工具可用它会列出自动生成的描述。如果发现列表里的参数还是旧版就手动卸载重装对应扩展或者在设置里清空扩展缓存强制重建一次工具索引。这一条也提醒扩展作者升级扩展时尽量保持函数签名兼容把Literal枚举往大改没问题但把已发布的参数名删掉或者改类型会让用户侧的缓存描述与实际实现短期错位体验很差。5. 避坑实录自动描述机制的边界与调试方法5.1 自动提取失败与变形的三种典型情况自动描述不是万能的我在实际使用中遇到过三种翻车场景写出来帮你避雷。情况一函数参数没有类型注解。有一阵子我图省事把get_video_info的path参数写成无注解形式自动描述直接退化成一个光秃秃的string参数。模型拿到这种描述完全不知道路径格式传入C:/Users/...还是/mnt/data/...全靠碰运气。解决办法很直接补上str注解后面如果涉及路径语义再进一步标注成Path类型。情况二docstring 第一句是空话套话。我写过获取信息。这种毫无信息量的 docstring结果自动生成描述里工具说明就只剩获取信息四个字。模型面对获取什么信息、什么场景用完全没有判断依据只要有一丁点沾边就乱调。改成读取视频文件的元数据用于回答时长、编码、分辨率相关问题之后匹配准确率立竿见影。情况三返回类型写成了Any。返回结构对模型判断工具结果怎么用至关重要。Any会让模型把结果当成未知结构处理后续推理经常出错。我在做一个分子式解析工具时返回一个 dict忘记定义TypedDict结果模型每次都要猜返回里有什么字段。补上返回结构定义之后后续的推理步骤明显稳了。5.2 局部重写描述自动提取结果不够用时的兜底方案自动描述虽然省力但总有一些工具的行为没法靠命名和注解表达清楚。比如某个工具需要传入的参数之间互相依赖mode为remote时必须传url这类逻辑注解表达不了只能靠自然语言说明。Pi Agent 给我们留了一个describe参数来做局部覆盖from pi_agent import tool from typing import Literal tool(describe获取视频元数据。当 mode 为 remote 时需同时传入 url为 local 时用 path 指向本地文件。) def get_media_info( path: str | None None, url: str | None None, mode: Literal[local, remote] local ) - dict: ...注意这里加的是局部补充不是推翻整个自动描述机制。参数名、类型映射、枚举值仍然从签名里自动来describe只负责补充那些签名表达不了的约束信息。这种混合写法的维护成本仍然远低于手写全套 JSON Schema而且不会出现两处不一致的问题。5.3 实测心得把扩展调到稳定可复用的三个坑位最后说三个我反复踩、反复修的实战细节都是文档里不会写的。第一默认值尽量别给空字符串。我早期写的工具里有参数lang: str 提取器生成的描述是默认空字符串模型看到后经常传一个空字符串过去运行时判断逻辑还得额外处理。改成lang: Literal[auto, zh, en] auto之后模型只会选三个枚举值行为清晰得多。第二返回结构里不要混Optional和None之外的值。TypedDict里某个字段写成str | None生成后模型会理解为可能是字符串也可能为空它推理时会丢字段。如果确实可能没有建议在 docstring 第二句补充说明未取到时分秒字段返回 null这类信息帮助模型正确处理。第三工具名里别带版本号或序号。get_video_info_v2这种名字会被拆词解析成get / video / info / v2v2没有语义含义既干扰生成描述也容易让模型困惑和旧版本的区别。版本语义交给扩展包版本管理不要塞进函数名。至于扩展分发的时候我会额外看一眼生成的描述预览再发布。Pi Agent 的开发者工具里能看到自动提取器对每个函数生成的结果我会把生成描述通读一遍确认跟函数行为一致。这一步就是整个流程里唯一需要人工点评的地方花不了两分钟却能把 91% 的提示词成本省得明明白白。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →