Skip to content

Function Calling Modes in the Gemini API: AUTO, ANY and NONE

9 min read · updated August 11, 2026

By default Gemini decides for itself whether a prompt warrants a tool call. tool_config.function_calling_config.mode takes that decision away from it in one of two directions, and the mode you pick changes the type of thing that comes back, not just its content.

The field and its values

Tool configuration is a top-level sibling of tools, and its documented modes, per Google’s function-calling guide, are:

  • AUTO — the model chooses between a text answer and one or more function calls. This is the default when tools are declared and no tool_config is sent.
  • ANY — the model must emit a function call. Plain text is not an available output.
  • NONE — the model must not emit a function call, even though the declarations are in the request.
  • MODE_UNSPECIFIED — the proto default, treated as AUTO. You will see it in generated client type definitions; there is no reason to send it.

A request carrying both the declaration and the config looks like this:

{
  "contents": [
    { "role": "user", "parts": [{ "text": "Is order 88214 shipped yet?" }] }
  ],
  "tools": [
    {
      "function_declarations": [
        {
          "name": "get_order_status",
          "description": "Look up the fulfilment status of a customer order.",
          "parameters": {
            "type": "OBJECT",
            "properties": {
              "order_id": { "type": "STRING", "description": "The numeric order id." }
            },
            "required": ["order_id"]
          }
        }
      ]
    }
  ],
  "tool_config": {
    "function_calling_config": { "mode": "ANY" }
  }
}

Note the parameter schema uses uppercase type names—OBJECT, STRING, ARRAY—because it is an OpenAPI 3.0 schema subset expressed through protobuf enums. Lowercase "string" is accepted by the JSON mapping, but the uppercase form is what the reference shows and what comes back in errors.

AUTO: the model decides

Under AUTO, the candidate’s parts array may contain a text part, a functionCall part, several functionCall parts, or a mixture. For the order-status prompt above, the useful outcome is a call:

{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "functionCall": {
              "name": "get_order_status",
              "args": { "order_id": "88214" }
            }
          }
        ]
      },
      "finishReason": "STOP"
    }
  ]
}

Two details in that response trip people up. The finishReason is STOP, not a tool-specific value—Gemini does not have a separate stop reason for “stopped to call a tool”, so detecting a tool call means inspecting parts for a functionCall key rather than switching on finishReason. And args is a decoded JSON object, not a string containing JSON, which is a genuine difference from the OpenAI shape and one that silently breaks a ported JSON.parse.

For a prompt with nothing to look up—“what do you do?” —the same request returns an ordinary text part. That is the whole of AUTO: the response type is not known until it arrives, so your handler needs both branches.

ANY: a call is guaranteed, text is not

ANY constrains decoding so that the only permitted output is a function call. The response for the order prompt is the same as above. The interesting case is the prompt that does not warrant a call. Ask “what do you do?” with mode: "ANY" and you do not get a polite explanation—you get a call anyway, with whatever arguments the model can construct:

{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "functionCall": {
              "name": "get_order_status",
              "args": { "order_id": "" }
            }
          }
        ]
      },
      "finishReason": "STOP"
    }
  ]
}

This is the behaviour to design around, and it is a consequence of the mode rather than a model failure: you removed the option of saying “no tool applies”, so a tool is applied. Three practical rules follow.

  • Validate arguments server-side regardless. A forced call can carry empty, invented or type-correct-but-meaningless arguments. The schema constrains shape, not sense.
  • Give the model an escape hatch as a tool. The standard fix is to declare a no-op function—cannot_answer(reason) or similar—so that “nothing applies here” is a legal output under ANY rather than an impossible one.
  • Do not leave it on for the whole conversation. tool_config is per-request. The usual pattern is ANY on the turn where a call is required, then AUTO or no config on the turn where you send the results back—otherwise the model is forced to call something again instead of summarising what it just received.

NONE, and why you would declare unusable tools

NONE tells the model it may not call anything, while leaving the declarations in the request. That sounds pointless—why not omit tools?—and there are two real reasons.

The first is caching and consistency. If your tool declarations are part of a cached prefix, or simply a large constant your client always sends, changing the request body to remove them changes the prefix and costs you the cache hit. Flipping a mode does not.

The second is that the declarations are still information. A model that can see get_order_status exists, but cannot call it on this turn, can tell the user that order lookup is available—which is a reasonable thing to want on a turn where you are collecting confirmation before acting.

The turn the mode sits inside

tool_config is per request, so understanding it means understanding where the requests fall. A single tool-using exchange is two calls to the API with your own code in between, and the mode is set separately on each.

  1. You send the user turn with tools and, if you are forcing, tool_config. The model replies with a functionCall part.
  2. You execute the function yourself. The API does not call anything; the name and arguments are a request addressed to your code.
  3. You send a second request containing the whole conversation so far — the original user turn, the model turn containing the functionCall, and a new user turn containing a functionResponse part with the result.
  4. The model replies with prose that uses the result, or with another call.

The third step is the one with a Gemini-specific shape. There is no tool role: the result travels as a part inside a user turn, and its name must match the function that was called.

"contents": [
  { "role": "user",  "parts": [{ "text": "Is order 88214 shipped yet?" }] },
  { "role": "model", "parts": [{ "functionCall": { "name": "get_order_status", "args": { "order_id": "88214" } } }] },
  { "role": "user",  "parts": [{
      "functionResponse": {
        "name": "get_order_status",
        "response": { "status": "in_transit", "carrier": "DPD", "eta": "2026-08-13" }
      }
  }] }
]

Note that response is an arbitrary JSON object rather than a stringified result, matching the way args arrived as a decoded object rather than a string. And note that the second request must not carry mode: "ANY": the model has the answer and needs to write prose, and forcing another call at that point is the single most common way a tool loop becomes an infinite one. Set the mode where a call is required and clear it where it is not.

Where an error occurred, put it in the response object rather than omitting the turn. A functionResponse of {"error": "order not found"} gives the model something to say to the user; a missing turn leaves a dangling call in the history and confuses the next request. The full threading rules are covered in multi-turn function responses.

Narrowing to one tool with allowed_function_names

function_calling_config takes a second field, allowed_function_names, which restricts ANY to a subset of the declared functions. This is how you force one specific tool:

"tool_config": {
  "function_calling_config": {
    "mode": "ANY",
    "allowed_function_names": ["get_order_status"]
  }
}

With one name in the list you have the equivalent of a forced single-function call: the model must call, and it must call that one. With several, the model must call, and must pick from those. The field is documented as applying when the mode is ANY; setting it alongside AUTO is not the way to express a preference, and “prefer this tool but text is fine” is a prompt-level request rather than a config-level one.

The state-machine use of this is worth stating plainly, because it is the pattern that makes forced calling useful rather than dangerous: at each step of a workflow, set allowed_function_names to exactly the tools legal at that step. You get the model’s judgement about arguments while keeping control of sequence, which is a much better division of labour than hoping a system instruction is obeyed. When you want several calls at once instead of one, parallel function calling is the relevant behaviour.