Function calling architecture in the Responses API
On this page
Updated November 2, 2026 based on OpenAI documentation: the Code Interpreter tool now requires a
containerparameter (added to all examples), input text parts use theinput_texttype, and web search uses theweb_searchtool (web_search_previewis now a legacy tool without domain filters). Generated files are retrieved using thefichiers_generesandtelechargerfunctions from Lesson 10. Sources: Code Interpreter · Web search.
Function calling architecture in the Responses API
Function calling is the core mechanism that lets OpenAI models interact with the outside world. In the Responses API, this mechanism has been redesigned to be more robust, more predictable, and natively integrated with built-in tools like Web Search and Code Interpreter.
The fundamental principle
The model never does anything directly. It emits structured intentions that your code intercepts and executes. Stated that way, it might sound like a technical limitation; in reality, it’s the design choice that makes everything else possible.
Consider the alternative. If the model executed actions itself, you’d have to hand over your database credentials, API keys, and write permissions—and you’d have no point at which to verify anything. With decoupling, every intention passes through your code: you validate arguments, enforce business rules, and refuse if necessary. The model proposes, your code disposes, and that boundary is what lets you put function calling into production without losing control.
from openai import OpenAI
client = OpenAI()
# Declare a custom tool
tools = [{
"type": "function",
"name": "rechercher_client",
"description": "Search for a client in the CRM by name or email",
"parameters": {
"type": "object",
"properties": {
"terme": {
"type": "string",
"description": "Client name, email, or ID"
},
"champ": {
"type": "string",
"enum": ["nom", "email", "id"],
"description": "Field to search in"
}
},
"required": ["terme"]
}
}]
response = client.responses.create(
model="gpt-5.6-terra",
input="Find the client Jean Dupont",
tools=tools
)
Anatomy of a response
A response doesn’t contain either text or tool calls: output is a heterogeneous list that can mix both. The habit to build from your first line of code is to iterate over this list rather than reading output[0]—a model that explains its reasoning before calling a function produces exactly that case, and code that reads the first element will break the day it starts adding commentary.
for item in response.output:
if item.type == "function_call":
print(f"Call ID : {item.call_id}")
print(f"Function : {item.name}")
print(f"Args : {item.arguments}")
The call_id is essential: it links the call to its result when you send the response back to the model. On a single call, you barely notice it; on three parallel calls, it’s the only thing preventing you from attributing Lyon’s weather to the request about Marseille. Always return the result with the call_id you received, never with an ID you reconstruct on your side.
The complete loop
This four-step cycle is invariant: it holds for one function or twenty, for a simple call or an agent chaining ten tools. All the complexity you’ll encounter later—parallelism, errors, fallbacks—consists of instrumenting this loop, never replacing it.
import json
# 1. First call with declared tools
response = client.responses.create(
model="gpt-5.6-terra",
input="What is the balance for client [email protected]?",
tools=tools
)
# 2. Detect and execute function calls
resultats = []
for item in response.output:
if item.type == "function_call":
if item.name == "rechercher_client":
args = json.loads(item.arguments)
resultat = votre_crm.rechercher(args["terme"])
resultats.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(resultat)
})
# 3. Send results back to the model
response_finale = client.responses.create(
model="gpt-5.6-terra",
input=response.output + resultats,
tools=tools
)
# 4. The model formulates its natural response
print(response_finale.output_text)
Key differences from Chat Completions
The Responses API brings several architectural improvements.
Native handling of built-in tools
Built-in tools are declared exactly like your functions, but they execute on OpenAI’s side. The practical upshot is pleasant: you don’t write any loop for them. The model calls, gets the result, and continues, all in a single round trip. The loop described above concerns only your functions—the ones running on your machine.
# Mix built-in and custom tools
tools = [
{"type": "web_search"},
{"type": "code_interpreter", "container": {"type": "auto"}},
{
"type": "function",
"name": "ma_fonction",
"description": "My custom function",
"parameters": {"type": "object", "properties": {}}
}
]
Stable call identifiers
Every function call receives a unique call_id that lets you trace precisely which result corresponds to which call, even with parallel calls.
Simplified flow
No need to manually manage assistant and tool roles in messages. The input field directly accepts concatenating previous outputs with function results.
When does the model call a function?
The model decides to call a function when the user’s request requires information it doesn’t have, when the function’s description matches the identified need, and when the required parameters can be inferred from context.
Three conditions must therefore be met simultaneously, and that’s useful for debugging: when a function isn’t called even though it should be, the cause is almost always the second one—a description that doesn’t cover the way the user phrased their request. The tool_choice parameter lets you force behavior when the description isn’t enough:
# Let the model decide
tool_choice = "auto"
# Force a call to a specific function
tool_choice = {"type": "function", "name": "rechercher_client"}
# Prohibit all function calls
tool_choice = "none"
# Require at least one call without specifying which
tool_choice = "required"
Key takeaways
- Function calling is an intent protocol, not an execution protocol
- The call-execute-return loop is the fundamental pattern
- Built-in and custom tools coexist in the same
toolsarray call_idensures traceability for each calltool_choicelets you finely control the model’s behavior