Spaces:
Runtime error
Runtime error
feat: implement chatbot engine for project validation, classification, and management
Browse files- src/recommendation_engine/__pycache__/chatbot_engine.cpython-313.pyc +0 -0
- src/recommendation_engine/__pycache__/full_project_generator.cpython-313.pyc +0 -0
- src/recommendation_engine/__pycache__/llm_router.cpython-313.pyc +0 -0
- src/recommendation_engine/chatbot_engine.py +37 -3
- src/recommendation_engine/full_project_generator.py +5 -3
- src/recommendation_engine/llm_router.py +16 -2
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__/full_project_generator.cpython-313.pyc
CHANGED
|
Binary files a/src/recommendation_engine/__pycache__/full_project_generator.cpython-313.pyc and b/src/recommendation_engine/__pycache__/full_project_generator.cpython-313.pyc differ
|
|
|
src/recommendation_engine/__pycache__/llm_router.cpython-313.pyc
CHANGED
|
Binary files a/src/recommendation_engine/__pycache__/llm_router.cpython-313.pyc and b/src/recommendation_engine/__pycache__/llm_router.cpython-313.pyc differ
|
|
|
src/recommendation_engine/chatbot_engine.py
CHANGED
|
@@ -506,6 +506,8 @@ def chatbot(user_id: str, user_input: str):
|
|
| 506 |
domain = analysis.get("domain")
|
| 507 |
project_title = analysis.get("project_title")
|
| 508 |
number = analysis.get("number")
|
|
|
|
|
|
|
| 509 |
|
| 510 |
if action == "reply_directly":
|
| 511 |
if project_title and not state.get("project_title"):
|
|
@@ -513,11 +515,25 @@ def chatbot(user_id: str, user_input: str):
|
|
| 513 |
if domain and not state.get("domain"):
|
| 514 |
state["domain"] = domain
|
| 515 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
save_user_memory(user_id, {"history": history, "state": state})
|
| 517 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
return finalize_response(
|
| 519 |
user_input,
|
| 520 |
-
|
| 521 |
history,
|
| 522 |
state,
|
| 523 |
user_id
|
|
@@ -529,6 +545,8 @@ def chatbot(user_id: str, user_input: str):
|
|
| 529 |
if domain_lower in ["other", "others", "general", "any"]:
|
| 530 |
state["domain"] = "general"
|
| 531 |
state["waiting_for_domain"] = False
|
|
|
|
|
|
|
| 532 |
else:
|
| 533 |
state["domain"] = domain
|
| 534 |
state["waiting_for_domain"] = False
|
|
@@ -629,11 +647,16 @@ def chatbot(user_id: str, user_input: str):
|
|
| 629 |
)
|
| 630 |
state["features"] = feature_result.get("recommended_features", [])
|
| 631 |
|
|
|
|
|
|
|
|
|
|
| 632 |
result = generate_full_project(
|
| 633 |
title=state.get("project_title"),
|
| 634 |
features=state.get("features", []),
|
| 635 |
-
description=state.get("description", ""),
|
| 636 |
-
abstract=state.get("abstract", "")
|
|
|
|
|
|
|
| 637 |
)
|
| 638 |
|
| 639 |
state = update_state(state, result, mode="merge")
|
|
@@ -684,5 +707,16 @@ def chatbot(user_id: str, user_input: str):
|
|
| 684 |
save_user_memory(user_id, {"history": history, "state": state})
|
| 685 |
return finalize_response(user_input, "No problem! Let's try something else.", history, state, user_id)
|
| 686 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 687 |
else:
|
| 688 |
return finalize_response(user_input, "I am not sure how to handle that.", history, state, user_id)
|
|
|
|
| 506 |
domain = analysis.get("domain")
|
| 507 |
project_title = analysis.get("project_title")
|
| 508 |
number = analysis.get("number")
|
| 509 |
+
abstract = analysis.get("abstract")
|
| 510 |
+
description = analysis.get("description")
|
| 511 |
|
| 512 |
if action == "reply_directly":
|
| 513 |
if project_title and not state.get("project_title"):
|
|
|
|
| 515 |
if domain and not state.get("domain"):
|
| 516 |
state["domain"] = domain
|
| 517 |
|
| 518 |
+
custom_saved = False
|
| 519 |
+
if abstract:
|
| 520 |
+
state["abstract"] = abstract
|
| 521 |
+
state["custom_abstract"] = True
|
| 522 |
+
custom_saved = True
|
| 523 |
+
if description:
|
| 524 |
+
state["description"] = description
|
| 525 |
+
state["custom_description"] = True
|
| 526 |
+
custom_saved = True
|
| 527 |
+
|
| 528 |
save_user_memory(user_id, {"history": history, "state": state})
|
| 529 |
|
| 530 |
+
final_reply = reply_text or "I didn't quite catch that. Can you clarify?"
|
| 531 |
+
if custom_saved:
|
| 532 |
+
final_reply = "✅ I have saved your custom project details!\n\n" + final_reply
|
| 533 |
+
|
| 534 |
return finalize_response(
|
| 535 |
user_input,
|
| 536 |
+
final_reply,
|
| 537 |
history,
|
| 538 |
state,
|
| 539 |
user_id
|
|
|
|
| 545 |
if domain_lower in ["other", "others", "general", "any"]:
|
| 546 |
state["domain"] = "general"
|
| 547 |
state["waiting_for_domain"] = False
|
| 548 |
+
elif domain_lower in ["domain", "domains", "list", "options", "help"]:
|
| 549 |
+
state["domain"] = None
|
| 550 |
else:
|
| 551 |
state["domain"] = domain
|
| 552 |
state["waiting_for_domain"] = False
|
|
|
|
| 647 |
)
|
| 648 |
state["features"] = feature_result.get("recommended_features", [])
|
| 649 |
|
| 650 |
+
custom_desc = state.get("custom_description", False)
|
| 651 |
+
custom_abs = state.get("custom_abstract", False)
|
| 652 |
+
|
| 653 |
result = generate_full_project(
|
| 654 |
title=state.get("project_title"),
|
| 655 |
features=state.get("features", []),
|
| 656 |
+
description=state.get("description", "") if custom_desc else "",
|
| 657 |
+
abstract=state.get("abstract", "") if custom_abs else "",
|
| 658 |
+
custom_description=custom_desc,
|
| 659 |
+
custom_abstract=custom_abs
|
| 660 |
)
|
| 661 |
|
| 662 |
state = update_state(state, result, mode="merge")
|
|
|
|
| 707 |
save_user_memory(user_id, {"history": history, "state": state})
|
| 708 |
return finalize_response(user_input, "No problem! Let's try something else.", history, state, user_id)
|
| 709 |
|
| 710 |
+
elif action == "clear_session":
|
| 711 |
+
state = default_state()
|
| 712 |
+
save_user_memory(user_id, {"history": history, "state": state})
|
| 713 |
+
return finalize_response(
|
| 714 |
+
user_input,
|
| 715 |
+
"✅ Session cleared! We are starting fresh. How can I help you today?",
|
| 716 |
+
history,
|
| 717 |
+
state,
|
| 718 |
+
user_id
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
else:
|
| 722 |
return finalize_response(user_input, "I am not sure how to handle that.", history, state, user_id)
|
src/recommendation_engine/full_project_generator.py
CHANGED
|
@@ -72,7 +72,9 @@ def generate_full_project(
|
|
| 72 |
title,
|
| 73 |
features,
|
| 74 |
description="",
|
| 75 |
-
abstract=""
|
|
|
|
|
|
|
| 76 |
):
|
| 77 |
|
| 78 |
context = {
|
|
@@ -100,10 +102,10 @@ def generate_full_project(
|
|
| 100 |
extract_section(raw, "CATEGORY"),
|
| 101 |
|
| 102 |
"abstract":
|
| 103 |
-
extract_section(raw, "ABSTRACT"),
|
| 104 |
|
| 105 |
"description":
|
| 106 |
-
extract_section(raw, "DESCRIPTION"),
|
| 107 |
|
| 108 |
|
| 109 |
|
|
|
|
| 72 |
title,
|
| 73 |
features,
|
| 74 |
description="",
|
| 75 |
+
abstract="",
|
| 76 |
+
custom_description=False,
|
| 77 |
+
custom_abstract=False
|
| 78 |
):
|
| 79 |
|
| 80 |
context = {
|
|
|
|
| 102 |
extract_section(raw, "CATEGORY"),
|
| 103 |
|
| 104 |
"abstract":
|
| 105 |
+
abstract if custom_abstract else extract_section(raw, "ABSTRACT"),
|
| 106 |
|
| 107 |
"description":
|
| 108 |
+
description if custom_description else extract_section(raw, "DESCRIPTION"),
|
| 109 |
|
| 110 |
|
| 111 |
|
src/recommendation_engine/llm_router.py
CHANGED
|
@@ -16,7 +16,7 @@ def word_to_number(text: str):
|
|
| 16 |
text = text.lower()
|
| 17 |
|
| 18 |
for word, num in mapping.items():
|
| 19 |
-
if word
|
| 20 |
return num
|
| 21 |
|
| 22 |
nums = re.findall(r'\d+', text)
|
|
@@ -63,12 +63,16 @@ Evaluate the input and choose exactly ONE action from the following list:
|
|
| 63 |
4. "trigger_full_project_generation": Choose this if the user wants to generate the complete project specification.
|
| 64 |
- Example: "generate full project", "fill all fields", or "2" (if selecting option 2 to generate the full project).
|
| 65 |
|
|
|
|
|
|
|
|
|
|
| 66 |
5. "confirmation_yes": Choose this if the user is saying yes/confirming a prompt.
|
| 67 |
6. "confirmation_no": Choose this if the user is saying no/declining.
|
| 68 |
|
| 69 |
CRITICAL INSTRUCTIONS:
|
| 70 |
- You are the orchestrator. If a request is invalid, missing context, or vague, use "reply_directly" to ask the user to clarify. Do NOT trigger a generation task if the input is bad.
|
| 71 |
- NEVER choose "trigger_feature_generation" if the project title is just a single word, a brand name, or lacks technical description (e.g. "Fixy", "My Project", "Test"). You MUST choose "reply_directly" and ask the user to provide a descriptive technical title that explains what the system actually does.
|
|
|
|
| 72 |
- Return ONLY a valid JSON object. Do NOT include markdown blocks.
|
| 73 |
|
| 74 |
JSON Format:
|
|
@@ -76,7 +80,9 @@ JSON Format:
|
|
| 76 |
"action": "exact_action_name",
|
| 77 |
"reply_text": "text to reply to the user (ONLY if action is reply_directly, otherwise null)",
|
| 78 |
"extracted_domain": "extract_domain_if_mentioned_else_null",
|
| 79 |
-
"extracted_title": "extract_project_title_if_mentioned_else_null"
|
|
|
|
|
|
|
| 80 |
}}
|
| 81 |
"""
|
| 82 |
try:
|
|
@@ -99,12 +105,16 @@ JSON Format:
|
|
| 99 |
reply_text = parsed.get("reply_text")
|
| 100 |
domain = parsed.get("extracted_domain") or extract_domain(text)
|
| 101 |
project_title = parsed.get("extracted_title")
|
|
|
|
|
|
|
| 102 |
|
| 103 |
return {
|
| 104 |
"action": action,
|
| 105 |
"reply_text": reply_text,
|
| 106 |
"domain": domain,
|
| 107 |
"project_title": project_title,
|
|
|
|
|
|
|
| 108 |
"number": number
|
| 109 |
}
|
| 110 |
except Exception as e:
|
|
@@ -115,11 +125,15 @@ JSON Format:
|
|
| 115 |
action = "reply_directly"
|
| 116 |
reply_text = None
|
| 117 |
project_title = None
|
|
|
|
|
|
|
| 118 |
|
| 119 |
return {
|
| 120 |
"action": action,
|
| 121 |
"reply_text": reply_text,
|
| 122 |
"domain": domain,
|
| 123 |
"project_title": project_title,
|
|
|
|
|
|
|
| 124 |
"number": number
|
| 125 |
}
|
|
|
|
| 16 |
text = text.lower()
|
| 17 |
|
| 18 |
for word, num in mapping.items():
|
| 19 |
+
if re.search(rf"\b{word}\b", text):
|
| 20 |
return num
|
| 21 |
|
| 22 |
nums = re.findall(r'\d+', text)
|
|
|
|
| 63 |
4. "trigger_full_project_generation": Choose this if the user wants to generate the complete project specification.
|
| 64 |
- Example: "generate full project", "fill all fields", or "2" (if selecting option 2 to generate the full project).
|
| 65 |
|
| 66 |
+
5. "clear_session": Choose this if the user explicitly asks to start over, clear the chat, start a new project, reset the memory, or forget the current project.
|
| 67 |
+
- Example: "restart", "start over", "clear memory", "let's do a different project".
|
| 68 |
+
|
| 69 |
5. "confirmation_yes": Choose this if the user is saying yes/confirming a prompt.
|
| 70 |
6. "confirmation_no": Choose this if the user is saying no/declining.
|
| 71 |
|
| 72 |
CRITICAL INSTRUCTIONS:
|
| 73 |
- You are the orchestrator. If a request is invalid, missing context, or vague, use "reply_directly" to ask the user to clarify. Do NOT trigger a generation task if the input is bad.
|
| 74 |
- NEVER choose "trigger_feature_generation" if the project title is just a single word, a brand name, or lacks technical description (e.g. "Fixy", "My Project", "Test"). You MUST choose "reply_directly" and ask the user to provide a descriptive technical title that explains what the system actually does.
|
| 75 |
+
- If the user provides a summary or context about their project, extract it into "extracted_abstract". If they provide a detailed breakdown or scope, extract it into "extracted_description".
|
| 76 |
- Return ONLY a valid JSON object. Do NOT include markdown blocks.
|
| 77 |
|
| 78 |
JSON Format:
|
|
|
|
| 80 |
"action": "exact_action_name",
|
| 81 |
"reply_text": "text to reply to the user (ONLY if action is reply_directly, otherwise null)",
|
| 82 |
"extracted_domain": "extract_domain_if_mentioned_else_null",
|
| 83 |
+
"extracted_title": "extract_project_title_if_mentioned_else_null",
|
| 84 |
+
"extracted_abstract": "extract_abstract_if_user_provides_one_else_null",
|
| 85 |
+
"extracted_description": "extract_description_if_user_provides_one_else_null"
|
| 86 |
}}
|
| 87 |
"""
|
| 88 |
try:
|
|
|
|
| 105 |
reply_text = parsed.get("reply_text")
|
| 106 |
domain = parsed.get("extracted_domain") or extract_domain(text)
|
| 107 |
project_title = parsed.get("extracted_title")
|
| 108 |
+
abstract = parsed.get("extracted_abstract")
|
| 109 |
+
description = parsed.get("extracted_description")
|
| 110 |
|
| 111 |
return {
|
| 112 |
"action": action,
|
| 113 |
"reply_text": reply_text,
|
| 114 |
"domain": domain,
|
| 115 |
"project_title": project_title,
|
| 116 |
+
"abstract": abstract,
|
| 117 |
+
"description": description,
|
| 118 |
"number": number
|
| 119 |
}
|
| 120 |
except Exception as e:
|
|
|
|
| 125 |
action = "reply_directly"
|
| 126 |
reply_text = None
|
| 127 |
project_title = None
|
| 128 |
+
abstract = None
|
| 129 |
+
description = None
|
| 130 |
|
| 131 |
return {
|
| 132 |
"action": action,
|
| 133 |
"reply_text": reply_text,
|
| 134 |
"domain": domain,
|
| 135 |
"project_title": project_title,
|
| 136 |
+
"abstract": abstract,
|
| 137 |
+
"description": description,
|
| 138 |
"number": number
|
| 139 |
}
|