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,而不是遗漏必填声明。

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

用原始 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 的自定义函数调用就会变成一条可控的工程流程。
-
284 收藏
-
387 收藏
-
328 收藏
-
426 收藏
-
147 收藏
-
234 收藏
-
292 收藏
-
378 收藏
-
455 收藏
-
科技周边 · 人工智能 | 5小时前 | python · 人工智能 · snapshot_download Hugging Face Hub HF_HUB_CACHE HF_HOME 模型缓存 huggingface_hub247 收藏
-
113 收藏
-
174 收藏
-
327 收藏
-
131 收藏
-
268 收藏
-
349 收藏
-
297 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习