跳到主要内容

工具文档字符串考古学:描述字段是你杠杆率最高的提示词

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的智能体中杠杆率最高的 prompt 并不在你的系统 prompt(system prompt)中。它是你六个月前在某个工具定义下写的那句描述,它随实现代码一起提交,之后就再也没动过。模型在每一轮对话中都会读取它,以此决定是否调用该工具、绑定哪些参数,以及当响应不符合预期时如何恢复。工程师将其视为面向人类的 API 文档,而模型则将其视为一个 prompt。

这两种视角之间的鸿沟,正是最糟糕的工具使用(tool-use)类 bug 的温床:模型调用了正确的函数名,传入了正确的参数,发出了正确的 API 调用 —— 但原因却是错的,场景是错的,或者它放着旁边更合适的工具不用。没有任何异常抛出。你的评估套件依然通过。这种退化(regression)只会表现为衡量智能体是否真正起到帮助的指标在缓慢下降。

最近一项针对包含 856 个工具的 103 个 MCP 服务器的实证研究发现,仅靠增强工具描述,就能使任务成功率在统计学意义上显著提升 5.85 个百分点。对于一项在推理时零成本且不需要更换新模型的改动来说,这种效果规模(effect size)是非常惊人的。但同一项研究也发现,在 16.67% 的案例中出现了退化 —— 这意味着大约每六次改动中,就有一次盲目的描述修改反而起到了反作用。描述字段功能强大,但也非常锋利。大多数团队使用它的方式都搞错了。

Docstring 会被编译为 Prompt Token,而非渲染给人类阅读

当你使用 LangChain 或 Anthropic SDK 之类的框架注册工具时,该函数的 docstring 会在每次模型调用时直接拉入系统上下文(system context)。在 LangChain 的 @tool 装饰器中,docstring 会逐字变成工具描述。在 Anthropic API 中,每个工具 schema 的 description 字段会被内联到模型看到的 prompt 中。它不是文档,它是多绕了几步路的 prompt 文本。

这种视角的重塑改变了对“好描述”的定义。传统的 docstring 优点 —— 简洁、中性、强调“它做什么”而非“何时使用” —— 当读者是需要在 12 个相似工具中做选择的模型时,这些优点反而会有害。模型需要的是边界条件,而不是口号。对比一下:

  • 差的写法:“在内部知识库中搜索并呈现最相关的结果。”
  • 好的写法:“在客户支持知识库中搜索与用户问题相关的文章。仅用于关于产品功能、定价或故障排除的问题。不要用于关于账单历史、账户状态或任何需要个性化数据的问题 —— 针对这些问题请使用 get_account_status。返回最多 5 个排序结果;空列表表示无匹配项,而不代表置信度低。”

第二种写法读起来像是糟糕的文案,却是优秀的 prompt 工程。它点出了与其竞争的工具,建立了“何时不调用”的规则,并告诉模型如何解释空输出。这些信息对于浏览代码库的人类读者毫无帮助。但对于一个试图在凌晨三点的生产环境中进行正确路由的 LLM 来说,这至关重要。

在大规模应用中,这比任何单个工具都重要。一个典型的包含 58 个工具的五服务器 MCP 配置,在用户进行第一轮对话前就会消耗大约 55,000 个 context token。仅增加一个 Jira 服务器就可能让这个数字冲到 70,000。每个描述中的每个字符都在上下文窗口中占用成本,而一个工具描述中每一个不精确的词组,都会改变所有其他工具被调用概率的分布。工具描述并非彼此独立。它们构成了一个共同的 prompt,其中每一个工具都通过排除其他工具来定义自己。

真实世界工具描述的四个考古地层

挖掘任何生产环境中的智能体代码库,你会发现工具描述属于项目不同的地质年代:

  1. MVP 层:由最初搭建工具的人编写,为通过“能否调用成功”测试而优化。通常对函数的机制描述准确,但对于何时优先选择它而非其他替代方案闭口不谈,因为当时还没有替代方案。
  2. Bug 修复层:在特定的生产事故后添加的行。“不要用空字符串作为参数调用此工具。”“仅在用户明确确认后使用。”这些读起来像是伪装成文档的编译器警告。
  3. 功能漂移层:实现代码变了,但描述没变。参数 customer_id 在代码、schema 和每个调用方中都变成了 account_id —— 除了那个仍在谈论“customer”的自然语言描述。
  4. 能力扩张层:有人通过在原本只针对一个场景的描述中堆砌新的要点,强行增加了第二个用例。现在,模型看到一个声称能做两件关联性不强的事情的工具,并自信地将其用于这两个场景,但效果很差。

沉积物不断堆积。新贡献者看到的是一段看起来连贯、实际上却是四个人在两年间写就的重写羊皮纸(palimpsest),每一层都针对不同的故障模式进行了优化,而没有人能把全局逻辑装在脑子里。这就是为什么描述修改会有 16.67% 的退化概率 —— “修复”与早期地层建立的不变量产生了冲突,而已经没人记得那些不变量了。

考古式的修复方法不是从头重写。而是将描述视为具有所有权、版本历史和明确变更理由的一等代码产物(first-class code artifacts)。当你修改描述时,请说明原因 —— 写在 commit message 里,而不是描述本身。描述保持为一个 prompt;而 commit 历史则成为考古记录。

这种通过了所有测试的隐性失败模式

最可怕的工具使用 bug 并不是模型调用了错误的工具。这种 bug 至少在工具失败或返回无意义内容时会产生可见的错误。真正可怕的是模型因为错误的理由调用了正确的工具,且在这种情况下工具恰好能运行并返回一个看起来合理的结果。

具体模式:一个 search_users 工具的描述写着“通过姓名、电子邮件或用户 ID 查找用户”。用户询问“查找来自 dorathy 的支持工单”。由于模型在描述空间中找不到 search_tickets 工具,它将 dorathy 绑定为姓名并调用了 search_users。API 返回了一个匹配的用户。接着,智能体从该用户的个人资料中幻觉出了工单内容。用户得到了一个自信但错误的答案。工具运行了。API 成功了。没有触发任何异常。你的评估套件(测试了使用姓名、电子邮件和 ID 调用 search_users 的情况)也通过了。

关于工具选择幻觉的研究将其归类为“工具类型幻觉 (tool-type hallucination)”与“参数绑定幻觉 (parameter binding hallucination)”的结合。当可用工具列表存在模型不知情的覆盖空白时,这种情况出现的比例会异常高。模型只能从它看到的工具中选择;如果没有描述明确排除该工具适用于用户的真实意图,那么最接近的工具将默认胜出。在实践中,“什么时候不要调用我”比“什么时候调用我”是更重要的信息,因为调用错误的工具绝对比完全不调用要糟糕——如果不调用,智能体至少可以询问一个澄清性问题。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates