Gemini — Function calling (tools[].functionDeclarations)
Status: DOCUMENTED + LIVE_VERIFIED (2026-09-18 run — see "Live verification" section at the end)
Sources:
- https://ai.google.dev/gemini-api/docs/generate-content/function-calling (generateContent flavour) · https://ai.google.dev/gemini-api/docs/function-calling (Interactions flavour)
- https://ai.google.dev/gemini-api/docs/generate-content/thought-signatures · https://ai.google.dev/gemini-api/docs/thinking#signatures
- https://ai.google.dev/gemini-api/docs/tool-combination · https://ai.google.dev/gemini-api/docs/live-tools · https://ai.google.dev/api/live
- https://ai.google.dev/api/generate-content (#Tool #FunctionDeclaration #FunctionCallingConfig #FunctionCall #FunctionResponse #FunctionResponsePart #Behavior #Scheduling)
- SDK: https://github.com/googleapis/python-genai (README: AFC, MCP) · https://github.com/googleapis/js-genai
Last verified: 2026-09-18 (docs only)
1. Flow
- Declare functions in
tools[].functionDeclarations[]. - Model answers with
functionCallparts{name, args, id}— Gemini 3 always returns anid; the firstfunctionCallof each step carries athoughtSignature. - You execute the function(s) and append a content (role
user— the reference also mentions rolefunction) withfunctionResponseparts{name, response, id}. - Model produces text or further calls (compositional). Repeat.
Check candidates[].finishReason: STOP, MALFORMED_FUNCTION_CALL, UNEXPECTED_TOOL_CALL (tool called but none enabled), TOO_MANY_TOOL_CALLS, MISSING_THOUGHT_SIGNATURE.
2. Declaration fields (FunctionDeclaration)
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | [A-Za-z0-9_.:-], ≤128 chars; SDK: must start with letter/underscore; guide: no spaces/periods/dashes |
description |
string | yes (REST ref) | Drives tool selection — be specific |
parameters |
Schema |
no | OpenAPI 3.03 subset: type, properties, required, enum, description, items, format, nullable, anyOf, propertyOrdering, minimum/maximum, minItems/maxItems, minLength/maxLength, pattern, default, example, title, minProperties/maxProperties. Type names may be upper-case (OBJECT, STRING). Param names ≤64 chars. Mutually exclusive with parametersJsonSchema |
parametersJsonSchema |
JSON Schema | no | Full JSON Schema object (additionalProperties, etc.). Mutually exclusive with parameters |
response / responseJsonSchema |
Schema / JSON Schema | no | Return-type description; mutually exclusive pair |
behavior |
UNSPECIFIED | BLOCKING | NON_BLOCKING |
no | Live API only; default BLOCKING |
Max 512 declarations per request (SDK docstring). Best practice: 10–20 active tools; low temperature; strong typing/enums; validate high-impact calls with the user.
3. Modes (toolConfig.functionCallingConfig)
mode |
Behaviour | allowedFunctionNames |
|---|---|---|
AUTO (default when only function declarations) |
call or natural-language reply | ignored |
ANY |
always a function call, schema-constrained; large/deep schemas may be rejected | restricts choice |
NONE |
never call (same as sending no declarations) | — |
VALIDATED |
call or text, calls schema-validated by constrained decoding; default and only allowed mode when built-in tools or structured outputs are combined / includeServerSideToolInvocations=true |
restricts choice |
MODE_UNSPECIFIED |
do not use |
Interactions API twin: generation_config.tool_choice: "auto"|"any"|"none"|"validated" or {"allowed_tools":{"mode":"any","tools":[...]}}.
4. Parallel and compositional calls
| Parallel | Compositional (sequential) | |
|---|---|---|
| Shape | several functionCall parts in one model content |
one call per step, chained across turns |
| Signatures (Gemini 3) | only the first functionCall part carries thoughtSignature |
each step's first call carries one; all must be returned |
| Reply | all functionResponse parts in one user content, after all calls; order may differ (matched by id/name); interleaving FC1,FR1,FC2,FR2 → 400 |
one response per step |
Supported per the guide table: Gemini 3.8/3.7/3.6/3.5 Flash, 3.5 Flash-Lite, 3.1 Pro Preview, 3.1 Flash-Lite, 2.5 Pro/Flash/Flash-Lite (function, parallel and compositional all ✔).
5. Thought signatures (mandatory on Gemini 3)
| Rule | Detail |
|---|---|
| Where | Part.thoughtSignature (base64) on the first functionCall of each step; on the last part when no call (Gemini 3); Gemini 2.5: on the first part of any type, optional to return |
| Must echo | Gemini 3: yes for the current turn (API walks back to the last user content with standard text; every step after it is validated) → HTTP 400 "Function call X in the N. content block is missing a thought_signature" |
| Non-FC parts | echo recommended, not validated |
| Don'ts | don't merge a signed part with an unsigned one; don't combine two signed parts; don't interleave parallel FC/FR |
| Injected history | dummy signatures "skip_thought_signature_validator" or "context_engineering_is_the_way_to_go" skip validation |
| Streaming | with no FC, the signature may arrive in an empty-text part — read until finishReason |
| SDKs | handled automatically when you append the full response content to history |
| OpenAI compat | signatures travel in extra_content (see thought-signatures guide) |
6. Function responses
| Field | Notes |
|---|---|
name |
required |
response |
required JSON object (any keys: output, result, error…) |
id |
echo functionCall.id (mandatory mapping on Gemini 3) |
parts[] |
FunctionResponsePart{inlineData{mimeType,data}} — multimodal responses (Gemini 3): image/png, image/jpeg, image/webp, application/pdf, text/plain; reference from response with {"$ref": "<inlineData.display_name>"} |
scheduling |
Live NON_BLOCKING only: SILENT | WHEN_IDLE (default) | INTERRUPT |
willContinue |
Live NON_BLOCKING only: generator-style multiple responses; false + empty response ends the call (add scheduling: SILENT to avoid triggering generation) |
7. Live API (BidiGenerateContent)
- Declare tools in
setup.tools; server sendstoolCall {functionCalls[{id,name,args}]}and may sendtoolCallCancellation {ids[]}on interruption; client answerstoolResponse {functionResponses[]}(session.send_tool_response). No automatic tool handling. behavior: NON_BLOCKING= asynchronous calling: default ongemini-3.8-live(BLOCKING still allowed), only mode ongemini-3.8-live-extended-thinking(BLOCKING = hard error, scheduling unsupported), unsupported ongemini-3.1-flash-live-preview(sync only), sync + async ongemini-2.5-flash-native-audio-preview-12-2025.- Only
googleSearchcan be combined with function declarations in Live.
8. Multi-tool (built-in + custom) — Gemini 3, Preview
Set toolConfig.includeServerSideToolInvocations: true; response contains toolCall/toolResponse (server tools), executableCode/codeExecutionResult (code execution) and functionCall parts, each with id and thoughtSignature. Echo every part unchanged, then append your functionResponse. Mode is VALIDATED. See index.md §4.
9. SDK conveniences
| Feature | Python google-genai |
JS @google/genai |
|---|---|---|
| Automatic function calling (AFC) | pass Python callables in config.tools; SDK declares (FunctionDeclaration.from_callable), executes, loops (automatic_function_calling.maximum_remote_calls default 10, disable, ignore_call_history; history in response.automatic_function_calling_history). Allowed param types: `int |
float |
| MCP | tools=[ClientSession] (experimental) |
tools: [mcpToTool(client)] (experimental) — see mcp.md |
| README warning | AFC will move from Models.generate_content to Chats in the next major version |
same |
10. Minimal examples
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash-lite:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY" -H "Content-Type: application/json" \
-d '{"contents":[{"role":"user","parts":[{"text":"Weather in Paris?"}]}],
"tools":[{"functionDeclarations":[{"name":"get_weather","description":"Weather for a city.",
"parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}]}],
"toolConfig":{"functionCallingConfig":{"mode":"ANY","allowedFunctionNames":["get_weather"]}}}'from google import genai
from google.genai import types
client = genai.Client() # GEMINI_API_KEY
decl = types.FunctionDeclaration(name="get_weather", description="Weather for a city.",
parameters_json_schema={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]})
r = client.models.generate_content(model="gemini-3.5-flash-lite", contents="Weather in Paris?",
config=types.GenerateContentConfig(tools=[types.Tool(function_declarations=[decl])]))
fc = r.candidates[0].content.parts[0]
# echo the model part (with thought_signature) then reply:
follow = client.models.generate_content(model="gemini-3.5-flash-lite", contents=[
types.Content(role="user", parts=[types.Part(text="Weather in Paris?")]),
r.candidates[0].content,
types.Content(role="user", parts=[types.Part(function_response=types.FunctionResponse(
name=fc.function_call.name, id=fc.function_call.id, response={"temp_c": 18}))])],
config=types.GenerateContentConfig(tools=[types.Tool(function_declarations=[decl])]))import { GoogleGenAI } from '@google/genai';
const ai = new GoogleGenAI({});
const r = await ai.models.generateContent({ model: 'gemini-3.5-flash-lite', contents: 'Weather in Paris?',
config: { tools: [{ functionDeclarations: [{ name: 'get_weather', description: 'Weather for a city.',
parametersJsonSchema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] } }] }] } });
console.log(r.functionCalls);Live verification (2026-09-18)
gemini-3.5-flash-lite, raws tmp-live/gemini-tools/a*.json (all HTTP 200 unless stated):
- Forced call (
toolConfig.functionCallingConfig.mode: ANY+allowedFunctionNames) → one part{functionCall:{name:"get_weather", args:{city:"Paris"}, id:"call_356059"}, thoughtSignature:"…"},finishReason: STOP. - Round trip: appending the model turn verbatim +
{functionResponse:{name, response:{…}}}→ text answer. StrippingthoughtSignature→ 400Function call is missing a thought_signature in functionCall parts. This is required for tools to work correctly… function call default_api:get_weather, position 2. - Parallel: two declarations, mode ANY → parts
[functionCall+thoughtSignature, functionCall](call_243490,call_243491) — only the first carries a signature, exactly as the docs describe. parametersJsonSchema(plain JSON Schema with an enum) →args {city:"Rome", unit:"C"}.mode: NONE→ text only;mode: VALIDATED→ accepted (functionCall returned).behavior: NON_BLOCKINGin generateContent → 400FunctionDeclaration.behavior is only supported by the BidiGenerateContent method. In the Live API the same declaration producedtoolCall{functionCalls:[{name,args,id:"function-call-11967657623789764093"}]}and the model kept talking (audio) — non-blocking behaviour confirmed;setup.toolConfigis rejected (close 1007), so the mode cannot be forced in Live.- Interactions API twin:
tools:[{type:"function",…}]+generation_config.tool_choice{allowed_tools{mode:"any", tools:[…]}}→status:"requires_action", step{id:"call_297992", type:"function_call", name, arguments}; top-leveltool_choice→ 400Unknown parameter 'tool_choice'; reply withinput:[{type:"function_result", name, call_id, result:[{type:"text", text}]}]+previous_interaction_id.
Examples: examples/gemini/tools/function-calling/{function_calling.py,forced_call.sh,function_calling.ts}, examples/shared/tool-loop/gemini_tool_loop.{py,ts} (all executed). Tests: tests/gemini/test_tools.py.