GPT-5 Function Calling Guide: Build AI Agents That Use Tools

Function calling is the bridge between a language model's reasoning ability and the real world. Without it, GPT-5 is a brilliant but isolated text generator. With it, GPT-5 becomes an agent that can search databases, call APIs, execute code, and interact with any system you connect it to. This guide covers everything from basic tool definitions to production-grade multi-step agent architectures using GPT-5's native function calling capabilities.

What Is Function Calling?

Function calling (also called tool use) lets you define functions that the model can choose to call during a conversation. You provide a JSON schema describing available tools. The model decides when to call a tool, which arguments to pass, and how to use the results. The model never executes code itself—it returns a structured request that your application interprets and executes.

The workflow is elegantly simple: (1) You send a message along with your tool definitions. (2) The model decides a tool call is needed and returns the function name and arguments. (3) Your code executes the function. (4) You send the result back to the model. (5) The model uses the result to formulate its response or calls another tool.

GPT-5 represents a significant leap over GPT-4 in function calling reliability. It handles complex nested schemas, supports parallel tool calls natively, and maintains consistent structured output across long conversations. In benchmarks, GPT-5 achieves 94% accuracy on function calling tasks versus 81% for GPT-4 Turbo.

Defining Your First Tool

Tools are defined as JSON objects with a name, description, and parameter schema. The description is critical—the model uses it to decide when and how to call the function:

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a city. "
                           "Returns temperature, humidity, "
                           "and conditions.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name, e.g. 'San Francisco'"
                    },
                    "units": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature units"
                    }
                },
                "required": ["city"],
                "additionalProperties": False
            }
        }
    }
]

The description field is the most important part. GPT-5 reads it to understand what the tool does and when to use it. Be specific: "Get current weather for a city" is better than "Weather API". If the tool should only be used in certain contexts, say so in the description.

Making a Function Calling Request

Here is a complete example using the OpenAI-compatible API available on the DrAI platform:

from openai import OpenAI

client = OpenAI(
    api_key="your-drai-api-key",
    base_url="https://ai.dr-ai.top/v1"
)

response = client.chat.completions.create(
    model="gpt-5",
    messages=[
        {"role": "system", "content": "You are a helpful assistant "
         "that can check the weather for users."},
        {"role": "user", "content": "What's the weather like in Tokyo?"}
    ],
    tools=tools,
    tool_choice="auto"  # Let the model decide
)

message = response.choices[0].message

if message.tool_calls:
    for tool_call in message.tool_calls:
        print(f"Model wants to call: {tool_call.function.name}")
        print(f"Arguments: {tool_call.function.arguments}")
        # Output:
        # Model wants to call: get_weather
        # Arguments: {"city": "Tokyo", "units": "celsius"}

Executing Tools and Returning Results

When the model requests a tool call, you execute it and feed the result back. This completes the function calling loop:

import json

def get_weather(city: str, units: str = "celsius") -> dict:
    """Your actual weather API call goes here."""
    # Example: call OpenWeatherMap, WeatherAPI, etc.
    return {
        "city": city,
        "temperature": 22 if units == "celsius" else 72,
        "humidity": 65,
        "conditions": "Partly cloudy",
        "units": units
    }

# Build conversation history
messages = [
    {"role": "system", "content": "You are a helpful weather assistant."},
    {"role": "user", "content": "What's the weather like in Tokyo?"}
]

# First call - model decides to use a tool
response = client.chat.completions.create(
    model="gpt-5",
    messages=messages,
    tools=tools
)
assistant_message = response.choices[0].message
messages.append(assistant_message)

# Execute requested tool calls
for tool_call in assistant_message.tool_calls:
    func_name = tool_call.function.name
    args = json.loads(tool_call.function.arguments)

    if func_name == "get_weather":
        result = get_weather(**args)
    else:
        result = {"error": f"Unknown function: {func_name}"}

    # Append tool result to conversation
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(result)
    })

# Second call - model uses tool results to answer
final_response = client.chat.completions.create(
    model="gpt-5",
    messages=messages,
    tools=tools
)
print(final_response.choices[0].message.content)
# "The weather in Tokyo is currently 22°C, partly cloudy
# with 65% humidity."

Parallel Tool Calls

GPT-5 can call multiple tools in a single turn. This dramatically speeds up multi-step tasks. If a user asks "Compare the weather in Tokyo, London, and New York," the model can call get_weather three times simultaneously:

# GPT-5 returns multiple tool_calls in one response
response = client.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content":
        "Compare weather in Tokyo, London, and New York"}],
    tools=tools,
    parallel_tool_calls=True  # GPT-5 supports this
)

# response.choices[0].message.tool_calls contains
# 3 calls: get_weather(Tokyo), get_weather(London),
# get_weather(New York) - all in one turn

Execute all calls, append all results, then send back. The model synthesizes the results into a comparison.

Building a Multi-Step Agent Loop

Real-world agents do not stop after one tool call. They reason, act, observe, and iterate. This is the ReAct (Reasoning + Acting) pattern. Here is a production-ready agent loop:

def run_agent(client, model, messages, tools, max_steps=10):
    """Multi-step agent loop with function calling."""
    for step in range(max_steps):
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
            tool_choice="auto"
        )

        msg = response.choices[0].message
        messages.append(msg)

        # If no tool calls, the agent is done
        if not msg.tool_calls:
            return msg.content

        # Execute each tool call
        for tool_call in msg.tool_calls:
            func_name = tool_call.function.name
            args = json.loads(tool_call.function.arguments)

            try:
                result = execute_tool(func_name, args)
            except Exception as e:
                result = {"error": str(e)}

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result)
            })

    return "Agent reached maximum steps without completing."

def execute_tool(name, args):
    """Dispatch to registered tool functions."""
    TOOL_REGISTRY = {
        "get_weather": get_weather,
        "search_web": search_web,
        "calculate": calculate,
        "get_stock_price": get_stock_price,
    }
    if name not in TOOL_REGISTRY:
        raise ValueError(f"Unknown tool: {name}")
    return TOOL_REGISTRY[name](**args)

Structured Outputs: Beyond Function Calling

GPT-5's structured outputs feature guarantees that responses conform to your JSON schema. This is essential when you need reliable, parseable output for downstream systems:

from pydantic import BaseModel

class CustomerAnalysis(BaseModel):
    sentiment: str
    """positive, negative, or neutral"""
    urgency: int
    """1-5 priority level"""
    key_issues: list[str]
    suggested_actions: list[str]
    summary: str

response = client.beta.chat.completions.parse(
    model="gpt-5",
    messages=[{"role": "user", "content": customer_email}],
    response_format=CustomerAnalysis
)

analysis = response.choices[0].message.parsed
# analysis.sentiment = "negative"
# analysis.urgency = 4
# analysis.key_issues = ["billing error", "slow response"]
# analysis.suggested_actions = ["refund duplicate charge",
#   "escalate to billing team"]

Unlike function calling (where the model optionally calls tools), structured outputs guarantees the entire response matches your schema. Use function calling for actions, structured outputs for data extraction.

Advanced Patterns

Conditional Tool Choice

You can force or restrict tool usage with tool_choice:

# Force a specific tool
"tool_choice": {"type": "function",
    "function": {"name": "get_weather"}}

# Force any tool (must call exactly one)
"tool_choice": "required"

# No tools allowed for this turn
"tool_choice": "none"

# Let the model decide (default)
"tool_choice": "auto"

Streaming with Function Calls

GPT-5 supports streaming even when tool calls are involved. Stream chunks contain incremental tool call data that you can assemble:

stream = client.chat.completions.create(
    model="gpt-5",
    messages=messages,
    tools=tools,
    stream=True
)

tool_calls_accumulator = {}
for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.tool_calls:
        for tc in delta.tool_calls:
            idx = tc.index
            if idx not in tool_calls_accumulator:
                tool_calls_accumulator[idx] = {
                    "id": "", "name": "", "args": ""
                }
            if tc.id:
                tool_calls_accumulator[idx]["id"] = tc.id
            if tc.function:
                if tc.function.name:
                    tool_calls_accumulator[idx]["name"] += tc.function.name
                if tc.function.arguments:
                    tool_calls_accumulator[idx]["args"] += tc.function.arguments

Best Practices for Tool Design

The quality of your tool definitions directly affects agent performance. Follow these principles:

Write clear descriptions. The model cannot read your code. It relies entirely on the description to know when and how to use a tool. "Searches the company knowledge base for product documentation" is far better than "search_kb".

Use enums for constrained values. If a parameter accepts only certain values, use an enum. This prevents the model from passing invalid arguments: "priority": {"type": "string", "enum": ["low", "medium", "high", "critical"]}.

Keep parameter schemas simple. Avoid deeply nested objects. If a tool needs complex input, consider splitting it into multiple simpler tools. GPT-5 handles flat schemas more reliably.

Return structured data. Return JSON from your tools, not free-form text. The model parses structured data more accurately. Include error fields: {"error": "City not found", "suggestion": "Did you mean 'Tokyo'?"}.

Limit the number of tools. GPT-5 handles up to 128 tools, but accuracy degrades beyond 15-20. If you have more, use a two-stage approach: first call a "router" that selects relevant tools, then call the main model with only those tools.

Error Handling and Edge Cases

Production agents must handle failures gracefully. Common scenarios:

Invalid arguments: The model passes arguments that do not match your function's expectations. Validate and return descriptive errors so the model can self-correct: {"error": "Invalid date format. Use YYYY-MM-DD."}.

Tool execution failures: APIs go down, databases timeout. Catch exceptions and return error objects. The model will retry or inform the user.

Infinite loops: The model repeatedly calls the same tool with identical arguments. Implement a loop counter and duplicate detection. If the same call appears 3 times, break and report.

Hallucinated function names: Rare with GPT-5 but possible. Always validate function names against your registry and reject unknown calls.

Cost and Performance Optimization

Function calling consumes tokens for tool definitions and results. Each tool schema adds 50-200 tokens to every request. For strategies to minimize this overhead, see our Token Optimization Techniques guide. Key tips: use prompt caching for static tool definitions, minimize description length, batch independent tool calls, and choose the right model for each step—use GPT-5-mini for simple tool routing and GPT-5 for complex reasoning.

For a broader comparison of agent architectures, see our AI Agent Frameworks Comparison article covering AutoGPT, CrewAI, and LangGraph.

Pro tip: Log every tool call with timing data. This helps you identify slow tools, optimize the critical path, and debug unexpected agent behavior. Tools that take more than 3 seconds should be candidates for optimization or caching.

Real-World Example: Customer Support Agent

Let us build a customer support agent that can look up orders, process refunds, and escalate tickets. This demonstrates multiple tools working together:

SUPPORT_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "lookup_order",
            "description": "Look up a customer's order by order ID "
                           "or email. Returns order details, status, "
                           "and items.",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {"type": "string"},
                    "email": {"type": "string"}
                }
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "process_refund",
            "description": "Issue a refund for an order. Requires "
                           "order_id and reason. Only available for "
                           "orders less than 30 days old.",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {"type": "string"},
                    "amount": {"type": "number",
                        "description": "Refund amount in USD"},
                    "reason": {"type": "string",
                        "enum": ["defective", "not_as_described",
                                 "changed_mind", "shipping_issue"]}
                },
                "required": ["order_id", "amount", "reason"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "escalate_to_human",
            "description": "Escalate the conversation to a human "
                           "agent. Use when the issue cannot be "
                           "resolved automatically.",
            "parameters": {
                "type": "object",
                "properties": {
                    "reason": {"type": "string"},
                    "priority": {"type": "string",
                        "enum": ["normal", "urgent", "critical"]}
                },
                "required": ["reason"]
            }
        }
    }
]

# The agent can now: look up an order -> verify it's eligible
# -> process refund -> confirm to customer
# All autonomously through the agent loop

Notice how each tool description includes constraints ("Only available for orders less than 30 days old"). This guides the model to check conditions before calling, reducing invalid attempts. For security considerations when building agents with powerful tools, see our LLM Security Best Practices guide.

Conclusion

Function calling transforms GPT-5 from a chatbot into a capable agent. Start with simple single-tool implementations, then graduate to multi-step agent loops with parallel calls. The key to success is clear tool descriptions, robust error handling, and thoughtful tool design that guides the model toward correct usage.

The patterns in this guide work identically across all OpenAI-compatible providers. The DrAI platform provides a unified API gateway where you can experiment with GPT-5, Claude Opus 4, DeepSeek R1, and 40+ other models with the same function calling interface. Check our pricing for pay-as-you-go rates with no minimum commitment.

Start Building AI Agents with DrAI →

📚 Related Reading

Claude 4 vs GPT-5: Full Benchmark Comparison 2026Comprehensive 2026 benchmark comparison of Claude 4 vs GPT-5 across reasoning, c... GPT-5 API Pricing Comparison 2026: Cheapest OpenAI API ProviderComplete GPT-5 API pricing comparison across OpenAI, DrAI, Azure, and proxy prov... GPT-5 Vision API Guide: Image Analysis, OCR, and Multimodal AIComplete guide to GPT-5 Vision API for image analysis, OCR, and multimodal AI. L... AI Agent Frameworks 2026: AutoGPT vs CrewAI vs LangGraph ComparedComprehensive comparison of AI agent frameworks in 2026. AutoGPT vs CrewAI vs La...
🌐 English