# OpenAI function calling: let the model call your code

> OpenAI function calling lets a model ask your application to run a function you described in JSON Schema. The model never runs it. It returns a `function_call` item with arguments, your code runs the function, and you send the result back as a `function_call_output` with the same `call_id`. Web search and file search are built-in tools that OpenAI runs for you.

Mindket editors · Published 2026-11-05 · Updated 2026-11-05 · https://mindket.com/learn/openai-api/openai-function-calling

## In short

- Write each description so a newcomer could call the function with nothing else to go on.
- Leave `strict` on and fix the schema when a request is rejected.
- Treat every reply as possibly holding several calls, and answer each one by its id.
- Load a small set of functions per turn and defer the rest.
- Show cited links clearly whenever your interface displays search results.

## Before you start

You need the setup from lesson 1: an API key in `OPENAI_API_KEY`, the `openai` package for Python 3.10 or later, and a model that supports tools. The samples use `gpt-6-astra`, the model the docs use on 5 November 2026. GPT-6 Astra and GPT-6.1 Sol only call tools through the Responses API, so this lesson does not touch Chat Completions.

Three terms carry the lesson. A function is a tool you define with a JSON Schema, a JSON document that names each argument and its type. A function call is the model asking for that function with arguments it filled in. A function call output is the result your code sends back. The model only asks. Your application decides whether to run anything. The Responses API also accepts custom tools and remote MCP servers, and this lesson stays with functions and the two built-in search tools.

If you did the [tool use lesson](https://mindket.com/learn/claude-api/claude-tool-use) in the Claude API course, the loop will look familiar. The shapes differ. Claude returns `tool_use` content blocks and expects `tool_result` blocks in a user message. OpenAI returns `function_call` items in the `output` list and expects `function_call_output` items in the next `input`, paired by `call_id`.

## Run the tool-call loop end to end

Send "What is my horoscope? I am an Aquarius." with one function attached, and the docs' flow shows what happens next. The first response carries no horoscope. It carries a `function_call` item that names `get_horoscope` and holds the arguments as a JSON string, here the sign. Your code parses that string, runs the function and appends the result. Only the second request produces the text a user reads. That split is the whole pattern.

The sample is the [function calling guide](https://developers.openai.com/api/docs/guides/function-calling)'s complete example, copied as published. In JavaScript the guide does the same with `toResponseInputItems`, imported from `openai/lib/responses/ResponseInputItems`, and pushes its result onto the input array. Two lines in the Python version do work that is easy to miss. `input_list += response.output` puts the model's own items, the call and any reasoning items, back into the history. The guide asks for exactly that, because reasoning items returned with tool calls must travel back with the outputs.

The second request adds `instructions` that tell the model to answer only from the tool. Instructions apply to one request, as lesson 1 showed, so they sit on the call where they matter. The loop has no fixed length. With Responses the loop can repeat for as many calls as the task needs, and each pass follows the same five steps.

````python
from openai import OpenAI
import json

client = OpenAI()

# 1. Define a list of callable tools for the model
tools = [
    {
        "type": "function",
        "name": "get_horoscope",
        "strict": True,
        "description": "Get today's horoscope for an astrological sign.",
        "parameters": {
            "type": "object",
            "properties": {
                "sign": {
                    "type": "string",
                    "description": "An astrological sign like Taurus or Aquarius",
                },
            },
            "required": ["sign"],
            "additionalProperties": False,
        },
    },
]

def get_horoscope(sign):
    return f"{sign}: Next Tuesday you will befriend a baby otter."

# Create a running input list we will add to over time
input_list = [{"role": "user", "content": "What is my horoscope? I am an Aquarius."}]

# 2. Prompt the model with tools defined
response = client.responses.create(
    model="gpt-6-astra",
    tools=tools,
    input=input_list,
)

# Save function call outputs for subsequent requests
input_list += response.output

for item in response.output:
    if item.type == "function_call":
        if item.name == "get_horoscope":
            # 3. Execute the function logic for get_horoscope
            sign = json.loads(item.arguments)["sign"]
            horoscope = get_horoscope(sign)

            # 4. Provide function call results to the model
            input_list.append(
                {
                    "type": "function_call_output",
                    "call_id": item.call_id,
                    "output": horoscope,
                }
            )

response = client.responses.create(
    model="gpt-6-astra",
    instructions="Respond only with a horoscope generated by a tool.",
    tools=tools,
    input=input_list,
)

# 5. The model should be able to give a response!
print(response.output_text)
````

1. Send the request with the `tools` list and the user message.
2. Find each `function_call` item in `response.output` and read its `name` and `arguments`.
3. Parse the arguments with `json.loads` and run your own function.
4. Append a `function_call_output` with the same `call_id` and the result as a string.
5. Send the grown input list again. The reply is text, or more calls to run.

## Define functions the model can use well

A function definition has five fields, listed in the table. `parameters` takes standard JSON Schema, so enums, nested objects and descriptions on each property all work. Recursive objects work too. The sample is the guide's `get_weather` definition. Its `units` enum stops the model from inventing a unit your code cannot read.

The guide's advice on writing definitions comes down to one test it calls the intern test. Could a person use the function correctly given only what you gave the model? If not, the questions they would ask belong in the description. State what each argument means and its format, and say in your instructions when the function should and should not be used. Examples and edge cases help fix recurring failures, though the guide notes that examples may hurt reasoning models.

Shape the functions themselves with the same care. A signature like `toggle_light(on: bool, off: bool)` allows a call that makes no sense, so use an enum. Do not ask the model for values your code already has. If you know the `order_id`, define `submit_refund()` with no parameters and pass the id in code. Two functions that always run in sequence can become one.

Count matters as well. The guide suggests fewer than 20 functions available at the start of a turn, as a soft target. Definitions are injected into the system message, so they count against the context window and are billed as input tokens on every request. For a larger set, group tools into a namespace, such as `crm` or `billing`, and mark rarely used ones with `defer_loading`. Then `tool_search` loads them only when the model needs them, on `gpt-5.4` and later.

Strict mode is the last field and the one that matters most. With `strict` set to true, the model's arguments follow your schema exactly, built on the same machinery as Structured Outputs. In return the schema has two rules. Every object sets `additionalProperties` to false, and every field in `properties` is listed in `required`. An optional field becomes a type list with `null` in it, such as `["string", "null"]`.

If you send `strict: true` and the schema breaks a rule, the request is rejected with the missing constraints listed. If you leave `strict` out, Responses tries to convert the schema to strict mode and falls back to best effort when it cannot. The tool in the response then shows `strict: false`. The guide recommends always turning it on.

````json
{
  "type": "function",
  "name": "get_weather",
  "description": "Retrieves current weather for the given location.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and country e.g. Bogotá, Colombia"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Units the temperature will be returned in."
      }
    },
    "required": ["location", "units"],
    "additionalProperties": false
  },
  "strict": true
}
````

| Field | What it holds |
| --- | --- |
| `type` | Always `function` |
| `name` | The function name the model calls, such as `get_weather` |
| `description` | When and how to use the function |
| `parameters` | A JSON Schema for the arguments |
| `strict` | Whether the arguments must match the schema exactly |

## Handle several calls, pick a tool choice, stream the arguments

One response can hold zero, one or several function calls, so the guide asks you to assume several. Its sample response holds three items: `get_weather` for Paris, `get_weather` for Bogotá and `send_email` to Bob. Each has an `id` that starts with `fc_`, a `call_id` that starts with `call_`, a `name` and the `arguments` as a JSON string. A small router, the guide's `call_function` below, sends every call to the right code and raises an error for a name it does not know.

What you return is usually a string, in any format you like: JSON, an error code or plain text. A function with nothing to return, such as `send_email`, should still return something like `"success"` so the model knows it ran. Functions that produce images or files can return a list of image or file objects instead. Once every output is appended, the next request gives the model all it needs. In the guide's example the final answer reads "It's about 15°C in Paris, 18°C in Bogotá, and I've sent that email to Bob."

By default the model decides when to call tools and how many. Set `parallel_tool_calls` to false and it calls zero or one tool per turn. On models from GPT-5 onward, functions can run in parallel while built-in tools are also available. A built-in tool never joins a parallel batch of function calls, though. The `tool_choice` parameter gives you firmer control, and the table lists its settings.

`allowed_tools` has a use beyond control. You can keep the full `tools` list identical on every request and narrow the callable set per turn. An identical tools list keeps prompt caching effective, and prompt caching reuses an earlier request's processed prompt prefix.

Streaming works for calls too. With `stream` on, a `response.output_item.added` event announces each `function_call` item, `response.function_call_arguments.delta` events deliver the argument text piece by piece, and `response.function_call_arguments.done` carries the finished call. An interface can show which function is running while the model is still writing its arguments. The deltas are raw JSON text. Parse them only once the done event arrives, because a half-written argument string is not valid JSON yet.

````python
def call_function(name, args):
    if name == "get_weather":
        return get_weather(**args)
    if name == "send_email":
        return send_email(**args)
    raise ValueError(f"Unknown function: {name}")
````

**Function calling changes in the OpenAI changelog**

1. **November 6, 2023** `functions` gives way to `tools`. Chat Completions deprecates the `functions` parameter.
2. **April 29, 2024** `tool_choice` gains `required`. The model must call at least one function.
3. **June 6, 2024** Parallel calls can be switched off. In Chat Completions and the Assistants API.
4. **March 11, 2025** The Responses API ships. A new API built for agents and tools.
5. **May 20, 2025** Strict mode with parallel calls. Strict tool schemas now work when calls run in parallel.
6. **August 7, 2025** Custom tools. Tool calls with free-form input and output instead of JSON.
7. **March 5, 2026** Tool search. Models can defer large sets of tools until they need them.
8. **September 3, 2026** GPT-6 Astra. Its tool calls need the Responses API, which also gains async tool calling.

Source: [OpenAI Docs: Changelog](https://developers.openai.com/api/docs/changelog) (read 2026-11-05)

| `tool_choice` | What the model may do |
| --- | --- |
| `"auto"` (default) | Call zero, one or several functions |
| `"required"` | Call one or more functions |
| `{"type": "function", "name": "get_weather"}` | Call exactly that one function |
| `{"type": "allowed_tools", "mode": "auto", "tools": [...]}` | Call only from the listed subset |
| `"none"` | Behave as if no functions were passed |

## Add web search and file search

Built-in tools run on OpenAI's side. You add them to `tools` like a function, and the model decides whether to use them, but there is nothing for your code to execute and no output to send back. The sample is the web search guide's first example. One entry, `{"type": "web_search"}`, is the whole setup. The older `web_search_preview` tool still works for legacy code, but it lacks `filters` and other newer controls.

The response then holds a `web_search_call` item whose `action` says what the model did: `search`, `open_page` or `find_in_page`, the last two on reasoning models. After it comes the `message`, with `url_citation` annotations that give each cited source's URL, title and position in the text. When your interface shows web results, the guide requires those citations to appear as clear, clickable links.

Web search takes a few settings. `filters` limits results to up to 100 `allowed_domains`, or excludes up to 100 `blocked_domains`, written without the https prefix. The `sources` field lists every URL the model consulted, usually more than it cited. The `search_context_size` setting, `low`, `medium` or `high`, controls how much of the results the model reads. Setting `external_web_access` to false keeps it to cached results. A `user_location` with a country, city, region or timezone refines results by place. The search context window is capped at 128,000 tokens whatever the model's own window is. For a question that needs several sources, the guide suggests a reasoning model, which refines its searches as it reads, with background mode for runs that take minutes.

File search answers from your documents instead of the web. You upload files, add them to a vector store, an index OpenAI builds for semantic and keyword search over them, and wait until each file's status is `completed`. Then add a `file_search` entry to `tools` with the id of that store in its `vector_store_ids` list. The response holds a `file_search_call` item and a message with `file_citation` annotations naming each file.

Two settings shape file search. `max_num_results` caps how many results come back, which cuts tokens and latency and can cost answer quality. The raw results stay out of the response unless you ask for them with `include=["file_search_call.results"]`. With `tool_choice` left on auto, both searches are optional. Set it to `required` when a search must run.

````python
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    tools=[{"type": "web_search"}],
    input="What was a positive news story from today?",
)

print(response.output_text)
````

| Tool | Who runs it | What you set up | What comes back |
| --- | --- | --- | --- |
| Function | Your application | A name, a description and a JSON Schema | A `function_call` item you answer with an output |
| `web_search` | OpenAI | One entry in `tools`, plus optional filters | A `web_search_call` item and a message with URL citations |
| `file_search` | OpenAI | Uploaded files in a vector store | A `file_search_call` item and a message with file citations |

## Ilan Bigio and OpenAI Developers on writing and finding functions

Ilan Bigio, who works on research and tooling at OpenAI, [announced a rewritten function calling guide on X](https://x.com/ilanbigio/status/1878940258349510764) on 13 January 2025. The team had acted on feedback, in his words: the guide came out half as long, with new best practices and a full example built on a weather API. The weather schema in this lesson comes from the current version of that guide, so read its best practices before you write a description of your own.

OpenAI Developers [announced GPT-5.4 on 5 March 2026](https://x.com/OpenAIDevs/status/2029620984853188738), and one line in the post was tool search across larger tool ecosystems. That is the `defer_loading` and `tool_search` pair from the definitions part of this lesson, which works on `gpt-5.4` and later. Once an app carries more functions than the guide's soft target, group them into namespaces and defer the rarely used ones.

> Scalable tool search across larger ecosystems.
>
> OpenAI Developers, OpenAI's official account for developers, [X](https://x.com/OpenAIDevs/status/2029620984853188738) (read 2026-11-05)

[![Ilan Bigio (@ilanbigio) on X, 13 January 2025: announcing our brand new function calling guide @openai. We heard your feedback and made some key changes: 50% shorter and clearer, new best practices, in-doc function generation, and a fully-functional example with a weather API.](https://mindket.com/blog/shots/x-ilanbigio-function-calling-guide.webp?v=4370c6434138)](https://x.com/ilanbigio/status/1878940258349510764)

Ilan Bigio on X, 13 January 2025

[![OpenAI Developers (@OpenAIDevs) on X, 5 March 2026: GPT-5.4 is here. Native computer-use capabilities. Up to 1M tokens of context in Codex and the API. Best-in-class agentic coding for complex tasks. Scalable tool search across larger ecosystems. More efficient reasoning for long, tool-heavy workflows.](https://mindket.com/blog/shots/x-openaidevs-gpt-5-4-tool-search.webp?v=e7c533e76632)](https://x.com/OpenAIDevs/status/2029620984853188738)

OpenAI Developers on X, 5 March 2026

[Build Hour: Agentic Tool Calling](https://www.youtube.com/watch?v=7E-qdsVEoB8)

OpenAI's Build Hour on agentic tool calling, from OpenAI's own YouTube channel.

> Screenshots of X's own embeds of the posts, taken on 5 November 2026, with the time in UTC. Select one to open the post on X. X cuts Ilan Bigio's post short with Show more.

## Common mistakes

- Reading only the first `function_call` when the response holds three.
- Sending a `function_call_output` without the `call_id` of the call it answers.
- Appending your outputs but not the model's own items, so reasoning items never travel back.
- Leaving a field out of `required` with strict mode on, then wondering why the request is rejected.
- Asking the model to fill an id your code already holds.
- Parsing streamed arguments before `response.function_call_arguments.done` arrives.
- Loading dozens of functions on every turn and paying for their definitions each time.
- Expecting a web search on every question while `tool_choice` is still auto.
- Showing search answers without their citation links.
- Starting new code on `web_search_preview`, which ignores domain filters.

## Try it yourself

- Run the horoscope sample, then print `response.output` after the first call. Which item types are in it?
- Remove `"units"` from `required` in the weather schema, keep `strict` on, and read the error.
- Ask about the weather in two cities at once and count the `function_call` items.
- Set `parallel_tool_calls` to false and ask the same question again.
- Add `web_search` with `filters` for one domain you trust and compare the citations.
- Upload one PDF to a vector store and ask a question only that file can answer.

## Next

[Lesson 3](https://mindket.com/learn/openai-api/openai-structured-outputs) takes the strict mode from this lesson and applies it to the model's reply itself. You pass a schema as `text.format`, or a Pydantic or Zod class, and get JSON your code can parse without checks.

Remote MCP servers are another tool type the Responses API accepts. [What is MCP](https://mindket.com/blog/what-is-mcp) explains the protocol, and the [MCP course](https://mindket.com/learn/mcp/connect-mcp-servers) shows how servers connect to an AI app, which helps once your own functions outgrow one file.

## Check yourself

1. Who runs get_horoscope when the model decides to call it?

   A. OpenAI's servers
   B. The model, inside its reasoning
   C. Your application code
   D. The SDK, without being asked

   Answer: C. The model only returns a function_call item. Your code runs the function and sends back the output.

2. With strict mode on, which rule must every object in parameters follow?

   A. Every property has a default value
   B. It has at most 20 properties
   C. Optional fields are left out of required
   D. additionalProperties is false and every field is in required

   Answer: D. Strict mode needs additionalProperties set to false and every property listed in required, with null in the type for optional fields.

3. You set parallel_tool_calls to false. How many tools can the model call in that turn?

   A. Zero or one
   B. Exactly one
   C. Up to 20
   D. Only built-in tools

   Answer: A. With parallel_tool_calls off, the model calls zero or one tool per turn.

4. What ties a function_call_output to the call it answers?

   A. The function name
   B. The call_id
   C. The response id
   D. The order of the items

   Answer: B. Each output carries the call_id of its function_call, which is how the model pairs them.

## Frequently asked questions

### What are custom tools, and when do they beat functions?

A custom tool takes free-form text instead of JSON arguments. The model passes a string, such as a block of Python for a `code_exec` tool, straight to your code. The guide suggests it when wrapping the input in JSON adds nothing, or when you want to bind the input to a grammar, written in Lark or as a regular expression.

### Does web search cost more than the tokens?

Yes. Each `search` action is billed as a tool call on top of the tokens the model reads and writes, and the pricing page lists the current rates. Web search in Responses also shares the rate limits of the model you call it with.

### Can I still use function calling with Chat Completions?

Yes, with different request and response shapes, and with strict mode off unless you turn it on. GPT-6 Astra and GPT-6.1 Sol need the Responses API for tool calling, and the guide's own Chat Completions samples use GPT-5.6 Terra with reasoning disabled. The [migration guide](https://developers.openai.com/api/docs/guides/migrate-to-responses) maps the changes.

### Does strict mode behave the same on a fine-tuned model?

Not quite. A fine-tuned model processes each new schema on its first request and caches it, so schemas that change between requests add latency. Cached schemas are not eligible for zero data retention. If a fine-tuned model calls several functions in one turn, strict mode is switched off for those calls.

## Sources

- [OpenAI Docs: Function calling](https://developers.openai.com/api/docs/guides/function-calling) (read 2026-11-05)
- [OpenAI Docs: Using tools](https://developers.openai.com/api/docs/guides/tools) (read 2026-11-05)
- [OpenAI Docs: Web search](https://developers.openai.com/api/docs/guides/tools-web-search) (read 2026-11-05)
- [OpenAI Docs: File search](https://developers.openai.com/api/docs/guides/tools-file-search) (read 2026-11-05)
- [OpenAI Docs: Migrate to the Responses API](https://developers.openai.com/api/docs/guides/migrate-to-responses) (read 2026-11-05)
- [OpenAI Docs: Changelog](https://developers.openai.com/api/docs/changelog) (read 2026-11-05)
- [X: Ilan Bigio announces the rewritten function calling guide](https://x.com/ilanbigio/status/1878940258349510764) (read 2026-11-05)
- [X: OpenAI Developers announce GPT-5.4 with tool search](https://x.com/OpenAIDevs/status/2029620984853188738) (read 2026-11-05)
- [YouTube (OpenAI): Build Hour: Agentic Tool Calling](https://www.youtube.com/watch?v=7E-qdsVEoB8) (read 2026-11-05)

[Browse AI agents](https://mindket.com/agents)
