你的智能体中杠杆率最高的 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,其中每一个工具都通过排除其他工具来定义自己。
真实世界工具描述的四个考古地层
挖掘任何生产环境中的智能体代码库,你会发现工具描述属于项目不同的地质年代:
- MVP 层:由最初搭建工具的人编写,为通过“能否调用成功”测试而优化。通常对函数的机制描述准确,但对于何时优先选择它而非其他替代方案闭口不谈,因为当时还没有替代方案。
- Bug 修复层:在特定的生产事故后添加的行。“不要用空字符串作为参数调用此工具。”“仅在用户明确确认后使用。”这些读起来像是伪装成文档的编译器警告。
- 功能漂移层:实现代码变了,但描述没变。参数
customer_id在代码、schema 和每个调用方中都变成了account_id—— 除了那个仍在谈论“customer”的自然语言描述。 - 能力扩张层:有人通过在原本只针对一个场景的描述中堆砌新的要点,强行增加了第二个用例。现在,模型看到一个声称能做两件关联性不强的事情的工具,并自信地将其用于这两个场景,但效果很差。
沉积物不断堆积。新贡献者看到的是一段看起来连贯、实际上却是四个人在两年间写就的重写羊皮纸(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)”的结合。当可用工具列表存在模型不知情的覆盖空白时,这种情况出现的比例会异常高。模型只能从它看到的工具中选择;如果没有描述明确排除该工具适用于用户的真实意图,那么最接近的工具将默认胜出。在实践中,“什么时候不要调用我”比“什么时候调用我”是更重要的信息,因为调用错误的工具绝对比完全不调用要糟糕——如果不调用,智能体至少可以询问一个澄清性问题。
余下内容仅对会员开放。
会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
- 完整文章,包含未公开存档的部分
- 可落地的工作框架,附带权衡与决策依据
- 新文章抢先看,先于公开发布
随时取消 · 一次订阅,畅读全部
