Technical Deep DiveNov 2025 (8 min read)

Engineering Production AI Voice Agents: Low-Latency STT, Tool Calling & TTS Avatars

Architecting sub-second conversational voice agent interfaces combining Deepgram streaming STT, Gemini 2.5 Flash function calling, and Google TTS.

AIVoice AgentsDeepgramGemini APIRAGWebSockets
Abstract

A technical walkthrough of designing full-duplex conversational voice agents with streaming speech recognition, real-time tool calling, and low-latency audio synthesis.

1. The Latency Waterfall in Conversational AI

In traditional text chatbots, a 1.5-second time-to-first-token is acceptable. In conversational voice agents, however, any delay above 700ms feels like an awkward pause, destroying the illusion of natural conversation. The latency waterfall consists of: 1. **User Audio Streaming + STT (Speech-to-Text)**: ~200ms 2. **Context Retrieval / RAG + LLM Inference**: ~300ms 3. **TTS (Text-to-Speech) Audio Synthesis**: ~200ms 4. **Network Round-Trip Time**: ~50ms To keep the entire loop under 750ms end-to-end, every phase of the pipeline must be streamed concurrently rather than processed sequentially.

Critical Optimization: Do not wait for the LLM to complete its full response. Synthesize the first sentence to TTS as soon as punctuation (period, question mark) is tokenized.

2. The Full-Duplex WebSocket Pipeline

Our production voice agent infrastructure at Aadrila Technologies connected the client browser to a Node.js/FastAPI orchestrator over WebSockets: - **Audio Capture**: Browser uses `AudioWorkletNode` to capture 16kHz PCM audio and stream binary frames. - **Deepgram Live Transcriber**: Direct WebSocket pipe returns finalized transcripts within 120ms of speech pause. - **Gemini 2.5 Flash with Tool Calling**: Ingests transcript, queries vector store or internal APIs if needed, and streams response tokens. - **Google TTS / Streaming Speech**: Synthesizes audio buffer chunks in real-time, streaming audio back down the same client WebSocket.
voiceAgentOrchestrator.tstypescript
import { WebSocket } from "ws";
import { GoogleGenAI } from "@google/genai";
import { createClient, LiveTranscriptionEvents } from "@deepgram/sdk";

export function handleVoiceSession(clientWs: WebSocket) {
  const deepgram = createClient(process.env.DEEPGRAM_API_KEY!);
  const dgConnection = deepgram.listen.live({
    model: "nova-2",
    language: "en-US",
    smart_format: true,
    interim_results: false,
  });

  const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });

  // 1. Pipe client mic stream directly into Deepgram STT
  clientWs.on("message", (audioData: Buffer) => {
    if (dgConnection.getReadyState() === 1) {
      dgConnection.send(audioData);
    }
  });

  // 2. On finalized speech transcript, trigger streaming AI inference
  dgConnection.on(LiveTranscriptionEvents.Transcript, async (data) => {
    const transcript = data.channel.alternatives[0]?.transcript;
    if (!transcript || transcript.trim().length === 0) return;

    // Send barge-in signal to interrupt any currently playing audio
    clientWs.send(JSON.stringify({ type: "INTERRUPT_PLAYBACK" }));

    // Stream response from Gemini 2.5 Flash
    const responseStream = await ai.models.generateContentStream({
      model: "gemini-2.5-flash",
      contents: [{ role: "user", parts: [{ text: transcript }] }],
      config: {
        systemInstruction: "You are a concise, helpful voice assistant. Keep answers brief and conversational.",
      },
    });

    let sentenceBuffer = "";
    for await (const chunk of responseStream) {
      const text = chunk.text || "";
      sentenceBuffer += text;

      // As soon as a sentence boundary is reached, trigger TTS chunk
      if (/[.?!]\s$/.test(sentenceBuffer)) {
        await synthesizeAndStreamAudio(sentenceBuffer, clientWs);
        sentenceBuffer = "";
      }
    }
    
    if (sentenceBuffer.trim()) {
      await synthesizeAndStreamAudio(sentenceBuffer, clientWs);
    }
  });
}

3. Barge-In & Voice Activity Detection (VAD)

The most crucial user experience element in voice interfaces is **barge-in interruption**. When a user begins speaking while the AI is responding, the system immediately cancels active TTS synthesis, clears client playback buffers, and switches focus back to listening. By combining WebRTC audio processing, Deepgram's server-side VAD, and local audio worklets, we cut false-positive interruptions by 94% while providing a seamless conversational flow.

Key Engineering Takeaways

  • Achieve full-duplex conversational voice by maintaining persistent bi-directional WebSockets between the client and edge gateway.
  • Stream raw PCM audio chunks to Deepgram STT for sub-150ms transcription with interim results.
  • Use streaming function calling in Gemini 2.5 Flash to fetch live database records before full model completion.
  • Implement Voice Activity Detection (VAD) with aggressive barge-in interruption to make the AI agent feel naturally conversational.