ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

030、工具定义与参数Schema设计

2026/9/11 3:18:11 拓冰建站 浏览量
030、工具定义与参数Schema设计 030、工具定义与参数Schema设计昨天半夜被线上一条报警吵醒某个Agent在调用天气查询工具时把temperature传成了字符串20度而我们的工具定义里明明写着type: integer模型偏偏视而不见。更离谱的是后台日志显示这个工具被连续调用了十几次每次都拿到同样的参数错误Agent像跟一个听不懂人话的接口死磕。我当时坐在床上盯着手机屏幕脑子里只有一个念头工具定义不单单是给模型看的说明书它本身就是一道隐形的校验闸门闸门设计得稀烂下游的解析代码就得给模型擦一辈子屁股。后来我拉出那几天的工具调用日志发现类似问题占了将近三成——不是模型不懂JSON Schema而是我们的定义写得过于“宽松”给了模型太多自由发挥的空间。比如有个查询用户订单的工具user_id字段居然没设required模型经常漏传然后后端返回一个模棱两可的错误码Agent就懵了开始瞎猜重试。还有sort_order字段我们只写了个string类型没写enum模型传过asc、ascending、ASC甚至升序后端每种都要兼容兼容到最后代码里全是if-else。工具定义这个事往小说是接口文档往大了说是你跟模型之间唯一的“契约语言”它设计得越严格跑得越稳。先聊最基础的工具定义结构。OpenAI的function calling带火了这套玩法现在各家平台基本都兼容一个工具定义里就三样东西name、description、parameters。name别搞花活只允许字母、数字、下划线和连字符别用中文别用空格别带点号别以为模型聪明到能自动翻译成函数名。我见过有人把名字写成get user weather结果模型调用时输出一个乱七八糟的字符串解析器直接炸了。description这块要命很多人的写法是“获取天气”这等于没说。你得告诉模型这个工具什么时候用、需要哪些关键信息、返回结果大概是什么样子。比如“当用户询问某个城市的当前天气或未来几天天气预报时使用返回包含温度、湿度、风力的结构化数据。如果用户没有明确城市先向用户确认”。这么写模型就能把“今天上海冷不冷”跟“天气查询工具”关联起来而不是去调用一个叫“获取信息”的通用接口。parameters是重头戏JSON Schema这套东西早年搞OpenAPI的同学都熟但放到Agent场景里它的重要性被放大了一百倍。因为模型不像人它不会拿着文档去“理解”你的字段语义它只会根据你的schema约束来填值。所以type一定要写对integer就是整数number就是浮点string就是字符串boolean就是布尔值别用string糊弄过去然后指望后端转换。我踩过一个大坑有个工具参数page定义成string模型传了1后端处理时忘了parseInt结果拼接SQL的时候就成了WHERE page 1MySQL居然能跑但排序结果全乱了。你说这锅该谁背定义的时候偷懒下游全得还债。再往下是properties里每个字段的细节。description不是写给人看的是写给模型看的。比如city字段你写“城市名”模型可能拿不准你写“城市中文名称例如北京、上海、广州如果用户只说了地名但没带‘市’字也要能正确识别”模型就懂了。有人问字段描述要不要写例子我的经验是要写尤其是那些容易歧义的。比如date字段你写“日期字符串格式YYYY-MM-DD例如2025-04-10”模型基本不会给你传“明天”或者“4月10日”这种自然语言它会自己去翻译。这相当于你在给模型做few-shot而且是免费的那种。required数组千万不能漏。别觉得所有字段都填上required会限制模型的灵活性实际恰恰相反模型最喜欢偷懒你越不强制它越会挑最简单的方式传参。比如一个发邮件的工具to、subject、body你全设了required模型就会乖乖把三个都凑齐你要是只设了to是required模型可能写一个空主题的邮件就发出去了。但也要注意不是所有字段都必须required比如有个可选参数cc你把它放进required里模型就不知道该怎么办有时候会硬编一个空字符串传进来后端校验直接懵。所以合理的做法是业务上必须的字段进required可选的字段留外面同时在description里写清楚“可选不传时默认XXX”。Enum这个东西用过的人都说香。凡是取值范围固定的参数不要犹豫直接上enum。比如排序方向只有asc和desc两种你写enum: [asc, desc]模型就再也不会传ASC或者升序了。再比如语言类型只有zh和en你写enum: [zh, en]模型传Chinese的概率直接降为零。有个风险是你定义enum之后如果业务上新增了一个取值得记得同步更新工具定义否则模型永远不会传新值。这不算麻烦反而是一种保护防止模型自作主张发明新枚举。还有一种情况是嵌套对象和数组。比如一个工具要创建定时任务参数里有schedule对象包含hour和minute两个字段。你的properties里就写schedule: {type: object, properties: {hour: {type: integer, minimum: 0, maximum: 23}, minute: {type: integer, minimum: 0, maximum: 59}}, required: [hour, minute]}。模型就能正确构造出{hour: 10, minute: 30}这种结构。如果遇到数组比如批量删除商品参数ids是数组你得写type: array, items: {type: integer}同时description里写清“要删除的商品ID列表最多支持100个”。别忘了加上minItems和maxItems否则模型可能给你传一个空数组或者一次性塞500个ID后端处理起来想哭。还有几个容易踩的细节。第一个是additionalProperties默认JSON Schema是允许额外字段的但在工具调用场景我强烈建议你显式写成additionalProperties: false。为什么因为模型有时候会“脑补”出你根本没定义的字段比如你定义了city和date模型自作主张加了个unit: 摄氏度你的解析器如果严格按schema校验就会报错如果不校验这个字段就被静默丢弃了。丢一个字段倒也罢了怕的是模型把本该放在city里的值放在那个额外字段里导致核心参数缺失。所以设置false让模型明白“就这些字段别瞎扩展”。第二个是nullable的问题JSON Schema有type: [string, null]这种写法但各家模型支持程度不一样我试过有些平台直接把null类型当成非法定义。更稳妥的做法是可选的string字段不要用null表示“没传”而是让模型直接省略这个字段。你可以在required里不加它然后在description里写“可选如果用户未提及则不要返回该字段”。这样模型就不会给你塞个null进去。第三个是数字范围凡是数值型参数尽量写minimum和maximum。比如温度查询days字段表示未来几天你写minimum: 1, maximum: 7模型就不会传个10出来回头再让你去截断。还有一个很多人忽略的点工具的description里要把“什么时候不要用这个工具”也写出来。不是所有对话都要调用工具模型需要学会拒绝。比如你有个“查询天气”的工具如果用户问“今天股票行情怎么样”模型不该调天气工具。但如果你只在description里写“查询天气”模型可能会觉得股票行情也算一种“市场天气”就傻乎乎调了。你可以在description末尾加一句“该工具仅适用于气象信息的查询不包含金融、交通等其他领域的‘天气’含义”。虽然看起来有点傻但真的能降低误调用率。参数Schema设计完还得考虑怎么跟后端代码对接。我见过一种坑前端定义的工具参数是下划线风格user_id后端解析代码用的是驼峰userId中间没有自动转换层结果每次模型返回{user_id: 123}后端都要手动map。这倒还好最怕的是模型偶尔返回{userId: 123}一下符合Java风格一下符合Python风格后端两个字段都处理结果匹配不上。所以建议全链路统一命名风格要么全下划线要么全驼峰。我个人偏好下划线因为模型训练语料里JSON大多是下划线成功率会高那么几个点。另一个建议是工具定义和实际执行的函数签名之间加一层薄薄的适配器专门负责把模型传过来的参数映射成函数实参。别直接拿模型的结构体去调后端接口因为你永远不知道模型会不会在某次更新后改变键名大小写或者嵌套层级。适配器里做严格校验任何不合法直接抛错返回给模型一个清晰错误信息而不是让它继续瞎试。聊一个真实案例。之前做一个“日程管理”Agent有个工具叫create_event参数设计成了这样title字符串、start_time字符串、end_time字符串、attendees字符串数组。刚开始觉得挺简单但很快发现模型经常把start_time传成“明天下午三点”这种自然语言因为description里只写了“开始时间”。我们后来把description改成了“开始时间ISO 8601格式例如2025-04-10T15:00:00”同时加了pattern正则约束。但发现有些模型平台不支持pattern校验或者说支持但模型不一定遵守。最后我们干脆在description里加了个few-shot示例“如果用户说‘明天下午三点’则转换为2025-04-10T15:00:00”。效果立竿见影误传率降了一大半。当然你也可以在后续增加一个预处理步骤让模型先调用一个“时间解析工具”把自然语言转换了再把结果传给创建日程工具但这会增加一次模型调用成本和延迟都上去了。权衡下来先把schema写清楚是性价比最高的。还有人说工具定义里的description全拿中文写模型是英文训练的会不会理解不好我的经验是跟随模型语言走。如果你用的是GPT-4级别的模型中英文都能懂但英文的description在少数情况下更精准尤其是那些技术术语。不过大多数国产模型中文描述效果更好。关键是要统一别一个工具里中英混杂模型容易困惑。我自己习惯把所有工具定义都用中文写因为最终跟Agent对话的用户大概率是中文模型的语义理解也更贴近中文语境。但name一定用英文这个没法商量。最后聊一个思维习惯的转变。传统API设计参数Schema是给后端工程师看的用来生成文档和校验数据。而Agent工具定义参数Schema是给模型看的它更像是一个“提示词”的延伸。你写的每一句话模型都在用它的概率分布去猜测你的意图。你写“string”类型模型觉得只要是个字符串就行结果传了“N/A”。你写“整数”它觉得“1.0”也没错因为大部分语言里浮点数和整数可以互相转换。所以你把expectation写得更死模型越不会犯错这不是限制而是给模型减负。我甚至见过有人把description里写成一段完整的“使用说明”包括“如果用户未提供城市必须反问用户严禁猜测默认城市”这已经超越了Schema的范畴变成了一种行为约束但很有效。工具定义就是一个你跟模型之间的“小型的、一次性的协议栈”协议越明确通信越顺畅。现在每次上线新工具我都会花十分钟做一件事把模型可能犯的所有错误列出来然后在schema层面一个个堵死。比如可选字段要不要默认值如果默认值是业务相关的我干脆在description里写清楚“不传则取服务端默认配置”。比如数值单位是什么温度是摄氏度还是华氏度我直接写个unit字段并加上enum。又比如空字符串算不算合法值我会在description里加一句“如果该字段没有值请务必省略该字段不要传空字符串”。这些细节累积起来一个工具从“偶尔出错”变成“基本靠谱”靠的就是这种强迫症级别的schema设计。经验谈到这里如果你正准备设计Agent的工具定义我建议你从明天开始做两件事。第一件把你现在所有工具定义打印出来用一个全新的视角——一个故意捣乱的模型视角——逐个字段攻击它看能不能找出漏洞。第二件在你现有工具的解析层加一个“schema校验日志”记录每次模型返回的原始参数每周花十分钟扫一眼那些失败案例你会发现90%的错误都能通过加强schema描述来避免。工具定义不是写完就完事的它需要跟着真实流量不断迭代。模型在变用户的表达也在变你唯一的武器就是把schema这张网织得越来越密密到模型想犯错都找不到缝。