n4nAI

Parallel function calling with GPT-4o: an example

Hands-on Python tutorial for parallel function calling with GPT-4o: define tools, trigger multiple calls, run them concurrently, and merge results.

n4n Team2 min read423 words

Audio narration

Coming soon — every post will get a voice note here.

When you need a model to fetch independent pieces of data in one turn, a parallel function calling gpt-4o example is the fastest way to see how the API behaves. This tutorial builds a runnable Python script that issues two tool calls from a single GPT-4o response and executes them concurrently, then merges the results into a final answer.

Prerequisites

  • Python 3.10 or newer
  • openai Python package (>=1.10.0)
  • An API key from OpenAI, or an OpenAI-compatible gateway. If you use n4n.ai, set base_url="https://api.n4n.ai/v1" and use your n4n.ai key; the same parallel function calling gpt-4o example code works unchanged.
pip install openai

Define the client and tools

We start by creating the client and declaring two simple tools. GPT-4o reads the tools array and decides whether to call one, both, or neither.

from openai import OpenAI

client = OpenAI()  # or client = OpenAI(base_url="https://api.n4n.ai/v1", api_key="sk-...")

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Return current weather for a given city.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name, e.g. Tokyo"}
                },
                "required": ["city"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_stock_price",
            "description": "Return the latest stock price for a ticker symbol.",
            "parameters": {
                "type": "object",
                "properties": {
                    "symbol": {"type": "string", "description": "Ticker, e.g. AAPL"}
                },
                "required": ["symbol"],
            },
        },
    },
]

The mock implementations simulate latency so the concurrency win is visible.

import time

def get_weather(city: str) -> str:
    time.sleep(1.0)  # simulate network
    return f"Sunny in {city}, 22°C"

def get_stock_price(symbol: str) -> str:
    time.sleep(1.0)
    return f"{symbol} last traded at $150.00"

Trigger parallel tool calls

We send a prompt that clearly needs both tools. GPT-4o will emit an assistant message containing a tool_calls list with two entries.

messages = [
    {"role": "user", "content": "What is the weather in Tokyo and the AAPL stock price?"}
]

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

assistant_msg = resp.choices[0].message
print(f"Tool calls requested: {len(assistant_msg.tool_calls)}")
for tc in assistant_msg.tool_calls:
    print(f"  {tc.function.name}({tc.function.arguments})")

Expected output at this checkpoint:

Tool calls requested: 2
  get_weather({"city": "Tokyo"})
  get_stock_price({"symbol": "AAPL"})

If you see only one call, the prompt was ambiguous or the model guessed. Rephrase to force independence.

Execute the tools concurrently

The two calls are independent, so running them sequentially wastes a full second. We use ThreadPoolExecutor to map each tool_call to a worker.

import json
from concurrent.futures import ThreadPoolExecutor

def run_tool_call(tc):
    """Dispatch a single tool call and return (tool_call_id, result)."""
    args = json.loads(tc.function.arguments)
    if tc.function.name == "get_weather":
        return tc.id, get_weather(**args)
    elif tc.function.name == "get_stock_price":
        return tc.id, get_stock_price(**args)
    return tc.id, "unknown tool"

tool_results = {}
with ThreadPoolExecutor(max_workers=2) as ex:
    futures = [ex.submit(run_tool_call, tc) for tc in assistant_msg.tool_calls]
    for fut in futures:
        tc_id, result = fut.result()
        tool_results[tc_id] = result

print(f"Fetched {len(tool_results)} results in ~1s instead of 2s")

Expected output:

Fetched 2 results in ~1s instead of 2s

The tool_results dict keys are the id values from the assistant’s tool_calls. Those IDs must be echoed back in the next request.

Send results back to GPT-4o

We append the assistant message (with its tool_calls) to the conversation, then add one tool message per result. The model uses these to synthesize a final answer.

messages.append(assistant_msg)  # contains the tool_calls

for tc_id, result in tool_results.items():
    messages.append({
        "role": "tool",
        "tool_call_id": tc_id,
        "content": result,
    })

final = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
)

print(final.choices[0].message.content)

Expected final output:

The weather in Tokyo is sunny with a temperature of 22°C. Apple (AAPL) last traded at $150.00.

Full parallel function calling gpt-4o example

Below is the consolidated script. Copy it, set your key, and run.

from openai import OpenAI
import json
import time
from concurrent.futures import ThreadPoolExecutor

client = OpenAI()  # swap base_url for n4n.ai if desired

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Return current weather for a given city.",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_stock_price",
            "description": "Return the latest stock price for a ticker symbol.",
            "parameters": {
                "type": "object",
                "properties": {"symbol": {"type": "string"}},
                "required": ["symbol"],
            },
        },
    },
]

def get_weather(city: str) -> str:
    time.sleep(1.0)
    return f"Sunny in {city}, 22°C"

def get_stock_price(symbol: str) -> str:
    time.sleep(1.0)
    return f"{symbol} last traded at $150.00"

def run_tool_call(tc):
    args = json.loads(tc.function.arguments)
    if tc.function.name == "get_weather":
        return tc.id, get_weather(**args)
    elif tc.function.name == "get_stock_price":
        return tc.id, get_stock_price(**args)
    return tc.id, "unknown tool"

messages = [
    {"role": "user", "content": "What is the weather in Tokyo and the AAPL stock price?"}
]

resp = client.chat.completions.create(
    model="gpt-4o", messages=messages, tools=tools, tool_choice="auto"
)
assistant_msg = resp.choices[0].message

tool_results = {}
with ThreadPoolExecutor(max_workers=2) as ex:
    futs = [ex.submit(run_tool_call, tc) for tc in assistant_msg.tool_calls]
    for f in futs:
        tc_id, res = f.result()
        tool_results[tc_id] = res

messages.append(assistant_msg)
for tc_id, res in tool_results.items():
    messages.append({"role": "tool", "tool_call_id": tc_id, "content": res})

final = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
print(final.choices[0].message.content)

This parallel function calling gpt-4o example shows the minimal moving parts: declare tools, let the model batch calls, run them concurrently, and feed results back.

Production notes

A few things the toy script ignores:

  • Timeouts: wrap run_tool_call in a timeout, or the whole request stalls.
  • Errors: a tool may raise. Catch exceptions and return a string error so the model can recover.
  • Streaming: if you stream the assistant message, tool_calls arrive in delta fragments; you must accumulate them before executing.
  • Routing: when you run this against an OpenAI-compatible gateway such as n4n.ai, the same code gains automatic fallback when a provider is rate-limited or degraded, without changing the call site.

The core pattern stays identical: one model round-trip to get tool_calls, concurrent execution, one round-trip to summarize. Once that clicks, multi-step agent loops are straightforward extensions.

Tagsgpt-4oparallel-function-callingtutorialtool-use

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All parallel & multi-step tool use posts →