vadirajkrishna's picture
initial commit - InterviewCopilotLocal
4e8fb20 unverified
|
Raw
History Blame Contribute Delete
4.23 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade

InterviewCoach β€” Project Brief

Silent real-time coaching during interviews. Runs 100% local on your Mac.


What It Does

When user is in an interview, the app listens to the interviewer's question via mic β†’ transcribes it β†’ classifies the question type β†’ displays a compact coaching card with the framework to follow. You glance at it, then answer. No cloud. No latency. No trace.

APp will add if the question is asked by interviewer or candidate and also tags if answer is answered by the candidate or interviewer. If there is a low cofidence, flags for user updates later.

As a second part, there is another agent who goes through the transcripts and rates the interview by adding critical feedbacks highlighting areas for improvements.


Architecture

LangGraph agentic pipeline. Each node is a discrete async function. State flows through the graph. AudioNode β†’ TranscriptNode β†’ ClassifierAgent β†’ FrameworkNode β†’ UINode ↓ EvaluationAgent (post-session)

Stack

  • Framework: LangGraph + LangChain
  • LLM: Qwen2.5 7B Instruct via Ollama (Part 1) / fine-tuned Qwen2.5 3B via llama.cpp (Part 2)
  • STT: mlx-whisper (whisper-small-mlx)
  • UI: Gradio with custom dark-mode CSS
  • Database: SQLite (via aiosqlite for async)
  • Runtime: Python 3.11+, Apple Silicon M1 Pro

Coding Rules

  • All nodes and DB calls must be async/await β€” no blocking I/O on the main thread
  • Use aiosqlite for all database operations
  • Use asyncio.Queue for audio chunk passing between nodes
  • LangGraph state must be a typed TypedDict
  • Each agent/node lives in its own file under agents/

Agents

ClassifierAgent

  • Input: transcribed question string
  • Output: {type: str, steps: list[str]}
  • Uses Qwen2.5 7B Instruct with structured output prompt
  • Must return valid framework type β€” fallback to "General" if uncertain

EvaluationAgent

  • Input: {question: str, answer: str, framework: str, steps: list[str]}
  • Output: {steps_covered: list[bool], score: int, feedback: str}
  • Runs post-session, not real-time
  • One evaluation card per Q&A exchange

Database (SQLite)

File: interviews.db sql sessions (id, date, company, role, duration) exchanges (id, session_id, question, answer, framework_used, timestamp) evaluations (id, exchange_id, steps_covered_json, score, feedback) transcripts (id, session_id, raw_text, labelled_json) patterns (framework, times_shown, avg_score, most_missed_step) - All DB access via aiosqlite - Init schema on app startup if tables don't exist - Never block the event loop with synchronous sqlite3 calls

Gradio UI

  • Dark mode custom CSS β€” no default Gradio theme
  • Three tabs: Live (coaching cards) Β· Session Log (transcript) Β· Evaluate (post-interview report)
  • Coaching cards: colour-coded by framework type, step-by-step list
  • Use gr.Blocks not gr.Interface
  • UI updates via async generator / queue=True

File Structure

interview-coach/
β”œβ”€β”€ AGENTS.md
β”œβ”€β”€ app.py                  # Gradio entry point
β”œβ”€β”€ graph.py                # LangGraph pipeline definition
β”œβ”€β”€ state.py                # TypedDict state schema
β”œβ”€β”€ agents/
β”‚   β”œβ”€β”€ classifier.py       # ClassifierAgent
β”‚   └── evaluator.py        # EvaluationAgent
β”œβ”€β”€ nodes/
β”‚   β”œβ”€β”€ audio.py            # Whisper capture node
β”‚   β”œβ”€β”€ transcript.py       # Speaker labelling node
β”‚   └── framework.py        # Framework lookup node
β”œβ”€β”€ db/
β”‚   β”œβ”€β”€ schema.py           # Table definitions + init
β”‚   └── queries.py          # Async CRUD functions
β”œβ”€β”€ frameworks.yaml          # Question types + steps
β”œβ”€β”€ prompts.py              # All LLM prompt templates
β”œβ”€β”€ data/
β”‚   └── train.jsonl         # Fine-tuning dataset
└── requirements.txt

Models

  • Part 1: ollama pull qwen2.5:7b
  • Part 2: fine-tuned Qwen2.5 3B via mlx-lm LoRA, exported to GGUF for llama.cpp
  • Swap model in one place only: config.py β†’ MODEL_PATH