ai-interview-system / interviewer.py
sugitora
AI้ขๆŽฅใ‚ทใ‚นใƒ†ใƒ  - ๅˆๅ›žใƒชใƒชใƒผใ‚น (Streamlit + Claude API)
6d1fe52
from datetime import datetime
from config import Config
from evaluator import ClaudeEvaluator, evaluate_answer
from models import Answer, InterviewReport, Question, QuestionResult
from reporter import ReportGenerator
from speech import (
DryRunRecognizer,
DryRunSynthesizer,
SpeechRecognizer,
SpeechSynthesizer,
)
class Interviewer:
def __init__(self, config: Config, questions: list[Question]):
self.config = config
self.questions = questions
if config.dry_run:
self.recognizer = DryRunRecognizer()
self.synthesizer = DryRunSynthesizer()
else:
self.recognizer = SpeechRecognizer(
model_name=config.whisper_model,
sample_rate=config.sample_rate,
silence_timeout=config.silence_timeout,
max_duration=config.max_answer_duration,
)
self.synthesizer = SpeechSynthesizer()
self.claude = ClaudeEvaluator(
api_key=config.anthropic_api_key,
model=config.claude_model,
)
self.reporter = ReportGenerator(output_dir=config.output_dir)
def run(self) -> InterviewReport:
candidate_name = self._greet()
results: list[QuestionResult] = []
for i, question in enumerate(self.questions, start=1):
print(f"\n--- ่ณชๅ• {i}/{len(self.questions)} [{question.category}] ---")
answer = self._ask_question(question)
if not answer.transcribed_text.strip():
print(" ๅ›ž็ญ”ใชใ—ใ€‚ใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ™ใ€‚")
answer = Answer(transcribed_text="๏ผˆๅ›ž็ญ”ใชใ—๏ผ‰", audio_duration_sec=0.0)
print(f" ๐Ÿ“ ๆ›ธใ่ตทใ“ใ—: {answer.transcribed_text}")
print(" ่ฉ•ไพกไธญ...")
result = evaluate_answer(question, answer, self.claude)
results.append(result)
print(f" ใ‚นใ‚ณใ‚ข: {result.total_score:.1f}/{question.max_score}็‚น")
print(f" ๐Ÿ’ฌ {result.ai_feedback}")
# ่ฟฝๅŠ ่ณชๅ•ใฎๅˆคๅฎš
if question.follow_up and result.total_score < question.max_score * 0.4:
follow_up_result = self._ask_follow_up(question, result)
if follow_up_result:
results.append(follow_up_result)
if i < len(self.questions):
self.synthesizer.speak("ใ‚ใ‚ŠใŒใจใ†ใ”ใ–ใ„ใพใ™ใ€‚ๆฌกใฎ่ณชๅ•ใซ้€ฒใฟใพใ™ใ€‚")
self._close()
total_score = sum(r.total_score for r in results)
max_possible = sum(r.question.max_score for r in results)
percentage = total_score / max_possible if max_possible > 0 else 0
is_passed = percentage >= self.config.pass_threshold
report = InterviewReport(
candidate_name=candidate_name,
interview_date=datetime.now(),
results=results,
total_score=total_score,
max_possible_score=max_possible,
pass_threshold=self.config.pass_threshold,
is_passed=is_passed,
)
filepath = self.reporter.generate(report)
self.reporter.print_summary(report)
print(f"\n๐Ÿ“„ ใƒฌใƒใƒผใƒˆไฟๅญ˜ๅ…ˆ: {filepath}")
return report
def _greet(self) -> str:
self.synthesizer.speak(
"ใ“ใ‚“ใซใกใฏใ€‚ใ“ใ‚Œใ‹ใ‚‰AI้ขๆŽฅใ‚’้–‹ๅง‹ใ—ใพใ™ใ€‚"
"ๅ„่ณชๅ•ใซๅฏพใ—ใฆใ€้ŸณๅฃฐใงใŠ็ญ”ใˆใใ ใ•ใ„ใ€‚"
"ๆบ–ๅ‚™ใŒใงใใพใ—ใŸใ‚‰ใ€ใŠๅๅ‰ใ‚’ใŠ่žใ‹ใ›ใใ ใ•ใ„ใ€‚"
)
print("\nๅ€™่ฃœ่€…ใฎใŠๅๅ‰:")
if self.config.dry_run:
name = input("> ").strip()
else:
name, _ = self.recognizer.listen()
name = name.strip()
if not name:
name = "ๅๅ‰ๆœชๅ…ฅๅŠ›"
print(f"ๅ€™่ฃœ่€…: {name}")
self.synthesizer.speak(f"{name}ใ•ใ‚“ใ€ใ‚ˆใ‚ใ—ใใŠ้ก˜ใ„ใ„ใŸใ—ใพใ™ใ€‚ใใ‚Œใงใฏ้ขๆŽฅใ‚’ๅง‹ใ‚ใพใ—ใ‚‡ใ†ใ€‚")
return name
def _ask_question(self, question: Question) -> Answer:
self.synthesizer.speak(question.question_text)
text, duration = self.recognizer.listen()
# ็ฉบใฎๅ ดๅˆใฏ1ๅ›žใƒชใƒˆใƒฉใ‚ค
if not text.strip():
self.synthesizer.speak("่žใๅ–ใ‚Œใพใ›ใ‚“ใงใ—ใŸใ€‚ใ‚‚ใ†ไธ€ๅบฆใŠ็ญ”ใˆใใ ใ•ใ„ใ€‚")
text, duration = self.recognizer.listen()
return Answer(transcribed_text=text, audio_duration_sec=duration)
def _ask_follow_up(
self, question: Question, original_result: QuestionResult
) -> QuestionResult | None:
print(f" ๐Ÿ“Œ ่ฟฝๅŠ ่ณชๅ•: {question.follow_up}")
self.synthesizer.speak(question.follow_up)
text, duration = self.recognizer.listen()
if not text.strip():
return None
print(f" ๐Ÿ“ ่ฟฝๅŠ ๅ›ž็ญ”: {text}")
print(" ่ฟฝๅŠ ๅ›ž็ญ”ใ‚’่ฉ•ไพกไธญ...")
# ่ฟฝๅŠ ่ณชๅ•ใฎ่ฉ•ไพก๏ผˆๅ…ƒใฎ่ณชๅ•ใฎๅŠๅˆ†ใฎ้…็‚น๏ผ‰
follow_up_question = Question(
id=question.id,
category=question.category,
question_text=question.follow_up,
expected_keywords=question.expected_keywords,
keyword_weight=question.keyword_weight,
ai_weight=question.ai_weight,
improv_weight=question.improv_weight,
max_score=question.max_score // 2,
scoring_criteria=question.scoring_criteria + "๏ผˆ่ฟฝๅŠ ่ณชๅ•ใธใฎๅ›ž็ญ”๏ผ‰",
follow_up="",
)
answer = Answer(transcribed_text=text, audio_duration_sec=duration)
result = evaluate_answer(follow_up_question, answer, self.claude)
print(f" ่ฟฝๅŠ ใ‚นใ‚ณใ‚ข: {result.total_score:.1f}/{follow_up_question.max_score}็‚น")
return result
def _close(self) -> None:
self.synthesizer.speak(
"ไปฅไธŠใง้ขๆŽฅใฏ็ต‚ไบ†ใงใ™ใ€‚"
"ๆœฌๆ—ฅใฏใŠๆ™‚้–“ใ‚’ใ„ใŸใ ใใ‚ใ‚ŠใŒใจใ†ใ”ใ–ใ„ใพใ—ใŸใ€‚"
"็ตๆžœใฏๅพŒๆ—ฅใŠ็Ÿฅใ‚‰ใ›ใ„ใŸใ—ใพใ™ใ€‚ใŠ็–ฒใ‚Œๆง˜ใงใ—ใŸใ€‚"
)
print("\n้ขๆŽฅ็ต‚ไบ†ใ€‚ใƒฌใƒใƒผใƒˆใ‚’็”Ÿๆˆไธญ...")