Parallel Function Calling in the Gemini API
8 min read · updated August 11, 2026
Gemini can decide that answering you requires three tool calls that do not depend on each other, and emit all three in one model turn. The API shape for that is simple — several functionCall parts in one content — and the handling rule is strict enough that a loop written for the single-call case will hang or error the first time it happens.
What a parallel call looks like
A Content holds an array of Part. Nothing restricts that array to one functionCall, and when the model has several independent things to look up it uses that freedom. Given three declared functions and a prompt like “set up the party: play some music, turn the lights down and open the blinds”, the documented response shape is:
{
"candidates": [{
"content": {
"role": "model",
"parts": [
{"functionCall": {"name": "play_music", "args": {"genre": "jazz", "volume": 0.4}}},
{"functionCall": {"name": "set_lights", "args": {"brightness": 0.2}}},
{"functionCall": {"name": "open_blinds", "args": {"percent": 100}}}
]
},
"finishReason": "STOP"
}]
}Note finishReason: STOP. Gemini does not use a distinct finish reason for “I want to call a tool” the way some APIs do — the turn ended normally and the content happens to be function calls. Branching on finishReason to detect tool use is therefore the wrong test. Branch on the presence of a functionCall part.
The behaviour requires no opt-in. Declaring several functions in tools[].functionDeclarations is enough; the model fans out when it judges the calls independent. Google documents the pattern in the Gemini function calling guide.
The rule: all responses in one turn
Here is the part that breaks naive implementations. Having received three calls, you must append one content to the conversation containing three functionResponse parts. Not three separate turns. Not one response followed by another request.
{"role": "user", "parts": [
{"functionResponse": {"name": "play_music",
"response": {"status": "playing", "track": "So What"}}},
{"functionResponse": {"name": "set_lights",
"response": {"status": "ok", "brightness": 0.2}}},
{"functionResponse": {"name": "open_blinds",
"response": {"status": "ok", "percent": 100}}}
]}The reason is structural rather than arbitrary. The conversation is a strict alternation of roles, and every function call in a model turn must be resolved before the next model turn begins. Send back one response for a turn that made three calls and you have a history with two dangling calls in it; the API will reject the request or the model will behave as though the other two never happened.
The practical shape of the loop is therefore: collect every functionCall part from the turn, execute them — genuinely concurrently, since the model has told you they are independent — gather every result, and send one turn back. The full three-turn transcript for the single-call case is walked through in sending function responses back in a multi-turn conversation.
Matching responses to calls
Matching is by name. The name in each functionResponse must equal the name of the functionCall it answers, and the order of the response parts should mirror the order of the calls.
That has a consequence you need to design around: if the model calls the same function twice with different arguments — two get_weather calls for two cities, which is a very common parallel pattern — you have two calls with identical names. Keep the calls in an ordered list and produce responses positionally rather than building a dictionary keyed by name, which would collapse them. A map keyed by function name is the single most common bug in hand-written Gemini tool loops.
id to a functionCall and expect it echoed on the matching functionResponse. If the field is present on the calls you receive, echo it; positional matching remains the safe fallback when it is absent.What you can and cannot control
There is no boolean to disable fan-out the way some providers offer. What you have is toolConfig.functionCallingConfig, with a mode:
AUTO— the default. The model decides whether to call anything, and how many.ANY— the model must emit a function call rather than prose. Combined withallowedFunctionNamesyou can restrict it to a named subset. See forcing a tool call with mode ANY.NONE— no function calls at all, even though the declarations are present.
Restricting allowedFunctionNames to a single function is the closest thing to forcing serial behaviour, and it works by removing the alternatives rather than by capping the count. The honest conclusion is that your executor has to be able to handle N calls, and the cheapest way to get there is to write the N-call path first and let N=1 be a special case of it.
Failure modes worth handling
- A call fails. Do not drop it. Send a
functionResponsewhoseresponseobject describes the error —{"error": "timeout contacting lighting hub"}— and let the model decide whether to retry, work around it or tell the user. A missing response is a protocol violation; a response describing a failure is ordinary information. - Calls that are not actually independent. The model can be wrong about that. If two of the calls in one turn conflict — two writes to the same record — your executor, not the model, is responsible for serialising or refusing them.
MALFORMED_FUNCTION_CALL. A documentedfinishReasonvalue meaning the model produced a call the service could not parse. There is nofunctionCallpart to execute; retry, or tighten the parameter schema.- Mixed text and calls. A turn can contain a
textpart alongside the calls. Do not assumeparts[0]is a function call — iterate the array and dispatch on which key each part carries.