登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

OpenAI Responses API 工具调用工具参数校验失败时怎样保留可诊断信息为什么结果为空或不符合预期

来源:17golang原创

时间:2026-11-10 03:00:08 354浏览 收藏

OpenAI Responses API 的工具调用出现“参数校验失败”时,最容易误判的是把空的 output_text 当成接口没有返回。实际上,Responses 可能返回的是 function_call 项而不是文本消息;如果工具 schema 在请求阶段就不符合严格模式要求,则连可处理的工具调用都不会产生。排查时要先判断失败层级,再保留原始响应中的调用标识和参数字符串。

官方地址:https://developers.openai.com/api/docs/guides/function-calling

实用做法是:先把整个响应和每个 function_call 的 call_id、工具名、原始 arguments 写入结构化日志,再解析 JSON 和执行业务校验。校验失败也要返回一个带错误信息的 function_call_output,不要直接丢弃这次调用。

先判断参数错误发生在哪一层

同一句“参数校验失败”可能指三种情况。第一种是请求中的函数 schema 本身不满足 strict 要求,例如对象没有关闭 additionalProperties,或属性没有全部列入 required;这类问题通常在请求阶段被拒绝。第二种是请求成功,模型返回了 function_call,但应用把 arguments 解析成业务对象时发现类型、范围或必填条件不满足。第三种是参数合法,但工具执行失败,例如库存服务超时。

三层错误的记录字段不能混在一起。请求阶段保存 HTTP 错误和 schema 摘要;模型返回阶段保存 response.id、call_id、name 与原始参数;执行阶段保存工具名、业务错误码和是否已经产生副作用。这样才能回答“模型没给参数”“参数不符合业务规则”还是“工具已经执行但依赖失败”。

严格模式下先把 schema 写完整

官方函数调用指南建议启用 strict。严格模式要求对象的 additionalProperties 为 false,并且 properties 中的字段都出现在 required。如果某个字段在业务上可选,可以把它的类型写成包含 null,但仍然保留在 required 数组里。下面的工具只表达“查询订单状态”这一件事,避免把多个业务分支塞进一个模糊 schema。

from openai import OpenAI

client = OpenAI()

tools = [{
    "type": "function",
    "name": "lookup_order",
    "description": "查询订单当前状态,不执行修改操作",
    "strict": True,
    "parameters": {
        "type": "object",
        "properties": {
            "order_id": {
                "type": "string",
                "description": "业务订单号"
            },
            "channel": {
                "type": ["string", "null"],
                "enum": ["web", "app", None],
                "description": "订单来源;未知时传 null"
            }
        },
        "required": ["order_id", "channel"],
        "additionalProperties": False
    }
}]

# 保留请求与工具定义的版本,便于把 schema 错误和业务错误分开。
response = client.responses.create(
    model="gpt-6-astra",
    input="查询订单 A-100 的状态",
    tools=tools,
)

这里的 channel 虽然可以是空值,但它仍是必填属性。不要为了“可选”而从 required 删除它,否则严格模式下模型和应用的契约会变得不一致。模型名称、账号权限等部署参数应按你的当前项目配置填写,示例重点是工具 schema 的结构。

不要只读 output_text,先保存 function_call

Responses 的输出数组可能同时包含推理、消息和函数调用项。工具调用项的 arguments 是 JSON 编码的字符串,不能把它当成已经校验过的 Python 字典。应用侧应先保存原文,再解析;解析失败时仍然要留下原始字符串,方便定位模型输出、序列化边界或 SDK 适配问题。

import json
import logging

logger = logging.getLogger(__name__)

def collect_tool_calls(response):
    calls = []
    for item in response.output:
        # 只有 function_call 才进入工具参数处理,文本项不能代替它。
        if item.type != "function_call":
            continue

        record = {
            "response_id": response.id,
            "call_id": item.call_id,
            "name": item.name,
            "arguments_raw": item.arguments,
        }
        try:
            # 先解析 JSON,再交给业务层做类型和权限校验。
            record["arguments"] = json.loads(item.arguments)
            record["parse_error"] = None
        except json.JSONDecodeError as exc:
            # 不覆盖原文,错误位置对复盘不应丢失。
            record["arguments"] = None
            record["parse_error"] = {
                "type": type(exc).__name__,
                "message": str(exc),
            }
        logger.info("responses_tool_call=%s", record)
        calls.append(record)
    return calls

calls = collect_tool_calls(response)
if not calls:
    # output_text 为空时,先记录输出类型,不要直接下结论说“没有结果”。
    logger.warning("no_function_call response_id=%s output_types=%s",
                   response.id, [item.type for item in response.output])
Responses API 工具调用中请求 schema、function_call 原始参数和应用校验三个边界的静态关系说明图
图1:结构说明图,查看请求契约、模型调用项和应用校验三个边界;它是原创静态说明图,不是运行截图。

如果这段日志显示输出类型是 function_call,而不是 message,那么 output_text 为空是可以解释的。真正要处理的是调用项中的 call_id 和参数,而不是反复读取文本字段。

校验失败时把诊断信息送回工具循环

解析成功不等于业务参数可用。以订单查询为例,order_id 为空、渠道值不在允许集合或当前用户无权查询,都应该生成可追踪的结构化错误。错误返回给模型时使用同一个 call_id,同时把日志中的敏感字段做脱敏;不要把数据库连接串、访问令牌或完整个人信息塞进工具输出。

def validate_order_args(args):
    # 业务校验和 JSON Schema 校验分层,便于定位真正的失败原因。
    if not isinstance(args, dict):
        return "arguments 必须是对象"
    if not isinstance(args.get("order_id"), str) or not args["order_id"].strip():
        return "order_id 不能为空"
    if args.get("channel") not in {"web", "app", None}:
        return "channel 只能是 web、app 或 null"
    return None

input_items = list(response.output)
for call in collect_tool_calls(response):
    error = call["parse_error"] or validate_order_args(call["arguments"])
    if error:
        # 用原 call_id 绑定失败结果,避免模型继续等待一个不存在的工具结果。
        input_items.append({
            "type": "function_call_output",
            "call_id": call["call_id"],
            "output": json.dumps({
                "ok": False,
                "error_code": "INVALID_TOOL_ARGUMENTS",
                "message": error,
            }, ensure_ascii=False),
        })
        continue

    # 通过校验后才调用有副作用的服务,并把结果序列化为字符串。
    result = lookup_order(call["arguments"]["order_id"], call["arguments"]["channel"])
    input_items.append({
        "type": "function_call_output",
        "call_id": call["call_id"],
        "output": json.dumps(result, ensure_ascii=False),
    })

follow_up = client.responses.create(
    model="gpt-6-astra",
    input=input_items,
    tools=tools,
)

function_call_output 的 output 通常使用字符串,内容可以是 JSON、错误码或普通文本。将错误写成稳定的 error_code 和简短 message,比把 Python 异常堆栈原样暴露给模型更容易控制。若工具已经产生副作用,则不要因为后续模型回复异常而盲目重试,应依靠 call_id 或业务幂等键判断是否处理过。

工具参数校验失败后通过 call_id 返回结构化错误并继续 Responses API 工具循环的静态关系说明图
图2:关系说明图,查看原始参数、结构化错误和后续 Responses 输入之间的静态关系;它不是实际运行结果。

为什么结果为空:一份最小复查清单

遇到“结果为空或不符合预期”时,按以下顺序复查,通常比先改提示词更快:

  • 先看 HTTP 请求是否成功,以及工具 schema 是否因 strict 约束被拒绝;若请求失败,检查 required 和 additionalProperties。
  • 再记录 response.id、每个输出项的 type、call_id、name 和 arguments_raw;不要只记录 output_text。
  • 解析参数后分别记录 JSON 解析错误和业务校验错误;两者修复方向不同。
  • 把失败结果绑定回原 call_id,再检查下一次 Responses 请求是否携带了该结果。
  • 如果工具有写操作,记录幂等键、执行状态和外部依赖错误,避免把“模型又调用了一次”当成安全重试。

这套方式的边界也很明确:它只能改善诊断和工具循环的可恢复性,不能替代权限检查、业务幂等、敏感信息脱敏或服务端超时治理。先让 schema 契约稳定,再让原始调用可见,最后把业务错误变成模型能够理解的工具结果,空的文本字段就不再是排查的唯一线索。

相关问题

为什么 strict 模式下请求直接失败?通常是对象 schema 没有设置 additionalProperties: false,或属性没有全部列入 required;先修复契约,再看模型输出。

为什么 function_call 有参数但 output_text 为空?因为响应当前项是工具调用而非文本消息。读取输出项的类型,并用 call_id 提交工具结果。

工具返回错误后还要不要重试?只有在错误可恢复、没有重复副作用且幂等条件明确时才重试;否则保留错误结果并交给上层业务处理。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>