๐ข Disclosure: I created this blog post as part of my submission to the Gemini Live Agent Challenge hackathon. The project โ InterviewAce โ was built specifically for this competition using Google AI models and Google Cloud. #GeminiLiveAgentChallenge
๐ Live Demo ยท ๐ป GitHub ยท ๐น Demo Video
The Problem Nobody Talks About
Everyone knows technical interviews are hard. But here is what nobody says out loud โ most people never actually practice them.
Not because they are lazy. Because real practice is expensive and inaccessible:
- Professional mock interview services charge $150โ$300 per session
- Asking friends to interview you is awkward and rarely useful
- AI chatbots give you text responses โ but real interviews are not text conversations
And here is the deeper issue: the things that fail candidates are not the answers โ they are the delivery. The "um"s and "uh"s. The slouched posture. The rambling answer that never reaches a conclusion. The eye contact that breaks every time you think.
No text-based AI tool has ever addressed this. Until now.
What I Built: InterviewAce
InterviewAce is a real-time, multimodal AI interview coach that puts you in a pixel-perfect Google Meet replica with an AI hiring manager called Coach Ace โ who simultaneously:
- ๐ฃ๏ธ Speaks to you with sub-500ms voice latency via Gemini 2.5 Flash Native Audio
- ๐ Watches your body language live through your webcam
- ๐ค Detects filler words in real time ("um", "uh", "like", "you know")
- ๐ Scores your answers across Confidence, Clarity, Content and STAR structure
- ๐ Searches Google live to give hallucination-free company-specific context
- ๐ Generates a full performance report with downloadable transcript
No typing. No text boxes. Just a real conversation with an AI that actually watches and listens.
Tech Stack at a Glance
| Layer | Technology |
|---|---|
| AI Agent | Google ADK + Gemini 2.5 Flash Native Audio |
| Live Streaming | Gemini Live API (bidiGenerateContent) |
| Backend | Python, FastAPI, Uvicorn, WebSockets |
| Frontend | Vanilla JavaScript, Web Audio API, MediaDevices API |
| Grounding | ADK built-in google_search + local knowledge base |
| Infrastructure | Google Cloud Run, Docker, Cloud Build |
System Architecture
Here is the complete picture of how every component connects โ from your microphone all the way to Gemini and back:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ฅ๏ธ BROWSER (Vanilla JS) โ
โ โ
โ ๐ค Microphone (PCM 16kHz) โโโ โ
โ ๐ท Camera (JPEG 1fps) โโโโโโโผโโโถ WebSocket Client โโโโโโโโโโ โ
โ โ โ โ โ
โ ๐ Audio Player โโโโโโโโโโโโโโผโโโโโโโโโโ โ โ
โ ๐ฌ Closed Captions โโโโโโโโโโโโค Audio + Images + JSON โ โ
โ ๐ Live Analytics โโโโโโโโโโโโโ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโ
โ WebSocket
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโ
โ โ๏ธ FASTAPI BACKEND (Python) โ
โ โ
โ WebSocket Server (main.py) โ
โ โ โ
โ โผ โ
โ LiveRequestQueue โโโถ ADK Runner โโโถ InMemorySessionService โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โฒ
Bidi Stream Audio + Tool Results
โ โ
โโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ค GOOGLE ADK AGENT โ
โ โ
โ Gemini 2.5 Flash Native Audio + Vision โ
โ โ โ
โ โผ Autonomous Tool Calls (silent, every 2-3 answers) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ๐ง 11 CUSTOM TOOLS โ โ
โ โ โ โ
โ โ TIER 1 โ Core Analysis: โ โ
โ โ save_session_feedback โ detect_filler_words โ โ
โ โ analyze_body_language โ evaluate_star_method โ โ
โ โ โ โ
โ โ TIER 2 โ Deep Coaching: โ โ
โ โ analyze_voice_confidence โ get_improvement_tips โ โ
โ โ fetch_grounding_data โ adjust_difficulty_level โ โ
โ โ โ โ
โ โ TIER 3 โ Session Reporting: โ โ
โ โ get_session_history โ save_session_recording โ โ
โ โ generate_session_report โ โ
โ โ โ โ
โ โ GROUNDING: google_search (ADK built-in) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ๏ธ GOOGLE CLOUD โ
โ โ
โ Cloud Run (Serverless Container) + Container Registry โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Data Flow โ Step by Step
STEP 1 โโ User speaks
Mic โ PCM audio (16kHz) โ WebSocket โ FastAPI โ LiveRequestQueue
STEP 2 โโ Camera streams
Webcam โ JPEG frame (1fps, 320ร240) โ WebSocket โ FastAPI โ LiveRequestQueue
STEP 3 โโ Gemini responds
LiveRequestQueue โ bidiGenerateContent โ Gemini 2.5 Flash
Gemini audio bytes โ WebSocket โ Browser AudioPlayer โ User hears voice
STEP 4 โโ Background tools fire (silently, every 2-3 answers)
Gemini calls โ detect_filler_words()
Gemini calls โ analyze_body_language()
Gemini calls โ evaluate_star_method()
Gemini calls โ save_session_feedback()
Tool results โ JSON side-channel โ WebSocket โ Sidebar updates live
STEP 5 โโ Transcription
Gemini โ Input + Output transcription โ Closed Captions rendered in UI
STEP 6 โโ Session ends
User clicks End Interview
โ generate_session_report()
โ Full modal: scores, breakdown, downloadable transcript
Project File Structure
IntyerviewBit/
โโโ README.md
โโโ cloudbuild.yaml โ Google Cloud Build CI/CD
โโโ interviewace/
โโโ Dockerfile โ Cloud Run container
โโโ .env.example
โโโ requirements.txt
โโโ app/
โโโ main.py โ FastAPI + WebSocket server
โโโ interview_coach_agent/
โ โโโ agent.py โ ADK Agent + 11 tools registered
โ โโโ prompts.py โ Coach Ace persona + instructions
โ โโโ tools.py โ All 10 custom tool implementations
โ โโโ grounding_data.py โ Verified local coaching knowledge base
โโโ static/
โโโ index.html โ Single-page Google Meet replica
โโโ css/
โ โโโ style.css โ Complete Meet-style CSS
โโโ js/
โโโ app.js โ Main app logic + WebSocket client
โโโ audio-player.js โ PCM audio playback engine
โโโ audio-recorder.js โ Mic capture + 48kHzโ16kHz downsample
โโโ camera.js โ Adaptive webcam frame capture
The Agent โ Coach Ace
COACH ACE โ FULL TOOL MAP
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
TIER 1 โ Core Analysis (fires silently every 2-3 answers)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Tool โ What It Does โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ save_session_feedback โ Scores 4 dimensions 0-100: โ
โ โ Confidence, Clarity, Content, โ
โ โ Body Language โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ detect_filler_words โ Counts um / uh / like / you know. โ
โ โ Updates live sidebar counter + tips โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ analyze_body_language โ Rates posture, eye contact, โ
โ โ expression from live camera frame โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ evaluate_star_method โ Checks S-T-A-R answer structure. โ
โ โ Lights up S T A R badges in real timeโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
TIER 2 โ Deep Coaching
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ analyze_voice_confidence โ Pace, volume, tone, pause analysis โ
โ get_improvement_tips โ Targeted coaching per weakness โ
โ fetch_grounding_data โ Pulls from verified local KB โ
โ adjust_difficulty_level โ Scales question difficulty up/down โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
TIER 3 โ Session Management
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ get_session_history โ Retrieves scores from past sessions โ
โ save_session_recording โ Persists transcript + all metrics โ
โ generate_session_report โ Builds full post-interview breakdown โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
GROUNDING
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ google_search โ ADK built-in. Live web search for โ
โ (ADK built-in) โ company-specific interview facts โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Architechture -
The Dual Grounding System
Early in development, Coach Ace would confidently hallucinate Amazon Leadership Principles or invent Google interview formats. I fixed it with two grounding layers:
CANDIDATE ASKS: "What is Google's interview process like?"
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GROUNDING ROUTER โ
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโดโโโโโโโโโโโโโ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ fetch_grounding_ โ โ google_search() โ
โ data() โ โ ADK built-in โ
โ โ โ โ
โ LOCAL KNOWLEDGE โ โ LIVE WEB SEARCH โ
โ BASE โ โ โ
โ (grounding_data.py) โ โ Searches for real, โ
โ โ โ current company โ
โ Covers: โ โ interview info โ
โ โข STAR method โ โ โ
โ โข Body language โ โ Prevents all โ
โ โข Voice delivery โ โ hallucination of โ
โ โข Common mistakes โ โ company-specific โ
โ โข Coaching tips โ โ facts โ
โโโโโโโโโโโโฌโโโโโโโโโโโ โโโโโโโโโโโโฌโโโโโโโโโโโ
โ โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โผ
GROUNDED RESPONSE
(accurate + verified)
Code Deep Dive
1. The WebSocket Bridge (main.py)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
session_service = InMemorySessionService()
session = await session_service.create_session(
app_name="interviewace",
user_id="candidate",
session_id=str(uuid.uuid4())
)
live_request_queue = LiveRequestQueue()
# Start ADK runner โ talks to Gemini Live API in background
runner_task = asyncio.create_task(
run_agent(runner, live_request_queue, websocket, session)
)
try:
async for message in websocket.iter_bytes():
data = parse_message(message)
if data["type"] == "audio":
live_request_queue.send_realtime(
Blob(data=data["chunk"], mime_type="audio/pcm;rate=16000")
)
elif data["type"] == "image":
live_request_queue.send_realtime(
Blob(data=data["frame"], mime_type="image/jpeg")
)
finally:
runner_task.cancel()
2. The ADK Agent (agent.py)
from google.adk.agents import Agent
from google.adk.tools import google_search
from .tools import (
save_session_feedback, detect_filler_words,
analyze_body_language, evaluate_star_method,
analyze_voice_confidence, get_improvement_tips,
fetch_grounding_data, adjust_difficulty_level,
get_session_history, save_session_recording,
generate_session_report,
)
coach_ace = Agent(
name="coach_ace",
model="gemini-2.5-flash-preview-native-audio-dialog",
description="Senior AI hiring manager โ real-time mock interviews",
instruction=COACH_ACE_PROMPT,
tools=[
save_session_feedback, detect_filler_words,
analyze_body_language, evaluate_star_method,
analyze_voice_confidence, get_improvement_tips,
fetch_grounding_data, adjust_difficulty_level,
get_session_history, save_session_recording,
generate_session_report,
google_search, # ADK built-in grounding tool
],
)
3. Silent Background Tool (Example)
def detect_filler_words(transcript: str, session_id: str) -> dict:
"""
Fires autonomously every 2-3 answers.
User never hears a pause โ runs between turns.
"""
filler_patterns = ["um", "uh", "like", "you know", "basically", "literally"]
counts = {f: transcript.lower().count(f) for f in filler_patterns if transcript.lower().count(f) > 0}
total = sum(counts.values())
update_session_analytics(session_id, "filler_words", {
"total": total,
"breakdown": counts,
"coaching_tip": get_filler_tip(total)
})
return {"filler_count": total, "breakdown": counts}
4. PCM Audio Engine (audio-recorder.js)
class AudioRecorder {
constructor(onAudioData) {
this.onAudioData = onAudioData;
this.targetSampleRate = 16000; // Gemini Live expects 16kHz
}
async start() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const context = new AudioContext(); // Native rate ~48kHz
const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
scriptProcessor.onaudioprocess = (event) => {
const inputData = event.inputBuffer.getChannelData(0);
// Downsample 48kHz โ 16kHz before sending to Gemini
const downsampled = this.downsample(inputData, context.sampleRate, 16000);
const pcm16 = this.floatTo16BitPCM(downsampled);
this.onAudioData(pcm16); // โ WebSocket โ FastAPI โ Gemini
};
source.connect(scriptProcessor);
scriptProcessor.connect(context.destination);
}
downsample(buffer, fromRate, toRate) {
const ratio = fromRate / toRate;
const result = new Float32Array(Math.round(buffer.length / ratio));
for (let i = 0; i < result.length; i++) {
result[i] = buffer[Math.round(i * ratio)];
}
return result;
}
}
5. Adaptive Webcam Capture (camera.js)
class CameraCapture {
captureFrame() {
const canvas = document.createElement('canvas');
canvas.width = 320; // Low-res โ body language doesn't need 1080p
canvas.height = 240;
const ctx = canvas.getContext('2d');
ctx.drawImage(this.videoElement, 0, 0, 320, 240);
// JPEG 60% quality โ sufficient for vision, minimal bandwidth
canvas.toBlob(
(blob) => blob.arrayBuffer().then(buf => this.onFrame(buf)),
'image/jpeg',
0.6
);
}
adaptFrameRate(networkQuality) {
// Drop to 0.33fps (1 frame/3 sec) under poor network
this.fps = Math.max(0.33, Math.min(1, networkQuality));
}
}
The UI โ Google Meet Replica
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ InterviewAce [Google logo] โฑ 12:34 [Participants] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ ๐ LIVE ANALYTICS โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ โ โ Confidence โโโโโโ โ
โ โ COACH ACE โ โ ELENA โ โ Clarity โโโโโโ โ
โ โ AI Interviewer โ โ AI Notetaker โ โ STAR Score โโโโโโ โ
โ โ โ โ โ โ Body Lang. โโโโโโ โ
โ โ [Volume Rings] โ โ [Volume Rings] โ โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ ๐ค Filler Words: 3 โ
โ โ um(2) uh(1) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ ๐ Eye Contact โโ โ
โ โ YOU โ โ ๐ง Posture โโ โ
โ โ (Live Webcam) โ โ ๐ Expression โโ โ
โ โ โ โ โ
โ โ [Equalizer bars animate when mic] โ โ STAR Badges: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ [Sโ][Tโ][Aโ][Rโ] โ
โ โ โ
โ ๐ฌ CC: "...tell me about a time you had โ ๐ก Use 'I' not 'we' โ
โ to debug a production issue..." โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโค
โ [๐ค Mic] [๐ท Cam] [CC] [Chat] [People] [๐ด End Interview] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Built in 100% Vanilla JavaScript โ no React, no Vue, no Angular. Frameworks add event loop overhead that impacts PCM audio timing. At 16kHz, every millisecond of jitter is audible.
Cloud Deployment Pipeline
Developer pushes to GitHub
โ
โผ
Google Cloud Build (cloudbuild.yaml)
โ
โโโ docker build -t gcr.io/PROJECT/interviewace .
โโโ docker push โ Container Registry
โโโ gcloud run deploy interviewace
โ
โโโ Region: us-central1
โโโ Memory: 1Gi
โโโ Port: 8080
โโโ session-affinity: TRUE โ critical for WebSockets
โโโ allow-unauthenticated: TRUE
โ
โผ
Cloud Run Serverless Container
โโโ Scales to zero when idle (cost: $0)
โโโ Handles WebSocket connections persistently
โโโ Scales instantly on demand
Key lesson learned the hard way: Cloud Run requires
--session-affinityfor any WebSocket-based app. Without it, the load balancer routes mid-session requests to a different container instance, breaking your persistent connection. This cost us hours to debug.
Challenges I Ran Into
1. Real-time audio + vision sync
Streaming 16kHz PCM audio and JPEG frames simultaneously over one WebSocket without dropped frames required bandwidth-adaptive throttling and decoupled queues.
2. Barge-in handling
When the user speaks mid-response, the agent must stop cleanly without corrupting the audio buffer. Getting reliable barge-in via the ADK streaming layer took multiple iterations.
3. Invisible tool calls
All background analysis tools need to feel completely invisible. I tuned them to fire silently between turns and stream analytics to the UI via a JSON side-channel on the same WebSocket.
4. Company hallucination
Early versions confidently invented interview formats. Fixed with two-layer grounding: verified local knowledge base + ADK's built-in Google Search.
5. Audio latency in production
Getting end-to-end voice latency under 500ms on Cloud Run required tuning buffer sizes, optimising the PCM pipeline, and keeping ASGI async throughout.
Key Lessons
- Native audio models are fundamentally different from text models. Design your system for async bidirectional streaming from the ground up.
- Grounding is non-negotiable for agentic apps. Prompt engineering alone is not enough โ even capable models hallucinate domain facts without grounding.
- Vanilla JS outperforms frameworks for latency-sensitive audio/video. Full control over the audio pipeline timing matters at the PCM level.
- ADK LiveRequestQueue needs careful queue management. Keep audio, vision, and tool-call result streams strictly decoupled.
- Always add session-affinity to Cloud Run WebSocket services. Stateless load balancing breaks persistent connections.
Try It Yourself
๐ Live Demo: https://interviewace-117780891544.us-central1.run.app/
No API key. No signup. No cost. Click the link, allow mic + camera, and start your mock interview.
๐ป GitHub: https://github.com/SameerAliKhan-git/IntyerviewBit
๐น Demo Video: https://youtu.be/JrjhgB5Ib_0
Built for the Gemini Live Agent Challenge using Google ADK ยท Gemini Live API ยท Google Cloud Run
#GeminiLiveAgentChallenge #GoogleAI #Gemini #ADK #GoogleCloud #Python #WebDev


Top comments (0)