CoolFace
Apppublic

MOSES3377/ai-interview-app

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
llm_utils.py199 linesDownload Raw Back to root
1import os2import json3from openai import OpenAI4import logging5 6# Configure logging7logging.basicConfig(level=logging.INFO)8 9# Load environment variables from .env file for local development10try:11    from dotenv import load_dotenv12    load_dotenv()13except ImportError:14    logging.warning("dotenv package not found. Make sure to set environment variables manually.")15 16# Initialize OpenAI client17# It's crucial to have OPENAI_API_KEY set in your environment18try:19    client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))20    OPENAI_MODEL = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")21except Exception as e:22    logging.error(f"Failed to initialize OpenAI client: {e}")23    client = None24 25def extract_json_from_string(s: str):26    """27    Safely extracts the first valid JSON object from a string.28    Handles cases where the JSON is embedded within other text.29    """30    try:31        start_index = s.find('{')32        end_index = s.rfind('}') + 133        if start_index != -1 and end_index != -1:34            json_str = s[start_index:end_index]35            return json.loads(json_str)36    except (json.JSONDecodeError, ValueError) as e:37        logging.error(f"JSON decoding failed: {e}")38        return None39    return None40 41def generate_questions(role: str, num_questions: int = 5) -> list:42    """43    Generates a list of interview questions for a given role using an LLM.44 45    Args:46        role (str): The job role for which to generate questions.47        num_questions (int): The number of questions to generate.48 49    Returns:50        list: A list of dictionaries, where each dictionary is a question.51              Returns a static list from questions.json on failure.52    """53    if not client:54        logging.error("OpenAI client not initialized. Falling back to static questions.")55        return load_static_questions()56 57    prompt = f"""58    You are an expert HR interviewer. Generate {num_questions} diverse interview questions for a candidate applying for the role of '{role}'.59    The questions should cover a range of topics, including technical skills, behavioral aspects, problem-solving abilities, and cultural fit.60    Return the output as a clean JSON array of objects, where each object has a single key "text" containing the question.61 62    Example format:63    [64        {{"text": "What is your experience with Python and Django?"}},65        {{"text": "Describe a time you had a conflict with a team member and how you resolved it."}}66    ]67    """68 69    try:70        response = client.chat.completions.create(71            model=OPENAI_MODEL,72            messages=[{"role": "user", "content": prompt}],73            temperature=0.7,74            response_format={"type": "json_object"} # Use JSON mode if available75        )76        content = response.choices[0].message.content77        # The content should already be a JSON object, but we handle cases where it might be wrapped.78        questions_data = json.loads(content)79        # The prompt asks for an array, but the model might return a dictionary with a key.80        # We need to find the list of questions within the returned object.81        if isinstance(questions_data, dict):82            for key, value in questions_data.items():83                if isinstance(value, list):84                    return value # Return the first list found85        elif isinstance(questions_data, list):86             return questions_data87 88        logging.error("LLM returned unexpected JSON structure. Falling back to static questions.")89        return load_static_questions()90 91    except Exception as e:92        logging.error(f"Error generating questions with LLM: {e}")93        return load_static_questions()94 95def evaluate_answer(question: str, answer: str) -> dict:96    """97    Evaluates a candidate's answer to a question using an LLM.98 99    Args:100        question (str): The interview question that was asked.101        answer (str): The candidate's transcribed answer.102 103    Returns:104        dict: A dictionary containing the score, feedback, and a suggested better answer.105    """106    if not client or not answer:107        return {"score": 0, "feedback": "Evaluation could not be performed.", "better_answer": "N/A"}108 109    prompt = f"""110    As an expert interviewer, evaluate the following answer to an interview question.111    Provide a constructive, encouraging, and brief feedback.112    Also, provide a score from 0 to 10, where 0 is very poor and 10 is excellent.113    Finally, provide an improved, concise version of the answer that would be considered ideal.114 115    Question: "{question}"116    Candidate's Answer: "{answer}"117 118    Return your evaluation as a clean JSON object with three keys: "score", "feedback", and "better_answer".119    Example format:120    {{121        "score": 8,122        "feedback": "This is a strong answer that clearly demonstrates your skills. You could make it even better by providing a more specific metric of your success.",123        "better_answer": "In my previous role, I led a project that increased user engagement by 15% in one quarter by implementing a new recommendation algorithm."124    }}125    """126    try:127        response = client.chat.completions.create(128            model=OPENAI_MODEL,129            messages=[{"role": "user", "content": prompt}],130            temperature=0.5,131            response_format={"type": "json_object"}132        )133        content = response.choices[0].message.content134        evaluation = json.loads(content)135        return evaluation136    except Exception as e:137        logging.error(f"Error evaluating answer with LLM: {e}")138        return {"score": 0, "feedback": "An error occurred during evaluation.", "better_answer": "Could not be generated."}139 140 141def get_interview_summary(evaluations: list) -> dict:142    """143    Generates a final summary of the interview based on all evaluations.144 145    Args:146        evaluations (list): A list of evaluation dictionaries for each answer.147 148    Returns:149        dict: A dictionary containing the final score and a summary paragraph.150    """151    if not client or not evaluations:152        return {"final_score": 0, "summary": "Could not generate a summary."}153 154    # Calculate average score155    total_score = sum(e.get('score', 0) for e in evaluations)156    num_questions = len(evaluations)157    final_score = round(total_score / num_questions, 1) if num_questions > 0 else 0158 159    # Prepare context for summary generation160    transcript = "\n\n".join(161        f"Question {i+1}: {e['question']}\nAnswer: {e['answer']}\nFeedback: {e['feedback']} (Score: {e['score']})"162        for i, e in enumerate(evaluations)163    )164 165    prompt = f"""166    Based on the following interview transcript and evaluations, provide a brief, overall summary of the candidate's performance.167    Highlight one key strength and one area for improvement. Keep the tone professional and constructive.168    Do not mention the final score in your summary text.169 170    Transcript:171    {transcript}172 173    Return a single JSON object with the key "summary".174    """175 176    try:177        response = client.chat.completions.create(178            model=OPENAI_MODEL,179            messages=[{"role": "user", "content": prompt}],180            temperature=0.6,181            response_format={"type": "json_object"}182        )183        content = response.choices[0].message.content184        summary_data = json.loads(content)185        return {"final_score": final_score, "summary": summary_data.get("summary", "Summary could not be generated.")}186 187    except Exception as e:188        logging.error(f"Error generating summary with LLM: {e}")189        return {"final_score": final_score, "summary": "An error occurred while generating the final summary."}190 191def load_static_questions() -> list:192    """Loads the fallback questions from the local JSON file."""193    try:194        with open("questions.json", "r") as f:195            return json.load(f)196    except (FileNotFoundError, json.JSONDecodeError):197        # A hardcoded ultimate fallback198        return [{"text": "Tell me about yourself."}]199