An AI-powered technical interview assistant. A candidate uploads their CV, is matched to a job's required topics, and answers questions that are personalized to their actual skills and projects (not generic trivia) — pulled from a curated question bank via retrieval and graded automatically against clear expected points, with an adaptive stop rule and a structured final report for the hiring team.
Built for an Arabic-speaking candidate base, but the interface and question bank currently run in English (see Future Improvements).
🌐 Live demo: YOUR_STREAMLIT_LINK_HERE
📹 Demo video:
WhatsApp.Video.2026-08-03.at.1.29.50.PM.mp4
- Problem
- How it works
- Data
- Screenshots
- Quick start
- Walkthrough example
- Monitoring
- Evaluation
- Repository structure
- Tech stack
- How this project maps to the evaluation criteria
- Future improvements
Technical recruiters and hiring teams spend a large amount of time conducting first-round technical interviews to screen candidates before they even reach a human interviewer. This is repetitive, time-consuming, and inconsistent — different interviewers ask different questions and grade answers by different standards.
interview-chatbot automates this first round: a candidate uploads their CV, is matched to a
specific job's required topics and difficulty, and receives interview questions personalized to
their actual skills and projects. Answers are graded automatically against clear expected
points, the interview adaptively stops early once a clear pass/fail pattern emerges, and the
hiring team receives a structured final report (strengths, weaknesses, recommendation) — cutting
down manual screening time significantly.
Candidate Recruiter / hiring team
│ │
▼ │
1. Picks a job + uploads CV (PDF/DOCX/TXT, optional) │
│ │
▼ │
2. CV is parsed → analyzed by an LLM into a structured │
profile (skills, projects, experience level) │
│ │
▼ │
3. For each question: │
a. Query rewriting: an LLM turns the topic + CV │
profile + weak points from previous answers │
into an optimized retrieval search query │
b. Hybrid retrieval (vector search + BM25) uses that │
query to pull candidate questions from the bank, │
filtered by job topic/difficulty │
c. A cross-encoder reranks the candidates │
d. The top question is rewritten again by an LLM to │
reference the candidate's real skills/projects │
(phrasing personalization, separate from a.) │
│ │
▼ │
4. Candidate answers → LLM grades the answer against │
the question's expected points (0–10, with feedback) │
│ │
▼ │
5. Adaptive stopping: interview ends early once scores │
show a clear pass pattern or fail pattern, or after │
a max number of questions │
│ │
▼ ▼
6. Candidate sees a summary ──────────────▶ Structured final report:
overall score, recommendation,
strengths, weaknesses
Retrieval details (topic/difficulty filtering, hybrid search, reranking) are in
retrieval.py; query rewriting (pre-retrieval) is in query_rewriter.py; question rewriting
(post-retrieval, for personalization) is in question_rewriter.py; grading and report
generation are in ai_service.py.
- Question bank (
data/questions.json): 500 curated technical questions across 11 topics (Python,Machine Learning,Deep Learning,NLP,LLM,MLOps,Data Engineering,Statistics,System Design,Computer Vision,Behavioral) and 3 difficulty levels (junior,mid,senior). Each question has an id, topic/subtopic, difficulty, the question text, and a set of weighted expected points used for grading, e.g.:{ "id": "python_001", "topic": "Python", "difficulty": "junior", "question": "What is the difference between a list and a tuple?", "expected_points": [ {"point": "Correct understanding of the concept", "weight": 0.4}, {"point": "Technical accuracy", "weight": 0.4}, {"point": "Relevant example or use case", "weight": 0.2} ] } - Jobs: created/managed through the app's "Manage Jobs" page — each job defines a title, description, required topics (mapped to the question bank's topics), and difficulty level.
- Candidate CVs: uploaded per-interview (PDF/DOCX/TXT), not stored as raw files — only the LLM-derived structured profile (skills, projects, experience) is kept.
Add screenshots here once you have a running instance — this section is a placeholder. Recommended shots: the job-selection screen, a personalized question in progress, the final report, and the monitoring dashboard.
| Interview flow | Final report | Monitoring dashboard |
|---|---|---|
docs/screenshots/interview.png |
docs/screenshots/report.png |
docs/screenshots/dashboard.png |
In the running Streamlit app, open the ⋮ menu (top-right) → Record a screencast (Streamlit docs), record a short walkthrough (job selection → CV upload → a question or two → final report), then drag and drop the downloaded video file into this README using the GitHub web editor — GitHub will host it and insert an embeddable link automatically, replacing the one above.
pip install uv
uv sync
cp .env.example .env # then add your GROQ_API_KEY
streamlit run app.pyFull setup (Docker, Streamlit Cloud, all environment variables, troubleshooting) is in docs/SETUP.md.
- Candidate selects "Backend Engineer" and uploads a CV mentioning a Django + PostgreSQL project and 2 years of Python experience.
- First question (topic: Python, difficulty: junior→mid based on the job) comes back personalized, e.g.: "You mentioned building a Django + PostgreSQL API — how would you avoid the N+1 query problem when serializing related objects?" instead of a generic textbook question.
- Candidate answers; the LLM grades it against the question's expected points and returns a score (0–10) plus specific missing points.
- After a few questions, if the candidate consistently scores ≥7, the interview ends early with a "meets requirements" message — the recruiter doesn't need 8 full questions to see a clear pass.
- The recruiter opens the final report: overall score, recommendation (e.g. "Recommend for next round"), and a bullet list of concrete strengths/weaknesses tied to what was actually asked.
The Monitoring Dashboard page (pages/2_dashboard.py, in the app sidebar) shows:
total/completed interviews, score distribution, interviews over time, why interviews ended
(pass/fail/max-questions), average score per job, and 👍/👎 user feedback collected at the end of
each report (see save_feedback in app.py).
Retrieval quality (hybrid search + reranking vs. vector-only) and LLM grading quality (prompt comparison against a hand-labeled golden set) are both evaluated with reproducible scripts. See docs/EVALUATION.md for methodology, metrics, and how to run them.
app.py Streamlit UI — the main candidate-facing app
main.py FastAPI backend (same interview flow as a JSON API)
ai_service.py Question generation, answer grading, report generation (LLM calls)
retrieval.py Hybrid retrieval (vector + BM25), reranking, Chroma indexing
query_rewriter.py Query rewriting: LLM-optimized retrieval query from topic + CV
profile + weak points from previous answers (pre-retrieval)
reranker.py Cross-encoder reranking model wrapper
cv_parser.py / cv_analyzer.py CV text extraction and LLM-based profile analysis
question_rewriter.py Rewrites a bank question to reference the candidate's real profile
(post-retrieval personalization, separate from query_rewriter.py)
providers.py LLM provider abstraction (Groq)
db/ SQLAlchemy models, session management, schema bootstrap
pages/ Streamlit extra pages: Manage Jobs, Monitoring Dashboard
scr/ Seeding, indexing, and evaluation scripts
data/questions.json The 500-question bank
docs/ Extended documentation (setup, evaluation)
Dockerfile / docker-compose.yml Containerized app + Postgres
- UI: Streamlit
- API: FastAPI
- LLM: Groq (Llama models)
- Retrieval: ChromaDB (vector store) + SentenceTransformers (embeddings) + rank-bm25 (lexical search) + a cross-encoder reranker
- Database: SQLAlchemy, SQLite (default/dev) or PostgreSQL (Docker/production)
- Deployment: Streamlit Cloud, or Docker Compose for self-hosting
| Criterion | Where to look |
|---|---|
| Problem description | Problem above |
| Retrieval flow (knowledge base + LLM) | retrieval.py (Chroma + BM25 + reranker) feeding into ai_service.py (LLM) |
| Retrieval evaluation | scr/evaluate_retrieval.py, methodology in docs/EVALUATION.md |
| LLM evaluation | scr/evaluate_llm.py, methodology in docs/EVALUATION.md |
| Interface | Streamlit UI (app.py) + FastAPI (main.py) |
| Ingestion pipeline | scr/seed_questions.py + scr/index_documents.py, also auto-run by init_db() on first startup (db/database.py) |
| Monitoring | Monitoring above — feedback collection + pages/2_dashboard.py |
| Containerization | Dockerfile + docker-compose.yml (app + Postgres) |
| Reproducibility | docs/SETUP.md — exact steps, pinned dependencies (uv.lock), sample data included |
| Hybrid search (best practice) | retrieval.py — BM25 + vector fusion before reranking |
| Document re-ranking (best practice) | reranker.py, fused in retrieval.py |
| Query rewriting (best practice) | query_rewriter.py — LLM rewrites the retrieval query using topic + CV profile + weak points from previous answers, before retrieval.py runs hybrid search |
| Cloud deployment (bonus) | Deployed on Streamlit Cloud — see the Live demo link at the top of this README |
- Expand the question bank with more realistic, up-to-date interview content.
- Add structured admin tooling for job/question management beyond the basic Manage Jobs page.
- Full Arabic UI support (question bank and interface are currently English-only).
- Automate the ingestion pipeline with a proper orchestration tool (e.g. Prefect/Airflow) instead of manually-triggered scripts, for the full ingestion-pipeline evaluation score.
- Cache/batch query-rewriting LLM calls (
query_rewriter.py) — each call tonext-questionadds one extra LLM round trip before retrieval, on top of the existing question-rewriting call.