mock-interview-backend / interview_modes.py
sindhupandrangi's picture
Update interview_modes.py
025f076 verified
# interview_modes.py
"""
Additional interview modes (HR, Behavioral, Coding) on top of main.py.
We reuse:
- FastAPI app from main.py
- HF tiered generation (finetuned -> base -> error)
- Resume parsing + RAG helpers
- Question / response Pydantic models
"""
from typing import List
from fastapi import HTTPException
from main import (
app as base_app, # existing FastAPI app
RESUME_STORE,
hf_generate_tiered,
parse_questions_from_model_output,
extract_candidate_name,
extract_project_names,
build_rag_context,
GenerateQuestionsRequest,
GenerateQuestionsResponse,
Question,
)
app = base_app # reuse the same app (so /generate_questions still works)
# -------------------------
# RAG fallback question sets
# -------------------------
def _rag_fallback_hr(
candidate_name: str,
domain: str,
role: str,
projects: List[str],
n_questions: int,
) -> List[Question]:
"""HR / culture-fit style fallback questions."""
base: List[str] = []
proj = projects[0] if projects else None
if proj:
base.append(
f"From your project \"{proj}\", what did you learn about collaborating with cross-functional teams (e.g., product, design, business)?"
)
base.append(
f"In \"{proj}\", describe a situation where priorities or requirements suddenly changed. How did you adapt and help the team move forward?"
)
# generic HR / culture-fit
base.extend(
[
"What kind of work environment helps you perform at your best, and how do you contribute to that culture in your team?",
f"In this {role} role, how would you handle a situation where you strongly disagree with your manager on a key decision?",
"How do you typically respond to critical feedback from peers or managers? Share a recent example.",
"What motivates you the most at work, and how do you stay engaged during long or difficult projects?",
"Describe how you build trust with new teammates when joining a project or company.",
]
)
questions: List[Question] = []
seen = set()
for text in base:
text = text.strip()
if not text:
continue
if not text.endswith("?"):
text = text.rstrip(". ") + "?"
if text in seen:
continue
seen.add(text)
questions.append(Question(id=len(questions) + 1, type="hr", question=text))
if len(questions) >= n_questions:
break
return questions
def _rag_fallback_behavioral(
candidate_name: str,
domain: str,
role: str,
projects: List[str],
n_questions: int,
) -> List[Question]:
"""Behavioral / STAR-style fallback questions."""
base: List[str] = []
proj = projects[0] if projects else None
if proj:
base.append(
f"Tell me about a time during \"{proj}\" when things were going badly or off-track. What did you do to turn it around?"
)
base.append(
f"Describe a time in \"{proj}\" where you had to make a difficult trade-off under time pressure. How did you decide and what was the outcome?"
)
base.extend(
[
"Tell me about a time you had a serious conflict with a teammate or stakeholder. How did you handle it and what did you learn?",
f"Describe a time you took ownership beyond your formal responsibilities in a {domain} / {role} context.",
"Tell me about a failure or mistake you made at work or in a project. How did you respond, and what changed afterwards?",
"Describe a situation where you had to influence people who did not report to you (e.g., other teams) to get something done.",
"Tell me about a time you had to deliver under very ambiguous or changing requirements. How did you bring clarity?",
]
)
questions: List[Question] = []
seen = set()
for text in base:
text = text.strip()
if not text.lower().startswith("tell me about a time"):
text = "Tell me about a time " + text[0].lower() + text[1:]
if not text.endswith("?"):
text = text.rstrip(". ") + "?"
if text in seen:
continue
seen.add(text)
questions.append(Question(id=len(questions) + 1, type="behavioral", question=text))
if len(questions) >= n_questions:
break
return questions
def _rag_fallback_coding(
candidate_name: str,
domain: str,
role: str,
projects: List[str],
n_questions: int,
) -> List[Question]:
"""Coding / algorithms fallback questions."""
base: List[str] = []
base.extend(
[
"Given an array of integers, write code to return the length of the longest increasing subsequence. Explain the time and space complexity.",
"Design a function that checks if a string containing '(', ')', '{', '}', '[' and ']' has valid and balanced brackets. What data structure would you use and why?",
"Given a large log file of user events, how would you efficiently find the top K most frequent user IDs? Discuss both the algorithm and complexity.",
"Write a function to perform a BFS (breadth-first search) on a graph represented as an adjacency list. How would you modify it to find the shortest path in an unweighted graph?",
f"In the context of a real {role} system, how would you design a solution to throttle API calls from a single user (rate limiter)? What data structures would you use in code?",
]
)
questions: List[Question] = []
seen = set()
for text in base:
text = text.strip()
if not text.endswith("?"):
text = text.rstrip(". ") + "?"
if text in seen:
continue
seen.add(text)
questions.append(Question(id=len(questions) + 1, type="coding", question=text))
if len(questions) >= n_questions:
break
return questions
# -------------------------
# Prompt builders per mode
# -------------------------
def _build_prompt_for_mode(
mode: str,
req: GenerateQuestionsRequest,
resume_text: str,
candidate_name: str,
) -> str:
"""Build a strong HF prompt for HR / Behavioral / Coding."""
rag_context = build_rag_context(
candidate_name=candidate_name,
resume_text=resume_text,
job_description=req.job_description,
domain=req.domain,
role=req.role,
)
base_header = f"""
You are a seasoned hiring manager designing a **{mode.capitalize()} interview**.
Candidate: {candidate_name or req.name}
Role: {req.role}
Domain: {req.domain}
Number of questions needed: {req.n_questions}
Below is RAG context from the candidate's resume + job description:
[CONTEXT START]
{rag_context}
[CONTEXT END]
"""
mode = mode.lower()
if mode == "hr":
specifics = f"""
Your task is to create EXACTLY {req.n_questions} **HR / culture-fit questions**.
Requirements:
- ALL questions must be HR-style (communication, teamwork, conflict, motivation, values, growth).
- Do **NOT** ask detailed algorithms, coding, or system-design questions.
- Use open-ended questions that cannot be answered with just one sentence.
- Reference the candidate's projects or experience from the context when it makes sense.
- Avoid yes/no questions.
- Each question must end with a question mark (?).
"""
elif mode == "behavioral":
specifics = f"""
Your task is to create EXACTLY {req.n_questions} **behavioral interview questions**.
Requirements:
- EVERY question must be STAR-style (Situation, Task, Action, Result).
- Start most questions with phrases like:
* "Tell me about a time when..."
* "Describe a situation where..."
* "Give me an example of..."
- Cover a mix of themes: ownership, failure, conflict, leadership, ambiguity, learning.
- Use the candidate's projects and domain when helpful, but keep questions reusable.
- No pure theory or trivia questions.
- Each question must end with a question mark (?).
"""
elif mode == "coding":
specifics = f"""
Your task is to create EXACTLY {req.n_questions} **coding / algorithmic questions**.
Requirements:
- Focus on practical coding problems: data structures, algorithms, debugging, complexity.
- For each question:
* Clearly describe the problem in words only (no full solutions).
* Mention what the candidate should implement or explain in code (e.g., in Python).
* Optionally ask them to discuss time/space complexity or edge cases.
- At least 3 questions should involve writing or designing code.
- You may also include 1 system-design-ish coding question (e.g., rate limiter, caching).
- Each question must end with a question mark (?).
"""
else:
raise HTTPException(status_code=400, detail=f"Unknown mode '{mode}'")
format_instructions = f"""
Output format (IMPORTANT):
- Return ONLY a numbered list of questions.
- No explanations, no JSON, no markdown headings.
Example:
1. First question that ends with a question mark?
2. Second question that ends with a question mark?
Now write the {req.n_questions} best {mode} questions in that format.
"""
return (base_header + specifics + format_instructions).strip()
async def _generate_for_mode(req: GenerateQuestionsRequest, mode: str) -> GenerateQuestionsResponse:
"""Shared implementation for the 3 new endpoints."""
# 1) get resume text if token present
resume_text = ""
if req.resume_token:
resume_text = RESUME_STORE.get(req.resume_token, "")
candidate_name = (req.name or "").strip()
if not candidate_name and resume_text:
candidate_name = extract_candidate_name(resume_text)
# 2) build prompt
prompt = _build_prompt_for_mode(mode, req, resume_text, candidate_name)
questions: List[Question] = []
# 3) HF tiered generation
try:
raw = hf_generate_tiered(prompt, max_tokens=768)
questions = parse_questions_from_model_output(raw, req.n_questions)
print(f"[{mode}] Parsed {len(questions)} questions from HF output.")
except Exception as e:
print(f"[{mode}] Error calling HF tiered models:", e)
# 4) RAG fallback if HF fails or parsing fails
if not questions:
print(f"[{mode}] No questions from HF. Using RAG fallback.")
projects = extract_project_names(resume_text)
if mode == "hr":
questions = _rag_fallback_hr(candidate_name, req.domain, req.role, projects, req.n_questions)
elif mode == "behavioral":
questions = _rag_fallback_behavioral(candidate_name, req.domain, req.role, projects, req.n_questions)
elif mode == "coding":
questions = _rag_fallback_coding(candidate_name, req.domain, req.role, projects, req.n_questions)
if not questions:
raise HTTPException(status_code=500, detail=f"Could not generate {mode} questions.")
return GenerateQuestionsResponse(questions=questions)
# -------------------------
# Public endpoints
# -------------------------
@app.post("/generate_questions_hr", response_model=GenerateQuestionsResponse)
async def generate_questions_hr(req: GenerateQuestionsRequest):
"""
HR / culture-fit interview questions.
"""
return await _generate_for_mode(req, "hr")
@app.post("/generate_questions_behavioral", response_model=GenerateQuestionsResponse)
async def generate_questions_behavioral(req: GenerateQuestionsRequest):
"""
Behavioral (STAR) interview questions.
"""
return await _generate_for_mode(req, "behavioral")
@app.post("/generate_questions_coding", response_model=GenerateQuestionsResponse)
async def generate_questions_coding(req: GenerateQuestionsRequest):
"""
Coding / algorithmic interview questions.
"""
return await _generate_for_mode(req, "coding")