markobinario commited on
Commit
47cf4fa
·
verified ·
1 Parent(s): 9cfd703

Update ai_chatbot.py

Browse files
Files changed (1) hide show
  1. ai_chatbot.py +100 -146
ai_chatbot.py CHANGED
@@ -1,160 +1,114 @@
1
- from sentence_transformers import SentenceTransformer
2
- import numpy as np
3
- from typing import List, Dict, Tuple
4
  import re
 
5
 
6
  class AIChatbot:
7
  def __init__(self):
8
- # Load the pre-trained model (can use a smaller model for more speed)
9
- self.model = SentenceTransformer('all-MiniLM-L6-v2')
10
- # Warm up the model to avoid first-request slowness
11
- _ = self.model.encode(["Hello, world!"])
12
- self.faq_embeddings = None
13
- self.faqs = None
14
- self.load_faqs()
15
-
16
- def load_faqs(self):
17
- """Load static FAQs and compute their normalized embeddings"""
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
- if self.faqs:
33
- # Compute and normalize embeddings for all questions
34
- questions = [faq['question'] for faq in self.faqs]
35
- embeddings = self.model.encode(questions, normalize_embeddings=True)
36
- self.faq_embeddings = np.array(embeddings)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- def save_unanswered_question(self, question):
39
- """Log unanswered questions to console (can be extended to save to file)"""
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 = text.strip().lower()
61
- # simple heuristic classification by leading wh-word
62
- for key in ['who', 'where', 'when', 'what', 'how', 'why', 'which']:
63
- if s.startswith(key + ' ') or s.startswith(key + "?"):
64
- return key
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
- # Apply WH-word intent consistency penalty
93
- q_wh = self._wh_class(question)
94
- if q_wh:
95
- for i, faq in enumerate(self.faqs):
96
- f_wh = self._wh_class(faq['question'])
97
- if f_wh and f_wh != q_wh:
98
- combined[i] *= 0.6 # penalize mismatched intent significantly
99
- best_idx = int(np.argmax(combined))
100
- best_semantic = float(similarities[best_idx])
101
- best_overlap = float(overlap_scores[best_idx])
102
- best_combined = float(combined[best_idx])
103
- best_wh = self._wh_class(self.faqs[best_idx]['question'])
104
-
105
- # Acceptance criteria: require good semantic OR strong combined with overlap
106
- accept = (
107
- best_semantic >= max(0.7, threshold)
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 get_suggested_questions(self, question: str, num_suggestions: int = 3) -> List[str]:
130
- """Get suggested questions based on the input question"""
131
- if not self.faqs or self.faq_embeddings is None:
132
- return []
 
 
 
 
 
 
133
 
134
- # Compute and normalize embedding for the input question
135
- question_embedding = self.model.encode([question], normalize_embeddings=True)[0]
 
 
 
136
 
137
- # Calculate cosine similarity
138
- similarities = np.dot(self.faq_embeddings, question_embedding)
139
 
140
- # Get top N similar questions
141
- top_indices = np.argsort(similarities)[-num_suggestions:][::-1]
142
- return [self.faqs[idx]['question'] for idx in top_indices if similarities[idx] > 0.3]
 
 
 
 
 
 
 
143
 
144
- def add_faq(self, question: str, answer: str) -> bool:
145
- """Add a new FAQ to the static list (for demonstration purposes)"""
146
- try:
147
- new_id = max([faq['id'] for faq in self.faqs]) + 1 if self.faqs else 1
148
- new_faq = {"id": new_id, "question": question, "answer": answer}
149
- self.faqs.append(new_faq)
150
-
151
- # Recompute embeddings
152
- questions = [faq['question'] for faq in self.faqs]
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
+ ]