在 Responses API 中加入 {"type":"programmatic_tool_calling"},再给允许程序调用的工具设置 allowed_callers:["programmatic"] 或 ["direct","programmatic"]。它适合有明确边界、能用代码过滤、连接、去重、聚合或验证多个工具结果的阶段;单次查询、逐步语义判断、审批敏感写操作和必须保留原生引用的任务,优先直接工具调用。
Programmatic Tool Calling 解决什么问题?
普通工具循环常把每次工具结果交给模型再决定下一步。结果多、字段大或控制流固定时,中间内容会反复进入上下文。Programmatic Tool Calling(PTC)让模型生成 JavaScript,把中间结果保留在运行时变量中,并在代码内完成并行调用、循环、条件分支与结构化压缩。
| 任务形态 | 推荐方式 | 原因 |
|---|---|---|
| 一个查询或一个动作 | 直接 Function Calling | 没有必要增加程序层 |
| 多个结果要筛选、连接、排序、去重或聚合 | Programmatic Tool Calling | 代码可只返回较小结构 |
| 后续参数由前一步确定,流程与失败边界明确 | Programmatic Tool Calling | 代码可稳定派生参数 |
| 每个结果都会改变下一次搜索策略 | 直接调用 | 需要模型逐步判断 |
| 付款、删除、发消息等高影响写操作 | 直接调用 + 明确审批 | 保留授权边界与审计链 |
| 最终回答必须保留引用或原生文件 | 直接调用 | 除非程序能完整保留并验证证据 |
运行时边界:它不是 Node.js 容器
OpenAI 会为每段生成程序启动新的隔离 V8 运行时。它支持 JavaScript 和顶层 await,但官方明确说明没有 Node.js、包安装、直接网络访问、通用文件系统、子进程、console 或跨程序持久 JavaScript 状态。
- 外部系统只能通过请求中已启用的
tools.*访问。 - 程序用
text(...)或image(...)发出最终结果。 - 客户端函数仍在你的应用服务器执行,V8 只负责编排。
- 每次程序都要把必要状态从工具结果或当前响应项目中重新获得。
Responses API 最小配置
下面把库存和需求查询仅开放给 program。parameters 约束输入,output_schema 描述函数结果 JSON 的稳定字段;使用前仍需在官方模型页确认所选模型支持 PTC。
import OpenAI from "openai";
const client = new OpenAI();
const tools = [
{
type: "function",
name: "get_inventory",
description: "Return stock for one SKU.",
parameters: {
type: "object",
properties: { sku: { type: "string" } },
required: ["sku"],
additionalProperties: false
},
output_schema: {
type: "object",
properties: {
sku: { type: "string" },
available_units: { type: "number" }
},
required: ["sku", "available_units"],
additionalProperties: false
},
allowed_callers: ["programmatic"],
strict: true
},
{
type: "function",
name: "get_demand",
description: "Return requested units for one SKU.",
parameters: {
type: "object",
properties: { sku: { type: "string" } },
required: ["sku"],
additionalProperties: false
},
output_schema: {
type: "object",
properties: {
sku: { type: "string" },
requested_units: { type: "number" }
},
required: ["sku", "requested_units"],
additionalProperties: false
},
allowed_callers: ["programmatic"],
strict: true
},
{ type: "programmatic_tool_calling" }
];
const response = await client.responses.create({
model: "gpt-6-astra",
store: false,
tools,
input: "比较 sku_123 的库存与需求,只返回缺口摘要。"
});allowed_callers 三种设置
| 配置 | 调用方式 | 适用场景 |
|---|---|---|
省略或 ["direct"] | 仅模型直接调用 | 需要逐步判断、审批或查看原始结果 |
["programmatic"] | 仅 program 中调用 | 返回结构稳定、只服务于有界处理阶段 |
["direct","programmatic"] | 两种方式都允许 | 同一工具确实承担两个已明确的工作流阶段 |
不要只写“高效使用 PTC”。官方建议把路由写具体:哪个有界阶段使用程序、可用哪些工具、何时并行、程序最终必须输出什么结构、停止条件、重试次数,以及哪些判断或审批必须回到直接调用。
output_schema 为什么重要?
程序会直接读取工具返回字段;如果结果只是自然语言,模型生成的 JavaScript 可能猜错字段或解析失败。为可预测结果定义 output_schema,并把实际对象编码为 function_call_output.output 字符串。若返回形态不可预期,就不要强行程序化,改用直接调用让模型查看结果。
- 返回紧凑结构,不要把大段无关日志塞给程序。
- 记录明确的错误字段、可重试类型和终止条件。
- 需要证据时,把证据字段写入程序最终结果 Schema。
- 函数尽量幂等,避免重放或重试重复产生副作用。
识别 program、function_call 与 program_output
PTC 不会创建新的响应信封,仍返回标准 Responses 对象。response.output 可能包含以下顶层项目:
| 项目 | 关键字段 | 应用动作 |
|---|---|---|
program | 生成代码、call_id、fingerprint | 保存完整响应项目,等待嵌套调用或结果 |
function_call | 自己的 call_id;caller.caller_id 指向 program | 应用执行客户端函数 |
program_output | program 的 call_id、result、completed/incomplete | 校验结果结构与状态 |
message | 最终文本或 refusal | 到达时才结束循环 |
客户端函数回传循环
你的应用仍要运行每个客户端函数,并把结果送回正确的 program。回传时保留函数的 call_id,还要原样复制 caller;否则服务无法恢复对应程序。
const calls = response.output.filter(
(item) => item.type === "function_call"
);
const outputs = await Promise.all(calls.map(async (call) => ({
type: "function_call_output",
call_id: call.call_id,
caller: call.caller,
output: JSON.stringify(
await implementations[call.name](JSON.parse(call.arguments))
)
})));
// store:false:继续请求时按原顺序重放完整 response.output,
// 再追加 outputs;不能只保留 function_call。循环要处理暂停多次、incomplete 状态和无待处理函数但尚未出现最终 message 的情况。存储响应可使用 previous_response_id;store:false 时要完整重放 program、reasoning、function_call、function_call_output 与 program_output。最终只在收到 message 后读取 response.output_text 或 refusal。
程序适合怎样编排工具?
const [stock, demand] = await Promise.all([
tools.get_inventory({ sku: "sku_123" }),
tools.get_demand({ sku: "sku_123" })
]);
text(JSON.stringify({
sku: stock.sku,
available_units: stock.available_units,
requested_units: demand.requested_units,
shortage_units: Math.max(
demand.requested_units - stock.available_units,
0
)
}));这类流程适合 PTC,因为两个只读调用可安全并行,字段稳定,最后只需返回四个数字。若下一步要根据语义质量决定搜索方向,或要修改库存,则应退出程序阶段,由模型直接判断并由应用审批写操作。
与 Tool Search 怎样配合?
Tool Search 先解决“大目录里要加载哪些工具”,PTC 再解决“已加载工具如何用代码编排”。Tool Search 是顶层工具,正在运行的 JavaScript 不能直接搜索工具。
- 函数、custom 或 MCP 先设置
defer_loading:true。 - 模型在顶层执行 Tool Search 并加载需要的定义。
- 工具还需允许
allowed_callers包含programmatic。 - 后续 program 才能通过
tools.*调用它们。
支持的工具与审批边界
官方当前列出的可编程调用类型包括 function、custom、MCP、Apply Patch、本地和托管 Shell、Code Interpreter。MCP 的 require_approval 可以暂停程序等待审批。工具进入 program 不会改变它真正运行的位置:Shell 仍在沙箱,MCP 仍连接相应服务器,函数仍由你的应用执行。
应用必须逐个校验参数、租户、资源范围和调用权限。付款、删除、写库、外发消息、代码发布等高影响动作保留应用级审批;即使工具允许 programmatic,也不能把模型生成代码当作授权。
ZDR 与数据保留要单独核对
Responses API 的 PTC 可以支持 Zero Data Retention 工作流,而且不要求持久代码执行容器,但 ZDR 必须先为组织或项目启用。store:false 只代表无状态续接,并不会自动启用 ZDR;最终资格取决于完整请求中的模型、工具和第三方服务。
如何评测成本与质量
PTC 可能减少进入模型上下文的中间工具输出,但不能只看 Token。先以直接调用建立基线,再用相同真实任务比较:
- 最终答案正确性、完整性和证据覆盖。
- 输入 Token、总 Token、端到端延迟和总成本。
- 模型轮次、工具调用数、重试与恢复次数。
- 副作用、审批、越权和错误降级结果。
- 实际走的调用路径是否符合设计阶段。
常见错误与排查顺序
- 没有 program:确认模型支持,并在 tools 中加入
programmatic_tool_calling。 - 工具不可用:检查
allowed_callers是否包含 programmatic;延迟工具是否已经先由 Tool Search 加载。 - 字段为 undefined:检查
output_schema和实际 output JSON 是否一致。 - 程序无法恢复:回传时是否保留原
call_id与caller。 - 无限续接:明确停止条件、最大轮次、重试和 incomplete 处理。
- 结果缺证据:把必需证据字段写进程序最终结果,不要只返回结论。
- 出现越权写入:缩小 allowed_callers,把高风险动作改为直接调用和审批。
上线前检查清单
- 在官方模型页确认生产模型支持 Programmatic Tool Calling。
- 逐个标注工具是 direct、programmatic 还是两者,默认最小授权。
- 为稳定工具结果定义 output_schema、错误形态和幂等行为。
- 循环完整处理 program、caller、function_call、program_output、message 和 incomplete。
- 限制程序可用工具、最大轮次、重试、超时和结果大小。
- 用直接调用基线比较质量、证据、Token、延迟、成本与安全。
- 对写操作、审批、引用与原生文件保留直接验证路径。
常见问题
OpenAI Programmatic Tool Calling 和普通 Function Calling 有什么区别?
普通 Function Calling 通常由模型逐次选择工具,应用执行后再把结果交回模型;Programmatic Tool Calling 让模型先生成 JavaScript,在托管 V8 运行时中并行、循环或组合多个符合条件的工具,并只把压缩后的程序结果送回模型。单次查询或每一步都需要语义判断时仍优先直接调用。
Programmatic Tool Calling 的 JavaScript 能使用 Node.js、网络或文件系统吗?
不能直接使用。官方说明每次程序都运行在新的隔离 V8 环境,支持顶层 await,但没有 Node.js、安装包、直接网络、通用文件系统、子进程、console 或跨程序持久状态。程序只能通过请求中允许的 tools.* 调用外部能力,并用 text(...) 或 image(...) 输出。
allowed_callers 应该怎么设置?
省略或设置为 direct 时工具只能直接调用;programmatic 表示只能由 program 调用;同时列出 direct 和 programmatic 表示两种方式都允许。应按具体工作流阶段授权,不要为了方便把所有写操作都开放给 program。
为什么建议给函数增加 output_schema?
parameters 约束输入,output_schema 描述 function_call_output.output 字符串中编码的 JSON 结构。程序需要稳定字段做过滤、聚合和计算时,明确输出 Schema 可减少猜测;如果返回结构不可预测,保留直接调用,让模型先查看结果。
应用怎样继续 program 发起的客户端函数调用?
应用仍负责执行客户端函数。回传 function_call_output 时必须保留原 function_call 的 call_id,并原样复制 caller,让服务知道该结果属于哪个 program。继续请求直到出现最终 message;store:false 时还要按顺序重放 program、reasoning、调用、输出和 program_output 等完整项目。
Programmatic Tool Calling 可以和 Tool Search 一起用吗?
可以,但 Tool Search 是顶层 Responses 工具,不能从正在运行的 JavaScript 内部调用。defer_loading 的函数、custom 或 MCP 工具必须先由模型搜索并加载;之后的 program 才能通过 tools.* 调用同时允许 programmatic 的工具。
Programmatic Tool Calling 一定会更省 Token、更快吗?
不保证。它可能把大型中间结果留在运行时并返回较小结构,但生成代码、暂停恢复、重试和错误处理也有成本。应以直接调用为基线,比较正确率、证据完整性、输入和总 Token、端到端延迟、工具调用数、重试及安全结果。
官方资料
如果任务需要多语言命令、文件转换或自有运行环境,请参阅 OpenAI Shell 工具教程;先区分托管容器与本地执行,并按最小权限限制文件、网络和命令。
本文核对日期为 2026-11-01。模型支持、工具类型、响应项目、ZDR 资格和 SDK 辅助函数可能调整;生产上线前请重新读取 Programmatic Tool Calling、Tools、Deployment checklist 与模型页。