Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -35,7 +35,9 @@ def clean_api_response(text: str) -> str:
|
|
| 35 |
return text
|
| 36 |
|
| 37 |
hf_token = os.environ.get("HF_TOKEN")
|
| 38 |
-
|
|
|
|
|
|
|
| 39 |
# --- نماذج البيانات (Pydantic Models) ---
|
| 40 |
|
| 41 |
class InterviewSession(BaseModel):
|
|
@@ -46,21 +48,15 @@ class InterviewSession(BaseModel):
|
|
| 46 |
difficulty: Literal["Junior", "Mid-level", "Senior"]
|
| 47 |
interviewer_personality: Literal["Friendly", "Aggressive", "Formal", "Adaptive"]
|
| 48 |
question_mode: Literal["Manual", "Dynamic"]
|
| 49 |
-
scenario: Literal["None", "Standard", "Pressure", "Conflict", "Simple-Client"] = "None"
|
| 50 |
num_questions: Optional[int] = 5
|
| 51 |
|
| 52 |
-
@field_validator('difficulty', 'interviewer_personality', 'question_mode',
|
| 53 |
@classmethod
|
| 54 |
def normalize_inputs(cls, v: str):
|
| 55 |
if isinstance(v, str):
|
| 56 |
v_clean = v.strip().lower()
|
| 57 |
if v_clean in ["mid-level", "mid level", "midlevel"]:
|
| 58 |
return "Mid-level"
|
| 59 |
-
if v_clean == "none":
|
| 60 |
-
return "None"
|
| 61 |
-
if "-" in v_clean:
|
| 62 |
-
parts = v_clean.split("-")
|
| 63 |
-
return "-".join([p.capitalize() for p in parts])
|
| 64 |
return v_clean.capitalize()
|
| 65 |
return v
|
| 66 |
|
|
@@ -89,51 +85,12 @@ class UserInput(BaseModel):
|
|
| 89 |
|
| 90 |
# --- دوال مساعدة ---
|
| 91 |
|
| 92 |
-
def get_scenario_logic(session: InterviewSession, question_count: int):
|
| 93 |
-
if question_count < 2 or session.scenario in ["None", "Standard"]:
|
| 94 |
-
return f"Standard technical interview for {session.track_name}. Ask a challenging technical question."
|
| 95 |
-
|
| 96 |
-
scenarios = {
|
| 97 |
-
"Pressure": (
|
| 98 |
-
"SCENARIO: HIGH PRESSURE. Act as a stressed manager. The company's main system just crashed! "
|
| 99 |
-
"Ask how they would use their technical skills to solve a critical, time-sensitive problem immediately."
|
| 100 |
-
),
|
| 101 |
-
"Conflict": (
|
| 102 |
-
"SCENARIO: TECHNICAL CONFLICT. Act as a senior colleague who strongly disagrees with their approach. "
|
| 103 |
-
"Challenge their technical decision and ask them to defend it."
|
| 104 |
-
),
|
| 105 |
-
"Simple-Client": (
|
| 106 |
-
"SCENARIO: NON-TECHNICAL CLIENT. Act as a client who knows nothing about code. "
|
| 107 |
-
"Ask them to explain a complex project or technical concept without using any technical jargon."
|
| 108 |
-
)
|
| 109 |
-
}
|
| 110 |
-
return scenarios.get(session.scenario, "Standard technical interview.")
|
| 111 |
-
|
| 112 |
def get_personality_instruction(personality: str):
|
| 113 |
personalities = {
|
| 114 |
-
"Friendly":
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
),
|
| 119 |
-
"Aggressive": (
|
| 120 |
-
"Act as a ruthless, elitist Senior Architect who hates wasting time. "
|
| 121 |
-
"Be cold, critical, and extremely hard to please. If the answer is even slightly incomplete, "
|
| 122 |
-
"mock their lack of depth with sarcasm. Use phrases like 'Is that the best you can do?', "
|
| 123 |
-
"'A student could answer better,' or 'You're clearly out of your league here.' "
|
| 124 |
-
"If the score is low, show pure disappointment and borderline anger."
|
| 125 |
-
),
|
| 126 |
-
"Formal": (
|
| 127 |
-
"Act as a high-level corporate executive. Be strictly professional, cold, and robotic. "
|
| 128 |
-
"No small talk, no emotions. Use sophisticated vocabulary and maintain a distance. "
|
| 129 |
-
"Focus only on facts and efficiency. Use 'Correct,' 'Proceed,' or 'That is insufficient' only."
|
| 130 |
-
),
|
| 131 |
-
"Adaptive": (
|
| 132 |
-
"Start neutral, then shift drastically: "
|
| 133 |
-
"If Score > 8: Become an impressed fan, praise their brilliance. "
|
| 134 |
-
"If Score 5-7: Stay professional and observant. "
|
| 135 |
-
"If Score < 5: Shift to 'Aggressive' mode immediately, lose your patience, and start questioning if they belong in this field."
|
| 136 |
-
)
|
| 137 |
}
|
| 138 |
return personalities.get(personality, "Be professional.")
|
| 139 |
|
|
@@ -185,8 +142,6 @@ async def generate_question(request: QuestionRequest):
|
|
| 185 |
|
| 186 |
history_text = "Previously Asked: " + " | ".join(request.asked_questions) if request.asked_questions else "None"
|
| 187 |
|
| 188 |
-
scenario_instruction = get_scenario_logic(session, questions_count)
|
| 189 |
-
|
| 190 |
encouragement_note = ""
|
| 191 |
if session.interviewer_personality == "Friendly" and request.previous_scores:
|
| 192 |
last_score = request.previous_scores[-1]
|
|
@@ -199,12 +154,13 @@ async def generate_question(request: QuestionRequest):
|
|
| 199 |
prompt = (
|
| 200 |
f"You are a Senior {session.track_name} Architect interviewing a {session.difficulty} candidate.\n"
|
| 201 |
f"Strict Technical Context: {session.track_name}\n"
|
| 202 |
-
f"Scenario Context: {scenario_instruction}\n"
|
| 203 |
f"{history_text}\n\n"
|
| 204 |
f"{encouragement_note}\n\n"
|
| 205 |
f"TASK:\n"
|
| 206 |
-
f"- Ask ONE
|
|
|
|
| 207 |
f"- The question must be challenging and suitable for a {session.difficulty} level.\n"
|
|
|
|
| 208 |
f"- DO NOT repeat any question from the list above.\n\n"
|
| 209 |
f"OUTPUT RULE:\n"
|
| 210 |
f"- Output ONLY the response text. No labels like 'Question:' or 'Feedback:'."
|
|
@@ -227,14 +183,17 @@ async def generate_question(request: QuestionRequest):
|
|
| 227 |
async def evaluate_answer(data: AnswerSubmission):
|
| 228 |
prompt = (
|
| 229 |
f"Role: Senior {data.session_data.track_name} Technical Interviewer.\n"
|
| 230 |
-
f"Context: Evaluate the technical accuracy
|
| 231 |
f"Question: {data.current_question}\n"
|
| 232 |
f"Candidate Answer: {data.user_answer}\n\n"
|
| 233 |
f"CRITERIA:\n"
|
| 234 |
f"- Look for technical keywords related to {data.session_data.track_name}.\n"
|
| 235 |
-
f"- Score 0-
|
|
|
|
|
|
|
| 236 |
f"OUTPUT FORMAT (STRICT):\n"
|
| 237 |
f"SCORE: <0-10> || EMOTION: <Type> || FEEDBACK: <Your direct response>\n"
|
|
|
|
| 238 |
)
|
| 239 |
|
| 240 |
try:
|
|
@@ -278,12 +237,28 @@ async def generate_summary(req: SummaryRequest):
|
|
| 278 |
for idx, item in enumerate(req.history):
|
| 279 |
history_str += f"Q{idx+1}: {item.question}\nA: {item.answer}\nScore: {item.score}/10\n"
|
| 280 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
prompt = (
|
| 282 |
f"Role: {req.session.interviewer_personality} Interviewer.\n"
|
| 283 |
f"Candidate: {req.session.name}, Track: {req.session.track_name}.\n"
|
| 284 |
f"INTERVIEW HISTORY:\n{history_str}\n"
|
| 285 |
f"STATISTICS: Total Score: {total_score}, Status: {is_passed}.\n\n"
|
| 286 |
-
f"TASK:
|
|
|
|
|
|
|
| 287 |
)
|
| 288 |
|
| 289 |
try:
|
|
|
|
| 35 |
return text
|
| 36 |
|
| 37 |
hf_token = os.environ.get("HF_TOKEN")
|
| 38 |
+
# تأكد من أن اسم الموديل مكتوب بشكل صحيح في حسابك على Hugging Face
|
| 39 |
+
client = InferenceClient("Meta-Llama-3.1-8B-Instruct", token=hf_token)
|
| 40 |
+
|
| 41 |
# --- نماذج البيانات (Pydantic Models) ---
|
| 42 |
|
| 43 |
class InterviewSession(BaseModel):
|
|
|
|
| 48 |
difficulty: Literal["Junior", "Mid-level", "Senior"]
|
| 49 |
interviewer_personality: Literal["Friendly", "Aggressive", "Formal", "Adaptive"]
|
| 50 |
question_mode: Literal["Manual", "Dynamic"]
|
|
|
|
| 51 |
num_questions: Optional[int] = 5
|
| 52 |
|
| 53 |
+
@field_validator('difficulty', 'interviewer_personality', 'question_mode', mode='before')
|
| 54 |
@classmethod
|
| 55 |
def normalize_inputs(cls, v: str):
|
| 56 |
if isinstance(v, str):
|
| 57 |
v_clean = v.strip().lower()
|
| 58 |
if v_clean in ["mid-level", "mid level", "midlevel"]:
|
| 59 |
return "Mid-level"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
return v_clean.capitalize()
|
| 61 |
return v
|
| 62 |
|
|
|
|
| 85 |
|
| 86 |
# --- دوال مساعدة ---
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
def get_personality_instruction(personality: str):
|
| 89 |
personalities = {
|
| 90 |
+
"Friendly": "Be supportive, warm, and encouraging.",
|
| 91 |
+
"Aggressive": "Be tough, challenging, and critical. If the score is low, show disappointment.",
|
| 92 |
+
"Formal": "Be professional, cold, and strictly business-like.",
|
| 93 |
+
"Adaptive": "Adjust based on score: If score > 7 be impressed, if score < 5 be stern/critical."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
}
|
| 95 |
return personalities.get(personality, "Be professional.")
|
| 96 |
|
|
|
|
| 142 |
|
| 143 |
history_text = "Previously Asked: " + " | ".join(request.asked_questions) if request.asked_questions else "None"
|
| 144 |
|
|
|
|
|
|
|
| 145 |
encouragement_note = ""
|
| 146 |
if session.interviewer_personality == "Friendly" and request.previous_scores:
|
| 147 |
last_score = request.previous_scores[-1]
|
|
|
|
| 154 |
prompt = (
|
| 155 |
f"You are a Senior {session.track_name} Architect interviewing a {session.difficulty} candidate.\n"
|
| 156 |
f"Strict Technical Context: {session.track_name}\n"
|
|
|
|
| 157 |
f"{history_text}\n\n"
|
| 158 |
f"{encouragement_note}\n\n"
|
| 159 |
f"TASK:\n"
|
| 160 |
+
f"- Ask ONE deep technical question about {session.track_name}.\n"
|
| 161 |
+
f"- Focus on: Core Concepts, Internal Architecture, Performance Optimization, or Differences (e.g., X vs Y).\n"
|
| 162 |
f"- The question must be challenging and suitable for a {session.difficulty} level.\n"
|
| 163 |
+
f"- DO NOT ask general or soft-skill questions. NO 'tell me about yourself'.\n"
|
| 164 |
f"- DO NOT repeat any question from the list above.\n\n"
|
| 165 |
f"OUTPUT RULE:\n"
|
| 166 |
f"- Output ONLY the response text. No labels like 'Question:' or 'Feedback:'."
|
|
|
|
| 183 |
async def evaluate_answer(data: AnswerSubmission):
|
| 184 |
prompt = (
|
| 185 |
f"Role: Senior {data.session_data.track_name} Technical Interviewer.\n"
|
| 186 |
+
f"Context: Evaluate ONLY the technical accuracy of this specific answer.\n"
|
| 187 |
f"Question: {data.current_question}\n"
|
| 188 |
f"Candidate Answer: {data.user_answer}\n\n"
|
| 189 |
f"CRITERIA:\n"
|
| 190 |
f"- Look for technical keywords related to {data.session_data.track_name}.\n"
|
| 191 |
+
f"- Score 0-4: Wrong or 'I don't know'.\n"
|
| 192 |
+
f"- Score 5-7: Correct but shallow.\n"
|
| 193 |
+
f"- Score 8-10: Detailed and precise.\n\n"
|
| 194 |
f"OUTPUT FORMAT (STRICT):\n"
|
| 195 |
f"SCORE: <0-10> || EMOTION: <Type> || FEEDBACK: <Your direct response>\n"
|
| 196 |
+
f"Note: Emotion can be (Natural, Happy, Disappointed, Excited, or Confused)."
|
| 197 |
)
|
| 198 |
|
| 199 |
try:
|
|
|
|
| 237 |
for idx, item in enumerate(req.history):
|
| 238 |
history_str += f"Q{idx+1}: {item.question}\nA: {item.answer}\nScore: {item.score}/10\n"
|
| 239 |
|
| 240 |
+
if req.session.interviewer_personality == "Friendly":
|
| 241 |
+
task_instruction = (
|
| 242 |
+
"- Start with a warm overall evaluation of the candidate.\n"
|
| 243 |
+
"- For each question, provide a brief feedback and a helpful 'Search Hint'.\n"
|
| 244 |
+
"- Use clear bullet points.\n"
|
| 245 |
+
"- Mention the Final Result and Total Score at the end."
|
| 246 |
+
)
|
| 247 |
+
else:
|
| 248 |
+
task_instruction = (
|
| 249 |
+
"- Provide a concise, professional technical assessment.\n"
|
| 250 |
+
"- List key strengths and weaknesses based on the history.\n"
|
| 251 |
+
"- State the Final Result and Total Score clearly."
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
prompt = (
|
| 255 |
f"Role: {req.session.interviewer_personality} Interviewer.\n"
|
| 256 |
f"Candidate: {req.session.name}, Track: {req.session.track_name}.\n"
|
| 257 |
f"INTERVIEW HISTORY:\n{history_str}\n"
|
| 258 |
f"STATISTICS: Total Score: {total_score}, Status: {is_passed}.\n\n"
|
| 259 |
+
f"TASK: {task_instruction}\n\n"
|
| 260 |
+
f"CRITICAL RULE: Output ONLY the summary text. DO NOT include introductions. "
|
| 261 |
+
f"DO NOT use markdown symbols like ** or __. No labels like 'Summary:'."
|
| 262 |
)
|
| 263 |
|
| 264 |
try:
|