Function calling lets the model use your data and actions; it is the core of every agent.
With function calling you describe functions your program can run, and the model decides when to call one. The API never runs your code; it asks you to.
A function tool is a dict with type: "function", a name, a description (be specific; the model uses it to decide), parameters as a JSON Schema, and usually strict: True so the arguments always match the schema.
The loop:
- Call
responses.createwithtools=[...]and your input list. - Add
response.outputto the input list. Look for items withtype == "function_call"; each has aname, acall_idandarguments, a JSON string you mustjson.loads. - Run your function and append
{"type": "function_call_output", "call_id": ..., "output": "..."}for every call. - Call the API again with the longer input. Repeat until no more function calls come back, then read
output_text.
The model can request several calls in one response, so handle them all. With reasoning models, keep the reasoning items from response.output in the input too (appending the whole output does this), or use previous_response_id.
tool_choice controls the decision ("auto" by default, "required", "none", or a specific function), and parallel_tool_calls=False limits it to one call at a time. Validate arguments before doing anything with side effects, such as payments or deletes.
Example
import json
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function", "name": "get_weather", "strict": True,
"description": "Current weather for a city.",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}},
"required": ["city"], "additionalProperties": False},
}]
def get_weather(city): return f"{city}: 18C, light rain" # fake lookup
items = [{"role": "user", "content": "Do I need an umbrella in Oslo?"}]
while True:
r = client.responses.create(model="gpt-6-astra", tools=tools, input=items)
items += r.output
calls = [i for i in r.output if i.type == "function_call"]
if not calls:
break
for c in calls:
args = json.loads(c.arguments)
items.append({"type": "function_call_output", "call_id": c.call_id,
"output": get_weather(**args)})
print(r.output_text)Remember
- Tools: type function + name + description + parameters (+ strict)
- Run each function_call; reply with function_call_output by call_id
- arguments is a JSON string; keep reasoning items in the input
Watch out for
- Forgetting json.loads on arguments and passing a string where a dict is expected.