skip to content

How do you define a FunctionDeclaration for the Gemini API?

level: middleimportance: must knowfreq 62%

answer

  1. Three fields: name, description, parameters
  2. Grouped into a Tool on the config
  3. OpenAPI subset, not full JSON Schema
  4. Descriptions drive tool selection
  5. Closed value sets belong in enum

basics

~20 s

A FunctionDeclaration carries a name, a description, and a parameters schema written in the OpenAPI subset Gemini accepts — object type, properties, required, enum, items. Declarations are grouped into a Tool and passed on the request config's tools list.

solid answer

~40 s

You build `types.FunctionDeclaration(name=..., description=..., parameters=...)`, wrap one or more in `types.Tool(function_declarations=[...])`, and pass that list as `tools` on `types.GenerateContentConfig`. The `parameters` value is a `types.Schema`: an OpenAPI 3.0 style schema subset with `type` (OBJECT, STRING, INTEGER, NUMBER, BOOLEAN, ARRAY), `properties`, `items`, `required`, `enum`, `nullable` and per-field `description` — not full JSON Schema, so exotic keywords such as `$ref` are not supported. The descriptions are not decoration: the model chooses a tool almost entirely from the function description and the per-parameter descriptions, so ambiguous wording is the top cause of wrong-tool selection. Keep names stable and machine-friendly, constrain free-text fields with `enum` where you can, and mark only genuinely mandatory fields `required`.

code

python · 28 lines
python
from google.genai import types

search_orders = types.FunctionDeclaration(
    name="search_orders",
    description=(
        "Find orders for a customer. Use for order status questions; "
        "do not use for refunds or payment details."
    ),
    parameters=types.Schema(
        type=types.Type.OBJECT,
        properties={
            "customer_id": types.Schema(
                type=types.Type.STRING,
                description="Internal customer identifier, e.g. cus_1024",
            ),
            "status": types.Schema(
                type=types.Type.STRING,
                enum=["pending", "shipped", "delivered", "cancelled"],
                description="Optional status filter",
            ),
            "limit": types.Schema(type=types.Type.INTEGER),
        },
        required=["customer_id"],
    ),
)

tools = [types.Tool(function_declarations=[search_orders])]
config = types.GenerateContentConfig(tools=tools)

go deeper

for a junior

Know the three parts of a declaration — name, description, parameters — and that declarations are wrapped in a Tool and passed on the request config.

for a middle

Explain the schema subset by name: object type, properties, items, required, enum, nullable, and why it is not full JSON Schema.

for a senior

Show judgment about tool-surface design: pruning the list, writing selection-driving descriptions, and keeping declarations in sync with dispatchers via tests.

for a principal

Own the contract question — where tool schemas are generated from, how they version, and how you keep a growing tool catalogue from degrading selection accuracy and cost.

## The three fields that matter A Gemini tool declaration is deliberately small: - **`name`** — the identifier the model will echo back in `function_call.name`. Keep it a stable, code-like token; you will dispatch on it. - **`description`** — natural-language instructions to the model about *when* to use this function, not just what it does. - **`parameters`** — a schema describing the argument object. Declarations do not travel alone. They are grouped into a `Tool` object (`types.Tool(function_declarations=[...])`) and that tool list goes on the request configuration, alongside other generation settings. ## The schema dialect The `parameters` value is a `Schema` modelled on the OpenAPI 3.0 schema object, and it is a *subset*. What you can rely on: - `type`: OBJECT, STRING, INTEGER, NUMBER, BOOLEAN, ARRAY. - `properties` for object fields, with each value being another schema. - `items` for array element type. - `required`, a list of property names. - `enum` on string-typed fields to constrain the value space. - `nullable`, and `description` on every level. What you should *not* assume: full JSON Schema. Constructs like `$ref` and schema composition are outside the supported subset, regex `pattern` validation is not a substitute for your own checking, and anything unsupported either errors at request time or is quietly ignored — both bad outcomes when you find out in production. The practical rule is: flatten your schemas, use plain types, and enforce the rest in your own code after you receive `args`. The top-level `parameters` schema is normally `type=OBJECT` with a `properties` map, because a function call is a named-argument bundle. A function with no arguments can declare no parameters at all. ## Descriptions are prompt engineering The single biggest quality lever in Gemini function calling is not the schema, it is the prose. The model has never seen your code; it sees the function name, the description, and the parameter descriptions, and from those it decides whether this turn calls a tool, which tool, and with which argument values. Good practice: - Say when the tool applies *and* when it does not: "Look up the current weather for a city. Do not use for historical weather." - Describe units, formats and defaults on each parameter: "ISO-8601 date, e.g. 2026-08-19", "temperature unit, celsius or fahrenheit". - Prefer `enum` over free text when the value space is closed — it removes a whole class of invalid arguments before they reach your dispatcher. - Do not overload one function with a `mode` switch that changes semantics; two clear declarations select better than one ambiguous one. ## Keeping the surface small Every declaration is tokens on every single request, and it competes for the model's attention. A tool list that has grown to dozens of near-synonymous functions degrades selection accuracy and inflates cost. Prune aggressively, group by task, and consider swapping the tool set per conversation phase rather than shipping the union of everything. ## Generating declarations from code Hand-writing schemas drifts from the implementation. Two mitigations are common. First, in the Python SDK you can pass a plain Python callable and let the SDK derive the declaration from its signature and docstring — convenient, but it also enables automatic execution unless you turn that off. Second, generate the `Schema` from your own typed model or dataclass at startup, so the declaration and the implementation cannot diverge. Either way, add a test that asserts every declared name has a dispatcher entry and vice versa — a missing dispatcher branch is the classic production incident, because the model will eventually call the tool you forgot to wire. ## Validation is still yours A declared schema shapes what the model *tends* to produce; it is not a hard contract you can lean on for safety. Values may be absent, out of range, semantically wrong, or influenced by hostile text in the user's input. Validate types and bounds, check authorization for the caller rather than for the model, and fail with a structured error you can return as the function response rather than throwing out of the loop. ## Checklist for a good declaration 1. Stable, unique, code-like `name`. 2. A description that says when to call and when not to. 3. Flat OBJECT parameters using only supported schema keywords. 4. `enum` on closed value sets, units spelled out in descriptions. 5. `required` limited to genuinely mandatory fields. 6. A dispatcher entry and a test that keeps the two in sync.

  • Why is enum on a string parameter worth the effort when you validate anyway?
    It moves the constraint upstream. The model sees the closed value set and overwhelmingly picks from it, so you get fewer invalid calls, fewer retry round-trips and less latency. Your validation still runs, but it becomes a backstop rather than the primary defence, and the error messages you return get rarer and more informative.
  • What happens if the model calls a function name your dispatcher does not know?
    Nothing on the provider side protects you — the call simply arrives. Handle it explicitly: return a function response describing the unknown-tool error so the model can correct itself on the next turn, and log it, because it usually means a declaration was added or renamed without updating the dispatcher.
  • Is there a downside to declaring many tools on every request?
    Yes, two. Every declaration is input tokens on every call, so cost and latency scale with the tool list, and a crowded list of similar functions measurably worsens selection accuracy. Ship the minimum viable set and consider swapping tool sets per conversation phase instead of always sending the union.

saying these in an interview costs you the question

  • Assuming parameters accepts arbitrary JSON Schema
  • Leaving descriptions empty and blaming the model
  • Marking every parameter required by default
  • Declaring dozens of overlapping tools at once
  • Treating the schema as a security guarantee

context