Gemini 3.8 Flash shipped on September 2, 2026, and Google built it to “call tools iteratively”: on a hard task it makes a call, checks the result, and makes another, instead of guessing everything in one shot. That is good news for agents and a new headache for anyone whose tool loop was tuned for 3.7 Flash. Two API details matter more than anything else. Every function result must carry both call_id and name, and the Interactions API, not generateContent, is now the primary way to run the loop.
This guide walks the full two-turn flow on the Interactions API, shows the legacy generateContent shape you are probably still running, explains why the new model spends more turns and tokens on tools, and finishes with a test setup you can run every day: mock the tool’s backend, chain both turns, and assert the call_id round-trips. If you need the model overview first, start with what Gemini 3.8 Flash is. The field names below come from Google’s function calling docs.
Every request here is plain HTTP with JSON, so you can build and debug it in Apidog before it goes into application code.
Function calling on Gemini 3.8 Flash at a glance
| Item | Gemini 3.8 Flash |
|---|---|
| Model ID | gemini-3.8-flash (stable, no preview suffix) |
| Primary API | Interactions API (POST /v1beta/interactions); generateContent is legacy but fully supported |
| Tool declaration | tools: [{"type": "function", "name", "description", "parameters"}] |
| The model’s call | function_call step with id, name, arguments |
| Your reply | function_result with call_id + name (both required) plus previous_interaction_id |
| Thinking | thinking_level low / medium (default) / high; minimal returns a validation error |
| Tool-use score | Tau3-Banking 45%, +12 points over 3.7 Flash (Artificial Analysis, independent) |
| Token cost | ~48k output tokens per task on the AA index, +30% vs 3.7 Flash |
| Price | $0.75 in / $3.75 out per 1M through 2026-12-31; thinking billed as output |
Step 1: declare the tool
On the Interactions API a tool is a flat object: a type of function, a name, a description the model reads to decide when to call it, and a JSON Schema under parameters. Keep the description specific. “Look up the current shipping status of an order by its ID” gets called at the right moment; “order helper” gets called at random.
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.8-flash",
"input": "Where is order A1029 right now?",
"generation_config": {"thinking_level": "low"},
"tools": [{
"type": "function",
"name": "get_order_status",
"description": "Look up the current shipping status of an order by its ID.",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"]
}
}]
}'
Two choices in this request are deliberate. thinking_level is low because a single lookup does not need the default medium; the thinking levels guide covers when to raise it. And there is no temperature. Google’s Gemini 3 guidance is to leave it at the default 1.0, because lowering it can cause looping, which is the last thing you want inside a tool loop.
Step 2: read the function_call step
The Interactions API does not answer with a single message. It returns the interaction’s own id plus a list of execution steps: model thoughts, tool calls, and finally a model_output step once the model has an answer. When the model decides it needs your tool, the list holds a function_call step instead of a model_output:
{
"type": "function_call",
"id": "call_8f2d...",
"name": "get_order_status",
"arguments": {"order_id": "A1029"}
}
Three fields, and you need all three. id is the handle you send back as call_id. name tells you which function to run and must be echoed back too. arguments is already parsed JSON, so validate it against your own rules before you execute anything; the model fills the shape you declared, but it does not know your order IDs are five characters long.
Store the interaction id from the top of the response at the same time. It becomes previous_interaction_id on the next turn.
Step 3: return the result with call_id and name
Run your function, then send a second request whose input is a function_result. Both call_id and name are required on Gemini 3.8 Flash. Drop either and the call fails, which is the single most common break when teams move loops written for older models.
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.8-flash",
"previous_interaction_id": "<interaction id from step 2>",
"input": [{
"type": "function_result",
"name": "get_order_status",
"call_id": "call_8f2d...",
"result": [{"type": "text", "text": "{\"status\":\"in_transit\",\"eta\":\"2026-09-05\"}"}]
}]
}'
result is a list of content parts, and the text part carries your JSON as a string. Because previous_interaction_id points at the earlier turn, the server already holds the original prompt, the tool declaration, and the model’s reasoning; you do not resend any of it. The response is another step list. If it ends in model_output, you are done, and the SDK exposes the text as interaction.output_text. If it holds another function_call, go back to step 2. That loop is the whole pattern.
In Python the flow is client.interactions.create(model="gemini-3.8-flash", input=..., ...) with the same JSON fields as keyword arguments, then a second create with previous_interaction_id and the function_result list as input. The Gemini 3.8 Flash API how-to covers keys, streaming, and reading token usage if the endpoint is new to you.
The legacy generateContent equivalent
Most existing Gemini code still calls models/gemini-3.8-flash:generateContent, and Google says it “remains fully supported” with no sunset date. The vocabulary is different; the rule is the same. Tools are declared under functionDeclarations, the model replies with a functionCall part, and you answer with a functionResponse part. On the legacy shape the model’s functionCall part carries an id, and your functionResponse part must echo that same value in its own id field alongside name and response. It is the same contract as call_id on the Interactions API under a different field name, and Google’s Gemini 3 guidance is explicit that both the id and the name are required.
Two practical differences. First, generateContent is stateless, so you carry the conversation yourself: the full contents history goes back on every turn, including the model’s functionCall part and any thought signatures it returned. Second, thinking is configured under generationConfig.thinkingConfig.thinkingLevel instead of generation_config.thinking_level:
{"generationConfig": {"thinkingConfig": {"thinkingLevel": "low"}}}
Thinking tokens show up as usageMetadata.thoughtsTokenCount in the response and are billed as output. If you are choosing between the two APIs for a new project, pick Interactions: the server-side state removes the class of bugs where a resent history is missing a signature or a call_id.
Why 3.8 Flash calls tools iteratively, and how to cap the loop
Google’s launch post says the model “works harder”: on complex tasks it “executes extra reasoning steps, and calls tools iteratively”, taking “smaller reasoning steps” and verifying its work along the way. Google also says it “can use more tokens on longer running and complex tasks, by design”. Artificial Analysis measured the effect: about 48k output tokens per task on their index, +30% over 3.7 Flash, and a cost per task of $0.58 at high versus $0.40 for 3.7 Flash at the same per-token prices. Medium came in at $0.41 and low at $0.24.
For a tool loop, this means more function_call steps per task. The upside is real: Tau3-Banking, AA’s tool-use eval, rose 12 points to 45%. The downside is a loop with no ceiling now runs longer than it did in August. Four controls, in the order to apply them:
- Max turns in your harness. Count
function_callsteps per task and stop at a limit you choose; 6 to 10 is a sane starting range for lookups, higher for agentic coding. When the limit hits, send a final turn without tools, or return an error to the user. The model will not cap itself. thinking_levelper route.lowfor lookups and single-hop tools,medium(the default) for multi-step work,highonly where the extra verification pays. Do not sendminimal; 3.8 Flash returns a validation error.- Timeouts on both sides. A per-request timeout on the Gemini call and a per-task wall clock on the loop. AA’s high-reasoning runs averaged 2.5 minutes per task, 0.8 minutes at low.
- Idempotent tools. An iterative model retries. Make
get_order_statussafe to call twice, and make anything with side effects (refunds, sends) require a confirmation step.
If your budget cannot absorb the extra turns, the 3.7 to 3.8 Flash migration guide covers keeping 3.7 Flash, which remains fully supported, behind a config flag.
Thought signatures, parallel calls, and structured outputs
Thought signatures. Gemini 3 models attach signatures to their reasoning. With the default stored Interactions flow, previous_interaction_id handles them for you. If you set store: false for a stateless setup, or you use generateContent, you must send thought blocks and signatures back exactly as received, on every part type. Do not trim, reorder, or re-serialize them; a signature is opaque and any edit invalidates it. Google’s Interactions API docs cover the stored versus stateless trade-off.
Parallel calls. The response is a list, so it can hold more than one function_call step when the model wants several independent lookups at once. Google’s function calling docs confirm that Gemini 3 models return a unique id with every call precisely so results can come back in any order. Handle it by returning one function_result per call in the same input array, each matched by its own call_id. Matching by name alone is not enough; two calls to the same function need two different call_id values.
Structured outputs. 3.8 Flash supports structured outputs and function calling on the same model. The clean pattern is tools for the loop and a JSON schema for the final answer, so the model_output that closes the loop is machine-readable instead of prose. Google’s function-calling and structured-output pages document the config. Do not fake it by declaring a dummy tool and reading its arguments; that breaks the moment the model decides it has nothing to call.
Everything above assumes the model reaches your system through declared functions. Google also lists Computer use (Preview) for 3.8 Flash; for when a structured API beats screen-driving an agent, see computer use vs structured APIs.
Testing the tool loop in Apidog
A tool loop has three places to break: the declaration, the id round-trip, and the final answer. You can cover all three in Apidog without touching your real backend.
1. Mock the tool’s backend. Define GET /orders/{order_id} as an endpoint and turn on its mock server. Give it a fixed response body, {"status": "in_transit", "eta": "2026-09-05"}, so every run gets identical input and any change in the model’s final answer is the model’s doing, not your database’s. Your harness points at the mock URL in the test environment and at the real service in production.
2. Chain both turns in a test scenario. Store GEMINI_API_KEY as an environment variable and reference it as {{GEMINI_API_KEY}} in the x-goog-api-key header. Then build a scenario with three steps:
- Step A: POST to
/v1beta/interactionswith the prompt and theget_order_statusdeclaration. Extract the interactionidand thefunction_callstep’sid,name, andarguments.order_idinto variables. - Step B: GET the mock endpoint with
{{order_id}}. This is your “run the function” step. - Step C: POST the
function_resultwithcall_idset to{{call_id}},nameset to{{tool_name}},previous_interaction_idset to{{interaction_id}}, and Step B’s body as the text part.
3. Assert what matters.
- Step A returns 200 and holds a step whose
typeisfunction_callwithnameequal toget_order_status. - The extracted
arguments.order_idequalsA1029, which proves the model parsed the prompt and honored the schema. - Step C returns 200 and ends in a step whose
typeismodel_outputwith no secondfunction_call, which proves thecall_idandnameyou sent were accepted and the loop closed in one round. - The final text contains
in_transit, which proves the model used the tool result instead of its own guess. - If you run the same scenario against
generateContent, add a ceiling onusageMetadata.thoughtsTokenCountperthinking_level. That catches “works harder” cost creep before it reaches your bill.
Schedule the scenario to run daily. Model behavior drifts across silent updates, and a loop that closed in one round last week can start needing two. The AI agent API testing guide goes deeper on multi-step assertions, and you can Download Apidog to build the scenario against the free tier before you spend a cent.
FAQ
Is call_id required on Gemini 3.8 Flash? Yes. On the Interactions API every function_result needs call_id and name; on generateContent every functionResponse needs the call’s id and name. Older code that sent only the name fails on Gemini 3 models.
Why does my tool loop run more turns on 3.8 Flash than on 3.7? By design. Google says the model “calls tools iteratively” and “can use more tokens on longer running and complex tasks”. Cap turns in your harness and lower thinking_level; the thinking levels guide has the measured cost per level.
Can I still use generateContent for function calling? Yes. Google calls it legacy but says it “remains fully supported” with no sunset date. You carry the history yourself, including thought signatures, and the call id (spelled id on this API) plus name still apply.
Does thinking_level “minimal” work with tools? No. It returns a validation error on 3.8 Flash. Use low.
How much does a tool-heavy task cost? Per-token pricing is $0.75 input and $3.75 output per 1M tokens through December 31, 2026, with thinking billed as output. Artificial Analysis measured $0.58 per task at high, $0.41 at medium, and $0.24 at low on their index. Your tasks will differ, so assert on token counts and measure.
Ship the loop with a ceiling
Declare the tool, read the function_call step, and return function_result with both call_id and name under previous_interaction_id. That is the whole contract. What changed with Gemini 3.8 Flash is the model’s willingness to loop, so the harness needs a turn cap, a per-route thinking_level, and a timeout before it goes to production. Mock the backend, chain the two turns, assert the id round-trips, and schedule the run. Google’s What’s new in Gemini 3.8 Flash page has the migration notes; the pillar guide has everything else about the model.



