Spaces:
Sleeping
Sleeping
Update ai_chatbot.py
Browse files- ai_chatbot.py +100 -146
ai_chatbot.py
CHANGED
|
@@ -1,160 +1,114 @@
|
|
| 1 |
-
|
| 2 |
-
import numpy as np
|
| 3 |
-
from typing import List, Dict, Tuple
|
| 4 |
import re
|
|
|
|
| 5 |
|
| 6 |
class AIChatbot:
|
| 7 |
def __init__(self):
|
| 8 |
-
|
| 9 |
-
self.
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
self.
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
# Static FAQ data
|
| 19 |
-
self.faqs = [
|
| 20 |
-
{"id": 1, "question": "What are the admission requirements?", "answer": "To apply for admission, you need to submit your high school diploma, transcript of records, 2x2 ID photo, and completed application form. You also need to take the entrance examination."},
|
| 21 |
-
{"id": 2, "question": "When is the application deadline?", "answer": "The application deadline is usually in March for the first semester and October for the second semester. Please check our website for the exact dates."},
|
| 22 |
-
{"id": 3, "question": "What courses are available?", "answer": "We offer various courses including BS Computer Science, BS Information Technology, BS Business Administration, BS Education, BS Nursing, BS Architecture, and more. Check our course catalog for the complete list."},
|
| 23 |
-
{"id": 4, "question": "How much is the tuition fee?", "answer": "Tuition fees vary by program. For undergraduate programs, it ranges from ₱15,000 to ₱25,000 per semester. Please contact the registrar's office for specific program fees."},
|
| 24 |
-
{"id": 5, "question": "Do you offer scholarships?", "answer": "Yes, we offer various scholarships including academic scholarships, athletic scholarships, and need-based financial aid. Applications are available at the student affairs office."},
|
| 25 |
-
{"id": 6, "question": "What is the minimum GWA requirement?", "answer": "The minimum GWA requirement is 80% for most programs. Some programs may have higher requirements. Please check the specific requirements for your chosen program."},
|
| 26 |
-
{"id": 7, "question": "How can I contact the admissions office?", "answer": "You can contact the admissions office at (02) 123-4567 or email admissions@psau.edu.ph. Office hours are Monday to Friday, 8:00 AM to 5:00 PM."},
|
| 27 |
-
{"id": 8, "question": "Is there a dormitory available?", "answer": "Yes, we have dormitory facilities for both male and female students. Dormitory fees are separate from tuition. Please contact the housing office for availability and rates."},
|
| 28 |
-
{"id": 9, "question": "What documents do I need for enrollment?", "answer": "For enrollment, you need your admission letter, original and photocopy of birth certificate, original and photocopy of high school diploma, 2x2 ID photos, and medical certificate."},
|
| 29 |
-
{"id": 10, "question": "Can I transfer from another school?", "answer": "Yes, we accept transferees. You need to submit your transcript of records, honorable dismissal, and other required documents. Some credits may be credited depending on the program."}
|
| 30 |
]
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
def
|
| 39 |
-
"""
|
| 40 |
-
print(f"Unanswered question logged: {question}")
|
| 41 |
-
# In a real implementation, you could save this to a file or send to an admin
|
| 42 |
-
|
| 43 |
-
def _tokenize(self, text: str):
|
| 44 |
-
if not text:
|
| 45 |
-
return []
|
| 46 |
-
return [t for t in re.findall(r"[a-z0-9]+", text.lower()) if len(t) > 2]
|
| 47 |
-
|
| 48 |
-
def _overlap_ratio(self, q_tokens, faq_tokens):
|
| 49 |
-
if not q_tokens or not faq_tokens:
|
| 50 |
-
return 0.0
|
| 51 |
-
q_set = set(q_tokens)
|
| 52 |
-
f_set = set(faq_tokens)
|
| 53 |
-
inter = len(q_set & f_set)
|
| 54 |
-
denom = max(len(q_set), 1)
|
| 55 |
-
return inter / denom
|
| 56 |
-
|
| 57 |
-
def _wh_class(self, text: str) -> str:
|
| 58 |
if not text:
|
| 59 |
-
return
|
| 60 |
-
s
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
# also check presence if not leading
|
| 66 |
-
for key in ['who', 'where', 'when', 'what', 'how', 'why', 'which']:
|
| 67 |
-
if f' {key} ' in f' {s} ':
|
| 68 |
-
return key
|
| 69 |
-
return ''
|
| 70 |
-
|
| 71 |
-
def find_best_match(self, question: str, threshold: float = 0.7) -> Tuple[str, float]:
|
| 72 |
-
print(f"find_best_match called with: {question}") # Debug print
|
| 73 |
-
if not self.faqs or self.faq_embeddings is None:
|
| 74 |
-
return "I'm sorry, I couldn't find any FAQs in the database.", 0.0
|
| 75 |
-
|
| 76 |
-
# Compute and normalize embedding for the input question
|
| 77 |
-
question_embedding = self.model.encode([question], normalize_embeddings=True)[0]
|
| 78 |
-
similarities = np.dot(self.faq_embeddings, question_embedding)
|
| 79 |
-
|
| 80 |
-
# Compute keyword overlap with each FAQ question
|
| 81 |
-
q_tokens = self._tokenize(question)
|
| 82 |
-
overlap_scores = []
|
| 83 |
-
for faq in self.faqs:
|
| 84 |
-
overlap_scores.append(self._overlap_ratio(q_tokens, self._tokenize(faq['question'])))
|
| 85 |
-
|
| 86 |
-
similarities = np.array(similarities)
|
| 87 |
-
overlap_scores = np.array(overlap_scores)
|
| 88 |
-
|
| 89 |
-
# Combined score to reduce false positives
|
| 90 |
-
combined = 0.7 * similarities + 0.3 * overlap_scores
|
| 91 |
|
| 92 |
-
#
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
or (best_combined >= threshold and best_overlap >= 0.3)
|
| 109 |
-
)
|
| 110 |
-
# Enforce WH intent match when present
|
| 111 |
-
if accept and q_wh and best_wh and q_wh != best_wh:
|
| 112 |
-
accept = False
|
| 113 |
-
|
| 114 |
-
if accept:
|
| 115 |
-
return self.faqs[best_idx]['answer'], best_combined
|
| 116 |
-
else:
|
| 117 |
-
# Log as unanswered so admins can curate (ignore errors)
|
| 118 |
-
try:
|
| 119 |
-
self.save_unanswered_question(question)
|
| 120 |
-
except Exception:
|
| 121 |
-
pass
|
| 122 |
-
fallback = (
|
| 123 |
-
"Sorry, I don’t have the knowledge to answer that yet.\n"
|
| 124 |
-
"I’ll notify an admin about your question and we’ll add the answer soon.\n"
|
| 125 |
-
"Please come back in a while."
|
| 126 |
-
)
|
| 127 |
-
return (fallback, best_combined)
|
| 128 |
|
| 129 |
-
def
|
| 130 |
-
"""
|
| 131 |
-
if not
|
| 132 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
|
| 134 |
-
#
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
-
#
|
| 138 |
-
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
-
def
|
| 145 |
-
"""
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
embeddings = self.model.encode(questions, normalize_embeddings=True)
|
| 154 |
-
self.faq_embeddings = np.array(embeddings)
|
| 155 |
-
|
| 156 |
-
print(f"FAQ added: {question}")
|
| 157 |
-
return True
|
| 158 |
-
except Exception as e:
|
| 159 |
-
print(f"Error adding FAQ: {e}")
|
| 160 |
-
return False
|
|
|
|
| 1 |
+
import random
|
|
|
|
|
|
|
| 2 |
import re
|
| 3 |
+
from typing import List, Dict
|
| 4 |
|
| 5 |
class AIChatbot:
|
| 6 |
def __init__(self):
|
| 7 |
+
"""Initialize the AI chatbot with basic interaction patterns"""
|
| 8 |
+
self.conversation_history = []
|
| 9 |
+
self.greeting_patterns = [
|
| 10 |
+
"hello", "hi", "hey", "good morning", "good afternoon", "good evening"
|
| 11 |
+
]
|
| 12 |
+
self.farewell_patterns = [
|
| 13 |
+
"bye", "goodbye", "see you", "farewell", "take care"
|
| 14 |
+
]
|
| 15 |
+
self.thanks_patterns = [
|
| 16 |
+
"thank you", "thanks", "appreciate", "grateful"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
]
|
| 18 |
|
| 19 |
+
# Basic response templates
|
| 20 |
+
self.responses = {
|
| 21 |
+
"greeting": [
|
| 22 |
+
"Hello! How can I help you today?",
|
| 23 |
+
"Hi there! What can I do for you?",
|
| 24 |
+
"Good day! How may I assist you?",
|
| 25 |
+
"Hello! I'm here to help. What would you like to know?"
|
| 26 |
+
],
|
| 27 |
+
"farewell": [
|
| 28 |
+
"Goodbye! Have a great day!",
|
| 29 |
+
"See you later! Take care!",
|
| 30 |
+
"Bye! Feel free to come back anytime!",
|
| 31 |
+
"Farewell! I hope I was helpful!"
|
| 32 |
+
],
|
| 33 |
+
"thanks": [
|
| 34 |
+
"You're welcome! Is there anything else I can help with?",
|
| 35 |
+
"My pleasure! Happy to help!",
|
| 36 |
+
"No problem! Feel free to ask if you need anything else!",
|
| 37 |
+
"You're very welcome! Anything else I can assist you with?"
|
| 38 |
+
],
|
| 39 |
+
"default": [
|
| 40 |
+
"That's an interesting question! I'm still learning, but I'd be happy to help with basic interactions.",
|
| 41 |
+
"I understand you're asking about that. While I'm designed for basic conversations, I'm here to chat!",
|
| 42 |
+
"Thanks for sharing that with me! I'm always happy to engage in conversation.",
|
| 43 |
+
"I appreciate your message! I'm here to have friendly conversations with you.",
|
| 44 |
+
"That's a thoughtful question! I'm designed to be helpful and engaging in our chat."
|
| 45 |
+
]
|
| 46 |
+
}
|
| 47 |
|
| 48 |
+
def _clean_text(self, text: str) -> str:
|
| 49 |
+
"""Clean and normalize input text"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
if not text:
|
| 51 |
+
return ""
|
| 52 |
+
return re.sub(r'[^\w\s]', '', text.lower().strip())
|
| 53 |
+
|
| 54 |
+
def _detect_intent(self, text: str) -> str:
|
| 55 |
+
"""Detect the intent of the user's message"""
|
| 56 |
+
cleaned_text = self._clean_text(text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
# Check for greetings
|
| 59 |
+
for pattern in self.greeting_patterns:
|
| 60 |
+
if pattern in cleaned_text:
|
| 61 |
+
return "greeting"
|
| 62 |
+
|
| 63 |
+
# Check for farewells
|
| 64 |
+
for pattern in self.farewell_patterns:
|
| 65 |
+
if pattern in cleaned_text:
|
| 66 |
+
return "farewell"
|
| 67 |
+
|
| 68 |
+
# Check for thanks
|
| 69 |
+
for pattern in self.thanks_patterns:
|
| 70 |
+
if pattern in cleaned_text:
|
| 71 |
+
return "thanks"
|
| 72 |
+
|
| 73 |
+
return "default"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
+
def generate_response(self, user_message: str) -> str:
|
| 76 |
+
"""Generate an AI response based on user input"""
|
| 77 |
+
if not user_message or not user_message.strip():
|
| 78 |
+
return "I didn't quite catch that. Could you please say something?"
|
| 79 |
+
|
| 80 |
+
# Add to conversation history
|
| 81 |
+
self.conversation_history.append({"user": user_message, "bot": ""})
|
| 82 |
+
|
| 83 |
+
# Detect intent
|
| 84 |
+
intent = self._detect_intent(user_message)
|
| 85 |
|
| 86 |
+
# Generate appropriate response
|
| 87 |
+
if intent in self.responses:
|
| 88 |
+
response = random.choice(self.responses[intent])
|
| 89 |
+
else:
|
| 90 |
+
response = random.choice(self.responses["default"])
|
| 91 |
|
| 92 |
+
# Update conversation history
|
| 93 |
+
self.conversation_history[-1]["bot"] = response
|
| 94 |
|
| 95 |
+
return response
|
| 96 |
+
|
| 97 |
+
def get_conversation_history(self) -> List[Dict]:
|
| 98 |
+
"""Get the conversation history"""
|
| 99 |
+
return self.conversation_history
|
| 100 |
+
|
| 101 |
+
def clear_history(self):
|
| 102 |
+
"""Clear the conversation history"""
|
| 103 |
+
self.conversation_history = []
|
| 104 |
+
return "Conversation history cleared!"
|
| 105 |
|
| 106 |
+
def get_suggested_responses(self) -> List[str]:
|
| 107 |
+
"""Get some suggested conversation starters"""
|
| 108 |
+
return [
|
| 109 |
+
"Hello! How are you today?",
|
| 110 |
+
"Tell me about yourself",
|
| 111 |
+
"What's on your mind?",
|
| 112 |
+
"How can I help you?",
|
| 113 |
+
"What would you like to talk about?"
|
| 114 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|