Spaces:
Sleeping
Sleeping
changes made to app.py
Browse files
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 |
-
|
13 |
-
|
|
|
|
|
|
|
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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
24 |
-
2. For follow-up questions, analyze the candidate's last response and ask questions that probe deeper into their understanding
|
25 |
-
3.
|
26 |
-
4.
|
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
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
return {
|
|
|
91 |
"domain": domain,
|
92 |
"company": company,
|
93 |
"level": level,
|
94 |
"asked_questions": set(),
|
95 |
"asked_subtopics": set(),
|
96 |
-
"conversation": []
|
|
|
|
|
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(
|
108 |
-
state["
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
return state["conversation"], state
|
114 |
|
115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
with gr.Blocks() as demo:
|
118 |
-
gr.Markdown("#
|
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 |
-
|
|
|
136 |
|
137 |
-
state = gr.State()
|
138 |
|
139 |
-
start_button.click(start_interview,
|
|
|
|
|
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()
|