Spaces:
Runtime error
Runtime error
feat: implement chatbot engine and prompt builder modules for graduation project recommendation and validation
Browse files
src/recommendation_engine/__pycache__/chatbot_engine.cpython-313.pyc
CHANGED
|
Binary files a/src/recommendation_engine/__pycache__/chatbot_engine.cpython-313.pyc and b/src/recommendation_engine/__pycache__/chatbot_engine.cpython-313.pyc differ
|
|
|
src/recommendation_engine/__pycache__/prompt_builder.cpython-313.pyc
CHANGED
|
Binary files a/src/recommendation_engine/__pycache__/prompt_builder.cpython-313.pyc and b/src/recommendation_engine/__pycache__/prompt_builder.cpython-313.pyc differ
|
|
|
src/recommendation_engine/chatbot_engine.py
CHANGED
|
@@ -119,31 +119,32 @@ Do not include any other text or punctuation.
|
|
| 119 |
except Exception as e:
|
| 120 |
return "general_chat"
|
| 121 |
|
| 122 |
-
def
|
| 123 |
# 1. Quick local validation for standard domains
|
| 124 |
extracted = extract_domain(domain)
|
| 125 |
if extracted and extracted.lower() != "others":
|
| 126 |
-
return
|
| 127 |
|
| 128 |
# 2. Fall back to LLM validation
|
| 129 |
prompt = f"""
|
| 130 |
Determine if the following domain/field is a valid academic, engineering, scientific, or technology domain suitable for a university graduation project (e.g., Computer Science, Engineering, Medicine, Business, Agriculture, Biology, etc.).
|
|
|
|
| 131 |
|
| 132 |
Domain to evaluate: "{domain}"
|
| 133 |
|
| 134 |
Rules:
|
| 135 |
-
- If it is a valid field of study, technology, or academic discipline (e.g., "artificial intelligence", "robotics", "bioinformatics", "educational games")
|
| 136 |
-
- If it is unrelated to academic/technology graduation projects, or contains names of celebrities, sports teams, food, pop culture, or random questions (e.g., "messi", "fc barcelona", "pizza", "what is this")
|
| 137 |
|
| 138 |
-
Return ONLY
|
| 139 |
"""
|
| 140 |
try:
|
| 141 |
-
res = generate_text(prompt, task="intent").strip()
|
| 142 |
-
if not res:
|
| 143 |
-
return
|
| 144 |
-
return "
|
| 145 |
except Exception:
|
| 146 |
-
return
|
| 147 |
|
| 148 |
def is_weak_project_title(title: str) -> bool:
|
| 149 |
|
|
@@ -532,10 +533,16 @@ def chatbot(user_id: str, user_input: str):
|
|
| 532 |
"ideas"
|
| 533 |
]
|
| 534 |
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
if not current_state.get("waiting_for_niche_domain"):
|
| 540 |
state = default_state()
|
| 541 |
state["waiting_for_domain"] = True
|
|
@@ -715,6 +722,11 @@ def chatbot(user_id: str, user_input: str):
|
|
| 715 |
state["waiting_for_project_idea_confirm"] = False
|
| 716 |
|
| 717 |
if is_yes:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
title = state.get("project_title", "")
|
| 719 |
abstract = state.get("abstract", "")
|
| 720 |
|
|
@@ -734,11 +746,9 @@ def chatbot(user_id: str, user_input: str):
|
|
| 734 |
f"π Title: {title}\n"
|
| 735 |
f"{domain_line}\n\n"
|
| 736 |
f"What would you like to do next?\n"
|
| 737 |
-
f"1οΈβ£ Generate features\n"
|
| 738 |
-
f"2οΈβ£ Talk with chatbot about the idea\n\n"
|
| 739 |
f"π You can also say:\n"
|
| 740 |
-
f"- generate features
|
| 741 |
-
f"- discuss project"
|
| 742 |
)
|
| 743 |
else:
|
| 744 |
reason = validation.get("reason", "")
|
|
@@ -814,11 +824,9 @@ def chatbot(user_id: str, user_input: str):
|
|
| 814 |
f"{project_title}\n\n"
|
| 815 |
"Choose what you want next:\n"
|
| 816 |
"1οΈβ£ Generate features\n"
|
| 817 |
-
"2οΈβ£
|
| 818 |
-
"3οΈβ£ Generate full project\n\n"
|
| 819 |
"π You can also say:\n"
|
| 820 |
"- generate features\n"
|
| 821 |
-
"- discuss project\n"
|
| 822 |
"- generate full project"
|
| 823 |
)
|
| 824 |
|
|
@@ -947,6 +955,7 @@ def chatbot(user_id: str, user_input: str):
|
|
| 947 |
state["category"] = state.get("domain")
|
| 948 |
|
| 949 |
state["waiting_for_project_idea_confirm"] = True
|
|
|
|
| 950 |
|
| 951 |
response = f"""
|
| 952 |
π¦ Full Project Generated
|
|
@@ -1098,7 +1107,6 @@ Reply **yes** to validate & categorize it, or **no** to explore other ideas.
|
|
| 1098 |
]
|
| 1099 |
|
| 1100 |
chat_words = [
|
| 1101 |
-
"2",
|
| 1102 |
"chat",
|
| 1103 |
"talk",
|
| 1104 |
"discussion",
|
|
@@ -1109,7 +1117,7 @@ Reply **yes** to validate & categorize it, or **no** to explore other ideas.
|
|
| 1109 |
]
|
| 1110 |
|
| 1111 |
full_project_words = [
|
| 1112 |
-
"
|
| 1113 |
"full project",
|
| 1114 |
"generate full project",
|
| 1115 |
"complete project",
|
|
@@ -1235,6 +1243,7 @@ Reply **yes** to validate & categorize it, or **no** to explore other ideas.
|
|
| 1235 |
state["category"] = state.get("domain")
|
| 1236 |
|
| 1237 |
state["waiting_for_project_idea_confirm"] = True
|
|
|
|
| 1238 |
|
| 1239 |
response = f"""
|
| 1240 |
π¦ Full Project Generated
|
|
@@ -1546,12 +1555,10 @@ Reply **yes** to validate & categorize it, or **no** to explore other ideas.
|
|
| 1546 |
Choose what you want:
|
| 1547 |
|
| 1548 |
1οΈβ£ Generate Features
|
| 1549 |
-
2οΈβ£
|
| 1550 |
-
3οΈβ£ Generate Full Project
|
| 1551 |
|
| 1552 |
π You can also type:
|
| 1553 |
- features
|
| 1554 |
-
- discuss
|
| 1555 |
- full project
|
| 1556 |
""",
|
| 1557 |
|
|
@@ -1575,11 +1582,9 @@ Choose what you want:
|
|
| 1575 |
f"π Project selected:\n"
|
| 1576 |
f"{idea}\n\n"
|
| 1577 |
"Choose what you want next:\n"
|
| 1578 |
-
"1οΈβ£ Generate features\n"
|
| 1579 |
-
"2οΈβ£ Talk with chatbot about the idea\n\n"
|
| 1580 |
"π You can also say:\n"
|
| 1581 |
-
"- generate features
|
| 1582 |
-
"- discuss project"
|
| 1583 |
)
|
| 1584 |
|
| 1585 |
|
|
@@ -1631,14 +1636,15 @@ Choose what you want:
|
|
| 1631 |
return finalize_response(user_input, response, history, state, user_id)
|
| 1632 |
else:
|
| 1633 |
domain = user_input.strip()
|
| 1634 |
-
|
|
|
|
| 1635 |
return (
|
| 1636 |
"β οΈ That does not seem to be a valid technology or academic domain.\n\n"
|
| 1637 |
"Please enter a valid domain (e.g. AI, Healthcare, Fintech, or 'Others')."
|
| 1638 |
)
|
| 1639 |
state["waiting_for_niche_domain"] = False
|
| 1640 |
state["suggested_domains"] = []
|
| 1641 |
-
analysis["domain"] =
|
| 1642 |
analysis["intent"] = "idea"
|
| 1643 |
if "project_title" in analysis:
|
| 1644 |
del analysis["project_title"]
|
|
@@ -1680,10 +1686,18 @@ Choose what you want:
|
|
| 1680 |
if "project_title" in analysis:
|
| 1681 |
del analysis["project_title"]
|
| 1682 |
else:
|
| 1683 |
-
|
| 1684 |
-
|
| 1685 |
-
"
|
| 1686 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1687 |
|
| 1688 |
intent = analysis.get("intent", "chat")
|
| 1689 |
|
|
@@ -1691,6 +1705,12 @@ Choose what you want:
|
|
| 1691 |
|
| 1692 |
user_project = analysis.get("project_title")
|
| 1693 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1694 |
# Override intent using the LLM-derived semantic context when no project is active yet
|
| 1695 |
if not state.get("project_title") and not is_in_domain_or_selection_state:
|
| 1696 |
if context == "idea_request":
|
|
@@ -1894,11 +1914,9 @@ Choose what you want:
|
|
| 1894 |
f"{user_project}\n\n"
|
| 1895 |
"Choose what you want next:\n"
|
| 1896 |
"1οΈβ£ Generate features\n"
|
| 1897 |
-
"2οΈβ£
|
| 1898 |
-
"3οΈβ£ Generate full project\n\n"
|
| 1899 |
"π You can also say:\n"
|
| 1900 |
"- generate features\n"
|
| 1901 |
-
"- discuss project\n"
|
| 1902 |
"- generate full project"
|
| 1903 |
)
|
| 1904 |
|
|
@@ -2106,6 +2124,7 @@ Choose what you want:
|
|
| 2106 |
state["category"] = state.get("domain")
|
| 2107 |
|
| 2108 |
state["waiting_for_project_idea_confirm"] = True
|
|
|
|
| 2109 |
|
| 2110 |
response = f"""
|
| 2111 |
π¦ Project Specification Ready!
|
|
@@ -2206,7 +2225,7 @@ User:
|
|
| 2206 |
|
| 2207 |
|
| 2208 |
|
| 2209 |
-
if not is_project_related(text):
|
| 2210 |
|
| 2211 |
return (
|
| 2212 |
"I'm designed specifically for graduation projects π\n\n"
|
|
|
|
| 119 |
except Exception as e:
|
| 120 |
return "general_chat"
|
| 121 |
|
| 122 |
+
def validate_and_format_domain(domain: str) -> str:
|
| 123 |
# 1. Quick local validation for standard domains
|
| 124 |
extracted = extract_domain(domain)
|
| 125 |
if extracted and extracted.lower() != "others":
|
| 126 |
+
return extracted
|
| 127 |
|
| 128 |
# 2. Fall back to LLM validation
|
| 129 |
prompt = f"""
|
| 130 |
Determine if the following domain/field is a valid academic, engineering, scientific, or technology domain suitable for a university graduation project (e.g., Computer Science, Engineering, Medicine, Business, Agriculture, Biology, etc.).
|
| 131 |
+
Also, correct any typos and format it cleanly (e.g., Title Case).
|
| 132 |
|
| 133 |
Domain to evaluate: "{domain}"
|
| 134 |
|
| 135 |
Rules:
|
| 136 |
+
- If it is a valid field of study, technology, or academic discipline (e.g., "artificial intelligence", "robotics", "bioinformatics", "educational games"), return ONLY the corrected and formatted domain name (e.g., "Artificial Intelligence").
|
| 137 |
+
- If it is unrelated to academic/technology graduation projects, or contains names of celebrities, sports teams, food, pop culture, or random questions (e.g., "messi", "fc barcelona", "pizza", "what is this"), return exactly "INVALID".
|
| 138 |
|
| 139 |
+
Return ONLY the formatted domain name or "INVALID". Do not include any other text.
|
| 140 |
"""
|
| 141 |
try:
|
| 142 |
+
res = generate_text(prompt, task="intent").strip()
|
| 143 |
+
if not res or res.upper() == "INVALID":
|
| 144 |
+
return ""
|
| 145 |
+
return res.strip('"').strip("'")
|
| 146 |
except Exception:
|
| 147 |
+
return ""
|
| 148 |
|
| 149 |
def is_weak_project_title(title: str) -> bool:
|
| 150 |
|
|
|
|
| 533 |
"ideas"
|
| 534 |
]
|
| 535 |
|
| 536 |
+
memory = get_user_memory(user_id)
|
| 537 |
+
current_state = memory.get("state") or default_state()
|
| 538 |
+
|
| 539 |
+
is_explicit_idea = False
|
| 540 |
+
if text in explicit_idea_requests or text in ["change domain", "new domain", "reset domain", "reset", "restart"]:
|
| 541 |
+
is_explicit_idea = True
|
| 542 |
+
elif any(x in text for x in explicit_idea_requests) and len(text.split()) <= 3 and not current_state.get("project_chat_mode"):
|
| 543 |
+
is_explicit_idea = True
|
| 544 |
+
|
| 545 |
+
if is_explicit_idea:
|
| 546 |
if not current_state.get("waiting_for_niche_domain"):
|
| 547 |
state = default_state()
|
| 548 |
state["waiting_for_domain"] = True
|
|
|
|
| 722 |
state["waiting_for_project_idea_confirm"] = False
|
| 723 |
|
| 724 |
if is_yes:
|
| 725 |
+
if state.get("from_full_project_generation"):
|
| 726 |
+
state["from_full_project_generation"] = False
|
| 727 |
+
save_user_memory(user_id, {"history": history, "state": state})
|
| 728 |
+
return "Do you want to add this to Submission?"
|
| 729 |
+
|
| 730 |
title = state.get("project_title", "")
|
| 731 |
abstract = state.get("abstract", "")
|
| 732 |
|
|
|
|
| 746 |
f"π Title: {title}\n"
|
| 747 |
f"{domain_line}\n\n"
|
| 748 |
f"What would you like to do next?\n"
|
| 749 |
+
f"1οΈβ£ Generate features\n\n"
|
|
|
|
| 750 |
f"π You can also say:\n"
|
| 751 |
+
f"- generate features"
|
|
|
|
| 752 |
)
|
| 753 |
else:
|
| 754 |
reason = validation.get("reason", "")
|
|
|
|
| 824 |
f"{project_title}\n\n"
|
| 825 |
"Choose what you want next:\n"
|
| 826 |
"1οΈβ£ Generate features\n"
|
| 827 |
+
"2οΈβ£ Generate full project\n\n"
|
|
|
|
| 828 |
"π You can also say:\n"
|
| 829 |
"- generate features\n"
|
|
|
|
| 830 |
"- generate full project"
|
| 831 |
)
|
| 832 |
|
|
|
|
| 955 |
state["category"] = state.get("domain")
|
| 956 |
|
| 957 |
state["waiting_for_project_idea_confirm"] = True
|
| 958 |
+
state["from_full_project_generation"] = True
|
| 959 |
|
| 960 |
response = f"""
|
| 961 |
π¦ Full Project Generated
|
|
|
|
| 1107 |
]
|
| 1108 |
|
| 1109 |
chat_words = [
|
|
|
|
| 1110 |
"chat",
|
| 1111 |
"talk",
|
| 1112 |
"discussion",
|
|
|
|
| 1117 |
]
|
| 1118 |
|
| 1119 |
full_project_words = [
|
| 1120 |
+
"2",
|
| 1121 |
"full project",
|
| 1122 |
"generate full project",
|
| 1123 |
"complete project",
|
|
|
|
| 1243 |
state["category"] = state.get("domain")
|
| 1244 |
|
| 1245 |
state["waiting_for_project_idea_confirm"] = True
|
| 1246 |
+
state["from_full_project_generation"] = True
|
| 1247 |
|
| 1248 |
response = f"""
|
| 1249 |
π¦ Full Project Generated
|
|
|
|
| 1555 |
Choose what you want:
|
| 1556 |
|
| 1557 |
1οΈβ£ Generate Features
|
| 1558 |
+
2οΈβ£ Generate Full Project
|
|
|
|
| 1559 |
|
| 1560 |
π You can also type:
|
| 1561 |
- features
|
|
|
|
| 1562 |
- full project
|
| 1563 |
""",
|
| 1564 |
|
|
|
|
| 1582 |
f"π Project selected:\n"
|
| 1583 |
f"{idea}\n\n"
|
| 1584 |
"Choose what you want next:\n"
|
| 1585 |
+
"1οΈβ£ Generate features\n\n"
|
|
|
|
| 1586 |
"π You can also say:\n"
|
| 1587 |
+
"- generate features"
|
|
|
|
| 1588 |
)
|
| 1589 |
|
| 1590 |
|
|
|
|
| 1636 |
return finalize_response(user_input, response, history, state, user_id)
|
| 1637 |
else:
|
| 1638 |
domain = user_input.strip()
|
| 1639 |
+
validated_domain = validate_and_format_domain(domain)
|
| 1640 |
+
if not validated_domain:
|
| 1641 |
return (
|
| 1642 |
"β οΈ That does not seem to be a valid technology or academic domain.\n\n"
|
| 1643 |
"Please enter a valid domain (e.g. AI, Healthcare, Fintech, or 'Others')."
|
| 1644 |
)
|
| 1645 |
state["waiting_for_niche_domain"] = False
|
| 1646 |
state["suggested_domains"] = []
|
| 1647 |
+
analysis["domain"] = validated_domain
|
| 1648 |
analysis["intent"] = "idea"
|
| 1649 |
if "project_title" in analysis:
|
| 1650 |
del analysis["project_title"]
|
|
|
|
| 1686 |
if "project_title" in analysis:
|
| 1687 |
del analysis["project_title"]
|
| 1688 |
else:
|
| 1689 |
+
validated_domain = validate_and_format_domain(user_input.strip())
|
| 1690 |
+
if validated_domain:
|
| 1691 |
+
state["waiting_for_domain"] = False
|
| 1692 |
+
analysis["domain"] = validated_domain
|
| 1693 |
+
analysis["intent"] = "idea"
|
| 1694 |
+
if "project_title" in analysis:
|
| 1695 |
+
del analysis["project_title"]
|
| 1696 |
+
else:
|
| 1697 |
+
return (
|
| 1698 |
+
"β οΈ Please enter a valid domain "
|
| 1699 |
+
"(AI, healthcare, fintech...)"
|
| 1700 |
+
)
|
| 1701 |
|
| 1702 |
intent = analysis.get("intent", "chat")
|
| 1703 |
|
|
|
|
| 1705 |
|
| 1706 |
user_project = analysis.get("project_title")
|
| 1707 |
|
| 1708 |
+
if state.get("project_chat_mode"):
|
| 1709 |
+
user_project = None
|
| 1710 |
+
exact_cmds = {"idea", "ideas", "menu", "exit", "quit", "generate features", "features", "generate full project"}
|
| 1711 |
+
if user_input.strip().lower() not in exact_cmds:
|
| 1712 |
+
intent = "chat"
|
| 1713 |
+
|
| 1714 |
# Override intent using the LLM-derived semantic context when no project is active yet
|
| 1715 |
if not state.get("project_title") and not is_in_domain_or_selection_state:
|
| 1716 |
if context == "idea_request":
|
|
|
|
| 1914 |
f"{user_project}\n\n"
|
| 1915 |
"Choose what you want next:\n"
|
| 1916 |
"1οΈβ£ Generate features\n"
|
| 1917 |
+
"2οΈβ£ Generate full project\n\n"
|
|
|
|
| 1918 |
"π You can also say:\n"
|
| 1919 |
"- generate features\n"
|
|
|
|
| 1920 |
"- generate full project"
|
| 1921 |
)
|
| 1922 |
|
|
|
|
| 2124 |
state["category"] = state.get("domain")
|
| 2125 |
|
| 2126 |
state["waiting_for_project_idea_confirm"] = True
|
| 2127 |
+
state["from_full_project_generation"] = True
|
| 2128 |
|
| 2129 |
response = f"""
|
| 2130 |
π¦ Project Specification Ready!
|
|
|
|
| 2225 |
|
| 2226 |
|
| 2227 |
|
| 2228 |
+
if not is_project_related(text) and not state.get("project_title"):
|
| 2229 |
|
| 2230 |
return (
|
| 2231 |
"I'm designed specifically for graduation projects π\n\n"
|
src/recommendation_engine/prompt_builder.py
CHANGED
|
@@ -243,6 +243,7 @@ RULES:
|
|
| 243 |
- Be realistic
|
| 244 |
- Be technically accurate
|
| 245 |
- Keep all answers related to the current project
|
|
|
|
| 246 |
""".strip()
|
| 247 |
|
| 248 |
def build_full_project_prompt(context):
|
|
|
|
| 243 |
- Be realistic
|
| 244 |
- Be technically accurate
|
| 245 |
- Keep all answers related to the current project
|
| 246 |
+
- STRICTLY REFUSE to discuss any illegal, inappropriate, explicit (e.g., sexual content), or unethical topics. If the user mentions such topics, reply immediately with a professional warning that you can only discuss academic graduation projects.
|
| 247 |
""".strip()
|
| 248 |
|
| 249 |
def build_full_project_prompt(context):
|