Task 6: Build the Complete Application
Let's combine everything into a polished, production-ready interview coach.
Final Project Structure
interview-coach/
├── main.py # CLI entry point
├── app.py # Streamlit web interface
├── config.py # Configuration
├── requirements.txt
├── .env
│
├── chains/
│ ├── __init__.py
│ ├── interviewer.py # Interview chain with memory
│ └── evaluator.py # Structured feedback
│
├── agents/
│ ├── __init__.py
│ ├── tools.py # Agent tools
│ └── coach.py # Main interview agent
│
├── rag/
│ ├── __init__.py
│ ├── loader.py # Document loaders
│ └── retriever.py # Vector store & retrieval
│
└── data/
└── job_descriptions/ # Sample JDs
Step 1: Configuration
# config.py
from pydantic_settings import BaseSettings
from typing import Literal
class Settings(BaseSettings):
# API Keys
openai_api_key: str
# Model settings
model_name: str = "gpt-4o-mini"
temperature: float = 0.7
max_tokens: int = 1000
# Interview settings
max_questions: int = 5
default_difficulty: Literal["easy", "medium", "hard"] = "medium"
# RAG settings
chunk_size: int = 500
chunk_overlap: int = 50
retriever_k: int = 3
class Config:
env_file = ".env"
settings = Settings()
Step 2: Complete Interview Coach Class
# interview_coach.py
from dataclasses import dataclass, field
from typing import List, Optional
from enum import Enum
from chains.interviewer import create_interviewer_with_history
from chains.evaluator import create_evaluator_simple, create_report_generator, AnswerFeedback, InterviewReport
from rag.setup import setup_interview_rag
from config import settings
class InterviewPhase(Enum):
NOT_STARTED = "not_started"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
@dataclass
class InterviewSession:
"""Holds all interview state."""
session_id: str
position: str
level: str
topics: List[str]
phase: InterviewPhase = InterviewPhase.NOT_STARTED
current_question: str = ""
current_topic_index: int = 0
questions_asked: List[str] = field(default_factory=list)
answers: List[str] = field(default_factory=list)
feedback: List[AnswerFeedback] = field(default_factory=list)
transcript: List[dict] = field(default_factory=list)
class InterviewCoach:
"""Complete AI Interview Coach."""
def __init__(
self,
job_description: str = None,
job_description_path: str = None,
interview_type: str = "technical",
difficulty: str = "adaptive",
position: str = "Software Engineer",
level: str = "senior"
):
self.interview_type = interview_type
self.difficulty = difficulty
self.position = position
self.level = level
# Initialize chains
self.interviewer = create_interviewer_with_history()
self.evaluator = create_evaluator_simple()
self.report_generator = create_report_generator()
# Setup RAG if job description provided
self.rag_enabled = False
if job_description_path:
rag_components = setup_interview_rag(job_description_path)
self.question_generator = rag_components["question_generator"]
self.retriever = rag_components["retriever"]
self.rag_enabled = True
elif job_description:
# Create in-memory RAG from string
from rag.loader import create_docs_from_text, split_documents
from rag.retriever import create_vector_store, create_retriever
docs = create_docs_from_text(job_description)
chunks = split_documents(docs)
vector_store = create_vector_store(chunks)
self.retriever = create_retriever(vector_store)
self.rag_enabled = True
# Session management
self.sessions: dict[str, InterviewSession] = {}
def start_interview(self, session_id: str, topics: List[str] = None) -> str:
"""Start a new interview session."""
if topics is None:
topics = ["Python fundamentals", "async programming",
"system design", "problem solving", "best practices"]
session = InterviewSession(
session_id=session_id,
position=self.position,
level=self.level,
topics=topics,
phase=InterviewPhase.IN_PROGRESS
)
self.sessions[session_id] = session
# Generate first question
question = self._generate_question(session)
session.current_question = question
session.questions_asked.append(question)
session.transcript.append({"role": "interviewer", "content": question})
return f"Welcome! Let's begin your {self.level} {self.position} interview.\n\n{question}"
def submit_answer(self, session_id: str, answer: str) -> dict:
"""Process candidate's answer and get next question."""
session = self.sessions.get(session_id)
if not session or session.phase != InterviewPhase.IN_PROGRESS:
return {"error": "No active interview session"}
# Save answer
session.answers.append(answer)
session.transcript.append({"role": "candidate", "content": answer})
# Evaluate answer
feedback = self.evaluator.invoke({
"question": session.current_question,
"level": self.level,
"answer": answer
})
session.feedback.append(feedback)
# Check if interview should end
if len(session.questions_asked) >= settings.max_questions:
session.phase = InterviewPhase.COMPLETED
return {
"feedback": feedback,
"is_complete": True,
"message": "Interview complete! Generating your report..."
}
# Adjust difficulty if adaptive
if self.difficulty == "adaptive":
self._adjust_difficulty(session)
# Generate next question
session.current_topic_index += 1
next_question = self._generate_question(session, previous_feedback=feedback)
session.current_question = next_question
session.questions_asked.append(next_question)
session.transcript.append({"role": "interviewer", "content": next_question})
return {
"feedback": feedback,
"next_question": next_question,
"is_complete": False,
"questions_remaining": settings.max_questions - len(session.questions_asked)
}
def _generate_question(
self,
session: InterviewSession,
previous_feedback: AnswerFeedback = None
) -> str:
"""Generate the next interview question."""
topic_index = session.current_topic_index % len(session.topics)
topic = session.topics[topic_index]
if self.rag_enabled:
# Use RAG to generate job-specific question
return self.question_generator.invoke({
"topic": topic,
"difficulty": self.difficulty,
"previous_questions": ", ".join(session.questions_asked[-3:])
})
else:
# Use standard interviewer chain
context = f"Ask a {self.difficulty} question about {topic}."
if previous_feedback and previous_feedback.follow_up_question:
context += f"\nConsider: {previous_feedback.follow_up_question}"
return self.interviewer.invoke(
{
"interview_type": self.interview_type,
"level": self.level,
"focus_area": topic,
"input": context
},
config={"configurable": {"session_id": session.session_id}}
)
def _adjust_difficulty(self, session: InterviewSession):
"""Adjust difficulty based on recent performance."""
if len(session.feedback) < 2:
return
recent_scores = [f.score for f in session.feedback[-2:]]
avg_score = sum(recent_scores) / len(recent_scores)
if avg_score >= 8:
self.difficulty = "hard"
elif avg_score <= 4:
self.difficulty = "easy"
else:
self.difficulty = "medium"
def generate_report(self, session_id: str) -> InterviewReport:
"""Generate final interview report."""
session = self.sessions.get(session_id)
if not session:
raise ValueError("Session not found")
# Format transcript
transcript_text = "\n\n".join([
f"{'Q' if t['role'] == 'interviewer' else 'A'}: {t['content']}"
for t in session.transcript
])
scores = [f.score for f in session.feedback]
report = self.report_generator.invoke({
"position": self.position,
"level": self.level,
"interview_type": self.interview_type,
"transcript": transcript_text,
"scores": scores
})
return report
@property
def is_complete(self) -> bool:
"""Check if current interview is complete."""
# For backward compatibility with simple usage
return False # Managed per-session now
Step 3: CLI Interface
# main.py
import argparse
from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress
from interview_coach import InterviewCoach
console = Console()
def run_cli():
parser = argparse.ArgumentParser(description="AI Interview Coach")
parser.add_argument("--job", "-j", help="Path to job description file")
parser.add_argument("--type", "-t", default="technical", help="Interview type")
parser.add_argument("--level", "-l", default="senior", help="Position level")
parser.add_argument("--questions", "-q", type=int, default=5, help="Number of questions")
args = parser.parse_args()
console.print(Panel.fit(
"[bold cyan]AI Interview Coach[/bold cyan]\n"
"Practice technical interviews with AI feedback",
border_style="cyan"
))
# Initialize coach
coach = InterviewCoach(
job_description_path=args.job,
interview_type=args.type,
level=args.level
)
session_id = "cli_session"
topics = ["Python", "system design", "algorithms", "best practices", "behavioral"]
# Start interview
welcome = coach.start_interview(session_id, topics[:args.questions])
console.print(f"\n[bold green]Interviewer:[/bold green] {welcome}\n")
while True:
answer = console.input("[bold blue]You:[/bold blue] ")
if answer.lower() in ['quit', 'exit', 'q']:
console.print("[yellow]Interview ended early.[/yellow]")
break
result = coach.submit_answer(session_id, answer)
# Show feedback
feedback = result["feedback"]
console.print(f"\n[dim]Score: {feedback.score}/10 - {feedback.understanding}[/dim]")
if result["is_complete"]:
# Generate and display report
console.print("\n[bold]Generating your interview report...[/bold]\n")
report = coach.generate_report(session_id)
console.print(Panel(
f"[bold]Overall Score: {report.overall_score}/10[/bold]\n"
f"Recommendation: [cyan]{report.recommendation.upper()}[/cyan]\n\n"
f"{report.summary}\n\n"
f"[green]Strengths:[/green]\n" +
"\n".join(f" • {s}" for s in report.strengths) + "\n\n"
f"[yellow]Areas to Improve:[/yellow]\n" +
"\n".join(f" • {a}" for a in report.areas_to_improve),
title="Interview Report",
border_style="green"
))
break
console.print(f"\n[bold green]Interviewer:[/bold green] {result['next_question']}\n")
console.print(f"[dim]({result['questions_remaining']} questions remaining)[/dim]\n")
if __name__ == "__main__":
run_cli()
Step 4: Streamlit Web Interface
# app.py
import streamlit as st
from interview_coach import InterviewCoach
import uuid
st.set_page_config(page_title="AI Interview Coach", page_icon="🎯", layout="wide")
# Initialize session state
if "coach" not in st.session_state:
st.session_state.coach = None
if "session_id" not in st.session_state:
st.session_state.session_id = None
if "messages" not in st.session_state:
st.session_state.messages = []
if "interview_complete" not in st.session_state:
st.session_state.interview_complete = False
# Sidebar configuration
with st.sidebar:
st.header("🎯 Interview Setup")
position = st.text_input("Position", "Senior Python Developer")
level = st.selectbox("Level", ["junior", "mid", "senior", "staff"])
interview_type = st.selectbox("Type", ["technical", "behavioral", "system_design"])
job_desc = st.text_area(
"Job Description (optional)",
placeholder="Paste job description for targeted questions..."
)
num_questions = st.slider("Number of Questions", 3, 10, 5)
if st.button("Start Interview", type="primary"):
st.session_state.coach = InterviewCoach(
job_description=job_desc if job_desc else None,
interview_type=interview_type,
level=level,
position=position
)
st.session_state.session_id = str(uuid.uuid4())
st.session_state.messages = []
st.session_state.interview_complete = False
# Get first question
topics = ["core skills", "system design", "problem solving", "experience", "culture fit"]
welcome = st.session_state.coach.start_interview(
st.session_state.session_id,
topics[:num_questions]
)
st.session_state.messages.append({"role": "assistant", "content": welcome})
st.rerun()
# Main content
st.title("🎯 AI Interview Coach")
if st.session_state.coach is None:
st.info("👈 Configure your interview in the sidebar and click 'Start Interview'")
else:
# Display chat messages
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.write(message["content"])
if "feedback" in message:
with st.expander("View Feedback"):
fb = message["feedback"]
st.metric("Score", f"{fb.score}/10")
st.write(f"**Understanding:** {fb.understanding}")
if fb.improvements:
st.write("**Tips:**")
for tip in fb.improvements:
st.write(f"- {tip}")
# Chat input
if not st.session_state.interview_complete:
if prompt := st.chat_input("Your answer..."):
# Add user message
st.session_state.messages.append({"role": "user", "content": prompt})
# Get response
result = st.session_state.coach.submit_answer(
st.session_state.session_id,
prompt
)
if result["is_complete"]:
st.session_state.interview_complete = True
# Generate report
report = st.session_state.coach.generate_report(st.session_state.session_id)
report_content = f"""
## Interview Complete! 🎉
**Overall Score: {report.overall_score}/10**
**Recommendation: {report.recommendation.upper()}**
### Summary
{report.summary}
### Strengths
{chr(10).join('- ' + s for s in report.strengths)}
### Areas to Improve
{chr(10).join('- ' + a for a in report.areas_to_improve)}
### Suggested Topics to Study
{chr(10).join('- ' + t for t in report.suggested_topics_to_study)}
"""
st.session_state.messages.append({
"role": "assistant",
"content": report_content
})
else:
st.session_state.messages.append({
"role": "assistant",
"content": result["next_question"],
"feedback": result["feedback"]
})
st.rerun()
else:
st.success("Interview complete! Check the report above.")
if st.button("Start New Interview"):
st.session_state.coach = None
st.session_state.session_id = None
st.session_state.messages = []
st.session_state.interview_complete = False
st.rerun()
Step 5: Requirements
# requirements.txt
langchain>=0.2.0
langchain-openai>=0.1.0
langchain-community>=0.2.0
chromadb>=0.4.0
python-dotenv>=1.0.0
pydantic>=2.0.0
pydantic-settings>=2.0.0
pypdf>=4.0.0
docx2txt>=0.8
tiktoken>=0.5.0
rich>=13.0.0 # For CLI
streamlit>=1.30.0 # For web UI
Running the Application
# CLI mode
python main.py --job data/job_descriptions/senior_python.txt --questions 5
# Web mode
streamlit run app.py
Key Takeaways
- Modular design - Separate chains, agents, and RAG components
- Session management - Track multiple interviews
- Configuration - Use environment variables and settings
- Multiple interfaces - CLI and web UI from same core
- Error handling - Graceful degradation when components fail
Congratulations! 🎉
You've built a complete AI Interview Coach that:
- Conducts multi-turn interviews with memory
- Provides structured feedback and scoring
- Generates job-specific questions using RAG
- Adapts difficulty based on performance
- Produces comprehensive reports
Next Steps
- Add more interview types (coding, system design with diagrams)
- Implement voice interface with speech-to-text
- Add company-specific question banks
- Build analytics dashboard for progress tracking
- Deploy as a SaaS product!