All insights

Model APIs

OpenAI Function Calling Guide: A Python Tutorial with the Responses API

Learn how OpenAI function calling works with a Python example: define a tool, execute returned calls in your application, send results back, and choose when tools may be used.

Illustration for Openai function calling guide

OpenAI function calling lets a model request a function you describe and provide its arguments. Your application—not the model—executes the code, checks whether the action is allowed, and returns the result. This guide walks through that loop in Python using the Responses API. OpenAI’s current guide says GPT-6 Astra requires the Responses API for tool calling. (developers.openai.com)

What function calling does

You provide a function definition, including its name, description, and argument schema. The model may return a function call when it determines that a tool can help with the request. Your application then runs the corresponding code and returns its result with the call’s ID. The model can use that result to answer or request another tool call. OpenAI also calls this tool calling. (developers.openai.com)

The key boundary: a model-generated tool call is a request, not authorization to execute arbitrary code. Your application selects the implementation and decides whether the requested action is allowed.

Before you start

  • Python and the OpenAI Python SDK (openai).
  • An API key in the OPENAI_API_KEY environment variable. Keep it server-side or in your local environment; do not put it in browser code or commit it to source control.
  • Access to gpt-6-astra. OpenAI’s function-calling guide says GPT-6 Astra requires the Responses API for tool calling. Check your project’s model access before running the example. (developers.openai.com)

Install the SDK and set your key, for example:

python -m pip install openai
export OPENAI_API_KEY="your-api-key"

In Windows PowerShell, set the variable for the current session with $env:OPENAI_API_KEY="your-api-key".

Complete example: define, call, execute, return

Save this as function_calling.py and run it with python function_calling.py. The order database is mock data; this example does not connect to a real store.

import json
from openai import OpenAI

client = OpenAI()  # Reads OPENAI_API_KEY from the environment.
MODEL = "gpt-6-astra"
INSTRUCTIONS = (
    "Help the user check an order. Use get_order_status for an order lookup. "
    "Do not claim an order status unless the tool result provides it."
)

# Demo data only. A real service should fetch records from its own backend
# and scope every lookup to the authenticated user or tenant.
ORDERS = {
    "12345": {"status": "shipped", "estimated_delivery": "Friday"},
}

def get_order_status(order_id: str) -> dict:
    order = ORDERS.get(order_id)
    if order is None:
        return {"found": False}
    return {"found": True, **order}

TOOLS = [
    {
        "type": "function",
        "name": "get_order_status",
        "description": "Look up the status of an order by its order ID.",
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {
                    "type": "string",
                    "description": "The order ID supplied by the customer.",
                }
            },
            "required": ["order_id"],
            "additionalProperties": False,
        },
        "strict": True,
    }
]

response = client.responses.create(
    model=MODEL,
    instructions=INSTRUCTIONS,
    input="Where is order 12345?",
    tools=TOOLS,
    tool_choice="auto",
)

# Handle every function call in the response, then return the results.
# The round limit prevents an unbounded tool-call loop.
rounds = 0
while True:
    calls = [item for item in response.output if item.type == "function_call"]
    if not calls:
        break

    rounds += 1
    if rounds > 5:
        raise RuntimeError("Tool-call round limit reached")

    outputs = []
    for call in calls:
        # Dispatch only functions this application explicitly supports.
        if call.name != "get_order_status":
            raise ValueError(f"Unsupported function: {call.name}")

        args = json.loads(call.arguments)
        if (
            not isinstance(args, dict)
            or set(args) != {"order_id"}
            or not isinstance(args["order_id"], str)
        ):
            raise ValueError("Invalid arguments for get_order_status")

        result = get_order_status(args["order_id"])
        outputs.append(
            {
                "type": "function_call_output",
                "call_id": call.call_id,
                "output": json.dumps(result),
            }
        )

    response = client.responses.create(
        model=MODEL,
        instructions=INSTRUCTIONS,
        previous_response_id=response.id,
        input=outputs,
        tools=TOOLS,
    )

print(response.output_text)

What happens in the example

  1. The app describes a tool. The definition gives the model a function name, description, and JSON Schema for its arguments. It sets strict: true, marks order_id as required, and disallows extra properties. OpenAI notes that strict schema adherence depends on a supported model and request configuration, and on meeting strict-mode schema requirements. (help.openai.com)
  2. The model may request the function. The code checks the response’s output items for function calls and reads the function name, JSON arguments, and call ID.
  3. The app validates and executes the call. It accepts only the known function and checks the parsed argument shape before running local code. A real application also needs to verify the user’s authorization and data scope.
  4. The app returns the result. Each function_call_output includes the matching call_id. The follow-up request continues from previous_response_id and resends both the tool definition and the same instructions.
  5. The model completes the turn. When the response contains no further function calls, response.output_text prints the final text. Given the mock record, the model has a result stating that the order is shipped and its estimated delivery is Friday; the exact wording is generated, not guaranteed.

The function-result loop is application code. The API does not run get_order_status for you. OpenAI describes tool outputs as plain text or structured JSON associated with the corresponding call ID. (developers.openai.com)

Choosing when a function can be called

Set tool_choice in the request to control tool selection:

# Let the model decide whether to call tools.
tool_choice = "auto"

# Require at least one tool call.
tool_choice = "required"

# Constrain the model to this named function.
tool_choice = {"type": "function", "name": "get_order_status"}

With auto, the model can make zero, one, or multiple function calls. required requires one or more calls, but does not specify which function. The named-function form forces exactly one call to that function. These settings control tool selection; they do not authorize a business action or replace application-side checks. For a policy like “look up an order when appropriate, otherwise send a message,” implement and test the fallback behavior explicitly rather than assuming a forced-function setting supplies it. The call-count distinctions are reproduced in an OpenAI Developer Community reply linking to the function-calling guide; consult the current guide for constraints applicable to your model and tools. (community.openai.com)

Older examples may use Chat Completions fields such as functions and function_call. Do not mix those with the Responses API request and result formats shown here. (developers.openai.com)

Production checklist

  • Allowlist functions. Map each accepted function name to application code. Reject unknown names; never execute model-supplied text as code or as an unrestricted URL.
  • Validate at the application boundary. Strict schemas can constrain generated arguments when supported and correctly configured. They do not prove that a value is true, a record exists, or the user may access it. Keep server-side validation. (help.openai.com)
  • Authorize consequential operations. Use authenticated application context to determine identity and permissions; do not trust the model to supply them. Require confirmation or review where appropriate for the risk.
  • Handle failures deliberately. Decide what to do with malformed arguments, missing records, timeouts, and downstream errors. Avoid returning credentials or sensitive internal details to the model.
  • Bound the workflow. Set a limit for tool-call rounds and decide what happens when a tool fails or the model continues requesting calls.
  • Test end to end. Test a valid lookup, an unknown order, invalid arguments, an unauthorized lookup, a tool timeout, and any multi-step path. Check both tool execution and the final response.

These are application-design recommendations, not a complete security architecture. Adapt them to your data, users, and the consequences of the actions your tools can perform.

Quick recap

Define the function tool in the request, inspect the returned call, validate it, and execute it in your application. Return a function_call_output with the matching call_id, then handle the next response or call. Use the Responses API syntax consistently, and keep authorization and execution under application control. (developers.openai.com)

Sources

Function calling | OpenAI API

Create chat completion | OpenAI API Reference

OpenAI Python SDK: Response function tool call output item

Function Calling in the OpenAI API | OpenAI Help Center

Tool choice = auto sending content and tool_calls - OpenAI Developer Community

Contact us