import os import json import asyncio import edge_tts from faster_whisper import WhisperModel from langchain_groq import ChatGroq import logging # Initialize models chat_groq_api = os.getenv("GROQ_API_KEY") if not chat_groq_api: raise ValueError("GROQ_API_KEY is not set in environment variables.") groq_llm = ChatGroq( temperature=0.7, model_name="llama-3.3-70b-versatile", api_key=chat_groq_api ) # Initialize Whisper model whisper_model = None def load_whisper_model(): global whisper_model if whisper_model is None: device = "cuda" if os.system("nvidia-smi") == 0 else "cpu" compute_type = "float16" if device == "cuda" else "int8" whisper_model = WhisperModel("base", device=device, compute_type=compute_type) return whisper_model def generate_first_question(profile, job): """Generate the first interview question based on profile and job""" try: prompt = f""" You are conducting an interview for a {job.role} position at {job.company}. The candidate's profile shows: - Skills: {profile.get('skills', [])} - Experience: {profile.get('experience', [])} - Education: {profile.get('education', [])} Generate an appropriate opening interview question that is professional and relevant. Keep it concise and clear. """ response = groq_llm.invoke(prompt) return response.strip() except Exception as e: logging.error(f"Error generating first question: {e}") return "Tell me about yourself and why you're interested in this position." def edge_tts_to_file_sync(text, output_path, voice="en-US-AriaNeural"): """Synchronous wrapper for edge-tts""" try: # Ensure the directory exists and is writable directory = os.path.dirname(output_path) if not directory: directory = "/tmp" # Fallback to /tmp if no directory specified output_path = os.path.join(directory, os.path.basename(output_path)) os.makedirs(directory, exist_ok=True) # Test write permissions test_file = os.path.join(directory, f"test_{os.getpid()}.tmp") try: with open(test_file, 'w') as f: f.write("test") os.remove(test_file) except (PermissionError, OSError) as e: logging.error(f"Directory {directory} is not writable: {e}") # Fallback to /tmp directory = "/tmp" output_path = os.path.join(directory, os.path.basename(output_path)) os.makedirs(directory, exist_ok=True) async def generate_audio(): communicate = edge_tts.Communicate(text, voice) await communicate.save(output_path) # Run async function in sync context try: loop = asyncio.get_event_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(generate_audio()) # Verify file was created and has content if os.path.exists(output_path) and os.path.getsize(output_path) > 0: return output_path else: logging.error(f"Audio file was not created or is empty: {output_path}") return None except Exception as e: logging.error(f"Error in TTS generation: {e}") return None def whisper_stt(audio_path): """Speech-to-text using Faster-Whisper""" try: if not audio_path or not os.path.exists(audio_path): logging.error(f"Audio file does not exist: {audio_path}") return "" # Check if file has content if os.path.getsize(audio_path) == 0: logging.error(f"Audio file is empty: {audio_path}") return "" model = load_whisper_model() segments, _ = model.transcribe(audio_path) transcript = " ".join(segment.text for segment in segments) return transcript.strip() except Exception as e: logging.error(f"Error in STT: {e}") return "" def evaluate_answer(question, answer, ref_answer, job_role, seniority): """Evaluate candidate's answer""" try: prompt = f""" You are evaluating a candidate's answer for a {seniority} {job_role} position. Question: {question} Candidate Answer: {answer} Reference Answer: {ref_answer} Evaluate based on technical correctness, clarity, and relevance. Respond with JSON format: {{ "Score": "Poor|Medium|Good|Excellent", "Reasoning": "brief explanation", "Improvements": ["suggestion1", "suggestion2"] }} """ response = groq_llm.invoke(prompt) # Extract JSON from response start_idx = response.find("{") end_idx = response.rfind("}") + 1 if start_idx >= 0 and end_idx > start_idx: json_str = response[start_idx:end_idx] return json.loads(json_str) else: raise ValueError("No valid JSON found in response") except Exception as e: logging.error(f"Error evaluating answer: {e}") return { "Score": "Medium", "Reasoning": "Evaluation failed", "Improvements": ["Please be more specific"] }