File size: 21,363 Bytes
86310e2
e10cad4
86310e2
 
fc63cc9
 
 
 
86310e2
e10cad4
86310e2
 
fc63cc9
86310e2
e10cad4
86310e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172ecd9
 
86310e2
 
 
 
 
 
 
 
 
 
 
 
e10cad4
 
86310e2
 
 
 
 
 
e10cad4
 
 
 
 
 
 
 
 
 
 
 
86310e2
e10cad4
86310e2
 
 
 
e10cad4
 
86310e2
 
 
 
 
e10cad4
 
 
 
 
 
 
 
 
 
 
86310e2
e10cad4
86310e2
 
 
 
 
 
e10cad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc63cc9
e10cad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86310e2
e10cad4
86310e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e10cad4
 
fc63cc9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e10cad4
 
 
 
fc63cc9
e10cad4
 
 
 
 
 
 
fc63cc9
e10cad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc63cc9
e10cad4
 
 
 
 
 
 
 
 
 
172ecd9
 
e10cad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc63cc9
 
e10cad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
import os
import random
from crewai import Agent, Task, Crew, Process, LLM
from crewai_tools import SerperDevTool
from crewai.tools import BaseTool
import asyncio
from mcp import ClientSession
from mcp.client.sse import sse_client

# Define the LLM with higher temperature for variety
def get_llm(api_key):
    return LLM(
        model="gemini/gemini-2.5-flash",
        api_key=api_key,
        temperature=0.9  # Higher temperature for more creative/varied questions
    )

class InterviewAgents:
    def __init__(self, api_key):
        self.llm = get_llm(api_key)
        self.serper_tool = SerperDevTool()

    def technical_interviewer(self):
        return Agent(
            role='Technical Interviewer',
            goal='Analyze the Job Description and CV to generate relevant technical interview questions.',
            backstory='You are an expert technical recruiter with years of experience in assessing candidate skills against job requirements. You focus on hard skills and technical proficiency.',
            llm=self.llm,
            verbose=True
        )

    def personality_interviewer(self):
        return Agent(
            role='Personality & Culture Fit Specialist',
            goal='Generate behavioral and personality-based interview questions using online resources to ensure best practices.',
            backstory='You are an organizational psychologist specializing in culture fit and soft skills. You use data-driven approaches and current trends to ask meaningful behavioral questions.',
            tools=[self.serper_tool],
            llm=self.llm,
            verbose=True
        )

    def interview_director(self):
        return Agent(
            role='Interview Director',
            goal='Compile the final interview plan and system instructions.',
            backstory='You are the Lead Interviewer. You oversee the process and ensure a balanced interview. You combine inputs from technical and personality specialists to create a cohesive interview script.',
            llm=self.llm,
            reasoning=True,
            memory=True,
            verbose=True
        )

class InterviewTasks:
    def __init__(self, jd_text, cv_text, num_questions):
        self.jd_text = jd_text
        self.cv_text = cv_text
        self.num_questions = num_questions
        self.n_tech = max(1, round(num_questions * 0.8))
        self.n_psych = max(1, num_questions - self.n_tech)

    def generate_technical_questions(self, agent):
        # Add randomization for variety
        seed = random.randint(1000, 9999)
        return Task(
            description=f"""
                Analyze the following Job Description (JD) and Curriculum Vitae (CV).
                JD: {self.jd_text[:2000]}...
                CV: {self.cv_text[:2000]}...
                
                IMPORTANT: Use seed {seed} to ensure variety. Generate {self.n_tech} UNIQUE technical interview questions.
                - Each question should be DIFFERENT from common interview questions
                - Focus on specific skills mentioned in the JD
                - Ask about practical scenarios or real-world applications
                - Keep questions VERY SHORT (max 15 words) for voice conversation
                - Make questions open-ended to encourage discussion
                - Vary question types: scenario-based, problem-solving, experience-based
                
                Example formats:
                - "How would you handle [specific technical scenario]?"
                - "Describe your experience with [technology]."
                - "What's your approach to [technical challenge]?"
            """,
            expected_output=f"A list of {self.n_tech} unique, concise technical questions (max 15 words each).",
            agent=agent
        )

    def generate_personality_questions(self, agent):
        # Add randomization for variety
        seed = random.randint(1000, 9999)
        return Task(
            description=f"""
                Analyze the JD and CV to understand the company culture and required soft skills.
                JD: {self.jd_text[:2000]}...
                
                IMPORTANT: Use seed {seed} to ensure variety. Generate {self.n_psych} UNIQUE behavioral/personality questions.
                - Use the Serper tool to find CURRENT, trending behavioral interview questions
                - Avoid cliché questions like "What's your greatest weakness?"
                - Focus on real scenarios and past experiences
                - Keep questions VERY SHORT (max 15 words) for voice conversation
                - Make questions conversational and natural
                
                Example formats:
                - "Tell me about a time you faced [specific challenge]."
                - "How do you handle [workplace situation]?"
                - "Describe a situation where you [behavioral trait]."
            """,
            expected_output=f"A list of {self.n_psych} unique, concise behavioral questions (max 15 words each).",
            agent=agent
        )

    def compile_interview(self, agent, tech_task, psych_task):
        return Task(
            description=f"""
                Compile the final interview plan from the technical and personality questions.
                
                CRITICAL REQUIREMENTS:
                
                1. QUESTIONS LIST:
                   - Combine all questions into a single numbered list
                   - Total must be exactly {self.num_questions} questions
                   - Mix: ~80% technical, ~20% behavioral
                
                2. SYSTEM INSTRUCTION (MUST BE CONCISE AND CONVERSATIONAL):
                   Create a SHORT, natural system prompt for a voice AI interviewer.
                   
                   **IMPORTANT: The system instruction MUST include the complete list of questions to ask.**
                   
                   Format the system instruction like this:
                   
                   "You are Alex, a friendly professional interviewer conducting a voice interview. Start with as soon as the connection is established 'Hi! I'm Alex. Let's begin with the first question.
                   
                   YOUR QUESTIONS (ask these in order):
                   1. [First question]
                   2. [Second question]
                   3. [Third question]
                   ... [all questions]
                   
                   CONVERSATION RULES:
                   - Ask ONE question at a time and WAIT for the complete answer
                   - Keep responses SHORT (1-2 sentences max)
                   - If interrupted, STOP talking immediately and listen
                   - After each answer, briefly acknowledge (e.g., 'Great!', 'I see', 'Thanks') then ask the next question
                   - Use a warm, conversational tone
                   - End with: 'Thanks for your time today!'
                   
                   Remember: Listen actively, don't interrupt, and keep it conversational."
                
                Output Format (JSON):
                {{
                    "questions_markdown": "# Interview Questions\\n\\n1. [Question 1]\\n2. [Question 2]...",
                    "system_instruction": "[Complete system instruction with embedded questions list as shown above]"
                }}
            """,
            expected_output="A JSON object with 'questions_markdown' (formatted list) and 'system_instruction' (concise prompt with embedded questions, under 300 words).",
            agent=agent,
            context=[tech_task, psych_task]
        )

def run_interview_crew(jd_text, cv_text, num_questions, api_key):
    agents = InterviewAgents(api_key)
    tasks = InterviewTasks(jd_text, cv_text, num_questions)

    tech_agent = agents.technical_interviewer()
    psych_agent = agents.personality_interviewer()
    director_agent = agents.interview_director()

    tech_task = tasks.generate_technical_questions(tech_agent)
    psych_task = tasks.generate_personality_questions(psych_agent)
    compile_task = tasks.compile_interview(director_agent, tech_task, psych_task)

    crew = Crew(
        agents=[tech_agent, psych_agent, director_agent],
        tasks=[tech_task, psych_task, compile_task],
        process=Process.sequential,
        verbose=True
    )

    result = crew.kickoff()
    return result



# --- Custom Tools ---

class SentimentAnalysisTool(BaseTool):
    name: str = "Sentiment Analysis Tool"
    description: str = "Analyzes the sentiment of a given text. Returns 'Positive', 'Negative', or 'Neutral'. Use this to gauge the candidate's attitude."

    def _run(self, text: str) -> str:
        async def call_mcp(text_input):
            sse_url = "https://uq-sentimentanalysismcpserver.hf.space/gradio_api/mcp/sse"
            try:
                async with sse_client(sse_url) as (read, write):
                    async with ClientSession(read, write) as session:
                        await session.initialize()
                        result = await session.call_tool(
                            "SentimentAnalysisMCPserver_predict_sentiment",
                            arguments={"text": text_input}
                        )
                        if result.content and len(result.content) > 0:
                            return result.content[0].text
                        return "Error: No content returned"
            except Exception as e:
                return f"Error connecting to MCP: {str(e)}"
        
        try:
            # Create a new event loop if one doesn't exist, or use the existing one if compatible
            # Since this is running in a thread (via asyncio.to_thread in app.py), 
            # we should be able to use asyncio.run() if no loop is running in this thread.
            # However, to be safe with nested loops or existing loops:
            try:
                loop = asyncio.get_event_loop()
                if loop.is_running():
                     # This is tricky if we are in a sync method called from an async context/loop.
                     # But app.py uses asyncio.to_thread, which runs in a separate thread.
                     # That thread likely doesn't have a running loop unless we started one.
                     # asyncio.to_thread runs in a ThreadPoolExecutor.
                     return loop.run_until_complete(call_mcp(text))
                else:
                     return loop.run_until_complete(call_mcp(text))
            except RuntimeError:
                return asyncio.run(call_mcp(text))
                
        except Exception as e:
            return f"Error analyzing sentiment: {str(e)}"

# --- Evaluation Agents ---

def get_evaluation_llm(api_key):
    return LLM(
        model="gemini/gemini-2.5-flash",
        api_key=api_key,
        temperature=0.7  # Lower temperature for more consistent evaluation
    )

class EvaluationAgents:
    def __init__(self, api_key):
        self.llm = get_evaluation_llm(api_key)
        self.sentiment_tool = SentimentAnalysisTool()

    def technical_evaluator(self):
        return Agent(
            role='Technical Skills Evaluator',
            goal='Evaluate the candidate\'s technical skills and knowledge based on their interview responses.',
            backstory='You are an expert technical recruiter with deep knowledge in assessing technical competencies. You analyze answers for depth, accuracy, and practical application of skills.',
            llm=self.llm,
            verbose=True
        )

    def behavioral_evaluator(self):
        return Agent(
            role='Behavioral & Culture Fit Evaluator',
            goal='Assess the candidate\'s soft skills, communication, and cultural fit based on behavioral questions.',
            backstory='You are an organizational psychologist specializing in evaluating interpersonal skills, problem-solving approaches, and alignment with company culture. You look for evidence of leadership, teamwork, and adaptability.',
            tools=[self.sentiment_tool],
            llm=self.llm,
            verbose=True
        )

    def evaluation_director(self):
        return Agent(
            role='Evaluation Director',
            goal='Compile a comprehensive scorecard with scores, feedback, and hiring recommendation.',
            backstory='You are the Lead Evaluator responsible for synthesizing all evaluation inputs into a clear, actionable scorecard. You ensure fairness and consistency in scoring.',
            llm=self.llm,
            reasoning=True,
            memory=True,
            verbose=True
        )

class EvaluationTasks:
    def __init__(self, transcript, jd_text, questions_text):
        self.transcript = transcript
        self.jd_text = jd_text
        self.questions_text = questions_text

    def evaluate_technical_skills(self, agent):
        return Task(
            description=f"""
                Evaluate the candidate's technical performance based on the interview transcript.
                
                TRANSCRIPT:
                {self.transcript[:3000]}
                
                JOB DESCRIPTION:
                {self.jd_text[:2000]}
                
                INTERVIEW QUESTIONS:
                {self.questions_text[:2000]}
                
                TASK:
                **CRITICAL: ONLY evaluate TECHNICAL questions. Identify which questions are technical (related to hard skills, technologies, tools, programming, systems, etc.) and ONLY score those.**
                
                For EACH technical question identified, provide:
                1. The exact question text
                2. Technical competency score (0-10)
                3. Detailed feedback on technical knowledge, problem-solving approach, and depth of understanding
                
                Also provide:
                4. Overall technical strengths
                5. Overall technical weaknesses
                6. Alignment with job requirements
                
                Focus on:
                - Accuracy and correctness of technical answers
                - Depth of knowledge demonstrated
                - Problem-solving methodology
                - Practical application of skills
                - Communication of technical concepts
                
                **DO NOT evaluate behavioral, personality, or soft skills questions. Only technical questions.**
            """,
            expected_output="A detailed technical evaluation with scores (0-10) and feedback for EACH TECHNICAL QUESTION ONLY. Format: For each technical question, provide: Question | Score (0-10) | Feedback. Plus overall technical strengths and weaknesses.",
            agent=agent
        )

    def evaluate_behavioral_skills(self, agent):
        return Task(
            description=f"""
                Evaluate the candidate's behavioral and soft skills based on the interview transcript.
                
                TRANSCRIPT:
                {self.transcript[:3000]}
                
                JOB DESCRIPTION:
                {self.jd_text[:2000]}
                
                INTERVIEW QUESTIONS:
                {self.questions_text[:2000]}
                
                TASK:
                **CRITICAL: ONLY evaluate BEHAVIORAL/SOFT SKILLS questions. Identify which questions are behavioral (related to past experiences, teamwork, leadership, culture fit, problem-solving scenarios, etc.) and ONLY score those.**
                
                For EACH behavioral question identified, provide:
                1. The exact question text
                2. Behavioral competency score (0-10)
                3. Sentiment Analysis: Use the 'Sentiment Analysis Tool' to analyze the candidate's answer. Include the result (Positive/Negative/Neutral) in your evaluation.
                4. Detailed feedback on communication, examples shared, and soft skills demonstrated
                
                Also provide:
                4. Assessment of communication skills, leadership, teamwork, and adaptability
                5. Cultural fit evaluation
                6. Examples of demonstrated soft skills
                
                Focus on:
                - Quality of examples and stories shared
                - Problem-solving approach in real situations
                - Interpersonal skills and communication clarity
                - Alignment with company values and culture
                - Emotional intelligence and self-awareness
                
                **DO NOT evaluate technical, programming, or hard skills questions. Only behavioral/soft skills questions.**
            """,
            expected_output="A detailed behavioral evaluation with scores (0-10) and feedback for EACH BEHAVIORAL QUESTION ONLY. Format: For each behavioral question, provide: Question | Score (0-10) | Feedback. Plus overall soft skills assessment and culture fit analysis.",
            agent=agent
        )

    def compile_scorecard(self, agent, tech_task, behavioral_task):
        return Task(
            description=f"""
                Compile a comprehensive interview scorecard from technical and behavioral evaluations.
                
                You have received evaluations from:
                1. Technical Evaluator - evaluated ONLY technical questions
                2. Behavioral Evaluator - evaluated ONLY behavioral questions
                
                CRITICAL REQUIREMENTS:
                
                1. SUMMARY:
                   - Brief overview of candidate performance (2-3 sentences)
                
                2. SCORECARD TABLE:
                   - Create a markdown table with columns: Question | Category | Score (0-10) | Feedback
                   - **CRITICAL: Each question must appear EXACTLY ONCE in the table - NO DUPLICATES**
                   - Merge the two evaluations: take technical questions from Technical Evaluator's output, behavioral questions from Behavioral Evaluator's output
                   - For each technical question: Use the exact question text, Category = "Technical", and the score/feedback from Technical Evaluator
                   - For each behavioral question: Use the exact question text, Category = "Behavioral", and the score/feedback from Behavioral Evaluator
                   - If a question appears in both evaluations, that's an error - each question should only be in one category
                   - List all questions in the order they appear in the interview
                
                3. OVERALL SCORES:
                   - Average Technical Score
                   - Average Behavioral Score
                   - Overall Score
                
                4. STRENGTHS:
                   - List 3-5 key strengths demonstrated
                
                5. AREAS FOR IMPROVEMENT:
                   - List 2-4 areas where the candidate could improve
                
                6. FINAL DECISION:
                   - One of: "Strong Hire", "Hire", "No Hire"
                   - Brief justification (1-2 sentences)
                
                Output Format (Markdown):
                # Interview Scorecard
                
                ## Summary
                [Brief overview]
                
                ## Scorecard
                | Question | Category | Score | Feedback |
                |----------|----------|-------|----------|
                | [Q1] | Technical | X/10 | [Feedback] |
                ...
                
                ## Overall Scores
                - **Technical Average**: X/10
                - **Behavioral Average**: X/10
                - **Overall Score**: X/10
                
                ## Strengths
                1. [Strength 1]
                2. [Strength 2]
                ...
                
                ## Areas for Improvement
                1. [Area 1]
                2. [Area 2]
                ...
                
                ## Final Decision
                **Decision**: [Strong Hire/Hire/No Hire]
                
                [Justification]
            """,
            expected_output="A comprehensive markdown scorecard with summary, detailed table, scores, strengths, weaknesses, and hiring recommendation.",
            agent=agent,
            context=[tech_task, behavioral_task]
        )

def run_evaluation_crew(transcript, jd_text, questions_text, api_key):
    """Run CrewAI evaluation crew to generate scorecard"""
    agents = EvaluationAgents(api_key)
    tasks = EvaluationTasks(transcript, jd_text, questions_text)

    tech_evaluator = agents.technical_evaluator()
    behavioral_evaluator = agents.behavioral_evaluator()
    director = agents.evaluation_director()

    tech_task = tasks.evaluate_technical_skills(tech_evaluator)
    behavioral_task = tasks.evaluate_behavioral_skills(behavioral_evaluator)
    compile_task = tasks.compile_scorecard(director, tech_task, behavioral_task)

    crew = Crew(
        agents=[tech_evaluator, behavioral_evaluator, director],
        tasks=[tech_task, behavioral_task, compile_task],
        process=Process.sequential,
        verbose=True
    )

    result = crew.kickoff()
    return result