Mohinikathro commited on
Commit
6550c45
·
verified ·
1 Parent(s): 64eed27

changes made to app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -47
app.py CHANGED
@@ -9,35 +9,58 @@ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
9
 
10
  hf_token = os.environ["HF_TOKEN"]
11
 
12
- # Load model and tokenizer from local files
13
- model_path = "AI-Mock-Interviewer/T5" # Assuming all files are in the root directory
 
 
 
14
  tokenizer = AutoTokenizer.from_pretrained(model_path)
15
  model = AutoModelForSeq2SeqLM.from_pretrained(model_path)
16
 
17
  # Move model to the appropriate device
18
  model.to(device)
19
 
20
- # ------------------- SYSTEM PROMPT -------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  system_prompt = """
22
  You are conducting a mock technical interview. The candidate's experience level can be entry-level, mid-level, or senior-level. Generate questions and follow-up questions based on the domain and the candidate's experience level. Consider these aspects:
23
- 1. The question should be relevant to the domain (e.g., software engineering, machine learning) and appropriate for the candidate's experience level.
24
- 2. For follow-up questions, analyze the candidate's last response and ask questions that probe deeper into their understanding, challenge their approach, or request clarification.
25
- 3. The follow-up question should aim to explore the candidate's depth of knowledge and ability to adapt.
26
- 4. Ensure each question is unique and does not repeat previously asked questions.
27
- 5. Ensure each question covers a different sub-topic within the domain, avoiding redundancy.
28
- 6. If no clear follow-up can be derived, generate a fresh, related question from a different aspect of the domain.
29
- Important: Ensure that each question is clear, concise, and allows the candidate to demonstrate their technical and communicative abilities effectively.
30
  """
31
 
32
- # Define sub-topic categories for different domains
33
  subtopic_keywords = {
34
  "data analysis": ["data cleaning", "missing data", "EDA", "visualization"],
35
  "machine learning": ["supervised learning", "overfitting", "hyperparameter tuning"],
36
  "software engineering": ["code optimization", "design patterns", "database design"],
37
  }
38
 
 
 
 
 
 
 
39
  def identify_subtopic(question, domain):
40
- """Identify the sub-topic of a question using predefined keywords."""
41
  domain = domain.lower()
42
  if domain in subtopic_keywords:
43
  for subtopic in subtopic_keywords[domain]:
@@ -45,19 +68,12 @@ def identify_subtopic(question, domain):
45
  return subtopic
46
  return None
47
 
48
- # Tracking asked questions
49
  def generate_question(prompt, domain, state=None):
50
- """
51
- Generates a unique question based on the prompt and domain.
52
- Uses 'state' to track uniqueness in the conversation session.
53
- """
54
  full_prompt = system_prompt + "\n" + prompt
55
  inputs = tokenizer(full_prompt, return_tensors="pt").to(device)
56
-
57
  outputs = model.generate(
58
  inputs["input_ids"],
59
  max_new_tokens=50,
60
- num_return_sequences=1,
61
  no_repeat_ngram_size=2,
62
  top_k=30,
63
  top_p=0.9,
@@ -65,17 +81,12 @@ def generate_question(prompt, domain, state=None):
65
  do_sample=True,
66
  pad_token_id=tokenizer.eos_token_id,
67
  )
68
-
69
  question = tokenizer.decode(outputs[0], skip_special_tokens=True).strip()
70
-
71
- # Ensure question ends with a question mark
72
  if not question.endswith("?"):
73
  question += "?"
74
 
75
- # Identify the subtopic
76
  subtopic = identify_subtopic(question, domain)
77
 
78
- # Check for uniqueness
79
  if state is not None:
80
  if (question not in state["asked_questions"] and
81
  (subtopic is None or subtopic not in state["asked_subtopics"])):
@@ -83,41 +94,121 @@ def generate_question(prompt, domain, state=None):
83
  if subtopic:
84
  state["asked_subtopics"].add(subtopic)
85
  return question
86
- return question # Fallback
87
-
88
- # Initialize conversation state
89
- def reset_state(domain, company, level):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  return {
 
91
  "domain": domain,
92
  "company": company,
93
  "level": level,
94
  "asked_questions": set(),
95
  "asked_subtopics": set(),
96
- "conversation": [] # List of (speaker, message) tuples
 
 
97
  }
98
 
99
- def start_interview(domain, company, level):
100
- state = reset_state(domain, company, level)
101
  prompt = f"Domain: {domain}. Candidate experience level: {level}. Generate the first question:"
102
-
103
  question = generate_question(prompt, domain, state)
104
  state["conversation"].append(("Interviewer", question))
105
  return state["conversation"], state
106
 
107
- def submit_response(candidate_response, state):
108
- state["conversation"].append(("Candidate", candidate_response))
109
- prompt = (f"Domain: {state['domain']}. Candidate's last response: {candidate_response}. Generate a follow-up question:")
110
-
111
- question = generate_question(prompt, state["domain"], state)
112
- state["conversation"].append(("Interviewer", question))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  return state["conversation"], state
114
 
115
- # ----------- Gradio UI -----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  with gr.Blocks() as demo:
118
- gr.Markdown("# Interactive AI-Powered Mock Interview")
119
-
120
  with gr.Row():
 
121
  domain_input = gr.Textbox(label="Domain", placeholder="e.g. Software Engineering")
122
  company_input = gr.Textbox(label="Company (Optional)", placeholder="e.g. Google")
123
  level_input = gr.Dropdown(
@@ -127,17 +218,21 @@ with gr.Blocks() as demo:
127
  )
128
 
129
  start_button = gr.Button("Start Interview")
130
- chatbot = gr.Chatbot(label="Interview Conversation")
131
 
132
  with gr.Row():
133
- response_input = gr.Textbox(label="Your Response")
134
  submit_button = gr.Button("Submit")
135
- clear_button = gr.Button("Clear Chat")
 
136
 
137
- state = gr.State() # Holds session data
138
 
139
- start_button.click(start_interview, inputs=[domain_input, company_input, level_input], outputs=[chatbot, state])
 
 
140
  submit_button.click(submit_response, inputs=[response_input, state], outputs=[chatbot, state]).then(lambda: "", None, response_input)
 
141
  clear_button.click(lambda: ([], None), outputs=[chatbot, state])
142
 
143
- demo.launch()
 
9
 
10
  hf_token = os.environ["HF_TOKEN"]
11
 
12
+
13
+ # ===============================
14
+ # Load Question Generation Model
15
+ # ===============================
16
+ model_path = "AI-Mock-Interviewer/T5"
17
  tokenizer = AutoTokenizer.from_pretrained(model_path)
18
  model = AutoModelForSeq2SeqLM.from_pretrained(model_path)
19
 
20
  # Move model to the appropriate device
21
  model.to(device)
22
 
23
+ # ===============================
24
+ # Load Evaluation Model (QwQ)
25
+ # ===============================
26
+ bnb_config = BitsAndBytesConfig(
27
+ load_in_8bit=True,
28
+ llm_int8_enable_fp32_cpu_offload=True,
29
+ )
30
+
31
+ qwq_model_id = "unsloth/QwQ-32B-unsloth-bnb-4bit"
32
+ qwq_tokenizer = AutoTokenizer.from_pretrained(qwq_model_id, trust_remote_code=True)
33
+ qwq_model = AutoModelForCausalLM.from_pretrained(
34
+ qwq_model_id,
35
+ quantization_config=bnb_config,
36
+ device_map="auto",
37
+ trust_remote_code=True
38
+ )
39
+
40
+ # ===============================
41
+ # Prompts and Scoring
42
+ # ===============================
43
  system_prompt = """
44
  You are conducting a mock technical interview. The candidate's experience level can be entry-level, mid-level, or senior-level. Generate questions and follow-up questions based on the domain and the candidate's experience level. Consider these aspects:
45
+ 1. The question should be relevant to the domain and appropriate for the candidate's experience level.
46
+ 2. For follow-up questions, analyze the candidate's last response and ask questions that probe deeper into their understanding.
47
+ 3. Avoid repeating previously asked questions or subtopics.
48
+ 4. Keep questions clear and concise, targeting core technical and communication skills.
 
 
 
49
  """
50
 
 
51
  subtopic_keywords = {
52
  "data analysis": ["data cleaning", "missing data", "EDA", "visualization"],
53
  "machine learning": ["supervised learning", "overfitting", "hyperparameter tuning"],
54
  "software engineering": ["code optimization", "design patterns", "database design"],
55
  }
56
 
57
+ rating_scores = {"Good": 3, "Average": 2, "Needs Improvement": 1}
58
+ score_categories = [(90, "Excellent"), (75, "Very Good"), (60, "Good"), (45, "Average"), (0, "Needs Improvement")]
59
+
60
+ # ===============================
61
+ # Helper Functions
62
+ # ===============================
63
  def identify_subtopic(question, domain):
 
64
  domain = domain.lower()
65
  if domain in subtopic_keywords:
66
  for subtopic in subtopic_keywords[domain]:
 
68
  return subtopic
69
  return None
70
 
 
71
  def generate_question(prompt, domain, state=None):
 
 
 
 
72
  full_prompt = system_prompt + "\n" + prompt
73
  inputs = tokenizer(full_prompt, return_tensors="pt").to(device)
 
74
  outputs = model.generate(
75
  inputs["input_ids"],
76
  max_new_tokens=50,
 
77
  no_repeat_ngram_size=2,
78
  top_k=30,
79
  top_p=0.9,
 
81
  do_sample=True,
82
  pad_token_id=tokenizer.eos_token_id,
83
  )
 
84
  question = tokenizer.decode(outputs[0], skip_special_tokens=True).strip()
 
 
85
  if not question.endswith("?"):
86
  question += "?"
87
 
 
88
  subtopic = identify_subtopic(question, domain)
89
 
 
90
  if state is not None:
91
  if (question not in state["asked_questions"] and
92
  (subtopic is None or subtopic not in state["asked_subtopics"])):
 
94
  if subtopic:
95
  state["asked_subtopics"].add(subtopic)
96
  return question
97
+ return question
98
+
99
+ def evaluate_response(response, question):
100
+ eval_prompt = (
101
+ "Evaluate the following candidate response to an interview question.\n\n"
102
+ f"**Question:** {question}\n"
103
+ f"**Candidate's Response:** {response}\n\n"
104
+ "Provide a rating as: 'Good', 'Average', or 'Needs Improvement'.\n"
105
+ "Also, provide a brief suggestion for improvement. Format:\n"
106
+ "Rating: <Rating>\nSuggestion: <Suggestion>"
107
+ )
108
+ inputs = qwq_tokenizer(eval_prompt, return_tensors="pt", padding=True).to(qwq_model.device)
109
+ outputs = qwq_model.generate(
110
+ inputs["input_ids"],
111
+ max_new_tokens=100,
112
+ top_k=30,
113
+ top_p=0.9,
114
+ temperature=0.7,
115
+ do_sample=True,
116
+ pad_token_id=qwq_tokenizer.eos_token_id,
117
+ )
118
+ evaluation = qwq_tokenizer.decode(outputs[0], skip_special_tokens=True)
119
+ rating, suggestion = "Unknown", "No suggestion available."
120
+ for line in evaluation.splitlines():
121
+ if "Rating:" in line:
122
+ rating = line.split("Rating:")[1].strip()
123
+ if "Suggestion:" in line:
124
+ suggestion = line.split("Suggestion:")[1].strip()
125
+ return rating, suggestion
126
+
127
+ def reset_state(name, domain, company, level):
128
  return {
129
+ "name": name,
130
  "domain": domain,
131
  "company": company,
132
  "level": level,
133
  "asked_questions": set(),
134
  "asked_subtopics": set(),
135
+ "conversation": [],
136
+ "evaluations": [],
137
+ "interview_active": True
138
  }
139
 
140
+ def start_interview(name, domain, company, level):
141
+ state = reset_state(name, domain, company, level)
142
  prompt = f"Domain: {domain}. Candidate experience level: {level}. Generate the first question:"
 
143
  question = generate_question(prompt, domain, state)
144
  state["conversation"].append(("Interviewer", question))
145
  return state["conversation"], state
146
 
147
+ def submit_response(response, state):
148
+ if not state["interview_active"]:
149
+ return state["conversation"], state
150
+
151
+ if not response.strip():
152
+ state["conversation"].append(("System", "⚠️ Please answer the question before proceeding."))
153
+ return state["conversation"], state
154
+
155
+ if response.strip().lower() == "exit":
156
+ return end_interview(state)
157
+
158
+ state["conversation"].append(("Candidate", response))
159
+ last_q = [msg for role, msg in reversed(state["conversation"]) if role == "Interviewer"][0]
160
+ rating, suggestion = evaluate_response(response, last_q)
161
+
162
+ state["evaluations"].append({
163
+ "question": last_q,
164
+ "response": response,
165
+ "rating": rating,
166
+ "suggestion": suggestion
167
+ })
168
+
169
+ state["conversation"].append(("Evaluator", f"Rating: {rating}\nSuggestion: {suggestion}"))
170
+ prompt = f"Domain: {state['domain']}. Candidate's last response: {response}. Generate a follow-up question:"
171
+ follow_up = generate_question(prompt, state["domain"], state)
172
+ state["conversation"].append(("Interviewer", follow_up))
173
  return state["conversation"], state
174
 
175
+ def end_interview(state):
176
+ state["interview_active"] = False
177
+ total = sum(rating_scores.get(ev["rating"], 0) for ev in state["evaluations"])
178
+ max_total = len(state["evaluations"]) * 3
179
+ percent = (total / max_total * 100) if max_total > 0 else 0
180
+ category = next(label for threshold, label in score_categories if percent >= threshold)
181
+
182
+ summary = {
183
+ "name": state["name"],
184
+ "domain": state["domain"],
185
+ "level": state["level"],
186
+ "company": state["company"],
187
+ "score": f"{total}/{max_total}",
188
+ "percentage": round(percent, 2),
189
+ "category": category,
190
+ "evaluations": state["evaluations"]
191
+ }
192
 
193
+ filename = f"sessions/{state['name'].replace(' ', '_').lower()}_session.json"
194
+ os.makedirs("sessions", exist_ok=True)
195
+ with open(filename, "w") as f:
196
+ json.dump(summary, f, indent=4)
197
+
198
+ state["conversation"].append(("System", f"✅ Interview ended.\nFinal Score: {summary['score']} ({summary['category']})"))
199
+ return state["conversation"], state
200
+
201
+ def clear_state():
202
+ return [], None
203
+
204
+ # ===============================
205
+ # Gradio UI
206
+ # ===============================
207
  with gr.Blocks() as demo:
208
+ gr.Markdown("# 🧠 AI Mock Interview with Evaluation, History & Exit")
209
+
210
  with gr.Row():
211
+ name_input = gr.Textbox(label="Your Name")
212
  domain_input = gr.Textbox(label="Domain", placeholder="e.g. Software Engineering")
213
  company_input = gr.Textbox(label="Company (Optional)", placeholder="e.g. Google")
214
  level_input = gr.Dropdown(
 
218
  )
219
 
220
  start_button = gr.Button("Start Interview")
221
+ chatbot = gr.Chatbot(label="Interview Conversation", height=450)
222
 
223
  with gr.Row():
224
+ response_input = gr.Textbox(label="Your Response (type 'exit' to quit)", lines=2)
225
  submit_button = gr.Button("Submit")
226
+ exit_button = gr.Button("Exit Interview")
227
+ clear_button = gr.Button("Clear Session")
228
 
229
+ state = gr.State()
230
 
231
+ start_button.click(start_interview,
232
+ inputs=[name_input, domain_input, company_input, level_input],
233
+ outputs=[chatbot, state])
234
  submit_button.click(submit_response, inputs=[response_input, state], outputs=[chatbot, state]).then(lambda: "", None, response_input)
235
+ exit_button.click(end_interview, inputs=state, outputs=[chatbot, state])
236
  clear_button.click(lambda: ([], None), outputs=[chatbot, state])
237
 
238
+ demo.launch()