Skip to main content

Build a Real-Time AI Voice Chatbot: FastAPI, Whisper & LLM + RAG


Build a Real-Time Voice AI Assistant: FastAPI, Whisper, and Llama-3 Guide

The Ultimate Guide to Real-Time Voice AI

Master the pipeline of modern Voice Assistants. This documentation covers a safe, efficient, and hallucination-free architecture from microphone input to speaker output.

#FastAPI #Whisper #Llama3 #Python

1 Architecture Overview

User Voice Input → Microphone ↓ Speech-to-Text (Faster-Whisper) ↓ LLM Context Synthesis (Llama-3.3) ↓ Text-to-Speech (Tacotron2) ↓ Speaker Output (Low Latency)

This pipeline is designed for production efficiency, ensuring zero-hallucination and rapid response times.

The Tech Stack

  • ✅ FastAPI – High-performance Async API
  • ✅ Faster-Whisper – SOTA Speech-to-Text
  • ✅ Groq / Llama-3 – Ultra-fast LLM Response
  • ✅ Coqui TTS – Natural Voice Synthesis

Project Structure

app/
 ├── frontend/
 │    └── index.html
 ├── backend/
 │    ├── server.py
 │    ├── stt.py
 │    ├── llm.py
 │    └── tts.py
 └── README.md

Frontend Implementation

The client-side handles audio recording and WebSocket communication.

frontend/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Realtime Voice AI</title>
  <style>
    body { background: #0f172a; color: white; font-family: Arial, sans-serif; margin: 0; }
    .container { max-width: 800px; margin: 40px auto; padding: 24px; }
    button { padding: 16px 30px; background: #06b6d4; border: none; color: white; border-radius: 12px; font-size: 18px; cursor: pointer; transition: 0.3s; }
    button:hover { background: #0891b2; transform: scale(1.05); }
    #messages { margin-top: 30px; white-space: pre-wrap; background: #111827; padding: 16px; border-radius: 12px; min-height: 200px; border: 1px solid #1e293b; }
  </style>
</head>
<body>
  <div class="container">
    <h1>🎙️ Realtime Voice AI</h1>
    <p>Speak for 4 seconds and get an AI voice response.</p>
    <button id="startBtn">Start Talking</button>
    <pre id="messages"></pre>
  </div>

  <script>
    const ws = new WebSocket("wss://your-ngrok-url.app/ws");
    const startBtn = document.getElementById("startBtn");
    const messages = document.getElementById("messages");
    let mediaRecorder;

    function log(msg) {
      messages.textContent += msg + "\n";
      messages.scrollTop = messages.scrollHeight;
    }

    ws.onopen = () => log("Connected to backend.");
    ws.onmessage = async (event) => {
      if (typeof event.data === "string") {
        if (event.data.startsWith("AI_TOKEN:")) {
          const token = event.data.replace("AI_TOKEN:", "");
          messages.textContent += token;
        } else { log(event.data); }
      } else {
        const audioBlob = new Blob([event.data], { type: "audio/wav" });
        const url = URL.createObjectURL(audioBlob);
        const audio = new Audio(url);
        await audio.play();
      }
    };

    startBtn.onclick = async () => {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm;codecs=opus" });
      let chunks = [];
      mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
      mediaRecorder.onstop = async () => {
        const blob = new Blob(chunks, { type: "audio/webm;codecs=opus" });
        chunks = [];
        const buffer = await blob.arrayBuffer();
        ws.send(buffer);
        log("\nListening complete. Processing...\n");
      };
      mediaRecorder.start();
      log("Recording for 4 seconds...");
      setTimeout(() => {
        mediaRecorder.stop();
        stream.getTracks().forEach(track => track.stop());
      }, 4000);
    };
  </script>
</body>
</html>

Backend: FastAPI WebSocket Server

backend/server.py
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from stt import transcribe_audio
from llm import stream_llm
from tts import generate_tts

app = FastAPI(title="Realtime Voice AI")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    print("Client connected")
    try:
        while True:
            audio_bytes = await ws.receive_bytes()
            print("Transcribing...")
            text = await transcribe_audio(audio_bytes)

            if not text:
                await ws.send_text("USER: [No speech detected]")
                continue

            print("USER:", text)
            await ws.send_text(f"USER: {text}")

            llm_text = ""
            async for token in stream_llm(text):
                llm_text += token
                await ws.send_text(f"AI_TOKEN:{token}")

            await ws.send_text("AI_DONE")
            print("Generating TTS...")
            audio = await generate_tts(llm_text)
            await ws.send_bytes(audio)

    except WebSocketDisconnect:
        print("Client disconnected")
    except Exception as e:
        print("Error:", e)
        try: await ws.send_text(f"ERROR: {str(e)}")
        except: pass

Speech-to-Text Logic

stt.py
from faster_whisper import WhisperModel
import tempfile
import subprocess
import os

try:
    import torch
    DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
    COMPUTE_TYPE = "float16" if DEVICE == "cuda" else "int8"
except Exception:
    DEVICE = "cpu"
    COMPUTE_TYPE = "int8"

model = WhisperModel("base", device=DEVICE, compute_type=COMPUTE_TYPE)

async def transcribe_audio(audio_bytes: bytes) -> str:
    with tempfile.NamedTemporaryFile(delete=False, suffix=".webm") as f:
        f.write(audio_bytes)
        input_path = f.name

    wav_path = input_path.replace(".webm", ".wav")
    try:
        subprocess.run([
            "ffmpeg", "-y", "-i", input_path, 
            "-ar", "16000", "-ac", "1", wav_path
        ], check=True, capture_output=True)

        segments, _ = model.transcribe(wav_path, beam_size=1, vad_filter=True)
        text = "".join(seg.text for seg in segments)
        return text.strip()
    finally:
        for p in [input_path, wav_path]:
            if os.path.exists(p): os.remove(p)

LLM Synthesis (Groq Llama-3)

from openai import OpenAI

client = OpenAI(
    base_url="https://api.groq.com/openai/v1",
    api_key="your_api_key"
)

async def stream_llm(text: str):
    response = client.chat.completions.create(
        model="llama-3.3-70b-versatile",
        stream=True,
        messages=[
            {"role": "system", "content": "You are a realtime AI voice assistant."},
            {"role": "user", "content": text}
        ]
    )
    for chunk in response:
        delta = chunk.choices[0].delta.content
        if delta:
            yield delta

Voice Synthesis (TTS)

from TTS.api import TTS
import tempfile
import os
import logging
import asyncio

logging.basicConfig(level=logging.INFO)
tts = TTS("tts_models/en/ljspeech/tacotron2-DDC")

async def generate_tts(text: str) -> bytes:
    if not text.strip(): raise ValueError("Empty text")
    with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f:
        path = f.name
    try:
        logging.info("Generating TTS")
        await asyncio.to_thread(tts.tts_to_file, text=text, file_path=path)
        with open(path, "rb") as audio:
            return audio.read()
    except Exception as e:
        logging.exception("TTS failed")
        raise e
    finally:
        if os.path.exists(path): os.remove(path)

Elevating with RAG

Retrieval-Augmented Generation (RAG) is the gold standard for voice bots. It prevents hallucinations and allows your bot to answer based on your own documentation.

Pros & Cons of Non-RAG

  • 🟢 Natural conversation style.
  • 🟢 Fully self-contained.
  • 🔴 Low coverage of specific data.
  • 🔴 Requires retraining for updates.
  • 🔴 Hallucinations on niche topics.

The RAG Advantage

  • ✅ Never makes up answers outside content.
  • ✅ Add new data in seconds (No Retraining).
  • ✅ Handles complex, paraphrased queries.

Suggested Transition

  1. Store your knowledge (FAQs/Docs) in a Vector Database (Pinecone/Chroma).
  2. Use a Retriever to find the most relevant document before calling the LLM.
  3. Set FAQ content as High Priority in the system prompt.
  4. Combine RAG with fine-tuning only if you need a very specific "persona" or voice tone.


Contact Us

Name

Email *

Message *

Popular Posts

GMSK BER vs SNR (with Simulation)

  GMSK Performance: Why the BER vs. SNR Curve Matters If you’ve ever wondered why GMSK (Gaussian Minimum Shift Keying) became the backbone of the global GSM revolution, the answer lies in its unique balance of spectral efficiency and power performance. But there is no free lunch in engineering. To get that sleek, narrow spectrum, you have to pay a price in Bit Error Rate (BER) . In this guide, we break down the theoretical BER vs. SNR performance of GMSK, the "magic" of the BT product, and how to optimize your link budget for real-world conditions. What is GMSK and Why Should You Care? GMSK is a derivative of MSK (Minimum Shift Keying) that uses a Gaussian pre-modulation filter. This filter "smooths" the phase transitions, significantly reducing sideband power. The result? A constant envelope signal that allows you to use high-efficiency Class-C amplifiers without distorting the signal. This is the secr...

UGC NET Electronic Science Previous Year Question Papers with Solutions

Download Papers and Solutions Exam Pattern Preparation Tips FAQs More Home / Engineering & Other Exams / UGC NET 2026 PYQ 📊 Exam Highlights: Electronic Science (88) Feature Details Junior Research Fellowship (JRF) ₹37,000 + HRA per month Eligibility M.Sc/M.Tech in Electronics (55%) Validity of Certificate JRF (3 Years) | Lectureship (Lifetime) 📥 Download UGC NET Electronics PDFs Complete collection of previous year question papers, answer keys and explanations for Subject Code 88. Start Downloading 📂 View All Question Papers June 2026 - Question Paper Download PDF June 202...

What is a Supernode?

  Circuit Analysis What Is a Supernode? A supernode is formed when an ideal voltage source connects two non-reference nodes. It allows us to apply Kirchhoff's Current Law (KCL) to the combined circuit boundary. 1. Understanding a Supernode In nodal analysis, a voltage source between two unknown-voltage nodes makes the current through that source difficult to express directly using Ohm's law. We combine the two nodes and the voltage source into one extended region called a supernode. Key idea Apply KCL around the outer boundary of the supernode, then use the voltage source to write an additional voltage equation. 2. Supernode Circuit Diagram The voltage source connects nodes V L an...

A binary ripple counter is designed to count (0)₁₀ to (64)₁₀. Which of the following is/are the number of flip-flops required ...

  Q. A binary ripple counter is designed to count  (0)₁₀ to (64)₁₀ . Which of the following is/are the number of flip-flops required to design the counter?   (A) 6 (B) 7 (C) 4 (D) 5 Solution A binary ripple counter must count from (0)₁₀ to (64)₁₀ , including both endpoints. Step 1: Calculate the total number of states Since both 0 and 64 are included, add 1 to the difference: N = Last value − First value + 1 N = 64 − 0 + 1 = 65 states The counter visits every integer from 0 through 64: 0, 1, 2, 3, ..., 63, 64 There are 65 distinct states in total. Step 2: Calculate the number of flip-flops Each flip-flop stores one bit. With n flip-flops, the counter can represent 2ⁿ distinct states. 2⁶ = 64 states (not enough) 2⁷ = 128 states (eno...

The cutoff frequency (in GHz) for the dominant TE10 mode of an air-filled rectangular waveguide of ...

  Q. The cutoff frequency (in GHz) for the dominant TE10 mode of an air-filled rectangular waveguide of inner dimension 0.28 inch×0.14 inch is __. (rounded off to two decimal places). Given Information Waveguide dimensions = 0.28 inch × 0.14 inch Wider dimension (a) = 0.28 inch Smaller dimension (b) = 0.14 inch Medium = Air Dominant mode = TE 10 Speed of light (c) = 3 × 10 8 m/s Step 1: Cutoff Frequency Formula The cutoff frequency of a rectangular waveguide is: f c = (c / 2) √[(m/a) 2 + (n/b) 2 ] For the dominant TE 10 mode: m = 1, n = 0 f c = c / 2a Step 2: Convert Inch into Meter We know that 1 inch = 0.0254 meter. a = 0.28 × 0.0254 a = 0.007112 m ...

SRAM, DRAM, EPROM, MRAM, and DDRAM Explained

Computer Fundamentals Types of Memory and Their Technologies Understand the fundamental relationship between different types of computer memory and the technologies used to implement them. Fundamental Matching Memory type, technology, and key characteristics Memory Type Technology Key Characteristic Cache Memory SRAM Very fast; stores frequently accessed data Main Memory DRAM Holds programs and data currently in use Au...

Parametric Down-Conversion of Photons

  Nonlinear Optics One incoming photon is converted into two lower-energy photons, typically in a nonlinear optical medium. Energy is conserved: E in = E 1 + E 2 E in ​ = E 1 ​ + E 2 ​ . Parametric Down-Conversion A nonlinear optical process in which one pump photon is converted into two lower-energy photons, known as the signal and idler photons. 1 Energy Conservation The energy of the pump photon equals the sum of the energies of the signal and idler photons. \[ \boxed{\hbar\omega_p=\hbar\omega_s+\hbar\omega_i} \] \[ \boxed{\omega_p=\omega_s+\omega_i} \] ωₚ Pump angular frequency ...

An S-band radar transmitting at 3 GHz radiates 200 kW of power. Determine the signal power density at a range of 100 ...

Q. An S-band radar transmitting at 3 GHz radiates 200 kW of power. Determine the signal power density at a range of 100 nautical miles if the effective area of the radar antenna is 9 m². Solution Given, Transmitted power: P t = 200 kW = 200,000 W Frequency: f = 3 GHz = 3 × 10⁹ Hz Range: R = 100 nautical miles Effective antenna area: A e = 9 m² Speed of light: c = 3 × 10⁸ m/s Step 1: Convert Nautical Miles to Meters One nautical mile equals 1852 meters. R = 100 × 1852 = 185200 m Step 2: Calculate the Wavelength The wavelength is calculated using the relationship between the speed of light and frequency: λ = c / f λ = (3 × 10⁸) / (3 × 10⁹) λ = 0.1 m Step 3: Calculate the Antenna Gain The relationship between effective aperture and antenna gain is: G = (4πAₑ) / λ² ...