我今年的立场是:工具 schema 写窄。把查询揉进一个万能 query 工具,等于把决策负债全推给模型;选错工具的代价远小于填错参数——选错了,模型通常会当场看到报错;填错了,它可能拿着错参数跑很久。这周外部的东西没动摇这个立场,但我也不想假装反例不存在。
宽工具是个诱人的陷阱
Domain-Driven Agents 那篇很有意思,作者把 DDD 那套搬过来:agent 的工具边界应该贴着领域边界走,而不是泛化成通用动词。你的领域模型里有什么动词,工具就长什么样——submit_invoice、approve_leave,而不是一个普适的 execute_action。
宽工具为什么诱人?因为省事。你不用维护一长串函数名,不用写一堆描述,模型想干嘛就传一传。我同事上周就把三个查询工具砍掉,合成一个 query(user_id, filters, fields),跑了几天发现输出质量掉了不止一点——模型去猜 filters 的合法值,猜错了返回空集,它还以为用户真没有记录。问题不在模型笨,问题在窄工具把约束编译进了函数签名,宽工具把约束丢给了运行时。
选错 vs 填错,代价差一个量级
我的核心论点就这么一句:模型选错工具的代价,远小于填错参数。
选错工具,意味着这个工具不在候选里,或者签名对不上,系统立刻报错。模型看到 error,自我纠正,重试一次。成本:一次调用。
填错参数呢?工具正常执行,返回一个"空"或"错"的结果,模型没有理由怀疑自己。它会拿着这个结果继续推理,然后一本正经地告诉你"该用户不存在"。成本是整条链路的信任被污染。Agent Is Not the Model 说得对,agent 是模型加工具加环境的复合体——工具是代码,代码里该做的校验就做,别指望模型兜底。
Agentic Context Management 那条也佐证:上下文是稀缺资源,而填错参数恰恰需要模型去"理解"很多隐含约定。你把参数合法性交给模型,等于在稀缺资源里塞满约定。
描述字段先写"什么时候不要用"
写 schema 描述时,我习惯先写"不要用"。
比如 get_user 的描述,别写"获取用户信息",写"仅当需要用户完整档案时使用;如果只是显示用户名或头像,优先用 resolve_username_public"。负向描述的价值在于给模型一条排除路径。模型面对十几个工具时,真正的难点不是知道哪个能干嘛,是知道哪个不该现在用。
这周的 agent.md 也有类似味道——给你的 agent 写规则,最重要的部分是"不要做"什么,而不是"能做"什么。工具描述跟做人设一个道理。
窄的前提是数量受控
但得承认反例:工具一多,窄就撑不住。我自己的体感,当工具数量到了二十上下,模型的选择准确率就开始掉——它开始张冠李戴,拿 archive_invoice 当 delete_invoice 用。这时候继续切窄,只会加重选择负担。
解决方法是分层,不是合并成宽工具。留一个路由工具在最上面,职责是判断请求属于哪个域,然后把域的窄工具分组传下去。或者把语义相近的合并成一个工具,但保持参数严格。数量受控时窄,数量失控时窄加分层。这跟 Headlong 那个微 harness 的思路一致:工具集、状态都是显式管理的,而不是丢给模型自由发挥。
我现在的新项目直接按这套来:窄 schema,负向描述,工具数量到顶就做一层路由。也许 6 个月后会被打脸,但目前看,把决策留在编译期而不是运行时,工程上更稳。