AI Resume Analysis: Interview Module

Design flow and implementation notes for the interview module in the interview-guide project

Interview Mock Interview Module Design and Implementation

This note records how I implemented the Interview module in the interview-guide project, including the core APIs and evaluation pipeline. The main goal is to build a complete closed loop for question generation, answering, evaluation, and report export, while keeping text interviews and voice interviews aligned under the same evaluation logic.

Module Capability Overview

  • Skill-driven question generation: supports 10+ interview tracks (Java backend, major-company tracks, frontend, Python, algorithms, system design, test development, AI Agent, etc.). Each track is defined by SKILL.md for scope and difficulty distribution.
  • Historical question deduplication: previously asked questions in historical sessions are excluded during session creation to reduce repeated assessment.
  • Interview stage duration linkage: after total duration changes, each stage (self-introduction, technical assessment, project deep-dive, reverse Q&A) is auto-allocated by ratio.
  • Intelligent follow-up flow: supports multi-round follow-up configuration (default: 1 round) to simulate realistic interview interactions.
  • Unified evaluation engine: text and voice interviews share the same evaluation architecture (batch evaluation + structured output + summarization + fallback).
  • Report export: supports asynchronous generation and export of PDF interview reports.
  • Interview center: unified entry for continue/restart/history operations.

Core State Flow

Key API Design

GET /api/interview/sessions List Interview Sessions

Purpose:

  • Used by the interview history page, returns session list in reverse creation order.

Call chain:

persistenceService.findAll().stream();

POST /api/interview/sessions Create Interview Session

Rate limiting:

  • Global limit + IP limit (5)

Core logic:

sessionService.createSession(request);
persistenceService.getHistoricalQuestions(skillId, request.resumeId());
sessionRepository.findTop10ByResumeIdAndSkillIdOrderByCreatedAtDesc(...);
sessionRepository.findTop10BySkillIdOrderByCreatedAtDesc(...);
questionService.generateQuestionsBySkill(...);
sessionCache.saveSession(...);
persistenceService.saveSession(...);

GET /api/interview/sessions/{sessionId} Get Session Info

Core logic:

sessionService.getSession(sessionId);
sessionCache.getSession(sessionId);
restoreSessionFromDatabase(sessionId);

GET /api/interview/sessions/{sessionId}/question Get Current Question

Core logic:

sessionService.getCurrentQuestionResponse(sessionId);
getCurrentQuestion(sessionId);
getOrRestoreSession(sessionId);
  • If session is in CREATED state, return question by currentIndex.

POST /api/interview/sessions/{sessionId}/answers Submit Answer and Move Forward

Rate limiting:

  • Global limit (10)

Core logic:

sessionService.submitAnswer(request);
  • Updates answer, session state, cache, and DB.
  • If this is the last question:
persistenceService.updateEvaluateStatus(sessionId, AsyncTaskStatus.PENDING, null);
evaluateStreamProducer.sendEvaluateTask(sessionId);

POST /api/interview/sessions/{sessionId}/answers Save Draft Answer (No Progress)

Core logic:

sessionService.saveAnswer(request);
  • Syncs both Redis and DB.

POST /api/interview/sessions/{sessionId}/complete Early Submit

Core logic:

sessionService.completeInterview(sessionId);
sessionCache.updateSessionStatus(sessionId, SessionStatus.COMPLETED);
  • Persists DB status.
evaluateStreamProducer.sendEvaluateTask(sessionId);

GET /api/interview/sessions/unfinished/{resumeId} Find Unfinished Session

Core logic:

sessionService.findUnfinishedSessionOrThrow(resumeId);
findUnfinishedSession(resumeId);
sessionCache.findUnfinishedSessionId(resumeId);
persistenceService.findUnfinishedSession(resumeId);

GET /api/interview/sessions/{sessionId}/report Generate Interview Evaluation Report

Core logic:

sessionService.generateReport(sessionId);
evaluationService.evaluateInterview(...);
unifiedEvaluationService.evaluate(...);
evaluateInBatches(...);
summarizeBatchResults(...);
structuredOutputInvoker.invoke(...);
securedSystemPrompt = systemPromptWithFormat + ANTI_INJECTION_INSTRUCTION;

Uses anti-injection instruction to reduce prompt contamination risk from user input.

GET /api/interview/sessions/{sessionId}/details Get Interview Detail

Call chain:

historyService.getInterviewDetail(sessionId);
interviewPersistenceService.findBySessionId(sessionId);

GET /api/interview/sessions/{sessionId}/export Export Interview Report as PDF

Call chain:

historyService.exportInterviewPdf(sessionId);
interviewPersistenceService.findBySessionId(sessionId);
pdfExportService.exportInterviewReport(session);

DELETE /api/interview/sessions/{sessionId} Delete Interview Session

Call chain:

persistenceService.deleteSessionBySessionId(sessionId);
sessionRepository.findBySessionId(sessionId);
sessionRepository.delete(session);

Evaluation Engine Implementation Highlights

  • A single evaluation pipeline supports both text and voice interviews, reducing branch complexity.
  • Batch-first then summarize strategy balances long-context stability and structured output quality.
  • Anti-injection prompt composition is applied to reduce malicious-input interference.
  • In failure scenarios, unified invoker + fallback fields avoid hard report failures.

Summary

The Interview module now covers the full workflow from session creation, dynamic question generation, answer progression, asynchronous evaluation, to report export. For me, the key value is separating interview process management from evaluation result production into two evolvable layers, so future changes to question strategy or model upgrades can stay controlled.