Mindket

OpenAI function calling: let the model call your code

A black banner titled "OpenAI API" over the line "Responses, tools, structured output and agents".

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.

In this lesson

  • 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.
All courses
On this page
  1. Before you start
  2. Run the tool-call loop end to end
  3. Define functions the model can use well
  4. Handle several calls, pick a tool choice, stream the arguments
  5. Add web search and file search
  6. Ilan Bigio and OpenAI Developers on writing and finding functions
  7. Common mistakes
  8. Try it yourself
  9. Next
  10. Check yourself
  11. Frequently asked questions
  12. Sources

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 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'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.

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.

{
  "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
}
From the function calling guide, read on 5 November 2026.
FieldWhat it holds
typeAlways function
nameThe function name the model calls, such as get_weather
descriptionWhen and how to use the function
parametersA JSON Schema for the arguments
strictWhether 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.

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. functions gives way to tools

    Chat Completions deprecates the functions parameter.

  2. tool_choice gains required

    The model must call at least one function.

  3. Parallel calls can be switched off

    In Chat Completions and the Assistants API.

  4. The Responses API ships

    A new API built for agents and tools.

  5. Strict mode with parallel calls

    Strict tool schemas now work when calls run in parallel.

  6. Custom tools

    Tool calls with free-form input and output instead of JSON.

  7. Tool search

    Models can defer large sets of tools until they need them.

  8. GPT-6 Astra

    Its tool calls need the Responses API, which also gains async tool calling.

Source: OpenAI Docs: Changelog (read November 5, 2026)

From the function calling guide, read on 5 November 2026.
tool_choiceWhat 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.

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)
From the web search and file search guides, read on 5 November 2026.
ToolWho runs itWhat you set upWhat comes back
FunctionYour applicationA name, a description and a JSON SchemaA function_call item you answer with an output
web_searchOpenAIOne entry in tools, plus optional filtersA web_search_call item and a message with URL citations
file_searchOpenAIUploaded files in a vector storeA 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 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, 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 (read November 5, 2026)
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.
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.
OpenAI Developers on X, 5 March 2026
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 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 explains the protocol, and the MCP course 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?

    Show answer

    C. Your application code 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?

    Show answer

    D. additionalProperties is false and every field is in required 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?

    Show answer

    A. Zero or one 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?

    Show answer

    B. The call_id 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 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

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

Share this lesson

See agents that call real tools

Browse AI agents other developers list on Mindket, read which tools each one uses, then list one of your own.

Browse AI agents

This lesson as Markdown · OpenAI API course: Responses, tools, structured output and agents · All courses