Tool use and function calling (Gemini)
Gemini’s function-calling surface centers on tools[].function_declarations[] (JSON-Schema subset of OpenAPI) + tool_config.function_calling_config.mode (one of AUTO, ANY, VALIDATED, NONE). The Python SDK adds a powerful sugar layer — automatic function calling — where you pass a regular Python callable and the SDK both declares it (introspecting signature + type hints + docstring) and executes it on your behalf when the model calls it, feeding the result back without your code touching the loop. The biggest 2026 change is the Gemini 3 unique-id requirement: every function call now gets a unique id, and the matching functionResponse must carry it — a breaking change from 2.x semantics that needs migration attention in agent loops.
See also
- structured-output-gemini — sibling feature (JSON schema constraint without invoking a tool)
- code-execution-and-grounding-gemini — built-in tools (Python sandbox + Google Search)
- gemini-api-and-sdks — surface methods called from here
- hidden-tricks-and-gotchas-gemini — function-call ordering quirks
- tool-use-and-function-calling — sister doc for Claude
- tool-use-and-function-calling-openai — sister doc for OpenAI
1. Function declaration shape
Per ai.google.dev/gemini-api/docs/function-calling. The shape is a constrained subset of OpenAPI schema.
from google.genai import types
get_weather = types.FunctionDeclaration(
name="get_weather",
description="Get current weather conditions for a city.",
parameters=types.Schema(
type="OBJECT",
properties={
"city": types.Schema(
type="STRING",
description="City name, e.g. 'San Francisco'",
),
"unit": types.Schema(
type="STRING",
enum=["celsius", "fahrenheit"],
description="Temperature unit",
),
},
required=["city"],
),
)Same shape as a JSON object:
{
"name": "get_weather",
"description": "Get current weather conditions for a city.",
"parameters": {
"type": "OBJECT",
"properties": {
"city": {"type": "STRING", "description": "City name, e.g. 'San Francisco'"},
"unit": {"type": "STRING", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit"}
},
"required": ["city"]
}
}Supported type values
STRING, NUMBER, INTEGER, BOOLEAN, OBJECT, ARRAY. (Note the uppercase — different from JSON Schema’s lowercase.)
Supported constraints
STRING—enum,format(date,date-time,time,enum),pattern(partial)NUMBER/INTEGER—enum,minimum,maximumARRAY—items(schema),minItems,maxItemsOBJECT—properties,required,additionalProperties(limited honoring)- All —
description,title,nullable
Naming rules
- Function name:
[a-zA-Z][a-zA-Z0-9_-]*, max 64 chars - Parameter name: standard identifier
- Avoid generic names like
do_taskorrun— be descriptive
2. Modes (function_calling_config.mode)
Per the function-calling docs, four modes:
| Mode | Behavior |
|---|---|
AUTO | Default. Model decides whether to call a function or return text. |
ANY | Model must call at least one function (cannot return plain text). Optionally constrain to a subset with allowed_function_names. |
VALIDATED | Constrains output to either valid function calls or text, with constrained decoding ensuring schema adherence. |
NONE | Disable function calling for this request even though tools are declared. |
config = types.GenerateContentConfig(
tools=[types.Tool(function_declarations=[get_weather, search_db])],
tool_config=types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(
mode="ANY",
allowed_function_names=["get_weather"], # narrows ANY to just weather
),
),
)VALIDATED is the recommended mode when you absolutely need schema-compliant function calls (constrained decoding prevents malformed args). Tradeoff: slightly higher latency, occasional model refusal when no good function matches the prompt.
3. Calling a function (the manual flow)
The full request → call → response → final-answer cycle:
# 1. First request: model decides to call a function
resp1 = client.models.generate_content(
model="gemini-3.5-flash",
contents="What's the weather in Paris?",
config=types.GenerateContentConfig(
tools=[types.Tool(function_declarations=[get_weather])],
automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
),
)
fc = resp1.candidates[0].content.parts[0].function_call
print(fc.name, fc.args, fc.id) # "get_weather", {"city": "Paris"}, "fc_abc123"
# 2. You execute the function client-side
result = {"temp": 18, "unit": "celsius", "condition": "cloudy"}
# 3. Send back the result with the matching id
resp2 = client.models.generate_content(
model="gemini-3.5-flash",
contents=[
types.Content(role="user", parts=[types.Part(text="What's the weather in Paris?")]),
resp1.candidates[0].content, # the model's function_call turn
types.Content(role="user", parts=[
types.Part(function_response=types.FunctionResponse(
id=fc.id, # CRITICAL on Gemini 3 — match the id
name="get_weather",
response={"result": result},
)),
]),
],
config=types.GenerateContentConfig(
tools=[types.Tool(function_declarations=[get_weather])],
),
)
print(resp2.text)Gemini 3 change: the model emits a unique id (fc_abc123 etc.) on each function call. You must echo it in the matching FunctionResponse.id. Gemini 2.x ignores the id (matched by name only) — code that worked on 2.x will warn or error on 3.x without the id round-trip.
4. Automatic function calling (Python SDK sugar)
The SDK can do the entire loop for you. Pass a callable:
def get_weather(city: str, unit: str = "celsius") -> dict:
"""Get current weather conditions for a city.
Args:
city: City name.
unit: Temperature unit, celsius or fahrenheit.
"""
# ... fetch from your weather API ...
return {"temp": 18, "unit": unit, "condition": "cloudy"}
resp = client.models.generate_content(
model="gemini-3.5-flash",
contents="What's the weather in Paris and Tokyo?",
config=types.GenerateContentConfig(tools=[get_weather]),
)
print(resp.text)
# The SDK introspects get_weather (signature + type hints + docstring),
# declares it to the model, executes it when called (possibly multiple times
# for parallel calls), and feeds results back automatically.To inspect the calls that happened:
for c in resp.automatic_function_calling_history:
print(c.role, c.parts)To disable auto-execution while still using callables:
config=types.GenerateContentConfig(
tools=[get_weather],
automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
)Use disable when:
- The function has side effects you want to gate (DB writes, sending email)
- You need to inspect args before execution
- You’re running in an async / distributed environment where the SDK loop doesn’t fit
Max function-call iterations in auto mode is configurable:
automatic_function_calling=types.AutomaticFunctionCallingConfig(
maximum_remote_calls=10, # default 10 — bump for deep agentic chains
)Auto mode propagates exceptions: a KeyError in your function aborts the loop with the same exception bubbling up. Wrap risky functions in try/except returning structured errors if you want the model to recover.
5. Parallel function calling
The model can return multiple function calls in a single turn when independent. Each appears as its own function_call part:
resp.candidates[0].content.parts
# [
# Part(function_call=FunctionCall(name="get_weather", args={"city": "Paris"}, id="fc_1")),
# Part(function_call=FunctionCall(name="get_weather", args={"city": "Tokyo"}, id="fc_2")),
# Part(function_call=FunctionCall(name="search_db", args={"query": "weather news"}, id="fc_3")),
# ]Execute all three (often in parallel), then return all three FunctionResponse parts in one user-role message:
types.Content(role="user", parts=[
types.Part(function_response=types.FunctionResponse(id="fc_1", name="get_weather", response={"result": paris_data})),
types.Part(function_response=types.FunctionResponse(id="fc_2", name="get_weather", response={"result": tokyo_data})),
types.Part(function_response=types.FunctionResponse(id="fc_3", name="search_db", response={"result": search_data})),
]),The SDK auto-flow executes them sequentially by default. For true parallelism, run them yourself with asyncio.gather or a thread pool around the manual flow.
6. Compositional function calling
The model chains calls: call A, see result, then call B based on A’s output. The flow is just the manual flow repeated — each generate_content call returns either the next function call(s) or the final text answer. Common pattern:
messages = [types.Content(role="user", parts=[types.Part(text=user_query)])]
for _ in range(10):
resp = client.models.generate_content(
model="gemini-3.5-flash",
contents=messages,
config=types.GenerateContentConfig(tools=tools),
)
if not any(p.function_call for p in resp.candidates[0].content.parts):
break # model returned text, done
messages.append(resp.candidates[0].content)
responses = []
for part in resp.candidates[0].content.parts:
if part.function_call:
result = execute(part.function_call.name, part.function_call.args)
responses.append(types.Part(function_response=types.FunctionResponse(
id=part.function_call.id,
name=part.function_call.name,
response={"result": result},
)))
messages.append(types.Content(role="user", parts=responses))
print(resp.text)7. Mixing function calls with other tools
You can declare multiple Tool blocks in the same request: function declarations + Google Search + code execution + URL context.
tools = [
types.Tool(function_declarations=[get_weather]),
types.Tool(google_search=types.GoogleSearch()),
types.Tool(code_execution=types.ToolCodeExecution()),
]The model picks among them based on the prompt. Practical: declare custom functions for proprietary actions (DB write, internal API), grounding for fresh facts, code execution for math.
Note: as of 2026-05, some combinations have restrictions on certain models — see code-execution-and-grounding-gemini.
8. Schema design tips
Use descriptions aggressively
The model uses description fields to decide whether and how to call. A function named lookup with no description is much less reliable than one with description="Look up a customer record by email or customer ID. Returns name, plan, and last-login timestamp.".
Constrain with enums and required
enum is constrained-decoded — the model cannot generate a value outside the enum. This is reliable for fixed taxonomies.
"status": types.Schema(type="STRING", enum=["active", "paused", "archived"]),required is honored — missing required fields trigger model retry / refusal. List every truly-required field.
Avoid deeply nested optional shapes
Model accuracy drops sharply past ~3 levels of nesting. For complex inputs, prefer flat objects with descriptive field names over nested hierarchies.
Avoid recursive schemas
Self-referential schemas (a node containing a list of nodes) are accepted but routinely fail. Flatten to lists.
9. Forced tool use (mode: ANY)
When you must get a function call (e.g. classifying a query into one of a fixed action set):
config=types.GenerateContentConfig(
tools=[types.Tool(function_declarations=[classify_intent])],
tool_config=types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(
mode="ANY",
allowed_function_names=["classify_intent"],
),
),
)This forces a function call (or a refusal). Useful for routing, classification, extraction-as-function-call.
10. Tracing and debugging
response.candidates[0].content.parts gives the raw model output. For function calls:
for part in response.candidates[0].content.parts:
if part.function_call:
print(f"call: {part.function_call.name}({dict(part.function_call.args)}) id={part.function_call.id}")
elif part.text:
print(f"text: {part.text}")For automatic mode, inspect response.automatic_function_calling_history — a list of all function calls + responses the SDK processed.
response.candidates[0].finish_reason of MALFORMED_FUNCTION_CALL means the model emitted something the parser couldn’t validate — usually means the schema is too complex or had unsupported types. Simplify and retry.
11. Schema validation gotchas
Per the docs, supported schema features:
- OK:
type,properties,required,description,title,enum,minimum,maximum,minItems,maxItems,items,nullable - Honored loosely:
additionalProperties,pattern,format - Not supported:
$ref/ definitions,allOf/oneOf/anyOf, recursive references,not,if/then/else
If your Pydantic model has Union types or Optional[Union[...]], the SDK serializer may produce an unsupported schema. Test with client.models.count_tokens (a low-stakes call that triggers schema validation).
12. Cost considerations
Function declarations live in the request (or the cache, if cached). They count as input tokens:
- Each function declaration ≈ 50-200 tokens depending on schema complexity
- A request with 20 tools and verbose descriptions can hit 5,000+ tokens of tool overhead
- Cache tools when stable across requests (see context-caching-gemini)
Function calls themselves contribute to output tokens (the call name + JSON args is generated text, billed at output rate).
13. Common pitfalls
- Forgetting the
idround-trip on Gemini 3 — code that worked on 2.5 silently fails on 3.x without echoingfunction_call.idin thefunction_response.id. - Uppercase
typerequirement —STRINGnotstring. JSON-Schema-y dictionaries assembled by hand fail validation. - Missing docstring in auto mode — the SDK uses the docstring as the function description. No docstring → empty description → unreliable calling.
- Mutable default args —
def f(items: list = [])confuses the SDK. UseOptional[list] = None. - Side-effect functions in auto mode —
send_emailruns on every model decision in auto mode. Either disable auto or gate with a confirmation parameter the model has to set explicitly. - Schema too rich — Union / recursive / deeply nested →
MALFORMED_FUNCTION_CALL. Flatten. - Function name conflicts — declaring
getandget_v2confuses the model. Use distinct, descriptive names. tool_configset buttoolsnot — silently ignored. Both must be present.- Implicit cache invalidation when tools change — adding or removing a function declaration invalidates implicit cache; explicit cache requires re-creation.
14. Comparison to other vendors
| Aspect | Gemini | Claude | OpenAI |
|---|---|---|---|
| Declaration shape | OpenAPI subset (UPPERCASE types) | JSON Schema (lowercase) | JSON Schema (lowercase) |
| Strict mode | VALIDATED (constrained decode) | strict_input: true | strict: true (full schema enforcement) |
| Parallel calls | Supported (multiple function_call parts per turn) | Supported | Supported (parallel_tool_calls: true) |
| Forced call | mode: ANY + allowed_function_names | tool_choice: {type: tool, name: "..."} | tool_choice: {type: "function", function: {name: "..."}} |
| Auto-execution sugar | Python SDK only (pass callable) | None (manual loop) | Agents SDK helper |
| Unique call IDs | Required on Gemini 3 | tool_use_id always required | tool_call_id always required |
15. Further reading
- Function calling — canonical reference
- Structured output — sibling feature
- google-gemini/cookbook function-calling notebooks — worked examples
- Gemini 3 function-calling changes —
idrequirement detail