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
openaiPython 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 sameparallel function calling gpt-4o examplecode 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_callin 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_callsarrive 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.