Building with the API

Call functions (tools) with the OpenAI API

3 min read

Structured outputs force the shape of the model’s text reply; function calling is different — the model asks you to run code and waits for the result. On the Responses API a tool is a flat object with no nested function wrapper, and you drive a short loop until the model stops asking.

1. Define a tool with a JSON-schema parameters block

pip install openai
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY

tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Get the current temperature for a city.",
    "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
        "additionalProperties": False,
    },
    "strict": True,
}]

Each tool carries type, name, description, a JSON-schema parameters, and strict right on the object, with no nested function key in the Responses API.

2. Send it and read the function_call item

def get_weather(city):
    return {"city": city, "temp_c": 18}

input_list = [{"role": "user", "content": "What's the weather in Paris?"}]
response = client.responses.create(model="gpt-6-astra", input=input_list, tools=tools)

The model answers with one or more function_call items in response.output, each holding a name, a call_id, and an arguments JSON string.

3. Run your function and return a function_call_output

import json

input_list += response.output
for item in response.output:
    if item.type == "function_call":
        result = get_weather(**json.loads(item.arguments))
        input_list.append({
            "type": "function_call_output",
            "call_id": item.call_id,
            "output": json.dumps(result),
        })

Parse arguments, call your function, then append a function_call_output whose call_id matches the call and whose output is a string.

4. Loop until the model stops calling tools

while True:
    response = client.responses.create(model="gpt-6-astra", input=input_list, tools=tools)
    calls = [i for i in response.output if i.type == "function_call"]
    if not calls:
        break
    input_list += response.output
    for item in calls:
        result = get_weather(**json.loads(item.arguments))
        input_list.append({"type": "function_call_output",
                            "call_id": item.call_id, "output": json.dumps(result)})
print(response.output_text)

Resend the growing input after every batch of outputs until a turn returns no function_call items, then read the final text from output_text.

5. Handle parallel tool calls

response = client.responses.create(
    model="gpt-6-astra",
    input=input_list,
    tools=tools,
    tool_choice="auto",
    parallel_tool_calls=False,
)

A single turn may emit several function_call items at once; tool_choice accepts "auto", "required", "none" or {"type": "function", "name": "get_weather"}, and parallel_tool_calls=False caps it at one call per turn.

Verify it worked

A correct run prints a sentence containing the number your function returned, proving the model consumed the tool result rather than guessing.

If it fails:

Start here if this is new: your first OpenAI request. The Claude equivalent, with its own loop, is tool use with the Claude API.

Source: OpenAI function calling guide.

Open the full interactive version (with copy buttons) ↗

← All guides