
Building a Sub-Second Voice Agent with Gemini Omni Flash
Voice agents die if they have latency. I used Gemini Omni Flash to build a real-time conversational agent that responds in under 500 milliseconds.
Building a text-based chatbot is forgiving. If a model takes three seconds to type out a response, users don't mind. They are used to watching the typing indicator. Voice is entirely different. If you pause for three seconds during a phone call, the human on the other end assumes the call dropped and starts saying "Hello?"
To build a voice agent, you need raw speed. I tested the new Gemini Omni Flash model specifically because it was designed for ultra-low-latency multimodal tasks. This tutorial covers how to build a voice agent step-by-step that bypasses traditional transcription layers and responds in under 500 milliseconds.
The Latency Problem with Traditional Pipelines
If you build a voice agent using the standard stack, your pipeline looks like this:
- Speech-to-Text (STT): User speaks -> Stream audio to Whisper/Deepgram -> Wait for text transcription (200ms - 500ms).
- LLM Inference: Send text to GPT-4/Claude -> Wait for the first token of the response (500ms - 1000ms).
- Text-to-Speech (TTS): Stream the LLM text to ElevenLabs -> Wait for the first audio byte (200ms - 400ms).
Best case scenario, you are looking at 1 to 2 seconds of silence before the bot replies. Gemini Omni Flash changes this architecture. It natively understands audio inputs. You don't need the STT layer. You just stream the raw microphone bytes directly into the model's context window.
Here is how to build it, step-by-step.
Step 1: Install Dependencies
To follow along, you need Python 3.10+, a Google Gemini API Key, and a microphone attached to your machine. First, install the required libraries for interacting with the Google API and capturing local audio.
pip install google-genai pyaudio asyncio
Step 2: Configure the Microphone
We need to capture raw audio from the microphone. We will use pyaudio to handle the hardware interface. We configure it to capture 16kHz, 16-bit PCM audio, which is the standard format expected by most voice models.
import pyaudio
# Audio configuration constants
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 1024
def setup_microphone():
"""Initializes the PyAudio stream for microphone input."""
audio = pyaudio.PyAudio()
stream = audio.open(
format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK
)
return stream
Step 3: Capture the Audio Stream
In a production application, you would implement Voice Activity Detection (VAD) to only send audio when the user is speaking. For this tutorial, we will capture a fixed 3-second block of audio.
def capture_audio(stream, seconds=3):
"""Reads raw audio chunks from the microphone for a set duration."""
print(f"Listening for {seconds} seconds...")
frames = []
# Calculate how many chunks we need to read to hit our target duration
total_chunks = int(RATE / CHUNK * seconds)
for _ in range(0, total_chunks):
data = stream.read(CHUNK)
frames.append(data)
# Combine the chunks into a single byte string
return b''.join(frames)
Step 4: Stream to Gemini Omni Flash
Here is where the magic happens. We take the raw bytes from the microphone and hand them directly to the gemini-omni-flash model. We bypass transcription entirely.
import google.genai as genai
import asyncio
# Initialize the ultra-fast Omni Flash model client
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
MODEL_ID = 'gemini-omni-flash'
async def ask_gemini(audio_data):
"""Sends the raw audio payload to Gemini and awaits a text response."""
print("Sending raw audio to Omni Flash...")
response = await client.models.generate_content_async(
model=MODEL_ID,
contents=[
"You are a helpful voice assistant. Keep your response under two sentences. Reply naturally to the audio provided.",
{"mime_type": "audio/wav", "data": audio_data}
],
config={"temperature": 0.3}
)
print(f"Agent Reply: {response.text}")
return response.text
Step 5: Put It Together and Connect TTS
Now we wire the pieces together. When you run this, notice how quickly the text comes back. To complete the voice loop, you would stream this resulting text back into a fast TTS engine.
async def main():
# 1. Setup Mic
stream = setup_microphone()
# 2. Record
audio_data = capture_audio(stream)
# 3. Get LLM response instantly
text_reply = await ask_gemini(audio_data)
# 4. Stream to Text-to-Speech (Pseudo-code integration)
# tts_socket = await connect_elevenlabs()
# await tts_socket.send(text_reply)
# ... play response through speakers
if __name__ == "__main__":
asyncio.run(main())
Why This Architecture Wins
When you run this code, the speed is jarring. Because Omni Flash does not have to wait for an external STT model to process a temporary .wav file, the Time-to-First-Token (TTFT) drops drastically. The model starts generating the text response almost exactly when the audio stream ends.
By avoiding transcription, you also preserve the acoustic features of the user's voice. The model can hear tone, emotion, and background noise. If the user sounds angry, the model knows it immediately. If you are building an AI receptionist or a real-time language tutor, drop the transcription layer. Let the model listen.
Related reading
Trusting AI generated code was never the right goal, and the 4 percent of developers who say they fully trust it prove nothing is broken: the fix is an AI code review process that makes verification cheap instead of asking how much to trust the output.
We built and shipped five open-source vertical AI agents. Every single one had the same class of defect: absent or unreadable input rendered as a confident, clean answer. Here is what that bug looks like, why tests miss it, and what actually catches it.
A practical look at the best open weight LLMs for agents in 2026, organized by which constraint, cost, latency, or data residency, should actually decide the pick.
Let's build something great.
Have a project in mind? We are an elite software and AI development studio ready to bring your ideas to production. Let's talk about your roadmap.