Codingo / backend /services /interview_engine.py
husseinelsaadi's picture
upadtee
562ec01
raw
history blame
5.46 kB
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"]
}