Update app.py
Browse files
app.py
CHANGED
|
@@ -2,9 +2,9 @@ import gradio as gr
|
|
| 2 |
from openai import OpenAI
|
| 3 |
import os
|
| 4 |
|
| 5 |
-
# ============================
|
| 6 |
user_profile = {
|
| 7 |
-
"mode": None, #
|
| 8 |
"specific_career": None,
|
| 9 |
"bg_info": None,
|
| 10 |
"work_value": None,
|
|
@@ -12,33 +12,33 @@ user_profile = {
|
|
| 12 |
"dream_day": None,
|
| 13 |
"forward_direction_choice": None,
|
| 14 |
|
| 15 |
-
# Forward
|
| 16 |
-
"recommended_directions": [], # 3
|
| 17 |
-
"selected_direction": None, #
|
| 18 |
-
"recommended_jobs": [], # 3
|
| 19 |
-
"final_choice": None #
|
| 20 |
}
|
| 21 |
|
| 22 |
-
# ============================
|
| 23 |
base_questions = [
|
| 24 |
-
("mode", "
|
| 25 |
]
|
| 26 |
|
| 27 |
-
# ============================ Forward
|
| 28 |
forward_additional_questions = [
|
| 29 |
-
("bg_info", """
|
| 30 |
-
("work_value", """
|
| 31 |
-
("personality_summary", "
|
| 32 |
-
("dream_day", "
|
| 33 |
]
|
| 34 |
|
| 35 |
-
# ============================ Backward
|
| 36 |
backward_additional_questions = [
|
| 37 |
-
("specific_career", "
|
| 38 |
-
("bg_info", "
|
| 39 |
]
|
| 40 |
|
| 41 |
-
# ============================
|
| 42 |
current_q_index = 0
|
| 43 |
questions = base_questions[:]
|
| 44 |
in_forward_flow = False
|
|
@@ -53,15 +53,15 @@ forward_deep_dive_done = False
|
|
| 53 |
roadmap_offered = False
|
| 54 |
roadmap_done = False
|
| 55 |
|
| 56 |
-
direction_chosen = False #
|
| 57 |
-
jobs_recommended = False #
|
| 58 |
-
job_chosen = False #
|
| 59 |
|
| 60 |
-
# ==========
|
| 61 |
-
post_career_detail_asked = False #
|
| 62 |
-
post_career_detail_done = False #
|
| 63 |
|
| 64 |
-
# ============================
|
| 65 |
model_default = "gpt-4o"
|
| 66 |
token_default = 2000
|
| 67 |
temp_default = 0.7
|
|
@@ -69,30 +69,30 @@ top_p_default = 0.95
|
|
| 69 |
|
| 70 |
|
| 71 |
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 72 |
-
#
|
| 73 |
def backward_strategy_plan(specific_career, bg_info):
|
| 74 |
prompt = f"""
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
1.
|
| 81 |
-
2.
|
| 82 |
-
3.
|
| 83 |
-
4.
|
| 84 |
-
5.
|
| 85 |
-
6.
|
| 86 |
-
|
| 87 |
-
|
| 88 |
"""
|
| 89 |
try:
|
| 90 |
api_key = os.environ.get("API_TOKEN")
|
| 91 |
if not api_key:
|
| 92 |
-
return "
|
| 93 |
client = OpenAI(api_key=api_key)
|
| 94 |
msgs = [
|
| 95 |
-
{"role": "system", "content": "
|
| 96 |
{"role": "user", "content": prompt}
|
| 97 |
]
|
| 98 |
resp = client.chat.completions.create(
|
|
@@ -105,9 +105,11 @@ def backward_strategy_plan(specific_career, bg_info):
|
|
| 105 |
)
|
| 106 |
return resp.choices[0].message.content
|
| 107 |
except Exception as e:
|
| 108 |
-
return f"
|
|
|
|
| 109 |
|
| 110 |
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
| 111 |
def answer_abc_questions(selected, bg_info, wv, ps, dd):
|
| 112 |
desired_parts = []
|
| 113 |
if "a" in selected.lower():
|
|
@@ -118,31 +120,46 @@ def answer_abc_questions(selected, bg_info, wv, ps, dd):
|
|
| 118 |
desired_parts.append("C")
|
| 119 |
|
| 120 |
if not desired_parts:
|
| 121 |
-
return "
|
| 122 |
|
| 123 |
prompt_text = f"""
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
-
|
| 128 |
-
-
|
| 129 |
-
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
"""
|
| 139 |
try:
|
| 140 |
api_key = os.environ.get("API_TOKEN")
|
| 141 |
if not api_key:
|
| 142 |
-
return "
|
| 143 |
client = OpenAI(api_key=api_key)
|
| 144 |
msgs = [
|
| 145 |
-
{"role": "system", "content": "
|
| 146 |
{"role": "user", "content": prompt_text}
|
| 147 |
]
|
| 148 |
resp = client.chat.completions.create(
|
|
@@ -155,35 +172,39 @@ def answer_abc_questions(selected, bg_info, wv, ps, dd):
|
|
| 155 |
)
|
| 156 |
return resp.choices[0].message.content
|
| 157 |
except Exception as e:
|
| 158 |
-
return f"
|
| 159 |
|
| 160 |
|
| 161 |
-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 162 |
def do_selected_career_detail(selected_career, bg_info, wv, ps, dd):
|
| 163 |
prompt = f"""
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
"""
|
| 179 |
try:
|
| 180 |
api_key = os.environ.get("API_TOKEN")
|
| 181 |
if not api_key:
|
| 182 |
-
return "
|
| 183 |
client = OpenAI(api_key=api_key)
|
| 184 |
|
| 185 |
msgs = [
|
| 186 |
-
{"role": "system", "content": "
|
| 187 |
{"role": "user", "content": prompt}
|
| 188 |
]
|
| 189 |
resp = client.chat.completions.create(
|
|
@@ -196,42 +217,46 @@ def do_selected_career_detail(selected_career, bg_info, wv, ps, dd):
|
|
| 196 |
)
|
| 197 |
return resp.choices[0].message.content
|
| 198 |
except Exception as e:
|
| 199 |
-
return f"
|
| 200 |
|
| 201 |
|
| 202 |
-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 203 |
def do_time_roadmap():
|
| 204 |
direction = user_profile.get("final_choice") or user_profile.get("selected_direction") or \
|
| 205 |
user_profile.get("forward_direction_choice") or user_profile.get("specific_career")
|
| 206 |
-
bg_info = user_profile["bg_info"] or "
|
| 207 |
-
wv = user_profile["work_value"] or "
|
| 208 |
-
ps = user_profile["personality_summary"] or "
|
| 209 |
-
dd = user_profile["dream_day"] or "
|
| 210 |
|
| 211 |
prompt_roadmap = f"""
|
| 212 |
-
|
| 213 |
-
-
|
| 214 |
-
-
|
| 215 |
-
-
|
| 216 |
-
-
|
| 217 |
-
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
"""
|
| 226 |
try:
|
| 227 |
api_key = os.environ.get("API_TOKEN")
|
| 228 |
if not api_key:
|
| 229 |
-
return "
|
| 230 |
client = OpenAI(api_key=api_key)
|
| 231 |
|
| 232 |
msgs = [
|
| 233 |
-
{"role":"system","content":"
|
| 234 |
-
{"role":"user","content": prompt_roadmap}
|
| 235 |
]
|
| 236 |
resp = client.chat.completions.create(
|
| 237 |
model=model_default,
|
|
@@ -243,39 +268,41 @@ def do_time_roadmap():
|
|
| 243 |
)
|
| 244 |
return resp.choices[0].message.content
|
| 245 |
except Exception as e:
|
| 246 |
-
return f"
|
| 247 |
|
| 248 |
|
| 249 |
-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 250 |
def recommend_3jobs_for_direction(direction, bg_info, wv, ps, dd):
|
| 251 |
prompt = f"""
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
|
|
|
|
|
|
| 270 |
"""
|
| 271 |
try:
|
| 272 |
api_key = os.environ.get("API_TOKEN")
|
| 273 |
if not api_key:
|
| 274 |
-
return "
|
| 275 |
client = OpenAI(api_key=api_key)
|
| 276 |
|
| 277 |
msgs = [
|
| 278 |
-
{"role": "system", "content": "
|
| 279 |
{"role": "user", "content": prompt}
|
| 280 |
]
|
| 281 |
resp = client.chat.completions.create(
|
|
@@ -288,65 +315,69 @@ def recommend_3jobs_for_direction(direction, bg_info, wv, ps, dd):
|
|
| 288 |
)
|
| 289 |
output_text = resp.choices[0].message.content
|
| 290 |
|
| 291 |
-
#
|
| 292 |
import re
|
| 293 |
job_pattern = r'(\d+)\.\s+\[?(.*?)\]?(?:\n|$)'
|
| 294 |
job_matches = re.findall(job_pattern, output_text)
|
| 295 |
job_list = [match[1].strip() for match in job_matches]
|
| 296 |
-
|
| 297 |
-
#
|
| 298 |
if len(job_list) < 3:
|
| 299 |
job_list = [
|
| 300 |
-
f"{direction} -
|
| 301 |
-
f"{direction} -
|
| 302 |
-
f"{direction} -
|
| 303 |
]
|
| 304 |
-
|
| 305 |
-
user_profile["recommended_jobs"] = job_list[:3] #
|
| 306 |
-
|
| 307 |
-
#
|
| 308 |
global jobs_recommended
|
| 309 |
jobs_recommended = True
|
| 310 |
-
|
| 311 |
return output_text
|
| 312 |
except Exception as e:
|
| 313 |
-
return f"
|
| 314 |
|
| 315 |
|
| 316 |
-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 317 |
def recommend_3directions():
|
| 318 |
-
bg_info = user_profile["bg_info"] or "
|
| 319 |
-
wv = user_profile["work_value"] or "
|
| 320 |
-
ps = user_profile["personality_summary"] or "
|
| 321 |
-
dd = user_profile["dream_day"] or "
|
| 322 |
|
| 323 |
rec_prompt = f"""
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
-
|
| 328 |
-
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
"""
|
| 342 |
try:
|
| 343 |
api_key = os.environ.get("API_TOKEN")
|
| 344 |
if not api_key:
|
| 345 |
-
return "
|
| 346 |
client = OpenAI(api_key=api_key)
|
| 347 |
msgs = [
|
| 348 |
-
{"role":"system","content":"
|
| 349 |
-
{"role":"user","content": rec_prompt}
|
| 350 |
]
|
| 351 |
resp = client.chat.completions.create(
|
| 352 |
model=model_default,
|
|
@@ -358,27 +389,24 @@ def recommend_3directions():
|
|
| 358 |
)
|
| 359 |
output_text = resp.choices[0].message.content
|
| 360 |
|
| 361 |
-
#
|
| 362 |
import re
|
| 363 |
dir_pattern = r'(\d+)\.\s+\[?(.*?)\]?(?:\n|$)'
|
| 364 |
dir_matches = re.findall(dir_pattern, output_text)
|
| 365 |
dir_list = [match[1].strip() for match in dir_matches]
|
| 366 |
-
|
| 367 |
-
#
|
| 368 |
if len(dir_list) < 3:
|
| 369 |
-
dir_list = [
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
"方向3"
|
| 373 |
-
]
|
| 374 |
-
|
| 375 |
-
user_profile["recommended_directions"] = dir_list[:3] # 确保只取前3个
|
| 376 |
return output_text
|
| 377 |
except Exception as e:
|
| 378 |
-
return f"
|
| 379 |
|
| 380 |
|
| 381 |
-
|
|
|
|
| 382 |
def generate_system_prompt():
|
| 383 |
mode = user_profile["mode"]
|
| 384 |
sc = user_profile["specific_career"]
|
|
@@ -388,39 +416,40 @@ def generate_system_prompt():
|
|
| 388 |
dd = user_profile["dream_day"]
|
| 389 |
fwd = user_profile["forward_direction_choice"]
|
| 390 |
|
| 391 |
-
if mode == "是":
|
| 392 |
return f"""
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
1.
|
| 398 |
-
2.
|
| 399 |
-
3.
|
| 400 |
-
4.
|
| 401 |
-
5.
|
| 402 |
-
|
| 403 |
-
|
|
|
|
| 404 |
"""
|
| 405 |
-
else:
|
| 406 |
return f"""
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
1.
|
| 415 |
-
2.
|
| 416 |
-
3.
|
| 417 |
-
📍
|
| 418 |
"""
|
| 419 |
|
| 420 |
|
| 421 |
-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 422 |
def predict(message, history):
|
| 423 |
-
# =========
|
| 424 |
global current_q_index, questions
|
| 425 |
global in_forward_flow, in_backward_flow
|
| 426 |
global forward_index, backward_index
|
|
@@ -432,7 +461,7 @@ def predict(message, history):
|
|
| 432 |
global direction_chosen, jobs_recommended, job_chosen
|
| 433 |
global post_career_detail_asked, post_career_detail_done
|
| 434 |
|
| 435 |
-
# ========================
|
| 436 |
if not history:
|
| 437 |
current_q_index = 0
|
| 438 |
questions[:] = base_questions
|
|
@@ -457,35 +486,32 @@ def predict(message, history):
|
|
| 457 |
post_career_detail_asked = False
|
| 458 |
post_career_detail_done = False
|
| 459 |
|
| 460 |
-
# ========================
|
| 461 |
if 0 < current_q_index <= len(questions):
|
| 462 |
key = questions[current_q_index - 1][0]
|
| 463 |
-
|
| 464 |
|
| 465 |
if key == "mode" and current_q_index == 1:
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
|
| 475 |
-
if current_q_index < len(questions)and not in_forward_flow and not in_backward_flow:
|
| 476 |
nxt = questions[current_q_index][1]
|
| 477 |
current_q_index += 1
|
| 478 |
return nxt
|
| 479 |
|
| 480 |
-
|
| 481 |
|
| 482 |
|
| 483 |
-
# ========================
|
| 484 |
-
|
| 485 |
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
if backward_index > 0 and backward_index <= len(backward_additional_questions):
|
| 490 |
prev_key = backward_additional_questions[backward_index - 1][0]
|
| 491 |
user_profile[prev_key] = message.strip()
|
|
@@ -493,9 +519,9 @@ def predict(message, history):
|
|
| 493 |
if backward_index < len(backward_additional_questions):
|
| 494 |
k, prompt_text = backward_additional_questions[backward_index]
|
| 495 |
backward_index += 1
|
| 496 |
-
return prompt_text # ✅
|
| 497 |
else:
|
| 498 |
-
#
|
| 499 |
strategy = backward_strategy_plan(
|
| 500 |
specific_career=user_profile.get("specific_career"),
|
| 501 |
bg_info=user_profile.get("bg_info")
|
|
@@ -505,37 +531,36 @@ def predict(message, history):
|
|
| 505 |
forward_deep_dive_done = True
|
| 506 |
post_career_detail_asked = True
|
| 507 |
|
| 508 |
-
return strategy + "\n\
|
| 509 |
-
"\n- A
|
| 510 |
-
"\n- B
|
| 511 |
-
"\n- C
|
| 512 |
-
"\
|
| 513 |
-
"\
|
| 514 |
|
| 515 |
-
|
| 516 |
-
# 处理 A/B/C 答疑
|
| 517 |
elif post_career_detail_asked and not post_career_detail_done:
|
| 518 |
user_choice = message.strip().lower()
|
| 519 |
if user_choice in ["不需要", "no", "n"]:
|
| 520 |
post_career_detail_done = True
|
| 521 |
roadmap_offered = True
|
| 522 |
-
return "
|
| 523 |
else:
|
| 524 |
abc_text = answer_abc_questions(
|
| 525 |
selected=user_choice,
|
| 526 |
bg_info=user_profile.get("bg_info", ""),
|
| 527 |
-
wv=user_profile.get("work_value", "
|
| 528 |
-
ps=user_profile.get("personality_summary", "
|
| 529 |
-
dd=user_profile.get("dream_day", "
|
| 530 |
)
|
| 531 |
post_career_detail_done = True
|
| 532 |
roadmap_offered = True
|
| 533 |
-
return abc_text + "\n\
|
| 534 |
|
| 535 |
-
#
|
| 536 |
elif job_chosen and not roadmap_offered and not roadmap_done:
|
| 537 |
roadmap_offered = True
|
| 538 |
-
return "
|
| 539 |
|
| 540 |
elif roadmap_offered and not roadmap_done:
|
| 541 |
ans = message.strip().lower()
|
|
@@ -546,19 +571,19 @@ def predict(message, history):
|
|
| 546 |
else:
|
| 547 |
roadmap_done = True
|
| 548 |
backward_done = True
|
| 549 |
-
return "
|
| 550 |
|
| 551 |
-
# Backward
|
| 552 |
if backward_done:
|
| 553 |
try:
|
| 554 |
api_key = os.environ.get("API_TOKEN")
|
| 555 |
if not api_key:
|
| 556 |
-
return "
|
| 557 |
client = OpenAI(api_key=api_key)
|
| 558 |
sprompt = generate_system_prompt()
|
| 559 |
msgs = [
|
| 560 |
{"role": "system", "content": sprompt},
|
| 561 |
-
{"role": "user", "content": f"
|
| 562 |
]
|
| 563 |
resp = client.chat.completions.create(
|
| 564 |
model=model_default,
|
|
@@ -570,165 +595,165 @@ def predict(message, history):
|
|
| 570 |
)
|
| 571 |
return resp.choices[0].message.content
|
| 572 |
except Exception as e:
|
| 573 |
-
return f"
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
user_profile[prev_key] = message.strip()
|
| 584 |
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
choice = message.strip().lower()
|
| 598 |
-
if choice in ["1","2","3"]:
|
| 599 |
-
idx = int(choice) - 1
|
| 600 |
-
if idx < len(user_profile["recommended_directions"]):
|
| 601 |
-
sel_dir = user_profile["recommended_directions"][idx]
|
| 602 |
-
user_profile["selected_direction"] = sel_dir
|
| 603 |
-
direction_chosen = True
|
| 604 |
-
# 不在这里设置jobs_recommended=True,而是在函数内部设置
|
| 605 |
-
return recommend_3jobs_for_direction(
|
| 606 |
-
direction=sel_dir,
|
| 607 |
-
bg_info=user_profile["bg_info"] or "",
|
| 608 |
-
wv=user_profile["work_value"] or "",
|
| 609 |
-
ps=user_profile["personality_summary"] or "",
|
| 610 |
-
dd=user_profile["dream_day"] or ""
|
| 611 |
-
)
|
| 612 |
-
else:
|
| 613 |
-
return "无效选项,请重新输入1/2/3或'换'"
|
| 614 |
-
elif choice == "换":
|
| 615 |
-
recommendation_round += 1
|
| 616 |
-
if recommendation_round > 2:
|
| 617 |
-
forward_done = True
|
| 618 |
-
return "已多次换方向,结束推荐。"
|
| 619 |
-
else:
|
| 620 |
-
return recommend_3directions()
|
| 621 |
-
else:
|
| 622 |
-
return "请回复1/2/3选择方向,或输入'换'来重新推荐"
|
| 623 |
-
|
| 624 |
-
# 已推荐具体职业 -> 等待用户选择
|
| 625 |
-
elif direction_chosen and jobs_recommended and not job_chosen:
|
| 626 |
choice = message.strip().lower()
|
| 627 |
-
if choice in ["1","2","3"]:
|
| 628 |
idx = int(choice) - 1
|
| 629 |
-
if idx < len(user_profile["
|
| 630 |
-
|
| 631 |
-
user_profile["
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
bg_info=user_profile["bg_info"] or "",
|
| 637 |
wv=user_profile["work_value"] or "",
|
| 638 |
ps=user_profile["personality_summary"] or "",
|
| 639 |
dd=user_profile["dream_day"] or ""
|
| 640 |
)
|
| 641 |
-
forward_deep_dive_done = True
|
| 642 |
-
|
| 643 |
-
# **询问 A/B/C 专项**
|
| 644 |
-
post_career_detail_asked = True
|
| 645 |
-
|
| 646 |
-
return detail_msg + "\n\n我还可以针对以下三方面提供更深入的分析建议:" \
|
| 647 |
-
"\n- A:专业/选课 & 求职方向" \
|
| 648 |
-
"\n- B:本校资源利用建议" \
|
| 649 |
-
"\n- C:实习 & networking 积累" \
|
| 650 |
-
"\n如果你想查看其中一个或多个,请输入 A / B / C / AB / BC / AC / ABC" \
|
| 651 |
-
"\n如果都不需要,请回复'不需要'。"
|
| 652 |
else:
|
| 653 |
-
return "
|
| 654 |
elif choice == "换":
|
| 655 |
recommendation_round += 1
|
| 656 |
if recommendation_round > 2:
|
| 657 |
forward_done = True
|
| 658 |
-
return "
|
| 659 |
else:
|
| 660 |
-
|
| 661 |
-
return recommend_3jobs_for_direction(
|
| 662 |
-
direction=sel_dir,
|
| 663 |
-
bg_info=user_profile["bg_info"] or "",
|
| 664 |
-
wv=user_profile["work_value"] or "",
|
| 665 |
-
ps=user_profile["personality_summary"] or "",
|
| 666 |
-
dd=user_profile["dream_day"] or ""
|
| 667 |
-
)
|
| 668 |
-
else:
|
| 669 |
-
return "请回复1/2/3选择具体职业,或输入'换'来重新推荐"
|
| 670 |
-
|
| 671 |
-
# 处理 A/B/C 答疑
|
| 672 |
-
elif post_career_detail_asked and not post_career_detail_done:
|
| 673 |
-
user_choice = message.strip().lower()
|
| 674 |
-
if user_choice in ["不需要", "no", "n"]:
|
| 675 |
-
post_career_detail_done = True
|
| 676 |
-
roadmap_offered = True
|
| 677 |
-
return "好的,不查看这三个专项。那需要一个时间轴式职业路线图吗?如需,则回复【要】,否则回复【不要】。"
|
| 678 |
else:
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
)
|
| 686 |
-
|
| 687 |
-
roadmap_offered = True
|
| 688 |
-
return abc_text + "\n\n以上是你所选的A/B/C专项信息。需要一个时间轴式职业路线图吗?如需,则回复【要】,否则回复【不要】。"
|
| 689 |
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
roadmap_offered = True
|
| 693 |
-
return "需要一个时间轴式职业路线图吗?如需,则回复【要】,否则回复【不要】。"
|
| 694 |
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
else:
|
| 702 |
-
|
|
|
|
|
|
|
|
|
|
| 703 |
forward_done = True
|
| 704 |
-
return "
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
sprompt = generate_system_prompt()
|
| 714 |
-
msgs = [
|
| 715 |
-
{"role":"system","content":sprompt},
|
| 716 |
-
{"role":"user","content": f"以下是资料: {user_profile}. 若需要更多咨询可再次输入"}
|
| 717 |
-
]
|
| 718 |
-
resp = client.chat.completions.create(
|
| 719 |
-
model=model_default,
|
| 720 |
-
messages=msgs,
|
| 721 |
-
max_tokens=token_default,
|
| 722 |
-
temperature=temp_default,
|
| 723 |
-
top_p=top_p_default,
|
| 724 |
-
stream=False
|
| 725 |
)
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
|
| 730 |
-
# 兜底
|
| 731 |
-
return "信息收集完毕,如还未得到最终回复,请再输入任意文字继续。"
|
| 732 |
|
| 733 |
# ============================ Gradio UI ============================
|
| 734 |
import gradio as gr
|
|
|
|
| 2 |
from openai import OpenAI
|
| 3 |
import os
|
| 4 |
|
| 5 |
+
# ============================ User Information Structure ============================
|
| 6 |
user_profile = {
|
| 7 |
+
"mode": None, # Only set when answering the first question (yes/no)
|
| 8 |
"specific_career": None,
|
| 9 |
"bg_info": None,
|
| 10 |
"work_value": None,
|
|
|
|
| 12 |
"dream_day": None,
|
| 13 |
"forward_direction_choice": None,
|
| 14 |
|
| 15 |
+
# Additional state for Forward mode
|
| 16 |
+
"recommended_directions": [], # 3 general career directions
|
| 17 |
+
"selected_direction": None, # The final general direction selected by the user
|
| 18 |
+
"recommended_jobs": [], # 3 specific job recommendations
|
| 19 |
+
"final_choice": None # The final specific job selected by the user
|
| 20 |
}
|
| 21 |
|
| 22 |
+
# ============================ Base Question ============================
|
| 23 |
base_questions = [
|
| 24 |
+
("mode", "Do you already have a preferred career path?\n- If yes, please reply 'Yes' (Backward mode)\n- If no, please reply 'No' (Forward mode)")
|
| 25 |
]
|
| 26 |
|
| 27 |
+
# ============================ Forward Mode Questions ============================
|
| 28 |
forward_additional_questions = [
|
| 29 |
+
("bg_info", """To get started, please tell me a bit about your academic background and interests. What's your school, year, major, and core courses? If you have a transcript or resume, that would be even better!"""),
|
| 30 |
+
("work_value", """Great! What do you think is the meaning or value of 'work'? What should an ideal job provide for you? (e.g., helping others, high income, free time, personal growth, creative space, etc.)"""),
|
| 31 |
+
("personality_summary", "Briefly describe your personality: Are you introverted or extroverted? Do you enjoy challenges? Detail-oriented? Do you dislike repetitive work?"),
|
| 32 |
+
("dream_day", "Now describe what your ideal workday looks like: Where are you working? What are you doing? Who are you collaborating with? Is it busy or relaxed? More freedom or more structure?")
|
| 33 |
]
|
| 34 |
|
| 35 |
+
# ============================ Backward Mode Questions ============================
|
| 36 |
backward_additional_questions = [
|
| 37 |
+
("specific_career", "Please describe the specific career path you want to pursue."),
|
| 38 |
+
("bg_info", "Please provide your school, year, major, and any internship or project experience you already have, so I can help you build a clearer path to your goal.")
|
| 39 |
]
|
| 40 |
|
| 41 |
+
# ============================ State Control Variables ============================
|
| 42 |
current_q_index = 0
|
| 43 |
questions = base_questions[:]
|
| 44 |
in_forward_flow = False
|
|
|
|
| 53 |
roadmap_offered = False
|
| 54 |
roadmap_done = False
|
| 55 |
|
| 56 |
+
direction_chosen = False # Whether a general direction has been selected
|
| 57 |
+
jobs_recommended = False # Whether 3 specific job options have been recommended
|
| 58 |
+
job_chosen = False # Whether the final job has been selected
|
| 59 |
|
| 60 |
+
# ========== Flags for A/B/C Deep Dive Questions ==========
|
| 61 |
+
post_career_detail_asked = False # Whether the user has been asked about A/B/C sections
|
| 62 |
+
post_career_detail_done = False # Whether A/B/C responses are complete
|
| 63 |
|
| 64 |
+
# ============================ Model Configuration ============================
|
| 65 |
model_default = "gpt-4o"
|
| 66 |
token_default = 2000
|
| 67 |
temp_default = 0.7
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 72 |
+
# Dynamic Function: Generate a personalized career roadmap based on A/B/C analysis
|
| 73 |
def backward_strategy_plan(specific_career, bg_info):
|
| 74 |
prompt = f"""
|
| 75 |
+
You are a professional career planning advisor.
|
| 76 |
+
A student has the target career: {specific_career}
|
| 77 |
+
Their current background is: {bg_info}
|
| 78 |
+
|
| 79 |
+
Please help the student build a clear plan to reach their goal, including:
|
| 80 |
+
1. Should they consider changing majors or taking a minor?
|
| 81 |
+
2. Are there any courses they should take? What are the key course topics?
|
| 82 |
+
3. Recommended certificates/exams (e.g., CFA) and study tips
|
| 83 |
+
4. Suggested internship directions (based on their background)
|
| 84 |
+
5. How to make use of university resources (career center, clubs, networking, etc.)
|
| 85 |
+
6. If their background is mismatched (e.g., mechanical engineering to finance), how to bridge the gap?
|
| 86 |
+
|
| 87 |
+
Use Markdown formatting in sections, with a minimum of 300 words. The content must align closely with the student's current background and goals.
|
| 88 |
"""
|
| 89 |
try:
|
| 90 |
api_key = os.environ.get("API_TOKEN")
|
| 91 |
if not api_key:
|
| 92 |
+
return "Error: API_TOKEN is not set"
|
| 93 |
client = OpenAI(api_key=api_key)
|
| 94 |
msgs = [
|
| 95 |
+
{"role": "system", "content": "You are an experienced career advisor, good at creating roadmaps based on user background."},
|
| 96 |
{"role": "user", "content": prompt}
|
| 97 |
]
|
| 98 |
resp = client.chat.completions.create(
|
|
|
|
| 105 |
)
|
| 106 |
return resp.choices[0].message.content
|
| 107 |
except Exception as e:
|
| 108 |
+
return f"Error generating career roadmap: {str(e)}"
|
| 109 |
+
|
| 110 |
|
| 111 |
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 112 |
+
# Dynamic function: Generate detailed analysis based on selected A/B/C modules
|
| 113 |
def answer_abc_questions(selected, bg_info, wv, ps, dd):
|
| 114 |
desired_parts = []
|
| 115 |
if "a" in selected.lower():
|
|
|
|
| 120 |
desired_parts.append("C")
|
| 121 |
|
| 122 |
if not desired_parts:
|
| 123 |
+
return "Alright, you chose not to view the A/B/C modules for now."
|
| 124 |
|
| 125 |
prompt_text = f"""
|
| 126 |
+
You are a professional career planning advisor. Below is the student's background information. Please provide detailed analysis and suggestions based on the selected module(s): A, B, or C.
|
| 127 |
+
|
| 128 |
+
Student Profile:
|
| 129 |
+
- Academic Background: {bg_info}
|
| 130 |
+
- Work Value: {wv}
|
| 131 |
+
- Personality: {ps}
|
| 132 |
+
- Ideal Workday: {dd}
|
| 133 |
+
|
| 134 |
+
The student wants insights on the following module(s): {', '.join(desired_parts)}
|
| 135 |
+
Please write a separate section for each selected module in order, using Markdown formatting.
|
| 136 |
+
|
| 137 |
+
(A: Course Selection & Career Entry)
|
| 138 |
+
- Analyze the student’s current academic background and what capabilities they already have.
|
| 139 |
+
- Based on their intended career, recommend what types of courses they should focus on.
|
| 140 |
+
- Suggest course names and keywords to guide their course planning.
|
| 141 |
+
|
| 142 |
+
(B: School Resource Utilization)
|
| 143 |
+
- Provide 3 types of suggestions:
|
| 144 |
+
1. How to utilize the university's career services.
|
| 145 |
+
2. How to get involved with career-related clubs, organizations, and platforms.
|
| 146 |
+
3. How to network with alumni in their field and improve networking skills.
|
| 147 |
+
|
| 148 |
+
(C: Internships & Experience Building)
|
| 149 |
+
- Suggest platforms to find internships.
|
| 150 |
+
- Recommend internship directions aligned with the student’s career goal.
|
| 151 |
+
- Provide tips for preparing materials and applying.
|
| 152 |
+
- Strategies for gaining experience through networking, internships, or campus jobs.
|
| 153 |
+
|
| 154 |
+
Only include the modules selected by the student. Do not generate content for unselected modules.
|
| 155 |
"""
|
| 156 |
try:
|
| 157 |
api_key = os.environ.get("API_TOKEN")
|
| 158 |
if not api_key:
|
| 159 |
+
return "Error: API_TOKEN is not set."
|
| 160 |
client = OpenAI(api_key=api_key)
|
| 161 |
msgs = [
|
| 162 |
+
{"role": "system", "content": "You are a professional career advisor who provides module-specific (A/B/C) analysis and suggestions."},
|
| 163 |
{"role": "user", "content": prompt_text}
|
| 164 |
]
|
| 165 |
resp = client.chat.completions.create(
|
|
|
|
| 172 |
)
|
| 173 |
return resp.choices[0].message.content
|
| 174 |
except Exception as e:
|
| 175 |
+
return f"Error generating A/B/C insights: {str(e)}"
|
| 176 |
|
| 177 |
|
| 178 |
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Career Detail Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 179 |
def do_selected_career_detail(selected_career, bg_info, wv, ps, dd):
|
| 180 |
prompt = f"""
|
| 181 |
+
You are a professional career planning advisor.
|
| 182 |
+
|
| 183 |
+
The student has selected the following specific career: {selected_career}
|
| 184 |
+
Student Profile:
|
| 185 |
+
- Academic Background: {bg_info}
|
| 186 |
+
- Work Value: {wv}
|
| 187 |
+
- Personality: {ps}
|
| 188 |
+
- Ideal Workday: {dd}
|
| 189 |
+
|
| 190 |
+
Please write a detailed description of this career including at least the following:
|
| 191 |
+
1. Average salary (entry-level / mid-level / senior)
|
| 192 |
+
2. Work environment (remote/hybrid/in-office, team size, etc.)
|
| 193 |
+
3. Promotion difficulty (what qualifications or milestones are needed)
|
| 194 |
+
4. Key skills and certifications required
|
| 195 |
+
5. Typical work rhythm (fast-paced or routine?)
|
| 196 |
+
6. Industry outlook
|
| 197 |
+
|
| 198 |
+
Please use Markdown formatting in separate sections, with a minimum of 300 words.
|
| 199 |
"""
|
| 200 |
try:
|
| 201 |
api_key = os.environ.get("API_TOKEN")
|
| 202 |
if not api_key:
|
| 203 |
+
return "Error: API_TOKEN is not set."
|
| 204 |
client = OpenAI(api_key=api_key)
|
| 205 |
|
| 206 |
msgs = [
|
| 207 |
+
{"role": "system", "content": "You are a professional career advisor who provides in-depth career descriptions."},
|
| 208 |
{"role": "user", "content": prompt}
|
| 209 |
]
|
| 210 |
resp = client.chat.completions.create(
|
|
|
|
| 217 |
)
|
| 218 |
return resp.choices[0].message.content
|
| 219 |
except Exception as e:
|
| 220 |
+
return f"Error generating detailed career info: {str(e)}"
|
| 221 |
|
| 222 |
|
| 223 |
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Timeline-Based Career Roadmap ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 224 |
def do_time_roadmap():
|
| 225 |
direction = user_profile.get("final_choice") or user_profile.get("selected_direction") or \
|
| 226 |
user_profile.get("forward_direction_choice") or user_profile.get("specific_career")
|
| 227 |
+
bg_info = user_profile["bg_info"] or "Unknown Major"
|
| 228 |
+
wv = user_profile["work_value"] or "Not provided"
|
| 229 |
+
ps = user_profile["personality_summary"] or "Not provided"
|
| 230 |
+
dd = user_profile["dream_day"] or "Not provided"
|
| 231 |
|
| 232 |
prompt_roadmap = f"""
|
| 233 |
+
Student Profile:
|
| 234 |
+
- Academic Background: {bg_info}
|
| 235 |
+
- Career Direction / Specific Role: {direction}
|
| 236 |
+
- Work Values: {wv}
|
| 237 |
+
- Personality: {ps}
|
| 238 |
+
- Ideal Workday: {dd}
|
| 239 |
+
|
| 240 |
+
Please create a timeline-style career roadmap based on the student's background and goal.
|
| 241 |
+
Use the following structure as a reference:
|
| 242 |
+
|
| 243 |
+
📍 Summer after Year 2: [What internship to apply for, what activities to participate in, what certifications to pursue]
|
| 244 |
+
🎓 Year 3: [Recommended courses, key projects or academic goals]
|
| 245 |
+
💼 Summer after Year 3: [Target internships, certifications, personal/professional development plans]
|
| 246 |
+
📄 6–12 Months Before Graduation: [Certifications to complete, job/grad school application materials to prepare]
|
| 247 |
+
🚀 Post-Graduation: [Target roles, how to apply, next steps]
|
| 248 |
+
|
| 249 |
+
Please write the roadmap using Markdown formatting and tailor your suggestions specifically to the student's profile.
|
| 250 |
"""
|
| 251 |
try:
|
| 252 |
api_key = os.environ.get("API_TOKEN")
|
| 253 |
if not api_key:
|
| 254 |
+
return "Error: API_TOKEN is not set."
|
| 255 |
client = OpenAI(api_key=api_key)
|
| 256 |
|
| 257 |
msgs = [
|
| 258 |
+
{"role": "system", "content": "You are a professional career advisor who generates timeline-based career roadmaps."},
|
| 259 |
+
{"role": "user", "content": prompt_roadmap}
|
| 260 |
]
|
| 261 |
resp = client.chat.completions.create(
|
| 262 |
model=model_default,
|
|
|
|
| 268 |
)
|
| 269 |
return resp.choices[0].message.content
|
| 270 |
except Exception as e:
|
| 271 |
+
return f"Error generating career roadmap: {str(e)}"
|
| 272 |
|
| 273 |
|
| 274 |
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Recommend Specific Careers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 275 |
def recommend_3jobs_for_direction(direction, bg_info, wv, ps, dd):
|
| 276 |
prompt = f"""
|
| 277 |
+
You are a professional career advisor.
|
| 278 |
+
The student has selected the following general career direction: {direction}
|
| 279 |
+
Student background: {bg_info}
|
| 280 |
+
Work values: {wv}
|
| 281 |
+
Personality: {ps}
|
| 282 |
+
Ideal workday: {dd}
|
| 283 |
+
|
| 284 |
+
Please recommend 3 specific job roles related to this direction, and provide details for each:
|
| 285 |
+
1) Why this job fits the chosen direction
|
| 286 |
+
2) Typical responsibilities
|
| 287 |
+
3) Required skills and certifications
|
| 288 |
+
4) Future career prospects
|
| 289 |
+
|
| 290 |
+
End your response with this format (do not repeat this format earlier in the message):
|
| 291 |
+
|
| 292 |
+
Please select your ideal job:
|
| 293 |
+
1. [Job Title 1]
|
| 294 |
+
2. [Job Title 2]
|
| 295 |
+
3. [Job Title 3]
|
| 296 |
+
If none of these suit you, enter 'change'.
|
| 297 |
"""
|
| 298 |
try:
|
| 299 |
api_key = os.environ.get("API_TOKEN")
|
| 300 |
if not api_key:
|
| 301 |
+
return "Error: API_TOKEN not set"
|
| 302 |
client = OpenAI(api_key=api_key)
|
| 303 |
|
| 304 |
msgs = [
|
| 305 |
+
{"role": "system", "content": "You are a professional career advisor, recommending 3 specific jobs based on the student's chosen direction."},
|
| 306 |
{"role": "user", "content": prompt}
|
| 307 |
]
|
| 308 |
resp = client.chat.completions.create(
|
|
|
|
| 315 |
)
|
| 316 |
output_text = resp.choices[0].message.content
|
| 317 |
|
| 318 |
+
# Try to extract job titles from the response
|
| 319 |
import re
|
| 320 |
job_pattern = r'(\d+)\.\s+\[?(.*?)\]?(?:\n|$)'
|
| 321 |
job_matches = re.findall(job_pattern, output_text)
|
| 322 |
job_list = [match[1].strip() for match in job_matches]
|
| 323 |
+
|
| 324 |
+
# If parsing fails, provide fallback options
|
| 325 |
if len(job_list) < 3:
|
| 326 |
job_list = [
|
| 327 |
+
f"{direction} - Job A",
|
| 328 |
+
f"{direction} - Job B",
|
| 329 |
+
f"{direction} - Job C"
|
| 330 |
]
|
| 331 |
+
|
| 332 |
+
user_profile["recommended_jobs"] = job_list[:3] # Ensure only the first 3 are used
|
| 333 |
+
|
| 334 |
+
# Mark that jobs have been recommended
|
| 335 |
global jobs_recommended
|
| 336 |
jobs_recommended = True
|
| 337 |
+
|
| 338 |
return output_text
|
| 339 |
except Exception as e:
|
| 340 |
+
return f"Error recommending specific jobs: {str(e)}"
|
| 341 |
|
| 342 |
|
| 343 |
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Recommend General Directions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 344 |
def recommend_3directions():
|
| 345 |
+
bg_info = user_profile["bg_info"] or "Unknown"
|
| 346 |
+
wv = user_profile["work_value"] or "Not provided"
|
| 347 |
+
ps = user_profile["personality_summary"] or "Not provided"
|
| 348 |
+
dd = user_profile["dream_day"] or "Not provided"
|
| 349 |
|
| 350 |
rec_prompt = f"""
|
| 351 |
+
Based on the following information, write a structured student profile and recommend 3 general career directions.
|
| 352 |
+
Address the student as "you" (not he/she).
|
| 353 |
+
|
| 354 |
+
- Academic Background: {bg_info}
|
| 355 |
+
- Work Values: {wv}
|
| 356 |
+
- Personality: {ps}
|
| 357 |
+
- Ideal Workday: {dd}
|
| 358 |
+
|
| 359 |
+
Two parts required:
|
| 360 |
+
|
| 361 |
+
[1. Student Profile]: Briefly summarize their academic background, core values, personality, and career expectations.
|
| 362 |
+
|
| 363 |
+
[2. Recommend 3 General Directions]: For each direction, write 1–2 paragraphs explaining why it's a good fit for the student.
|
| 364 |
+
|
| 365 |
+
End with only the following format (do not repeat it earlier):
|
| 366 |
+
|
| 367 |
+
Please choose your career direction:
|
| 368 |
+
1. [Direction Name 1]
|
| 369 |
+
2. [Direction Name 2]
|
| 370 |
+
3. [Direction Name 3]
|
| 371 |
+
If none are suitable, type 'change'.
|
| 372 |
"""
|
| 373 |
try:
|
| 374 |
api_key = os.environ.get("API_TOKEN")
|
| 375 |
if not api_key:
|
| 376 |
+
return "Error: API_TOKEN not set"
|
| 377 |
client = OpenAI(api_key=api_key)
|
| 378 |
msgs = [
|
| 379 |
+
{"role": "system", "content": "You are a career advisor who recommends 3 general directions based on the user's profile."},
|
| 380 |
+
{"role": "user", "content": rec_prompt}
|
| 381 |
]
|
| 382 |
resp = client.chat.completions.create(
|
| 383 |
model=model_default,
|
|
|
|
| 389 |
)
|
| 390 |
output_text = resp.choices[0].message.content
|
| 391 |
|
| 392 |
+
# Try to extract direction names from the response
|
| 393 |
import re
|
| 394 |
dir_pattern = r'(\d+)\.\s+\[?(.*?)\]?(?:\n|$)'
|
| 395 |
dir_matches = re.findall(dir_pattern, output_text)
|
| 396 |
dir_list = [match[1].strip() for match in dir_matches]
|
| 397 |
+
|
| 398 |
+
# Fallback if parsing fails
|
| 399 |
if len(dir_list) < 3:
|
| 400 |
+
dir_list = ["Direction 1", "Direction 2", "Direction 3"]
|
| 401 |
+
|
| 402 |
+
user_profile["recommended_directions"] = dir_list[:3]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
return output_text
|
| 404 |
except Exception as e:
|
| 405 |
+
return f"Error recommending general directions: {str(e)}"
|
| 406 |
|
| 407 |
|
| 408 |
+
|
| 409 |
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Generate System Prompt ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 410 |
def generate_system_prompt():
|
| 411 |
mode = user_profile["mode"]
|
| 412 |
sc = user_profile["specific_career"]
|
|
|
|
| 416 |
dd = user_profile["dream_day"]
|
| 417 |
fwd = user_profile["forward_direction_choice"]
|
| 418 |
|
| 419 |
+
if mode == "是": # Backward Mode
|
| 420 |
return f"""
|
| 421 |
+
You are a professional career advisor using the Backward Design method to help students achieve their career goals.
|
| 422 |
+
Target career: {sc}
|
| 423 |
+
Background information: {bg}
|
| 424 |
+
Please provide:
|
| 425 |
+
1. A brief analysis of the target career
|
| 426 |
+
2. Top companies or organizations in this field
|
| 427 |
+
3. Required skills and qualifications
|
| 428 |
+
4. Career development path
|
| 429 |
+
5. Academic/course suggestions, skill development, use of resources, internship planning, resume tips, etc.
|
| 430 |
+
|
| 431 |
+
Finally, output a career roadmap in Markdown format like this:
|
| 432 |
+
📍 Now → 🎓 Learning Suggestions → 💼 Practice Suggestions → 📄 Certification Advice → 🚀 Job Search Suggestions
|
| 433 |
"""
|
| 434 |
+
else: # Forward Mode
|
| 435 |
return f"""
|
| 436 |
+
You are a professional career advisor using the Forward Design method to help students explore suitable career paths.
|
| 437 |
+
Student background: {bg}
|
| 438 |
+
Work values: {wv}
|
| 439 |
+
Personality summary: {ps}
|
| 440 |
+
Ideal workday: {dd}
|
| 441 |
+
Student's selected direction: {fwd}
|
| 442 |
+
Please provide:
|
| 443 |
+
1. An analysis of the student’s strengths, personality traits, and values
|
| 444 |
+
2. 3 recommended specific jobs, including daily work content, fit, requirements, and preparation pathway
|
| 445 |
+
3. A final career roadmap:
|
| 446 |
+
📍 Now → 🎓 Learning Suggestions → 💼 Practice Suggestions → 📄 Certification Advice → 🚀 Job Search Suggestions
|
| 447 |
"""
|
| 448 |
|
| 449 |
|
| 450 |
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Main Dialogue Logic Function ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 451 |
def predict(message, history):
|
| 452 |
+
# ========= Declare all global variables at the beginning =========
|
| 453 |
global current_q_index, questions
|
| 454 |
global in_forward_flow, in_backward_flow
|
| 455 |
global forward_index, backward_index
|
|
|
|
| 461 |
global direction_chosen, jobs_recommended, job_chosen
|
| 462 |
global post_career_detail_asked, post_career_detail_done
|
| 463 |
|
| 464 |
+
# ======================== Initialization =========================
|
| 465 |
if not history:
|
| 466 |
current_q_index = 0
|
| 467 |
questions[:] = base_questions
|
|
|
|
| 486 |
post_career_detail_asked = False
|
| 487 |
post_career_detail_done = False
|
| 488 |
|
| 489 |
+
# ======================== First handle base questions =========================
|
| 490 |
if 0 < current_q_index <= len(questions):
|
| 491 |
key = questions[current_q_index - 1][0]
|
|
|
|
| 492 |
|
| 493 |
if key == "mode" and current_q_index == 1:
|
| 494 |
+
ans = message.strip()
|
| 495 |
+
user_profile["mode"] = ans
|
| 496 |
+
questions[:] = [] # Clear base questions
|
| 497 |
+
if "是" in ans:
|
| 498 |
+
in_backward_flow = True
|
| 499 |
+
else:
|
| 500 |
+
in_forward_flow = True
|
|
|
|
| 501 |
|
| 502 |
+
if current_q_index < len(questions) and not in_forward_flow and not in_backward_flow:
|
| 503 |
nxt = questions[current_q_index][1]
|
| 504 |
current_q_index += 1
|
| 505 |
return nxt
|
| 506 |
|
|
|
|
| 507 |
|
| 508 |
|
| 509 |
+
# ======================== Mode-based Flow Control =========================
|
| 510 |
+
mode = user_profile.get("mode") or ""
|
| 511 |
|
| 512 |
+
# ~~~~~~~~~ Backward Mode ~~~~~~~~~
|
| 513 |
+
if mode == "是":
|
| 514 |
+
if in_backward_flow and not backward_done:
|
| 515 |
if backward_index > 0 and backward_index <= len(backward_additional_questions):
|
| 516 |
prev_key = backward_additional_questions[backward_index - 1][0]
|
| 517 |
user_profile[prev_key] = message.strip()
|
|
|
|
| 519 |
if backward_index < len(backward_additional_questions):
|
| 520 |
k, prompt_text = backward_additional_questions[backward_index]
|
| 521 |
backward_index += 1
|
| 522 |
+
return prompt_text # ✅ Correct: properly indented inside the `if` block
|
| 523 |
else:
|
| 524 |
+
# All background info collected, generate career path strategy
|
| 525 |
strategy = backward_strategy_plan(
|
| 526 |
specific_career=user_profile.get("specific_career"),
|
| 527 |
bg_info=user_profile.get("bg_info")
|
|
|
|
| 531 |
forward_deep_dive_done = True
|
| 532 |
post_career_detail_asked = True
|
| 533 |
|
| 534 |
+
return strategy + "\n\nI can also provide in-depth suggestions for the following three aspects:" \
|
| 535 |
+
"\n- A: Major/Course Planning & Job Entry Advice" \
|
| 536 |
+
"\n- B: Making Use of Campus Resources" \
|
| 537 |
+
"\n- C: Internship & Networking Strategy" \
|
| 538 |
+
"\nIf you'd like to view one or more, please type A / B / C / AB / BC / AC / ABC" \
|
| 539 |
+
"\nIf not needed, just reply with 'No'."
|
| 540 |
|
| 541 |
+
# Handle A/B/C deep-dive answers
|
|
|
|
| 542 |
elif post_career_detail_asked and not post_career_detail_done:
|
| 543 |
user_choice = message.strip().lower()
|
| 544 |
if user_choice in ["不需要", "no", "n"]:
|
| 545 |
post_career_detail_done = True
|
| 546 |
roadmap_offered = True
|
| 547 |
+
return "Understood. Would you like a timeline-style career roadmap? Reply 'yes' or 'no'."
|
| 548 |
else:
|
| 549 |
abc_text = answer_abc_questions(
|
| 550 |
selected=user_choice,
|
| 551 |
bg_info=user_profile.get("bg_info", ""),
|
| 552 |
+
wv=user_profile.get("work_value", "Not provided"),
|
| 553 |
+
ps=user_profile.get("personality_summary", "Not provided"),
|
| 554 |
+
dd=user_profile.get("dream_day", "Not provided")
|
| 555 |
)
|
| 556 |
post_career_detail_done = True
|
| 557 |
roadmap_offered = True
|
| 558 |
+
return abc_text + "\n\nAbove is your selected A/B/C section. Would you like a timeline-style career roadmap? Reply 'yes' or 'no'."
|
| 559 |
|
| 560 |
+
# Ask if a roadmap is needed
|
| 561 |
elif job_chosen and not roadmap_offered and not roadmap_done:
|
| 562 |
roadmap_offered = True
|
| 563 |
+
return "Would you like a timeline-style career roadmap? Reply 'yes' or 'no'."
|
| 564 |
|
| 565 |
elif roadmap_offered and not roadmap_done:
|
| 566 |
ans = message.strip().lower()
|
|
|
|
| 571 |
else:
|
| 572 |
roadmap_done = True
|
| 573 |
backward_done = True
|
| 574 |
+
return "Alright, no roadmap generated. This concludes the planning session."
|
| 575 |
|
| 576 |
+
# Final summary for Backward Mode
|
| 577 |
if backward_done:
|
| 578 |
try:
|
| 579 |
api_key = os.environ.get("API_TOKEN")
|
| 580 |
if not api_key:
|
| 581 |
+
return "Error: API_TOKEN is not set"
|
| 582 |
client = OpenAI(api_key=api_key)
|
| 583 |
sprompt = generate_system_prompt()
|
| 584 |
msgs = [
|
| 585 |
{"role": "system", "content": sprompt},
|
| 586 |
+
{"role": "user", "content": f"Here is the user profile: {user_profile}. Feel free to ask more questions if needed."}
|
| 587 |
]
|
| 588 |
resp = client.chat.completions.create(
|
| 589 |
model=model_default,
|
|
|
|
| 595 |
)
|
| 596 |
return resp.choices[0].message.content
|
| 597 |
except Exception as e:
|
| 598 |
+
return f"An error occurred while generating summary: {str(e)}"
|
| 599 |
+
|
| 600 |
+
# ~~~~~~~~~ Forward Mode ~~~~~~~~~
|
| 601 |
+
else:
|
| 602 |
+
# Step 1: Collect user information and recommend 3 career directions
|
| 603 |
+
if in_forward_flow and not direction_chosen:
|
| 604 |
+
# Collect the 4 basic background questions
|
| 605 |
+
if forward_index > 0 and forward_index <= len(forward_additional_questions):
|
| 606 |
+
prev_key = forward_additional_questions[forward_index - 1][0]
|
| 607 |
+
user_profile[prev_key] = message.strip()
|
|
|
|
| 608 |
|
| 609 |
+
if forward_index < len(forward_additional_questions):
|
| 610 |
+
k, prompt_text = forward_additional_questions[forward_index]
|
| 611 |
+
forward_index += 1
|
| 612 |
+
return prompt_text
|
| 613 |
|
| 614 |
+
# After collecting the 4 questions → Recommend 3 directions
|
| 615 |
+
elif not forward_recommendation_given:
|
| 616 |
+
forward_recommendation_given = True
|
| 617 |
+
return recommend_3directions()
|
| 618 |
|
| 619 |
+
# Wait for user to choose 1 / 2 / 3 or ask to switch
|
| 620 |
+
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
choice = message.strip().lower()
|
| 622 |
+
if choice in ["1", "2", "3"]:
|
| 623 |
idx = int(choice) - 1
|
| 624 |
+
if idx < len(user_profile["recommended_directions"]):
|
| 625 |
+
sel_dir = user_profile["recommended_directions"][idx]
|
| 626 |
+
user_profile["selected_direction"] = sel_dir
|
| 627 |
+
direction_chosen = True
|
| 628 |
+
# jobs_recommended is set internally inside the function
|
| 629 |
+
return recommend_3jobs_for_direction(
|
| 630 |
+
direction=sel_dir,
|
| 631 |
bg_info=user_profile["bg_info"] or "",
|
| 632 |
wv=user_profile["work_value"] or "",
|
| 633 |
ps=user_profile["personality_summary"] or "",
|
| 634 |
dd=user_profile["dream_day"] or ""
|
| 635 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 636 |
else:
|
| 637 |
+
return "Invalid option. Please reply with 1/2/3 or 'switch'."
|
| 638 |
elif choice == "换":
|
| 639 |
recommendation_round += 1
|
| 640 |
if recommendation_round > 2:
|
| 641 |
forward_done = True
|
| 642 |
+
return "You've switched too many times. Ending recommendation."
|
| 643 |
else:
|
| 644 |
+
return recommend_3directions()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
else:
|
| 646 |
+
return "Please reply with 1/2/3 to select a direction, or 'switch' to change options."
|
| 647 |
+
|
| 648 |
+
# Step 2: Recommend 3 specific job roles and wait for user to choose
|
| 649 |
+
elif direction_chosen and jobs_recommended and not job_chosen:
|
| 650 |
+
choice = message.strip().lower()
|
| 651 |
+
if choice in ["1", "2", "3"]:
|
| 652 |
+
idx = int(choice) - 1
|
| 653 |
+
if idx < len(user_profile["recommended_jobs"]):
|
| 654 |
+
final_job = user_profile["recommended_jobs"][idx]
|
| 655 |
+
user_profile["final_choice"] = final_job
|
| 656 |
+
job_chosen = True
|
| 657 |
+
|
| 658 |
+
detail_msg = do_selected_career_detail(
|
| 659 |
+
selected_career=final_job,
|
| 660 |
+
bg_info=user_profile["bg_info"] or "",
|
| 661 |
+
wv=user_profile["work_value"] or "",
|
| 662 |
+
ps=user_profile["personality_summary"] or "",
|
| 663 |
+
dd=user_profile["dream_day"] or ""
|
| 664 |
)
|
| 665 |
+
forward_deep_dive_done = True
|
|
|
|
|
|
|
| 666 |
|
| 667 |
+
# Ask if user wants A/B/C section suggestions
|
| 668 |
+
post_career_detail_asked = True
|
|
|
|
|
|
|
| 669 |
|
| 670 |
+
return detail_msg + "\n\nI can also provide in-depth suggestions for the following 3 areas:" \
|
| 671 |
+
"\n- A: Major/Course Planning & Job Entry Advice" \
|
| 672 |
+
"\n- B: Making Use of Campus Resources" \
|
| 673 |
+
"\n- C: Internship & Networking Strategy" \
|
| 674 |
+
"\nIf you'd like to view one or more, please type A / B / C / AB / BC / AC / ABC" \
|
| 675 |
+
"\nIf not needed, just reply with 'no'."
|
| 676 |
else:
|
| 677 |
+
return "Invalid option. Please reply with 1/2/3 or 'switch'."
|
| 678 |
+
elif choice == "换":
|
| 679 |
+
recommendation_round += 1
|
| 680 |
+
if recommendation_round > 2:
|
| 681 |
forward_done = True
|
| 682 |
+
return "You've switched too many times. Ending recommendation."
|
| 683 |
+
else:
|
| 684 |
+
sel_dir = user_profile.get("selected_direction") or "Unknown Direction"
|
| 685 |
+
return recommend_3jobs_for_direction(
|
| 686 |
+
direction=sel_dir,
|
| 687 |
+
bg_info=user_profile["bg_info"] or "",
|
| 688 |
+
wv=user_profile["work_value"] or "",
|
| 689 |
+
ps=user_profile["personality_summary"] or "",
|
| 690 |
+
dd=user_profile["dream_day"] or ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 691 |
)
|
| 692 |
+
else:
|
| 693 |
+
return "Please reply with 1/2/3 to choose a specific role, or 'switch' to get new ones."
|
| 694 |
+
|
| 695 |
+
# Step 3: Answer user-selected A/B/C questions
|
| 696 |
+
elif post_career_detail_asked and not post_career_detail_done:
|
| 697 |
+
user_choice = message.strip().lower()
|
| 698 |
+
if user_choice in ["不需要", "no", "n"]:
|
| 699 |
+
post_career_detail_done = True
|
| 700 |
+
roadmap_offered = True
|
| 701 |
+
return "Okay, skipping the three detailed sections. Would you like a timeline-style career roadmap? Reply 'yes' or 'no'."
|
| 702 |
+
else:
|
| 703 |
+
abc_text = answer_abc_questions(
|
| 704 |
+
selected=user_choice,
|
| 705 |
+
bg_info=user_profile["bg_info"],
|
| 706 |
+
wv=user_profile["work_value"],
|
| 707 |
+
ps=user_profile["personality_summary"],
|
| 708 |
+
dd=user_profile["dream_day"]
|
| 709 |
+
)
|
| 710 |
+
post_career_detail_done = True
|
| 711 |
+
roadmap_offered = True
|
| 712 |
+
return abc_text + "\n\nAbove is the A/B/C section you selected. Would you like a timeline-style career roadmap? Reply 'yes' or 'no'."
|
| 713 |
+
|
| 714 |
+
# Step 4: Ask if user wants a timeline roadmap
|
| 715 |
+
elif job_chosen and not roadmap_offered and not roadmap_done:
|
| 716 |
+
roadmap_offered = True
|
| 717 |
+
return "Would you like a timeline-style career roadmap? Reply 'yes' or 'no'."
|
| 718 |
+
|
| 719 |
+
elif roadmap_offered and not roadmap_done:
|
| 720 |
+
ans = message.strip().lower()
|
| 721 |
+
if ans in ["要", "yes", "y"]:
|
| 722 |
+
roadmap_done = True
|
| 723 |
+
forward_done = True
|
| 724 |
+
return do_time_roadmap()
|
| 725 |
+
else:
|
| 726 |
+
roadmap_done = True
|
| 727 |
+
forward_done = True
|
| 728 |
+
return "Okay, no roadmap generated. This concludes the session."
|
| 729 |
+
|
| 730 |
+
# Step 5: Final wrap-up after Forward mode finishes
|
| 731 |
+
if forward_done:
|
| 732 |
+
try:
|
| 733 |
+
api_key = os.environ.get("API_TOKEN")
|
| 734 |
+
if not api_key:
|
| 735 |
+
return "Error: API_TOKEN is not set"
|
| 736 |
+
client = OpenAI(api_key=api_key)
|
| 737 |
+
sprompt = generate_system_prompt()
|
| 738 |
+
msgs = [
|
| 739 |
+
{"role": "system", "content": sprompt},
|
| 740 |
+
{"role": "user", "content": f"Here is the user profile: {user_profile}. Feel free to ask more questions if needed."}
|
| 741 |
+
]
|
| 742 |
+
resp = client.chat.completions.create(
|
| 743 |
+
model=model_default,
|
| 744 |
+
messages=msgs,
|
| 745 |
+
max_tokens=token_default,
|
| 746 |
+
temperature=temp_default,
|
| 747 |
+
top_p=top_p_default,
|
| 748 |
+
stream=False
|
| 749 |
+
)
|
| 750 |
+
return resp.choices[0].message.content
|
| 751 |
+
except Exception as e:
|
| 752 |
+
return f"An error occurred: {str(e)}"
|
| 753 |
+
|
| 754 |
+
# Fallback response
|
| 755 |
+
return "Information collection is complete. If you haven’t received a final reply, send any message to continue."
|
| 756 |
|
|
|
|
|
|
|
| 757 |
|
| 758 |
# ============================ Gradio UI ============================
|
| 759 |
import gradio as gr
|