Spaces:
Sleeping
Sleeping
| 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้ขๆฅ็ตไบใใฌใใผใใ็ๆไธญ...") | |