先把结论摆前面模型调工具传错参数最划算的兜底就一招——把哪儿不合格原样回喂给模型让它自己改一遍再调。别急着报错给用户也别写一堆规则去猜它想干嘛。我线上跑了一个多月一次自纠能救回七成多的失败调用。我是怎么撞上这个坑的做一个查物流的智能体挂了个query_express(order_id, phone_last4)的工具。模型经常把phone_last4传成完整 11 位手机号或者把 order_id 和 phone 调换位置。早期我的写法很暴力——参数不对就raise然后兜一句查询失败请重试。用户那头看到的就是干巴巴一句失败体验差到我自己都不好意思。后来翻日志才发现模型其实差一点点就对了缺的只是一次纠正的机会。核心做法把校验错误当成一轮对话喂回去关键改动就一处。工具执行前先用 schema 校验入参校验不过不抛异常而是构造一段给模型看得懂的错误描述作为 tool 角色的返回内容塞回对话让模型基于错误再生成一次工具调用。from jsonschema import validate, ValidationError express_schema { type: object, properties: { order_id: {type: string, pattern: ^[0-9]{12,18}$}, phone_last4: {type: string, pattern: ^[0-9]{4}$}, }, required: [order_id, phone_last4], } def call_with_self_correction(messages, tool_call, max_retry1): args json.loads(tool_call.arguments) try: validate(args, express_schema) return run_express(args) # 校验过正常跑 except ValidationError as e: if max_retry 0: return {error: 参数始终不合格已转人工} # 把人话错误塞回去让模型重来 messages.append({ role: tool, tool_call_id: tool_call.id, content: f参数校验未通过{e.message}。 fphone_last4 只要手机号后4位4个数字order_id 是12-18位纯数字。请修正后重新调用。 }) new_call llm_next_tool_call(messages) # 让模型再出一次工具调用 return call_with_self_correction(messages, new_call, max_retry - 1)几个我踩出来的细节错误信息要写成人话别直接把 jsonschema 那一长串原文丢回去。模型看does not match ^[0-9]{4}$的修正成功率明显低于看只要后4位、4个数字。我当工具描述在写。重试次数卡死 1 次,顶多 2 次。放开了会出现模型反复传错、来回烧 token 的死循环。我之前没限半夜被一个边界 case 刷掉好几块钱。自纠还失败就转人工或兜底文案别让它无限纠。一个反直觉的发现我一开始以为得把 schema 写得越严越好。结果太严反而触发自纠太频繁整体延迟被拖长——本来一次能成的因为我把一个可选字段标成必填硬生生多走一轮。后来我把校验分两档硬约束格式、必填才触发自纠软偏好比如建议传时间范围只记日志不拦。延迟立马回来了。说点不好的这套不是白来的。一旦触发自纠那次请求的延迟基本翻倍多走一次模型推理。对响应敏感的场景你得在自纠救回来和用户多等一秒之间自己掂量。还有就是它救的是参数小错模型如果是根本理解错了意图纠多少次都没用该转人工还得转。模型这块我图省事直接走的讯飞星辰 MaaS现成 API 调没自己部署自纠这套逻辑全在我业务侧包一层就行模型端不用动。你们处理工具调用参数错误是直接 raise 还是也搞了自纠评论区聊聊各自的重试上限设多少。