By Sagar Shankaran, Founder of CallSphere
Build an AI mock interview agent with adaptive question selection, response evaluation across behavioral and technical tracks, and detailed performance feedback with improvement coaching.
Key takeaways
Practicing for interviews with a static question list misses the most important dynamic: real interviews adapt. If a candidate gives a strong answer about system design, the interviewer probes deeper. If they struggle with concurrency, the interviewer might simplify or pivot to gauge the candidate's boundaries. An AI mock interview agent replicates this adaptive behavior, selecting follow-up questions based on the candidate's demonstrated strengths and weaknesses.
Start by defining the interview structure and question bank:
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class InterviewTrack(str, Enum):
BEHAVIORAL = "behavioral"
TECHNICAL = "technical"
SYSTEM_DESIGN = "system_design"
CODING = "coding"
class DifficultyLevel(str, Enum):
EASY = "easy"
MEDIUM = "medium"
HARD = "hard"
EXPERT = "expert"
@dataclass
class InterviewQuestion:
question_id: str
text: str
track: InterviewTrack
difficulty: DifficultyLevel
follow_ups: list[str] = field(default_factory=list)
evaluation_criteria: list[str] = field(default_factory=list)
ideal_answer_points: list[str] = field(default_factory=list)
time_limit_minutes: int = 5
tags: list[str] = field(default_factory=list)
@dataclass
class InterviewConfig:
role: str
company: str
tracks: list[InterviewTrack]
total_duration_minutes: int = 45
difficulty_start: DifficultyLevel = DifficultyLevel.MEDIUM
questions_per_track: int = 3
BEHAVIORAL_QUESTIONS = [
InterviewQuestion(
question_id="beh-001",
text="Tell me about a time you had to make a difficult "
"technical decision with incomplete information.",
track=InterviewTrack.BEHAVIORAL,
difficulty=DifficultyLevel.MEDIUM,
follow_ups=[
"What data would have changed your decision?",
"How did you communicate the risk to stakeholders?",
"What would you do differently now?",
],
evaluation_criteria=[
"Uses STAR format (Situation, Task, Action, Result)",
"Shows decision-making process, not just outcome",
"Demonstrates learning and self-awareness",
"Quantifies impact where possible",
],
ideal_answer_points=[
"Specific situation with context",
"Clear description of the tradeoffs considered",
"Action taken with reasoning",
"Measurable result or outcome",
"Reflection on what was learned",
],
),
]
The question selector adjusts difficulty and topic based on how the candidate has performed so far:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
@dataclass
class CandidatePerformance:
candidate_id: str
role: str
answers: list[dict] = field(default_factory=list)
track_scores: dict[str, list[float]] = field(default_factory=dict)
current_difficulty: dict[str, str] = field(default_factory=dict)
asked_questions: set[str] = field(default_factory=set)
def get_track_average(self, track: str) -> float:
scores = self.track_scores.get(track, [])
if not scores:
return 0.5
return sum(scores) / len(scores)
def adjust_difficulty(self, track: str, score: float):
"""Increase difficulty on strong answers, decrease on weak."""
levels = list(DifficultyLevel)
current = DifficultyLevel(
self.current_difficulty.get(track, "medium")
)
current_idx = levels.index(current)
if score >= 0.8 and current_idx < len(levels) - 1:
self.current_difficulty[track] = levels[current_idx + 1].value
elif score < 0.4 and current_idx > 0:
self.current_difficulty[track] = levels[current_idx - 1].value
def select_next_question(
performance: CandidatePerformance,
question_bank: list[InterviewQuestion],
track: InterviewTrack,
) -> Optional[InterviewQuestion]:
"""Select the next question based on adaptive difficulty."""
target_difficulty = DifficultyLevel(
performance.current_difficulty.get(track.value, "medium")
)
# Filter to unasked questions in the right track and difficulty
candidates = [
q for q in question_bank
if q.track == track
and q.difficulty == target_difficulty
and q.question_id not in performance.asked_questions
]
if not candidates:
# Fall back to adjacent difficulties
candidates = [
q for q in question_bank
if q.track == track
and q.question_id not in performance.asked_questions
]
if not candidates:
return None
# Prefer questions that test areas where candidate is weakest
return candidates[0]
The evaluation agent scores candidate responses against structured criteria:
from agents import Agent, Runner
from pydantic import BaseModel
class ResponseEvaluation(BaseModel):
score: float # 0.0 to 1.0
criteria_met: list[str]
criteria_missed: list[str]
strengths: list[str]
improvement_areas: list[str]
follow_up_question: Optional[str] = None
detailed_feedback: str
behavioral_evaluator = Agent(
name="Behavioral Interview Evaluator",
instructions="""You evaluate behavioral interview answers. Score
each response on a 0.0-1.0 scale using these criteria:
STAR FORMAT (25% of score):
- Clear Situation with relevant context
- Specific Task or challenge described
- Detailed Actions taken (not just "we" — what did YOU do?)
- Quantifiable Results or outcomes
COMMUNICATION (25% of score):
- Concise and focused (2-3 minutes ideal)
- Logical narrative flow
- Appropriate level of detail
SUBSTANCE (25% of score):
- Shows relevant skills for the role
- Demonstrates impact and ownership
- Includes specific metrics or outcomes
SELF-AWARENESS (25% of score):
- Acknowledges challenges honestly
- Shows learning and growth
- Demonstrates adaptability
If the answer is strong, generate a harder follow-up question that
probes deeper. If weak, note specific coaching advice.""",
output_type=ResponseEvaluation,
)
technical_evaluator = Agent(
name="Technical Interview Evaluator",
instructions="""You evaluate technical interview answers. Score
each response on a 0.0-1.0 scale using these criteria:
ACCURACY (30% of score):
- Technically correct statements
- Proper use of terminology
- No critical misconceptions
DEPTH (25% of score):
- Goes beyond surface-level explanation
- Discusses tradeoffs and alternatives
- Shows understanding of WHY, not just WHAT
PROBLEM SOLVING (25% of score):
- Structured approach to the problem
- Considers edge cases
- Breaks down complex problems into parts
COMMUNICATION (20% of score):
- Explains technical concepts clearly
- Uses appropriate abstractions
- Asks clarifying questions when needed
Generate a follow-up that tests the boundaries of their knowledge.""",
output_type=ResponseEvaluation,
)
Combine question selection, the interview flow, and evaluation into a cohesive interview session:
from agents import function_tool
import json
@function_tool
def evaluate_answer(
question_id: str,
candidate_answer: str,
track: str,
evaluation_criteria: str,
) -> str:
"""Evaluate a candidate's interview answer."""
# In production, dispatch to behavioral or technical evaluator
return json.dumps({
"status": "evaluated",
"question_id": question_id,
})
def build_interviewer_instructions(
config: InterviewConfig,
performance: CandidatePerformance,
current_question: InterviewQuestion,
) -> str:
track_avg = performance.get_track_average(
current_question.track.value
)
return f"""You are conducting a mock interview for a
{config.role} position at {config.company}.
CURRENT QUESTION: {current_question.text}
TRACK: {current_question.track.value}
DIFFICULTY: {current_question.difficulty.value}
EVALUATION CRITERIA:
{chr(10).join(f'- {c}' for c in current_question.evaluation_criteria)}
IDEAL ANSWER SHOULD COVER:
{chr(10).join(f'- {p}' for p in current_question.ideal_answer_points)}
Candidate's average performance in this track: {track_avg:.0%}
INTERVIEWER BEHAVIOR:
- Act like a real interviewer — professional but friendly
- Listen actively and ask relevant follow-up questions
- If the candidate is struggling, provide a gentle nudge
- After the candidate finishes, provide structured feedback
- Use follow-up questions to probe depth: {current_question.follow_ups}
- Never reveal the ideal answer — coach toward it instead"""
mock_interviewer = Agent(
name="Mock Interviewer",
instructions="Dynamic — set per question",
tools=[evaluate_answer],
)
After the session, generate a comprehensive feedback report:
def generate_interview_report(
performance: CandidatePerformance,
) -> dict:
"""Generate a post-interview performance report."""
report = {
"role": performance.role,
"questions_answered": len(performance.answers),
"track_performance": {},
"overall_score": 0.0,
"top_strengths": [],
"priority_improvements": [],
"practice_recommendations": [],
}
total_score = 0.0
for track, scores in performance.track_scores.items():
avg = sum(scores) / len(scores) if scores else 0
total_score += avg
report["track_performance"][track] = {
"average_score": f"{avg:.0%}",
"questions_answered": len(scores),
"trend": "improving" if len(scores) > 1
and scores[-1] > scores[0] else "stable",
}
num_tracks = len(performance.track_scores) or 1
report["overall_score"] = f"{total_score / num_tracks:.0%}"
return report
The evaluator scores off-topic answers low on the "substance" and "accuracy" criteria, which triggers the difficulty adjustment to lower the next question's complexity. The interviewer agent is also instructed to gently redirect — just as a real interviewer would say "That is interesting, but I am specifically asking about..." The system prompt includes the evaluation criteria so the agent knows exactly what a relevant answer should address.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
Yes. The interviewer persona is controlled by the system prompt. A "challenging" mode adds instructions like "Push back on vague statements, ask for specific numbers, and express skepticism that forces the candidate to defend their claims." A "friendly" mode uses more encouragement and broader follow-up questions. Candidates should practice with both styles to prepare for real interview variability.
The InterviewConfig specifies questions per track and total duration. The session manager tracks elapsed time and ensures each track gets proportional coverage. If the candidate spends too long on one question, the agent notes it in feedback and moves to the next topic — mirroring real interview time management expectations.
#InterviewPrep #MockInterviews #CareerAI #Python #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
An agentic-AI perspective on Anthropic Skills system, covering orchestration patterns, tool use, and how agent tooling fits production agent stacks.
Enterprise CIO Guide perspective on Comet's general-availability launch put an agentic browser in front of millions of consumers, and it works better than the demos suggested.
Enterprise CIO Guide perspective on Harvey AI's enterprise rollout numbers show legal agents have moved past the pilot stage at AmLaw 100 firms.
Enterprise CIO Guide perspective on Hippocratic AI's deployment numbers show healthcare voice agents are moving from pilot to production across major US health systems.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI