# Call functions (tools) with the OpenAI API

> Give the model your own functions with the OpenAI Responses API: define a tool, run the call it returns, and send the result back in a loop.

- Canonical: https://guides-ai.com/guides/openai-api-function-calling/
- Plate 15.23 · Topic: Building with the API (https://guides-ai.com/topics/api/)
- Published: 21 Oct 2026 · 3 min read
- Source site: guides-ai — https://guides-ai.com/

[Structured outputs](/guides/openai-api-structured-outputs/) force the shape of the model's
text reply; [function calling](/glossary/#tool-use) 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

```bash
pip install openai
```

```python
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

```python
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

```python
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

```python
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

```python
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:**

- `No tool output found for function call call_...` — every `function_call` needs a matching `function_call_output` with the same `call_id` before the next request.
- `TypeError: argument of type ...` or a `JSONDecodeError` — `arguments` is a JSON string, so `json.loads` it instead of treating it as a dict.
- The model answers without calling the tool — sharpen the `description`, or force it with `tool_choice={"type": "function", "name": "get_weather"}`.

Start here if this is new: [your first OpenAI request](/guides/openai-api-first-request/). The
Claude equivalent, with its own loop, is [tool use with the Claude API](/guides/claude-api-tool-use/).

Source: [OpenAI function calling guide](https://developers.openai.com/api/docs/guides/function-calling).
