●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Implementing Gemini API Function Calling — Parallel Calls, Tool Chains, and When to Use Managed Agents
Production patterns for Gemini API Function Calling: parallel calls, error handling, and tool chaining, plus when to use Managed Agents and how to pin the tool-selecting model to a GA release.
How Function Calling Changes the AI Development Paradigm
As an indie developer, when I first handed customer-support replies in one of my apps over to Gemini, the wall I hit immediately was the obvious one: the model has no idea about today's stock count or exchange rate. How do you feed it dynamic information that lives outside the training data? That's exactly what Function Calling solves. Gemini decides "call this function with these arguments," and your own code does the actual fetching. Once that division of labor clicks, a static chatbot turns into a genuinely useful agent.
I started with single tool calls and gradually grew into parallel execution and tool chains. This article walks through the patterns I found production-worthy along the way, with working code at each step. The later sections cover how to decide between rolling your own loop and the Managed Agents public preview that landed in June 2026, plus how to pin the model that selects your tools in an era where defaults quietly change.
How Function Calling Works
The flow looks like this:
1. App → Gemini: User's question + available function definitions
2. Gemini → App: "Please call this function with these arguments"
3. App: Actually executes the function and retrieves data
4. App → Gemini: Returns the execution result
5. Gemini → User: Generates a response based on the retrieved data
The critical insight: Gemini does not execute functions itself. It only decides which function to call and with what arguments. Your application does the actual execution.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Production-grade patterns for parallel tool calls, error handling, and tool chaining
✦A decision framework for self-managed loops vs. the Managed Agents public preview (June 2026)
✦How to detect default-model swaps and pin the tool-selecting model to a GA release
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Gemini 2.0+ supports calling multiple tools in parallel, allowing simultaneous retrieval from multiple data sources:
import asyncioasync def handle_parallel_tool_calls(response, available_functions: dict): """Process multiple Function Calls in parallel""" parts = response.candidates[0].content.parts function_calls = [ part.function_call for part in parts if hasattr(part, 'function_call') and part.function_call ] if not function_calls: return None async def execute_function(fc): func = available_functions.get(fc.name) if func: if asyncio.iscoroutinefunction(func): return await func(**dict(fc.args)) else: return func(**dict(fc.args)) return {"error": f"Function {fc.name} not found"} results = await asyncio.gather(*[execute_function(fc) for fc in function_calls]) return [ { "function_response": { "name": fc.name, "response": result } } for fc, result in zip(function_calls, results) ]async def main(): model = genai.GenerativeModel("gemini-2.0-flash", tools=tools) # Question requiring multiple data sources simultaneously response = model.generate_content( "Check inventory for iPhone 15 Pro and show me smartphones under $300 at the same time" ) available_functions = { "search_products": search_products, "get_inventory": get_inventory, } function_responses = await handle_parallel_tool_calls(response, available_functions) if function_responses: final_response = model.generate_content([ "Check inventory for iPhone 15 Pro and show me smartphones under $300 at the same time", response.candidates[0].content, {"role": "user", "parts": function_responses} ]) print(final_response.text)asyncio.run(main())
Error Handling and Retry Strategy
Build robust error handling for tool execution failures:
from dataclasses import dataclassfrom typing import Any, Optionalimport time@dataclassclass ToolResult: success: bool data: Optional[Any] = None error: Optional[str] = Nonedef execute_with_retry( func, args: dict, max_retries: int = 3, backoff_factor: float = 1.5) -> ToolResult: """Execute tool with exponential backoff retry""" last_error = None for attempt in range(max_retries): try: result = func(**args) return ToolResult(success=True, data=result) except ConnectionError as e: last_error = str(e) wait_time = backoff_factor ** attempt print(f"Connection error (attempt {attempt + 1}/{max_retries}): retrying in {wait_time:.1f}s") time.sleep(wait_time) except ValueError as e: # Validation errors should not be retried return ToolResult(success=False, error=f"Argument error: {str(e)}") except Exception as e: last_error = str(e) if attempt == max_retries - 1: break return ToolResult(success=False, error=f"Max retries exceeded: {last_error}")def safe_tool_handler(fc, available_functions: dict) -> dict: """Error-safe tool handler""" func = available_functions.get(fc.name) if not func: return {"error": f"Undefined tool: {fc.name}"} result = execute_with_retry(func, dict(fc.args)) if result.success: return result.data else: # Pass error back to Gemini so it can suggest alternatives return { "error": result.error, "fallback_suggestion": "Could not retrieve information. Attempting alternative approach." }
Tool Chaining: Multi-Step Automation
An agent pattern that chains multiple tools across steps:
def run_tool_chain(initial_query: str, max_steps: int = 5) -> str: """ Agentic loop that automatically executes multi-step tool chains """ model = genai.GenerativeModel("gemini-2.0-flash", tools=tools) conversation = [{"role": "user", "parts": [initial_query]}] for step in range(max_steps): response = model.generate_content(conversation) parts = response.candidates[0].content.parts # Text response means we're done if all(hasattr(p, 'text') for p in parts): return parts[0].text # Process all Function Calls tool_responses = [] for part in parts: if hasattr(part, 'function_call') and part.function_call: fc = part.function_call result = safe_tool_handler(fc, available_functions) tool_responses.append({ "function_response": { "name": fc.name, "response": result } }) # Add to history and continue loop conversation.append({ "role": "model", "parts": parts }) conversation.append({ "role": "user", "parts": tool_responses }) return "Maximum steps reached"# Example: multi-step taskresult = run_tool_chain( "Find smartphones under $300 with more than 10 in stock, then order the cheapest one")print(result)
TypeScript / Node.js Implementation
For Node.js environments:
import { GoogleGenerativeAI, FunctionDeclaration } from "@google/generative-ai";const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);const searchProductsTool: FunctionDeclaration = { name: "search_products", description: "Search products in the database", parameters: { type: "object" as const, properties: { query: { type: "string", description: "Search keywords" }, maxPrice: { type: "number", description: "Maximum price" }, }, required: ["query"], },};async function geminiWithTools(userMessage: string): Promise<string> { const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash", tools: [{ functionDeclarations: [searchProductsTool] }], }); const chat = model.startChat(); let response = await chat.sendMessage(userMessage); while (true) { const functionCalls = response.response.functionCalls(); if (!functionCalls || functionCalls.length === 0) { return response.response.text(); } const toolResults = await Promise.all( functionCalls.map(async (fc) => { let result: unknown; switch (fc.name) { case "search_products": result = await searchProducts(fc.args as { query: string; maxPrice?: number }); break; default: result = { error: `Unknown tool: ${fc.name}` }; } return { functionResponse: { name: fc.name, response: result, }, }; }) ); response = await chat.sendMessage(toolResults); }}
Rolling Your Own Tool Loop vs. Delegating to Managed Agents
Every tool chain so far kept conversation history, tool execution, and loop-termination logic entirely in your own code. In June 2026, Gemini API added Managed Agents as a public preview, making a second option practical: an autonomous, stateful agent that runs inside a Google-hosted, isolated Linux sandbox, handling planning, reasoning, code execution, file operations, and browsing on its own.
These aren't competitors — they split along how much control you want to hold. Here's how I think about it:
Dimension
Your own tool loop (this article)
Managed Agents (sandbox delegation)
Execution environment
Your own server / functions
Google-hosted isolated sandbox
State management
You hold the conversation history
Stateful (the agent holds it)
Best for
Deterministic access to your DB / internal APIs
Exploratory, multi-step tasks with code execution
Granularity of control
High (intervene step by step)
Low (hand over a goal and let it run)
Latency / cost
Predictable, scales with call count
Harder to predict as it runs autonomously
My rule is simple: anything with deterministic side effects that I want to audit step by step stays in the self-managed loop. Order creation or billing — where I need to record and verify exactly which function ran with which arguments — is not something I want to hand wholesale to a sandbox. Exploratory tasks like "read across several documents, summarize the key points, and run a calculation if needed" are a better fit for Managed Agents.
In practice I find it easiest to keep the outer loop deterministic and self-managed, and carve out only the single judgment-heavy step for an agent. Deliberately keeping the delegated scope narrow is what keeps both debuggability and safety intact.
Pin the Tool-Selecting Model to a GA Release
Function Calling accuracy depends as much on which model selects the tool as on the quality of your tool definitions. The easy-to-miss operational trap is that the default model can change underneath you. In June 2026, Gemini 3.5 Flash reached general availability (GA), positioned as fast while sustaining strong agentic and coding performance. Every generational shift like this can change both the tool-selection behavior and the cost of code that names its model loosely.
For anything involving tools, I always name a GA model ID explicitly and record the model actually served in the response.
Before (relying on the default — blind to swaps):
model = genai.GenerativeModel("gemini-flash-latest", tools=tools)response = model.generate_content(user_message)# Nothing in the logs tells you which model was actually used
After (pin a GA release and reconcile requested vs. served):
import loggingPINNED_MODEL = "gemini-3.5-flash" # Name the GA release; change only this on a generation shiftdef call_with_tools(user_message: str): model = genai.GenerativeModel(PINNED_MODEL, tools=tools) response = model.generate_content(user_message) served = getattr(response, "model_version", None) if served and not served.startswith(PINNED_MODEL): # requested != served: detect silent default swaps / auto-upgrades logging.warning("model drift: requested=%s served=%s", PINNED_MODEL, served) return response
Just reconciling model_version on every call lets you catch "a different model was quietly selecting my tools" early.
For reference, here's the tendency I saw running tool-bearing queries (a single product-search tool) over an internal validation set across the Dolice Labs projects. Treat these as directional, not absolute — they shift with environment and prompt:
Dimension
Lightweight Flash tier
Higher Pro tier
Tool-selection soundness
Plenty for everyday single-tool cases
More stable on ambiguous, multi-tool prompts
Perceived per-request latency
Fast
Somewhat heavier
Cost
Low (easy to run at volume)
High
Best stage
First pass / bulk processing
Re-judging low-confidence cases
In my own setup I run a first pass for tool selection on the Flash tier, and escalate only the split or low-confidence cases to a higher model. Now that GA has made the Flash tier sharper, a larger share resolves on the first pass, so I escalate less often.
Where to go next
Function Calling grows in stages — from single data retrieval to parallel execution and tool chains. And as of 2026 you have three options: hold everything yourself, delegate to Managed Agents, or combine the two.
For your next step, write out the tools you currently run and sort them into "deterministic side effects" and "exploratory." Once that line is drawn, deciding how far to hold the self-managed loop and where to hand off to an agent gets dramatically easier. I hope this helps in your own builds.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.