Tool calling校验工具参数并区分模型与业务错误的实现方法
来源:17golang原创
时间:2026-09-15 20:23:53 380浏览 收藏
我在第一次把工单查询接入 tool calling 时,最容易混淆的是“模型给错参数”和“工具执行失败”。前者属于调用契约问题,应该拒绝或让模型修正;后者属于业务结果,应该把可解释的失败回传给模型,而不是让模型继续猜参数。比较稳妥的实现是:工具声明负责约束形状,应用服务负责二次校验和白名单路由,业务函数只返回稳定的成功或失败结果。
官方文档:https://developers.openai.com/api/docs/guides/function-calling
strict: true能收紧函数参数契约,但不替代库存、权限、状态等业务校验。- 模型侧错误包括未知工具、JSON 解码失败和 schema 不匹配;业务侧错误包括订单不存在、无权访问和状态不允许。
- 回传
function_call_output时保留call_id,让日志、重试和最终回答能对应同一次调用。
先把工具调用拆成两条责任边界
OpenAI 的 function calling 本质上是一个多轮交互:应用带着工具定义请求模型,模型返回工具调用,应用执行函数,再把工具结果发回模型。对我来说,最重要的设计决定不是“要不要加重试”,而是先给这条数据流划线:模型产生的调用对象到达业务函数之前,全部按模型错误处理;业务函数已经拿到合法参数后发生的失败,才算业务错误。

工具声明也要尽量具体。官方文档把函数输入定义为 JSON Schema,并建议开启严格模式;严格模式下,对象需要设置 additionalProperties: false,属性通常都应列入 required,可选值可以用可空类型表达。这些规则解决的是“字段长什么样”,不是“这个订单此刻能不能查”。
用 schema 拦截形状错误,再做一次应用层判断
下面这个示例把两层错误显式分开。示例使用 Python 标准库解析 JSON;生产项目可以把同一份契约交给 JSON Schema 校验器,但不要因为模型开启了 strict 就跳过应用侧校验。模型输出仍需要经过工具名白名单、解码、字段范围和权限上下文检查。
import json
def classify_tool_call(tool_call):
# 先限制工具名,防止模型请求未注册的业务能力。
if tool_call.get("name") != "lookup_order":
return {"kind": "model_error", "code": "unknown_tool"}
try:
args = json.loads(tool_call.get("arguments", "{}"))
except json.JSONDecodeError:
# JSON 无法解析时,业务函数还没有被调用。
return {"kind": "model_error", "code": "invalid_arguments_json"}
if not isinstance(args, dict) or not isinstance(args.get("order_id"), str):
# 这是参数形状错误,不应进入订单查询重试。
return {"kind": "model_error", "code": "schema_mismatch"}
if not args["order_id"].startswith("ORD-"):
# 形状合法但业务字段格式不接受,仍在边界层拦截。
return {"kind": "business_error", "code": "invalid_order_id"}
return {"kind": "validated", "args": args}
这里的 invalid_order_id 也可以归入领域校验,关键是团队要固定分类并写进日志。不要有时把它当模型错误,有时又当订单服务错误,否则重试器很难判断是否应该再次请求模型。
| 现象 | 归属 | 处理动作 |
|---|---|---|
| 工具名不在注册表 | 模型错误 | 拒绝调用并记录原始 call_id |
| arguments 不是合法 JSON | 模型错误 | 让模型修正调用,避免触发业务重试 |
| 订单不存在或无权限 | 业务错误 | 返回稳定错误码,由模型组织用户可读答复 |
| 订单已关闭,不能执行动作 | 业务错误 | 回传状态和下一步建议,不伪装成成功 |
业务函数只接收已验证参数,结果也要可回传
验证通过后再走白名单路由。业务函数不要把 Python 异常、数据库堆栈或内部字段直接塞进模型上下文;可观测信息写日志,给模型的结果保持短小、结构稳定。成功返回订单摘要,失败返回 ok=false、错误码和可行动的提示即可。
def execute_lookup(validated, current_user):
# 通过验证后才读取业务数据,权限仍以当前用户为准。
order_id = validated["args"]["order_id"]
order = find_order(order_id)
if order is None:
return {"ok": False, "error_code": "ORDER_NOT_FOUND",
"message": "订单不存在,请核对订单号"}
if order["owner_id"] != current_user["id"]:
# 不泄露订单是否存在,权限失败统一返回可解释结果。
return {"ok": False, "error_code": "ORDER_FORBIDDEN",
"message": "当前账号无权查看该订单"}
return {"ok": True, "order_id": order_id,
"status": order["status"]}
def to_function_output(call_id, result):
# call_id 必须原样保留,便于把结果对应回这次工具调用。
return {"type": "function_call_output", "call_id": call_id,
"output": json.dumps(result, ensure_ascii=False)}

官方文档允许把工具结果作为字符串传回,字符串内部可以是 JSON、错误码或普通文本。我的取舍是统一使用 JSON,因为监控可以按 ok 和 error_code 聚合,模型也能稳定理解;但内部异常细节不放进去,避免把实现信息暴露给下一轮上下文。
重试只对症下药,别把业务失败变成参数修复
模型错误可以有限次重试,例如只针对 JSON 解码或 schema 不匹配重新请求;未知工具应直接报警,因为它更像注册表漂移。业务错误通常不该自动重试:订单不存在不是换个参数就一定能解决,无权限也不能靠模型多调用几次绕过。若同时开启并行工具调用,日志还要按每个 call_id 独立记录结果,不能把一条失败污染同轮其他合法调用。
我会把以下字段放进结构化日志:调用名、call_id、schema 校验结果、业务错误码、用户上下文摘要、耗时和最终是否重试。这样排查时先看错误层级,再决定修提示词、修工具契约还是修业务服务,不会从一条模糊的“tool failed”开始猜。
常见问题
开启 strict 后还需要应用层校验吗?
需要。strict 主要约束模型生成的参数形状,订单存在性、权限、状态和数据一致性仍必须由应用服务判断。
模型错误要不要把原始异常回传?
不建议。给模型稳定的错误码和修正方向即可;原始 JSON、堆栈和内部字段留在受控日志中。
业务错误应该让模型重新调用工具吗?
只有错误信息明确提示可修正输入时才考虑一次重试,例如订单号格式不对;不存在、无权限和状态冲突通常直接生成解释性答复。
-
278 收藏
-
483 收藏
-
291 收藏
-
195 收藏
-
111 收藏
-
191 收藏
-
科技周边 · 人工智能 | 4小时前 | 人工智能 · 结构化输出 · JSON解析 · 模型输出 · 接口容错 · 大模型结构化输出 模型输出JSON缺字段 JSON兜底解析 JSON Schema校验 AI接口异常处理272 收藏
-
251 收藏
-
357 收藏
-
473 收藏
-
381 收藏
-
195 收藏
-
162 收藏
-
246 收藏
-
145 收藏
-
科技周边 · 人工智能 | 16小时前 | 人工智能 · rag · 向量检索 · 检索增强生成 · rerank · 向量数据库 metadata filter 向量检索过滤条件 rerank顺序 RAG检索409 收藏
-
科技周边 · 人工智能 | 17小时前 | 上下文管理 · 向量检索 · AI工程 · RAG实践 · 文档切片 · chunk overlap RAG文档切片 RAG重叠窗口 上下文膨胀 向量检索召回238 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习