oceddyyy commited on
Commit
9dd97e1
·
verified ·
1 Parent(s): 7a8f00c

Upload 6 files

Browse files
Files changed (6) hide show
  1. README.md +39 -0
  2. app.py +183 -0
  3. dataset.json +0 -0
  4. feedback.json +1 -0
  5. requirements.txt +5 -0
  6. space.yaml +8 -0
README.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🎓 University Inquiries AI Chatbot
2
+
3
+ A conversational AI chatbot that helps students navigate and understand the official university handbook using natural language. Built with Gradio, Sentence Transformers, and FLAN-T5, it offers friendly and accurate responses based on handbook content — with built-in feedback tracking and upvote/downvote tuning.
4
+
5
+ ---
6
+
7
+ ## ✨ Features
8
+
9
+ - 🧠 **Semantic Search:** Uses Sentence Transformers to find the closest handbook Q&A to a student's question.
10
+ - 🗣️ **LLM Explanation:** Automatically rewrites formal handbook responses in a student-friendly tone using FLAN-T5.
11
+ - 👍 **Feedback Memory:** Stores user feedback (upvotes/downvotes) to improve future responses.
12
+ - 🔄 **Smart Matching:** Merges similar feedback questions (≥80% similarity) to avoid duplication.
13
+ - 🎨 **Custom UI:** Includes a PUP-themed responsive design with a gradient background and styled chat layout.
14
+
15
+
16
+ ---
17
+
18
+ ## 🧰 Tech Stack
19
+
20
+ - [Gradio](https://gradio.app/) — for UI interface
21
+ - [Sentence Transformers](https://www.sbert.net/) — for question embedding & similarity
22
+ - [Transformers (FLAN-T5)](https://huggingface.co/google/flan-t5-small) — for natural explanation generation
23
+ - JSON — for persistent feedback storage
24
+
25
+ ---
26
+
27
+ ## 📁 Files
28
+
29
+ - `app.py` — main chatbot logic & interface
30
+ - `dataset.json` — official university Q&A set
31
+ - `feedback.json` — live feedback database
32
+ - `README.md` — this file!
33
+
34
+ ---
35
+
36
+ ## 📦 Installation
37
+
38
+ ```bash
39
+ pip install -r requirements.txt
app.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ from sentence_transformers import SentenceTransformer
4
+ from transformers import pipeline
5
+ from sklearn.metrics.pairwise import cosine_similarity
6
+ import numpy as np
7
+ import os
8
+
9
+ # === Custom PUP-themed CSS ===
10
+ PUP_Themed_css = """
11
+ html, body, .gradio-container, .gr-app {
12
+ height: 100% !important;
13
+ margin: 0 !important;
14
+ padding: 0 !important;
15
+ background: linear-gradient(to bottom right, #800000, #ff0000, #ffeb3b, #ffa500) !important;
16
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
17
+ color: #1b4332 !important;
18
+ }
19
+ """
20
+
21
+ # === Load Models and Data ===
22
+ embedding_model = SentenceTransformer('paraphrase-mpnet-base-v2')
23
+ llm = pipeline("text2text-generation", model="google/flan-t5-small")
24
+
25
+ with open("dataset.json", "r") as f:
26
+ dataset = json.load(f)
27
+
28
+ questions = [item["question"] for item in dataset]
29
+ answers = [item["answer"] for item in dataset]
30
+ question_embeddings = embedding_model.encode(questions, convert_to_tensor=True)
31
+
32
+ chat_history = []
33
+ feedback_data = []
34
+ feedback_questions = []
35
+ feedback_answers = []
36
+ feedback_embeddings = None
37
+
38
+ if os.path.exists("feedback.json") and os.path.getsize("feedback.json") > 0:
39
+ with open("feedback.json", "r") as f:
40
+ try:
41
+ feedback_data = json.load(f)
42
+ feedback_questions = [item["question"] for item in feedback_data]
43
+ feedback_answers = [item["response"] for item in feedback_data]
44
+ if feedback_questions:
45
+ feedback_embeddings = embedding_model.encode(feedback_questions, convert_to_tensor=True)
46
+ except json.JSONDecodeError:
47
+ feedback_data = []
48
+
49
+ # === Chatbot Response Function ===
50
+ def chatbot_response(query, chat_history):
51
+ query_embedding = embedding_model.encode([query], convert_to_tensor=True)
52
+
53
+ # === Feedback Matching ===
54
+ if feedback_embeddings is not None:
55
+ feedback_scores = cosine_similarity(query_embedding.cpu().numpy(), feedback_embeddings.cpu().numpy())[0]
56
+ best_idx = int(np.argmax(feedback_scores))
57
+ best_score = feedback_scores[best_idx]
58
+ matched_feedback = feedback_data[best_idx]
59
+
60
+ base_threshold = 0.8
61
+ upvotes = matched_feedback.get("upvotes", 0)
62
+ downvotes = matched_feedback.get("downvotes", 0)
63
+ adjusted_threshold = base_threshold - (0.01 * upvotes) + (0.01 * downvotes)
64
+ dynamic_threshold = min(max(adjusted_threshold, 0.4), 1.0)
65
+
66
+ if best_score >= dynamic_threshold:
67
+ response = matched_feedback["response"]
68
+ chat_history.append((query, response))
69
+ return "", chat_history, gr.update(visible=True)
70
+
71
+ # === Main Handbook Matching ===
72
+ similarity_scores = cosine_similarity(query_embedding.cpu().numpy(), question_embeddings.cpu().numpy())[0]
73
+ best_idx = int(np.argmax(similarity_scores))
74
+ best_score = similarity_scores[best_idx]
75
+ matched_q = questions[best_idx]
76
+ matched_a = answers[best_idx]
77
+
78
+ if best_score < 0.4:
79
+ response = "Sorry, I couldn't find a relevant answer."
80
+ chat_history.append((query, response))
81
+ return "", chat_history, gr.update(visible=True)
82
+
83
+ prompt = (
84
+ f"The following is an official university handbook statement:\n"
85
+ f"\"{matched_a}\"\n\n"
86
+ f"Please explain this to a student in a short, natural, and easy-to-understand way. "
87
+ f"Use simple words, and do not add new information."
88
+ )
89
+
90
+ llm_response = llm(prompt, max_length=200, do_sample=True, temperature=0.7, top_p=0.9)[0]["generated_text"].strip()
91
+ if not llm_response:
92
+ llm_response = "I'm sorry, I couldn't simplify that at the moment."
93
+
94
+ a_embedding = embedding_model.encode([matched_a], convert_to_tensor=True)
95
+ llm_embedding = embedding_model.encode([llm_response], convert_to_tensor=True)
96
+ explanation_similarity = cosine_similarity(a_embedding.cpu().numpy(), llm_embedding.cpu().numpy())[0][0]
97
+
98
+ if explanation_similarity >= 0.95:
99
+ final_response = f"According to the university handbook, {matched_a}"
100
+ else:
101
+ final_response = f"According to the university handbook, {matched_a} In simpler terms, {llm_response}"
102
+
103
+ chat_history.append((query, final_response))
104
+ return "", chat_history, gr.update(visible=True)
105
+
106
+ # === Feedback Save & Upvote/Downvote Tracking ===
107
+ def record_feedback(feedback, chat_history):
108
+ global feedback_embeddings
109
+ if chat_history:
110
+ last_query, last_response = chat_history[-1]
111
+ matched = False
112
+
113
+ for item in feedback_data:
114
+ existing_embedding = embedding_model.encode([item["question"]], convert_to_tensor=True)
115
+ new_embedding = embedding_model.encode([last_query], convert_to_tensor=True)
116
+ similarity = cosine_similarity(existing_embedding.cpu().numpy(), new_embedding.cpu().numpy())[0][0]
117
+ if similarity >= 0.8 and item["response"] == last_response:
118
+ matched = True
119
+ votes = {"positive": "upvotes", "negative": "downvotes"}
120
+ item[votes[feedback]] = item.get(votes[feedback], 0) + 1
121
+ break
122
+
123
+ if not matched:
124
+ entry = {
125
+ "question": last_query,
126
+ "response": last_response,
127
+ "feedback": feedback,
128
+ "upvotes": 1 if feedback == "positive" else 0,
129
+ "downvotes": 1 if feedback == "negative" else 0
130
+ }
131
+ feedback_data.append(entry)
132
+
133
+ with open("feedback.json", "w") as f:
134
+ json.dump(feedback_data, f, indent=4)
135
+
136
+ # Update feedback embeddings
137
+ feedback_questions = [item["question"] for item in feedback_data]
138
+ if feedback_questions:
139
+ feedback_embeddings = embedding_model.encode(feedback_questions, convert_to_tensor=True)
140
+
141
+ return gr.update(visible=False)
142
+
143
+ # === Gradio UI ===
144
+ with gr.Blocks(css=PUP_Themed_css, title="University Handbook AI Chatbot") as demo:
145
+ gr.Markdown(
146
+ "<div style='"
147
+ "background-color: #ffffff; "
148
+ "border-radius: 16px; "
149
+ "padding: 24px 16px; "
150
+ "margin-bottom: 24px; "
151
+ "box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); "
152
+ "max-width: 700px; "
153
+ "margin-left: auto; "
154
+ "margin-right: auto; "
155
+ "text-align: center;'>"
156
+ "<h1 style='font-size: 2.2rem; margin: 0;'>University Inquiries AI Chatbot</h1>"
157
+ "</div>"
158
+ )
159
+
160
+ state = gr.State(chat_history)
161
+ chatbot_ui = gr.Chatbot(label="Chat", show_label=False)
162
+
163
+ with gr.Row():
164
+ query_input = gr.Textbox(placeholder="Type your question here...", show_label=False)
165
+ submit_btn = gr.Button("Submit")
166
+
167
+ with gr.Row(visible=False) as feedback_row:
168
+ gr.Markdown("Was this helpful?")
169
+ thumbs_up = gr.Button("👍")
170
+ thumbs_down = gr.Button("👎")
171
+
172
+ def handle_submit(message, chat_state):
173
+ return chatbot_response(message, chat_state)
174
+
175
+ submit_btn.click(handle_submit, [query_input, state], [query_input, chatbot_ui, feedback_row])
176
+ query_input.submit(handle_submit, [query_input, state], [query_input, chatbot_ui, feedback_row])
177
+
178
+ thumbs_up.click(lambda state: record_feedback("positive", state), inputs=[state], outputs=[feedback_row])
179
+ thumbs_down.click(lambda state: record_feedback("negative", state), inputs=[state], outputs=[feedback_row])
180
+
181
+ # === Launch App ===
182
+ if __name__ == "__main__":
183
+ demo.launch()
dataset.json ADDED
The diff for this file is too large to render. See raw diff
 
feedback.json ADDED
@@ -0,0 +1 @@
 
 
1
+ []
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ transformers
3
+ sentence-transformers
4
+ scikit-learn
5
+ numpy
space.yaml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # space.yaml
2
+ title: "University Inquiries AI Chatbot"
3
+ emoji: 🎓
4
+ colorFrom: red
5
+ colorTo: yellow
6
+ sdk: gradio
7
+ python_version: 3.10
8
+ app_file: app.py