Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Programmatic tool calling lets a model use code to orchestrate tools during a Responses API request. Instead of requiring a separate model turn for each tool invocation, the model can generate a program that calls tools and processes their results. The program can perform operations such as branching or iteration.
This article explains when programmatic tool calling is useful and how it differs from function calling, parallel tool calling, and tool search.
Availability considerations
Before implementing programmatic tool calling, confirm that your Azure OpenAI model deployment and SDK version support the feature. Don't assume that support for the Responses API also establishes support for programmatic tool calling.
For endpoint and authentication setup, see Use the Azure OpenAI Responses API.
Programmatic tool calling and function calling
With standard function calling, your application typically participates in each tool-calling step:
Model -> tool call -> application executes tool -> model -> next tool call -> application executes tool -> model
With programmatic tool calling, the model can generate code that orchestrates multiple tool operations:
Model -> generated program -> tool operations -> program result -> model
This approach can reduce the number of model turns needed for workflows involving several tools.
These flows illustrate the conceptual difference in orchestration. They don't specify which operations your application or the service executes.
Example: Compare inventory with demand
This Python example exposes two application-defined functions that return sample inventory and demand data. The model uses programmatic tool calling to compare the results. Your application executes the functions and returns their results so that the runtime can resume the generated program.
The code blocks form one script. Combine them in the order shown.
Azure OpenAI client
Use an Azure OpenAI deployment that supports programmatic tool calling and an OpenAI SDK version that includes output_schema, allowed_callers, and caller information. Install the openai and azure-identity packages in your Python environment:
pip install --upgrade openai azure-identity
Configure Microsoft Entra ID authentication as described in Use the Azure OpenAI Responses API. Your identity needs permission to call your Azure OpenAI deployment.
Replace YOUR-RESOURCE-NAME with your resource name. The model value later in the example is your Azure deployment name, not a model catalog identifier.
import json
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import OpenAI
# Use the Azure OpenAI v1 endpoint and Microsoft Entra ID authentication.
endpoint = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(
credential, "https://cognitiveservices.azure.com/.default"
)
openai = OpenAI(base_url=endpoint, api_key=token_provider)
Application-defined functions
The functions return fixed sample data: 42 available units and 31 requested units for the specified SKU. Replace their implementations with your inventory and demand data sources in your application.
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,
}
Tool definitions
Each function declares an input schema and an output_schema describing the JSON result. Setting allowed_callers to ["programmatic"] makes the function available to generated programs rather than direct model calls.
tools = [
{
"type": "function",
"name": "get_inventory",
"description": "Return the SKU and available inventory units.",
"strict": True,
"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"],
}
]
Add the demand function and the programmatic tool calling capability to the same list:
tools.extend([
{
"type": "function",
"name": "get_demand",
"description": "Return the SKU and requested demand units.",
"strict": True,
"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"},
])
Tool results and caller information
The application dispatches function calls by name and serializes each result. Preserve the call's caller information so that the runtime can resume the correct program. Unknown tool names and invalid arguments raise errors rather than returning fabricated results.
def run_tool(call):
run = implementations.get(call.name)
if run is None:
raise ValueError(f"Unknown tool: {call.name}")
arguments = json.loads(call.arguments)
if (
not isinstance(arguments, dict)
or set(arguments) != {"sku"}
or not isinstance(arguments["sku"], str)
):
raise ValueError(f"Invalid arguments for tool: {call.name}")
result = run(**arguments)
output = {
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
}
if call.caller is not None:
output["caller"] = call.caller.model_dump(exclude_none=True)
return output
Response continuation
Replace YOUR-MODEL-DEPLOYMENT with the name of your supported Azure OpenAI deployment.
With store=False, the application carries the conversation forward. Preserve every output item, including program and reasoning items, before adding tool results. The loop stops on a final answer or refusal and raises an error if a response fails or the request limit is reached.
input_items = [
{"role": "user", "content": "Compare inventory with demand for sku_123."}
]
MAX_REQUESTS = 10
for _ in range(MAX_REQUESTS):
response = openai.responses.create(
model="YOUR-MODEL-DEPLOYMENT",
store=False, input=input_items, tools=tools,
)
if response.status != "completed":
raise RuntimeError(f"Response ended with status {response.status}")
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 calls:
input_items.extend(run_tool(call) for call in calls)
continue
if response.output_text:
print(response.output_text)
break
refusals = [
part.refusal for item in response.output if item.type == "message"
for part in item.content if part.type == "refusal"
]
if refusals:
print("\n".join(refusals))
break
else:
raise RuntimeError("Programmatic tool calling exceeded the request limit")
The final answer should explain that inventory covers demand: 42 available units minus 31 requested units leaves 11 units. The wording can vary. A refusal is printed instead if the model declines the request.
The application executes the inventory and demand functions, while the generated program coordinates their use and processes the results.
When to use programmatic tool calling
Consider programmatic tool calling when a task:
- Requires several related tool calls.
- Contains conditional logic.
- Needs iteration over tool results.
- Performs intermediate data processing before producing an answer.
- Would otherwise require repeated model turns to coordinate tools.
For simple workflows that involve one or two independent functions, standard function calling might be easier to implement.
Programmatic tool calling and parallel tool calling
Parallel tool calling and programmatic tool calling address different orchestration needs.
Use parallel tool calling when several independent operations can execute at the same time. For example, retrieve weather for five independent locations.
Use programmatic tool calling when tool operations require coordination, intermediate computation, branching, or iteration. For example, retrieve a customer's orders, select orders that match a condition, and query shipping information for those orders.
Programmatic tool calling and tool search
Tool search controls which tool definitions are loaded into the model's context.
Programmatic tool calling controls how the model orchestrates tools after they're available.
The distinction is between tool discovery and tool orchestration. Their conceptual relationship doesn't establish whether your deployment supports using both features in the same request.
Security considerations
Treat generated programs as untrusted code.
When exposing tools:
- Follow least-privilege principles.
- Validate tool arguments.
- Restrict access to sensitive operations.
- Authenticate and authorize operations independently of model output.
- Log tool execution for auditing and diagnostics without recording secrets or unnecessary sensitive data.
- Avoid exposing unnecessary tools or data.
Don't rely on model-generated code as an authorization boundary.