Step by Step guide: Build a Voice Agent That Never Goes Silent using ADK Switching a voice agent's sales-report tool from a blocking call to a streaming tool in Google's open-source Agent Development Kit (ADK) with gemini-3.8-live cut the time to the agent's first words from 14.4 seconds to 1.2 seconds in a benchmark workflow, according to the guide. The guide shows that changing a tool's `return` to `yield` makes ADK mark the tool declaration as NON_BLOCKING and return an immediate status acknowledgement to the model, letting the agent narrate progress stages while a slow query runs instead of producing dead air. The approach targets live voice sessions via `run_live`, where a 13-second blocking database query otherwise yields 13 seconds of silence that leads users to assume the call dropped. A guide to streaming tools , non-blocking function calls, and response scheduling in the Agent Development Kit ADK Gemini Live API Toolkit . In text chat, a 13-second database query or RAG pipeline shows a loading spinner. In a live voice conversation, 13 seconds of blocking tool execution produces 13 seconds of dead air, causing users to assume the call dropped and hang up. You cannot always make backend queries sub-second, so the fix must be architectural: keep the agent conversational while slow tools run in the background. Rather than forcing you to wire up custom client-side WebSocket event loops to manage asynchronous tool state, the open-source ADK and gemini-3.8-live let you implement non-blocking voice tools natively in Python. In our benchmark sales-report workflow on gemini-3.8-live, switching from a blocking tool to a streaming tool cut the time to the agent's first words from 14.4 seconds to 1.2 seconds. This guide covers how to use streaming tools, non-blocking FunctionTool definitions, and response scheduling to build responsive, natural voice agents. Watch the 1-minute ADK Live AI Concierge demo video to hear scheduled responses in action: Implementing non-blocking tool calls in ADK provides several immediate user experience benefits: In standard agent development, a tool is defined as an async def function that completes its work and returns a single value: php from typing import Any async def query sales database region: str - dict str, Any : """Runs the quarterly sales report query for a region. Args: region: Sales region to report on, e.g. "EMEA" or "APAC". Returns: A dict containing report metrics upon completion. """ report = await run expensive query region e.g., 13 seconds of work return {"status": "success", "report": report} Single return at the end When passed directly to an Agent in ADK, this function creates a blocking tool call. During a Gemini Live API bidirectional voice session run live , the execution loop awaits the tool's return before generating further model turns. If query sales database takes 13 seconds, the agent produces 13 seconds of complete silence. Prompting instructions such as "tell the user you are working on it" cannot resolve this, because the model's turn is blocked waiting for the function response payload. To keep the conversation fluid, you can choose between two main non-blocking patterns depending on the desired user experience. When an operation consists of meaningful stages such as connecting to a database, aggregating records, and formatting results , a streaming tool lets the agent narrate progress as it happens. In ADK, turning a standard tool into a streaming tool requires changing only return to yield . By defining the function as an AsyncGenerator , ADK automatically marks the tool declaration as NON BLOCKING and returns an immediate status acknowledgement to the model. python import asyncio from typing import AsyncGenerator from google.adk.agents import Agent async def query sales database region: str - AsyncGenerator str, None : """Runs the quarterly sales report. Call this ONCE; it pushes updates automatically. Args: region: Sales region to report on, e.g. "EMEA" or "APAC". Yields: Progress updates while the query runs, followed by the final summary. """ stages = "Connecting to the sales warehouse...", 1.5 , "Scanning order records...", 3.5 , "Aggregating revenue by product line...", 3.5 , "Formatting the report...", 1.5 , for message, delay in stages: yield f"PROGRESS: {message}" await asyncio.sleep delay yield f"DONE: {region} Q2 revenue reached $4.81M, up 12.4% year over year." agent = Agent name="sales analyst", model="gemini-3.8-live", tools= query sales database , instruction= "You are a helpful sales assistant. When the user asks about sales data, " "call query sales database once. Acknowledge the request immediately, " "briefly paraphrase each progress update, and present the final figures " "when completed. Keep answers conversational and concise." , query sales database , ADK immediately dispatches an asynchronous status event to the model. The agent speaks right away "Looking into the EMEA figures for you now..." . yield sends a partial function response upstream. The model paraphrases each update as it arrives "Connecting to the warehouse...", "Aggregating product line data..." , though narration can trail the tool by several seconds. stop streaming function name: str in the agent's tools list, ADK intercepts the call and cancels the generator's underlying asyncio.Task , raising asyncio.CancelledError within your function so you can cleanly close database connections or file handles. interrupted=True so your client can stop audio playback. Running streaming or background tools are not cancelled, and their results are still delivered. To stop a tool, the model must call stop streaming . Continuous progress commentary is not always appropriate. If a user asks for a report and immediately pivots to another topic, an agent that continually interrupts with status updates can feel intrusive. When the wait has no story to narrate, run the task quietly in the background. An effective way to get an immediate verbal acknowledgment without ongoing progress chatter is an async generator that yields only once, upon completion: php async def query sales database region: str - AsyncGenerator dict, None : """Runs the quarterly sales report. Call this ONCE; results arrive when ready.""" report = await run expensive query region Background execution ~13s yield report Single yield upon completion Because this is an async generator, ADK issues an immediate pending response. The agent promptly acknowledges the request "I'm pulling that report together for you now" , remains silent during computation, and presents the figures once ready or when the user finishes speaking. If you prefer to keep standard coroutines async def ... - dict , wrap the function in a FunctionTool and configure response scheduling : python from google.adk.tools import FunctionTool from google.genai import types sales tool = FunctionTool query sales database sales tool.response scheduling = types.FunctionResponseScheduling.WHEN IDLE Note: In ADK 2.10+, you can also set sales tool.behavior = types.Behavior.NON BLOCKING to run a tool in the background without modifying scheduling mode. Understanding the Trade-Off: When using response scheduling with a return-once coroutine, the model receives no payload until the entire function completes. As a result, the model typically does not utter an initial verbal acknowledgment unless the user continues speaking. This pattern is ideal when the user immediately continues with other questions and you want the result woven into the dialogue at the next natural pause. The response scheduling attribute controls when the model reacts to an incoming tool response. WHEN IDLE never cuts into a turn in progress: if the model is still speaking or the user is still talking, the result waits until that turn ends, and the model responds at the next natural pause. ADK supports three scheduling behaviors: In benchmark sessions with gemini-3.8 -live: WHEN IDLE , an incoming sales report waited politely while the model finished answering a separate 20-second question, then delivered the figures seamlessly. INTERRUPT , the incoming result cut off current speech mid-sentence; the model then finished its answer and appended the figures. SILENT , the result was added to the conversation history, available for subsequent turns without prompting an unrequested announcement. Execution shape streaming vs. single-return and delivery timing response scheduling are independent and can be composed. Setting response scheduling on a streaming tool stamps every partial update with that behavior. Furthermore, in ADK 2.8 and newer, streaming tools can dynamically specify scheduling per update by yielding types.FunctionResponse : Acknowledge and keep intermediate progress quiet: yield types.FunctionResponse response={"stage": "aggregating"}, scheduling=types.FunctionResponseScheduling.SILENT, Deliver the final result when the speaker finishes: yield types.FunctionResponse response={"report": final data}, scheduling=types.FunctionResponseScheduling.WHEN IDLE, The rule of thumb: If the wait has a story, stream it. If the wait is just a wait, run it in the background and let the result land when idle. Non-blocking tools do not reduce backend execution time: a 13-second query still takes 13 seconds. What changes is the conversational presence of your agent. A web application that takes ten seconds to load is perceived as slow; a conversational voice agent that goes silent for ten seconds is perceived as broken. By pairing ADK's streaming tools and flexible response scheduling with the real-time capabilities of gemini-3.8-live , you can build voice assistants that remain attentive, natural, and present throughout complex operations. Try it yourself: the interactive demo https://adk-streaming-tools-761793285222.us-central1.run.app/demo runs the same 13-second query through every tool pattern and response scheduling mode on gemini-3.8-live, so you can hear the difference. google-adk = 2.10.0 gemini-3.8-live on run live .