程序化工具调用让模型能够编写并运行 JavaScript,协调其工具的调用。程序可以并行调用工具、使用循环和条件判断,并将中间结果保留在托管运行时中。当任务需要执行一系列相互关联的工具调用,或需要先处理大量工具输出再返回结果时,这项功能很有用。
在 Responses API 中,您的应用决定是否启用程序化工具调用,以及模型可以直接调用哪些符合条件的工具、可以通过程序调用哪些工具,或可以通过这两种方式调用哪些工具。由客户端负责的工具调用仍由您的应用执行。Agents API 默认启用程序化工具调用,并为您管理智能体循环。
启用程序化工具调用前,请查看模型页面。
了解运行时环境
OpenAI 在全新、隔离的 V8 运行时中运行每个生成的程序。该运行时支持使用顶层 await 的 JavaScript,但不提供 Node.js、软件包安装、直接网络访问、通用文件系统、子进程执行或控制台,也不会在程序的多次执行之间持久保留 JavaScript 状态。程序只能通过请求中启用的工具与外部系统交互,并可通过 text(...) 或 image(...) 输出内容。
对于 Responses API 请求,程序化工具调用支持零数据保留(ZDR)工作流,无需持久化的代码执行容器。必须为组织或项目启用 ZDR;设置 store: false 可让程序以无状态方式继续执行,但这一设置本身不会启用 ZDR。是否符合 ZDR 条件以及数据保留情况取决于完整请求,包括所用模型、工具和第三方服务;请参阅数据控制。
选择何时使用程序化工具调用
当某个阶段的控制流可预测,且代码可以返回更精简的结构化结果时,请使用程序化工具调用。如果一次调用就足够、每个结果都需要模型重新判断,或者任务需要审批或保留引用或原生产物,请使用直接工具调用。
| 任务特点 | 推荐模式 |
|---|---|
| 单次查询或操作 | 使用直接工具调用。 |
| 多个结果,可通过代码进行筛选、关联、排序、去重、聚合或验证 | 当程序可以返回更精简的结构化结果时,使用程序化工具调用。 |
| 存在依赖关系且数据流可预测的调用 | 当代码可以推导后续调用的参数,且限制和失败时的行为都已明确时,使用程序化工具调用。 |
| 自适应搜索或语义评估 | 当每个结果都应影响模型的下一步决策时,使用直接工具调用。 |
| 写入操作或对审批有严格要求的操作 | 默认使用直接工具调用,以保持清晰的授权边界。 |
| 最终引用或原生产物的验证 | 使用直接工具调用,除非程序能够保留原生输出并验证每个必需项。 |
配置程序化工具调用
使用 Responses API 时,请将 programmatic_tool_calling 托管工具添加到请求中,然后为程序可调用的每个符合条件的工具设置 allowed_callers。
[
{
"type": "function",
"name": "get_inventory",
"description": "Return an object with sku (string) and available_units (number).",
"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"]
},
{
"type": "programmatic_tool_calling"
}
]allowed_callers 控制模型可以通过哪些方式调用工具:
| 值 | 行为 |
|---|---|
省略或设为 ["direct"] | 模型可以直接调用该工具。 |
["programmatic"] | 只有 program 项中的代码可以调用该工具。 |
["direct", "programmatic"] | 模型可以直接调用该工具,也可以通过程序调用。 |
parameters 描述函数参数。当函数返回可预测的结构化数据时,output_schema 描述编码在其 function_call_output.output 字符串中的 JSON 对象。请同时定义这两项,以便生成的 JavaScript 能够可靠地使用返回的字段。
支持的工具
以下工具类型支持 allowed_callers: ["programmatic"]:
function和custommcpapply_patch- 本地和托管式
shell code_interpreter
对于 MCP 工具,工具的 require_approval 策略可以暂停程序,直到您批准该调用。
对于 OpenAI 托管的工具,请先查看该工具的数据保留和安全指南,再在程序中启用它。
结合工具搜索使用
工具搜索作为顶层 Responses API 工具运行,不在生成的 JavaScript 内部运行。设置了 defer_loading: true 的函数工具、自定义工具和 MCP 工具最初无法供程序使用。模型加载匹配的工具后,如果该工具的 allowed_callers 包含 "programmatic",后续程序就可以通过 tools.* 调用它。已经运行的程序无法调用工具搜索,因此模型必须先加载延迟加载的工具,再启动需要这些工具的程序。
在两种模式都可用时引导调用路径选择
当您的应用允许模型直接调用函数或通过程序调用函数时,请为每种调用路径指定相应的工作流阶段。“高效使用程序化工具调用”这类笼统指令无法明确预期的使用边界。例如:
<tool_orchestration>
Use Programmatic Tool Calling for [bounded stage] using only [eligible tools].
Run independent calls concurrently when safe. Use only documented tool input
and output fields.
Process and reduce the intermediate results, then emit exactly [program result shape],
including the evidence needed for the final answer.
Stop when [condition] is met. Retry transient failures at most [R] times.
Do not repeat completed calls or perform side-effecting actions. If a required
result is still missing, return a clear structured failure.
Use direct tool calls for [semantic judgment, approval, or final validation].
</tool_orchestration>
以下示例展示了如何使用此模板:
<tool_orchestration>
Use Programmatic Tool Calling to compare inventory with demand for sku_123
using only get_inventory and get_demand. Run both calls concurrently. Use
only documented tool input and output fields.
Process and reduce the intermediate results, then emit exactly one JSON object
with sku, available_units, requested_units, and shortage_units, where
shortage_units is max(requested_units - available_units, 0). Include
available_units and requested_units as evidence for the calculation.
Stop when both tool results contain the required fields. Retry transient
failures at most 1 time. Do not repeat completed calls or perform
side-effecting actions. If a required result is still missing, return a clear
structured failure.
Use direct tool calls only for approval before any inventory-changing action.
</tool_orchestration>
对于需要同时使用两种模式的工作流,请定义一个交接点,避免来回切换调用路径或重复执行工作。如果存在安全的回退方案,请统一定义,并限制其重试次数。
了解程序响应项
每次 API 调用仍返回标准的 Responses API 对象。程序化工具调用不会引入单独的响应封装结构。当模型使用程序化工具调用时,响应的 output 数组可以包含:
- 一个
program项,其中包含生成的 JavaScript、一个call_id,以及一个用于恢复或重放程序的不透明fingerprint。 - 一个由程序发起的
function_call项。它有自己的call_id,您的应用使用该值返回函数结果。它的caller.caller_id与程序的call_id一致。 - 一个
program_output项,其中包含程序的最终结果和状态。它的call_id与程序的call_id一致,其status为completed或incomplete。
这些是 response.output 中彼此独立的顶层项;caller 字段记录它们的执行关系。
例如,程序可以在您的应用运行 get_inventory 和 get_demand 时暂停:
[
{
"type": "program",
"id": "prog_123",
"call_id": "call_prog_123",
"code": "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) }));",
"fingerprint": "opaque_replay_state"
},
{
"type": "function_call",
"id": "fc_123",
"call_id": "call_inventory_123",
"name": "get_inventory",
"arguments": "{\"sku\":\"sku_123\"}",
"caller": {
"type": "program",
"caller_id": "call_prog_123"
}
},
{
"type": "function_call",
"id": "fc_456",
"call_id": "call_demand_123",
"name": "get_demand",
"arguments": "{\"sku\":\"sku_123\"}",
"caller": {
"type": "program",
"caller_id": "call_prog_123"
}
}
]这些示例仅展示 response.output 中的相关项,省略了外层的标准 Responses 对象。您的应用返回嵌套函数调用的结果后,后续响应中可以包含完整的 program_output 项:
{
"type": "program_output",
"id": "prog_out_123",
"call_id": "call_prog_123",
"result": "{\"sku\":\"sku_123\",\"available_units\":42,\"requested_units\":31,\"shortage_units\":0}",
"status": "completed"
}program_output.result 中的 JSON 字符串遵循您在指令中定义的程序结果结构。外层的 program_output 项遵循上文所示的 API 约定。这是两套独立的约定。最终的 message 可能与程序输出一起返回,也可能在后续响应中返回,因此请继续,直到收到该消息。
OpenAI 在托管运行时中运行模型生成的 JavaScript。您的应用程序执行返回的、由客户端负责的函数调用,而不执行生成的 JavaScript。
以 function_call_output 的形式返回函数结果。从函数调用中原样复制 caller,不要修改。服务使用该值恢复对应程序的执行。
在客户端负责的函数调用后继续执行
程序在执行到由客户端负责的工具时可能会多次暂停。请继续,直到响应中包含最终的助手消息:
- 发送请求,其中包含托管工具和允许程序化调用的函数。
- 执行返回的每个由客户端负责的函数调用。
- 返回每个函数结果,并附上原始的
call_id和caller。 - 先处理不完整的响应,再继续。
- 如果响应中既没有待处理的
function_call项,也没有最终的message项,请从该响应继续。使用store: false时,重放其输出项;对于已存储的响应,请使用previous_response_id。 - 当响应中包含最终的
message项时停止。读取response.output_text或消息中的拒绝内容。
以下示例使用 store: false,保留每个响应项,并将每个函数结果返回给程序:
import json
from openai import OpenAI
client = OpenAI()
model = "gpt-6-astra"
def get_inventory(sku):
return {"sku": sku, "available_units": 42}
def get_demand(sku):
return {"sku": sku, "requested_units": 31}
implementations = {
"get_inventory": get_inventory,
"get_demand": get_demand,
}
tools = [
{
"type": "function",
"name": "get_inventory",
"description": "Return an object with sku (string) and available_units (number).",
"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"],
},
{
"type": "function",
"name": "get_demand",
"description": "Return an object with sku (string) and requested_units (number).",
"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"],
},
{"type": "programmatic_tool_calling"},
]
input_items = [
{
"role": "user",
"content": "Compare inventory with demand for sku_123.",
}
]
while True:
response = client.responses.create(
model=model,
store=False,
input=input_items,
tools=tools,
)
if response.status != "completed":
raise RuntimeError(f"Response ended with status {response.status}")
# Preserve every output item, including program and reasoning items.
input_items.extend(item.model_dump(exclude_none=True) for item in response.output)
calls = [item for item in response.output if item.type == "function_call"]
if not calls:
message = next(
(item for item in response.output if item.type == "message"), None
)
if message:
refusal = next(
(part.refusal for part in message.content if part.type == "refusal"),
"",
)
print(response.output_text or refusal)
break
continue
for call in calls:
run = implementations.get(call.name)
if run is None:
raise ValueError(f"Unknown tool: {call.name}")
result = run(**json.loads(call.arguments))
input_items.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
# Preserve caller so the runtime can resume the correct program.
"caller": call.caller.model_dump() if call.caller else None,
}
)存储响应后,您可以通过 previous_response_id 继续,而无需重新发送此前的所有响应项。将新的 function_call_output 项作为下一次输入发送。使用 store: false 时,请按顺序重放完整序列,包括每个 program 项、推理项、函数调用项、函数调用输出项和 program_output 项。
对于无状态的推理模型请求,请重放返回的每个推理项。每个项默认包含 encrypted_content。有关通用的无状态处理模式,请参阅对话状态。
为程序设计工具
- 返回结构化且精简的数据,让 JavaScript 无需解析自然语言文本即可检查数据。
- 使用
output_schema定义每个工具预期返回的字段及其类型,并记录工具出错时的行为。如果无法预先确定返回结构,请让模型直接调用该工具,以便模型检查结果。 - 明确定义程序结果的结构和所需证据。当程序无法生成有效结果时,返回清晰的结构化失败信息。
- 尽可能让函数调用具有幂等性。重试或重放不应重复触发不安全的副作用。
- 在您的应用程序中检查每次调用的参数和权限,即使调用来自托管程序也不例外。
- 为工具提供具体的名称和描述,以便模型正确组合使用这些工具。
- 无论调用方是谁,执行影响重大的操作前都必须经过应用程序层面的审批。
评估程序化工具调用
程序化工具调用可以减少添加到模型上下文中的中间工具输出量,但效果取决于任务和工具响应。先以直接工具调用作为基准,再在具有代表性的任务上比较这两种方式。
衡量效率之前,先定义最终答案的质量标准和所需证据。在评估 Token 用量和工具调用的同时,也要评估正确性、完整性和证据覆盖情况,并明确说明任何可接受的质量取舍。
衡量以下方面:
- 最终答案的正确性、完整性和证据覆盖情况。
- 输入 Token 数和总 Token 数、端到端延迟及成本。
- 模型交互轮次、工具调用、重试和恢复行为。
- 安全方面的实际表现,尤其是副作用和审批要求方面。
- 实际采用的调用方式是否符合预期的工作流程阶段。
Agents API
在 Agents API 中,程序化工具调用在 OpenAI 管理的智能体执行框架中运行,并且默认启用。该框架为智能体提供 exec 工具,并让生成的 JavaScript 能够调用智能体已有的工具。您无需将这些工具封装为命令行程序,也无需将其安装到沙盒中。
要禁用程序化工具调用,请在 agent.tools 中加入以下条目:
{
"type": "programmatic_tool_calling",
"enabled": false
}
省略该条目或其中的 enabled 字段,程序化工具调用仍会保持启用。仅包含类型的条目 { "type": "programmatic_tool_calling" } 也会使其保持启用。上文的 allowed_callers 配置和 Responses 续接循环适用于 Responses API 集成。
程序化工具调用也适用于将 environment.type 设置为 none 的纯对话会话。Bash、执行器 MCP 以及其他在沙盒中运行的工具仍需要执行环境。
在 JavaScript 中编排工具调用不会改变工具的运行位置。Shell 调用在沙盒中执行命令;JavaScript 运行时本身不会启动系统进程。执行器 MCP 仍使用沙盒,函数工具仍调用您的应用服务器。智能体会先处理这些工具的结果,再决定将哪些内容纳入模型上下文。
请根据上文关于调用方式的指导,确定哪些工作流程阶段应使用代码。有关 Agents API 的配置和调用处理,请参阅函数和 MCP 连接。