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

OpenAI 工具调用返回多个 tool call 时如何逐个回传结果

来源:17golang原创

时间:2026-09-14 13:42:30 146浏览 收藏

遇到多个工具调用时,正确做法不是取 `response.output[0]`,而是保留整组输出,筛出每个 `function_call`,解析它自己的参数,执行完成后用同一个 `call_id` 生成一个 `function_call_output`。即使工具实际并行执行,回传仍然是一项一项绑定;少回一项或串了 ID,模型就拿不到完整上下文。

官方文档:https://developers.openai.com/api/docs/guides/function-calling

要点速览
  • Responses API 的多个调用都在 `response.output` 中,不能只读第一项。
  • `call_id` 是回传结果的唯一关联键,不能用数组下标代替。
  • 先用顺序循环理清映射;确认工具无副作用后,再考虑并发执行。

先识别 response.output 中的 function_call

我排查这类问题时,第一步总是把响应当成“调用清单”,而不是一条普通文本。Responses API 的输出项里,类型为 `function_call` 的对象至少要看三个字段:`call_id` 用于关联结果,`name` 用于选择本地函数,`arguments` 是 JSON 字符串,必须解析后再传给函数。

字段用途常见误区
call_id标记这一条具体调用用数组下标或工具名替代
name路由到本地工具把展示名称当成可执行代码
arguments携带本次调用参数忘记 JSON 解析,或复用上一项参数
OpenAI Responses API response.output 中多个 function_call 及 call_id、name、arguments 的结构示意图
图1:多个 function_call 的字段映射示意图;每个调用都保留独立 call_id。

保留完整响应并逐个执行工具

处理循环要先把原始输出放进下一轮输入,再为每个调用追加结果。下面的示例故意采用顺序执行,便于看清每个 `call_id` 的去向;生产代码可以把 `dispatch_tool` 换成真实服务适配器。

import json
from openai import OpenAI

client = OpenAI()

def dispatch_tool(name, arguments):
    # 只允许白名单工具,避免把模型给出的名称直接当作函数名执行。
    if name == "get_weather":
        return {"city": arguments["city"], "temperature": 23}
    if name == "send_email":
        return {"to": arguments["to"], "status": "queued"}
    raise ValueError(f"unknown tool: {name}")

def make_tool_outputs(response):
    # 保留整组输出,后续请求需要带回模型刚刚发出的调用上下文。
    next_input = list(response.output)
    for item in response.output:
        # 文本、推理等输出项不属于本轮需要执行的工具。
        if item.type != "function_call":
            continue
        try:
            arguments = json.loads(item.arguments)
            result = dispatch_tool(item.name, arguments)
            payload = {"ok": True, "data": result}
        except (ValueError, KeyError, json.JSONDecodeError) as exc:
            # 失败也要回传,让模型知道这一项失败,不要悄悄丢弃。
            payload = {"ok": False, "error": str(exc)}
        next_input.append({
            "type": "function_call_output",
            "call_id": item.call_id,
            "output": json.dumps(payload, ensure_ascii=False),
        })
    return next_input

这里最关键的不是 `try` 的写法,而是 `next_input` 的构造顺序:先保留 `response.output`,再为每个调用增加一条结果。这样模型下一轮既能看到自己发出的调用,也能看到每一项工具反馈。

按 call_id 逐条构造 function_call_output

完成工具执行后,结果对象的外壳固定为 `function_call_output`,关联键必须来自当前项的 `item.call_id`。不要把多个结果拼成一个字符串,也不要只回传成功项;某一项失败时,返回结构化错误通常比省略它更容易让模型决定重试、改参数或向用户解释。

response = client.responses.create(
    model="gpt-5",
    tools=tools,
    input="查询北京和上海天气,并准备一封通知邮件",
)

# 每个 function_call 都得到一条独立的 function_call_output。
tool_input = make_tool_outputs(response)
follow_up = client.responses.create(
    model="gpt-5",
    tools=tools,
    input=tool_input,
)
print(follow_up.output_text)

可以把回传关系记成一张简单的表:`item.call_id` → 工具执行结果 → `function_call_output.call_id`。只有左右两边相同,模型才知道“这个结果属于哪一次调用”。

OpenAI 多工具调用通过 tool router 按 call_id 生成 function_call_output 并进入下一轮 input 的结构示意图
图2:工具结果回传队列示意图;成功和失败都通过对应 call_id 返回。

处理并行调用、重复调用和边界错误

支持并行函数调用的模型可能在一轮里给出多个调用;这不代表应用必须并发执行。顺序执行更适合发邮件、写订单、扣库存等有副作用的工具。天气查询、只读检索等独立任务可以并发,但完成后仍要把每个结果映射回原来的 `call_id`,不要按完成先后重排成匿名数组。

  • 想强制一轮最多一个调用,可在请求中设置 parallel_tool_calls: false
  • 同一个工具出现两次时,两个 call_id 仍然是两次独立调用,不能用工具名做字典唯一键。
  • 参数 JSON 解析失败、工具不存在、下游超时,都应生成可识别的错误结果并保留关联键。
  • 继续请求前统计已处理的调用数;如果应用在相同响应上反复重试,要检查幂等键和响应状态保存。

相关问题

多个工具结果必须按返回顺序提交吗?

重点是每条结果都带正确的 `call_id`,而不是依赖完成顺序。为便于日志和重放,实际项目仍建议保存原始输出顺序。

一个工具调用失败,其他成功结果还要回传吗?

要。成功项和失败项都应各自生成回传项,让模型拥有完整状态,再由它决定是否补偿或重试。

为什么结果已经执行了,模型却像没收到?

优先检查是否把原始 `response.output` 带入下一轮,以及 `function_call_output.call_id` 是否严格等于对应调用的 `call_id`。

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