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

OpenAI Responses API 怎样让模型调用自定义函数

来源:17golang原创

时间:2026-10-27 02:48:15 223浏览 收藏

我们拿开发时经常用到的「查询项目发布状态」场景做演示,你可以直接把自定义的函数作为工具声明传给OpenAI Responses API,就能让模型自动判断什么时候需要调用工具、传什么参数,不需要自己手动处理多轮上下文的来回拼接,上手门槛很低。

直接把自定义函数的名称、参数结构、功能说明按规范格式放到tools请求字段里,调用API后模型会自行判断是否触发函数,拿到返回结果后再整理成自然语言回复用户。

让 OpenAI Responses API 调用自定义函数,关键不是把函数代码发给模型,而是把“函数契约”放进 tools,再由你的应用接住模型返回的 function_call、执行本地函数,最后提交对应的 function_call_output。这是一条明确的往返链路:模型决定调用什么,你的服务决定是否真的执行。

官方文档:https://developers.openai.com/api/docs/guides/tools

最小闭环可以概括为:定义一个 type: "function" 工具,读取 response.output 中的 function_call,用它的 call_id 回传执行结果,再请求一次 Responses API。模型不会直接访问你的数据库或业务函数。

先写一个最小可用的函数工具

下面用 get_project_status 作为示例:模型只负责根据用户问题填出项目名,真正的状态查询由应用里的 getProjectStatus 完成。Responses API 的函数工具可以直接写成 type、name、description、parameters 和 strict 这些字段。

const tools = [
  {
    type: "function",
    name: "get_project_status",
    description: "查询指定项目当前的发布状态。",
    parameters: {
      type: "object",
      properties: {
        project: {
          type: "string",
          description: "项目标识,例如 billing-api"
        }
      },
      required: ["project"],
      additionalProperties: false
    },
    // strict 让模型生成的参数遵守上面的 JSON Schema。
    strict: true
  }
];

这里的 description 要写成模型能理解的业务边界,参数名也要保持稳定。开启严格模式时,对象需要显式设置 additionalProperties: false,并把属性列入 required;如果某个业务字段可以缺省,可以把它声明为允许 null,而不是遗漏必填声明。

自定义函数工具从名称、描述和 JSON Schema 形成可调用契约的静态说明图
图1:工具契约说明图,展示 function 工具的名称、参数 Schema、strict 约束与模型可调用入口;这是原创静态说明图,不是运行截图。

把工具交给 Responses API

工具定义完成后,在第一次请求里把它放到 tools。默认情况下,模型会自己判断是否需要调用工具;如果一次业务只允许一个本地动作,可以同时设置 parallel_tool_calls: false,让这一轮最多出现一个函数调用。

import OpenAI from "openai";

const client = new OpenAI();

const response = await client.responses.create({
  model: "gpt-6-astra",
  input: "请告诉我 billing-api 当前是否可以发布。",
  tools,
  // 关闭并行调用,便于这个单动作示例按一个函数处理。
  parallel_tool_calls: false
});

console.log(response.output);

如果希望强制模型调用某一个工具,可以进一步使用 tool_choice;但一般的业务问答先使用默认的自动选择更自然。工具数量较多时,也要让每个函数的描述互相区分,避免模型面对多个相似入口时难以选择。

识别 function_call 并执行本地函数

响应的 output 不是一段固定文本数组,其中可能出现类型为 function_call 的项目。这个项目包含函数名、JSON 编码的 arguments 和后续回传必须使用的 call_id。应用先解析参数,再按函数名路由到白名单实现。

function getProjectStatus(project) {
  // 真实项目中可在这里访问受控的数据库或内部服务。
  const known = {
    "billing-api": { state: "ready", blockers: 0 },
    "search-api": { state: "blocked", blockers: 2 }
  };
  return known[project] ?? { state: "unknown", blockers: null };
}

function callFunction(name, args) {
  // 只允许显式列出的函数名,避免把任意输入当作可执行入口。
  if (name === "get_project_status") {
    return getProjectStatus(args.project);
  }
  throw new Error(`未知函数:${name}`);
}

const functionCalls = response.output.filter(
  (item) => item.type === "function_call"
);

for (const toolCall of functionCalls) {
  const args = JSON.parse(toolCall.arguments);
  const result = callFunction(toolCall.name, args);
  console.log(toolCall.call_id, result);
}

不要用 response.output_text 代替这一步,因为模型此时可能还没有最终回答。也不要根据函数名拼接任意方法调用;把可执行函数放进明确的路由表,未知名称和参数解析失败都应该进入应用自己的错误处理。

Responses API 返回 function_call、应用执行函数并按 call_id 回传结果的闭环关系图
图2:调用闭环说明图,展示模型请求、function_call、应用执行和 function_call_output 的往返关系;这是原创静态说明图,不是运行截图。

用原始 call_id 回传 function_call_output

执行结果需要作为新的输入项回传给下一次 Responses 请求。先把上一轮的 response.output 转成可继续使用的输入项,再追加一个与当前调用对应的 function_call_output。其中 call_id 必须来自原始函数调用,不能自己生成一个看似相近的编号。

import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";

const input = [
  { role: "user", content: "请告诉我 billing-api 当前是否可以发布。" }
];

// 保留模型这轮产生的完整 output,供下一次请求理解调用上下文。
input.push(...toResponseInputItems(response.output));

for (const toolCall of functionCalls) {
  let output;
  try {
    const args = JSON.parse(toolCall.arguments);
    output = JSON.stringify(callFunction(toolCall.name, args));
  } catch (error) {
    // 把可读错误作为工具结果交回模型,而不是让调用链静默中断。
    output = JSON.stringify({
      state: "error",
      message: error instanceof Error ? error.message : "函数执行失败"
    });
  }

  input.push({
    type: "function_call_output",
    call_id: toolCall.call_id,
    output
  });
}

const finalResponse = await client.responses.create({
  model: "gpt-6-astra",
  input,
  tools,
  parallel_tool_calls: false
});

console.log(finalResponse.output_text);

这次请求得到的 output_text 才是面向用户的最终回答。函数返回值最好是结构稳定、字段含义明确的 JSON 字符串;如果业务失败,也可以把失败状态作为工具结果交回模型,让它据此向用户解释,而不是把内部堆栈直接写进输出。

三个容易让调用链失效的边界

现象优先检查处理方向
模型没有调用函数用户任务是否真的需要该工具,工具描述是否清晰先保持默认自动选择;需要强制动作时再配置 tool_choice
请求因 Schema 被拒绝strict、required 和 additionalProperties 是否匹配补齐对象字段约束,可选值用允许 null 表达
回传后没有最终文本call_id 是否原样对应,是否把函数结果追加为新输入保留完整 output item,再提交 function_call_output
一轮出现多个调用是否允许并行函数调用单动作流程设置 parallel_tool_calls: false,批量任务再设计并行处理

最后要把“模型选择函数”和“应用授权执行”分开。模型输出的是建议调用及其参数,真正的权限校验、数据访问、超时、幂等和审计仍然属于你的应用。函数调用做得越接近真实业务,越应该在本地路由层再次检查参数,而不是只依赖模型给出的 JSON。

常见问题

模型会直接运行我写的 JavaScript 函数吗?

不会。模型只能输出符合工具契约的 function_call;你的应用要自行解析参数、调用本地函数,并把结果作为 function_call_output 发回。

为什么一定要保存 call_id?

它把函数结果和当前响应里的具体调用关联起来。尤其在允许并行调用时,不能只按函数名匹配结果,必须为每一个调用使用对应的 call_id。

strict=true 是否代表业务参数一定安全?

不是。严格模式主要约束输出参数符合 Schema;权限、资源范围、字段值合法性和业务副作用仍要由你的服务校验。

这条实现路径可以压缩成一句话:用 tools 声明契约,读取 function_call,由应用白名单路由并执行函数,再用原始 call_id 发送 function_call_output,最后读取新的 output_text。如果把并行策略、Schema 约束和业务授权都放在应用边界内,Responses API 的自定义函数调用就会变成一条可控的工程流程。

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