oceddyyy commited on
Commit
b46142c
·
verified ·
1 Parent(s): 9c60a78

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +297 -265
app.py CHANGED
@@ -1,265 +1,297 @@
1
- import os
2
- import json
3
- from sentence_transformers import SentenceTransformer
4
- from sklearn.metrics.pairwise import cosine_similarity
5
- import numpy as np
6
- from huggingface_hub import upload_file, hf_hub_download, InferenceClient
7
- from flask import Flask, request, jsonify
8
- import time
9
-
10
-
11
- os.environ["HF_HOME"] = "/tmp/.cache"
12
- os.environ["HF_DATASETS_CACHE"] = "/tmp/.cache"
13
- os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/.cache"
14
- os.makedirs("/tmp/.cache", exist_ok=True)
15
- os.makedirs("/tmp/outputs", exist_ok=True)
16
-
17
-
18
- embedding_model = SentenceTransformer('paraphrase-mpnet-base-v2')
19
- token = os.getenv("HF_TOKEN") or os.getenv("NEW_PUP_AI_Project")
20
- inference_client = InferenceClient(
21
- model="mistralai/Mixtral-8x7B-Instruct-v0.1",
22
- token=token
23
- )
24
-
25
-
26
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
27
- DATASET_PATH = os.path.join(BASE_DIR, "dataset.json")
28
- with open(DATASET_PATH, "r") as f:
29
- dataset = json.load(f)
30
-
31
- questions = [item["question"] for item in dataset]
32
- answers = [item["answer"] for item in dataset]
33
- question_embeddings = embedding_model.encode(questions, convert_to_tensor=True)
34
-
35
-
36
- feedback_data = []
37
- feedback_questions = []
38
- feedback_embeddings = None
39
- dev_mode = {"enabled": False}
40
- feedback_path = "/tmp/outputs/feedback.json"
41
- COMMENTS_PATH = "/tmp/outputs/Comments.json"
42
-
43
- if not os.path.exists(COMMENTS_PATH):
44
- with open(COMMENTS_PATH, "w") as f:
45
- json.dump([], f, indent=4)
46
-
47
- try:
48
- hf_token = os.getenv("NEW_PUP_AI_Project")
49
- downloaded_path = hf_hub_download(
50
- repo_id="oceddyyy/University_Inquiries_Feedback",
51
- filename="feedback.json",
52
- repo_type="dataset",
53
- token=hf_token
54
- )
55
- with open(downloaded_path, "r") as f:
56
- feedback_data = json.load(f)
57
- feedback_questions = [item["question"] for item in feedback_data]
58
- if feedback_questions:
59
- feedback_embeddings = embedding_model.encode(feedback_questions, convert_to_tensor=True)
60
-
61
- with open(feedback_path, "w") as f_local:
62
- json.dump(feedback_data, f_local, indent=4)
63
-
64
- except Exception as e:
65
- print(f"[Startup] Feedback not loaded from Hugging Face. Using local only. Reason: {e}")
66
- feedback_data = []
67
-
68
-
69
- def upload_file_to_hf(local_path, remote_filename):
70
- """Helper to upload any file to Hugging Face dataset repo."""
71
- hf_token = os.getenv("NEW_PUP_AI_Project")
72
- if not hf_token:
73
- raise ValueError("Hugging Face token not found in environment variables!")
74
-
75
- try:
76
- upload_file(
77
- path_or_fileobj=local_path,
78
- path_in_repo=remote_filename,
79
- repo_id="oceddyyy/University_Inquiries_Feedback",
80
- repo_type="dataset",
81
- token=hf_token
82
- )
83
- print(f"{remote_filename} uploaded to Hugging Face successfully.")
84
- except Exception as e:
85
- print(f"Error uploading {remote_filename} to HF: {e}")
86
-
87
-
88
- def chatbot_response(query, dev_mode_flag):
89
- query_embedding = embedding_model.encode([query], convert_to_tensor=True)
90
-
91
- if feedback_embeddings is not None:
92
- feedback_scores = cosine_similarity(query_embedding.cpu().numpy(), feedback_embeddings.cpu().numpy())[0]
93
- best_idx = int(np.argmax(feedback_scores))
94
- best_score = feedback_scores[best_idx]
95
- matched_feedback = feedback_data[best_idx]
96
-
97
- base_threshold = 0.8
98
- upvotes = matched_feedback.get("upvotes", 0)
99
- downvotes = matched_feedback.get("downvotes", 0)
100
- adjusted_threshold = base_threshold - (0.01 * upvotes) + (0.01 * downvotes)
101
- dynamic_threshold = min(max(adjusted_threshold, 0.4), 1.0)
102
-
103
- if best_score >= dynamic_threshold:
104
- return matched_feedback["response"], "Feedback", 0.0
105
-
106
- similarity_scores = cosine_similarity(query_embedding.cpu().numpy(), question_embeddings.cpu().numpy())[0]
107
- top_k = 3
108
- top_k_indices = np.argsort(similarity_scores)[-top_k:][::-1]
109
- top_k_items = [dataset[idx] for idx in top_k_indices]
110
- top_k_scores = [similarity_scores[idx] for idx in top_k_indices]
111
-
112
- matched_item = top_k_items[0]
113
- matched_a = matched_item.get("answer", "")
114
- matched_source = matched_item.get("source", "PUP Handbook")
115
- best_score = top_k_scores[0]
116
-
117
- if dev_mode_flag:
118
- context = ""
119
- for i, item in enumerate(top_k_items):
120
- context += f"Relevant info #{i+1} (score: {top_k_scores[i]:.2f}):\n\"{item.get('answer', '')}\"\n\n"
121
-
122
- prompt = (
123
- f"You are an expert university assistant. "
124
- f"A student asked: \"{query}\"\n"
125
- f"Here are the most relevant handbook information snippets:\n{context}"
126
- f"Using only the information above, answer the student's question in your own words. "
127
- f"If the handbook info is not relevant, say you don't know."
128
- )
129
-
130
- try:
131
- start_time = time.time()
132
- response = ""
133
-
134
- if hasattr(inference_client, "chat_completion"):
135
- conversation = [
136
- {"role": "system", "content": "You are an expert university assistant."},
137
- {"role": "user", "content": prompt}
138
- ]
139
- llm_response = inference_client.chat_completion(
140
- messages=conversation,
141
- model="mistralai/Mixtral-8x7B-Instruct-v0.1",
142
- max_tokens=200,
143
- temperature=0.7
144
- )
145
- if isinstance(llm_response, dict) and "choices" in llm_response:
146
- response = llm_response["choices"][0]["message"]["content"]
147
- elif hasattr(llm_response, "generated_text"):
148
- response = llm_response.generated_text
149
- else:
150
- llm_response = inference_client.text_generation(
151
- prompt,
152
- max_new_tokens=200,
153
- temperature=0.7
154
- )
155
- if isinstance(llm_response, dict) and "generated_text" in llm_response:
156
- response = llm_response["generated_text"]
157
- elif hasattr(llm_response, "generated_text"):
158
- response = llm_response.generated_text
159
-
160
- elapsed = time.time() - start_time
161
-
162
- if not response.strip() or response.strip() == matched_a.strip():
163
- if "month" in matched_item and "year" in matched_item:
164
- response = f"As of {matched_item['month']}, {matched_item['year']}, {matched_a}"
165
- else:
166
- response = f"According to 2019 Proposed PUP Handbook, {matched_a}"
167
- return response.strip(), matched_source, elapsed
168
-
169
- except Exception as e:
170
- error_msg = f"[ERROR] HF inference failed: {e}"
171
- return f"(UnivAI+++ error: {error_msg})", matched_source, 0.0
172
-
173
- if best_score < 0.4:
174
- response = "Sorry, but the PUP handbook does not contain such information."
175
- else:
176
- if "month" in matched_item and "year" in matched_item:
177
- response = f"As of {matched_item['month']}, {matched_item['year']}, {matched_a}"
178
- else:
179
- response = f"According to 2019 Proposed PUP Handbook, {matched_a}"
180
- return response.strip(), matched_source, 0.0
181
-
182
-
183
- def record_feedback(feedback, query, response, comment=None):
184
- """Records user feedback and optional comment."""
185
- global feedback_embeddings, feedback_questions
186
- matched = False
187
- new_embedding = embedding_model.encode([query], convert_to_tensor=True)
188
-
189
- for item in feedback_data:
190
- existing_embedding = embedding_model.encode([item["question"]], convert_to_tensor=True)
191
- similarity = cosine_similarity(existing_embedding.cpu().numpy(), new_embedding.cpu().numpy())[0][0]
192
- if similarity >= 0.8 and item["response"] == response:
193
- matched = True
194
- votes = {"positive": "upvotes", "negative": "downvotes"}
195
- item[votes[feedback]] = item.get(votes[feedback], 0) + 1
196
- break
197
-
198
- if not matched:
199
- entry = {
200
- "question": query,
201
- "response": response,
202
- "feedback": feedback,
203
- "upvotes": 1 if feedback == "positive" else 0,
204
- "downvotes": 1 if feedback == "negative" else 0
205
- }
206
- feedback_data.append(entry)
207
-
208
- with open(feedback_path, "w") as f:
209
- json.dump(feedback_data, f, indent=4)
210
-
211
- feedback_questions = [item["question"] for item in feedback_data]
212
- if feedback_questions:
213
- feedback_embeddings = embedding_model.encode(feedback_questions, convert_to_tensor=True)
214
-
215
- upload_file_to_hf(feedback_path, "feedback.json")
216
-
217
-
218
- if comment and comment.strip():
219
- try:
220
- with open(COMMENTS_PATH, "r") as f:
221
- comments_list = json.load(f)
222
- except json.JSONDecodeError:
223
- comments_list = []
224
-
225
- comment_entry = {
226
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
227
- "question": query,
228
- "response": response,
229
- "feedback": feedback,
230
- "comment": comment.strip()
231
- }
232
- comments_list.append(comment_entry)
233
-
234
- with open(COMMENTS_PATH, "w") as f:
235
- json.dump(comments_list, f, indent=4)
236
-
237
- upload_file_to_hf(COMMENTS_PATH, "Comments.json")
238
-
239
- app = Flask(__name__)
240
-
241
- @app.route("/api/chat", methods=["POST"])
242
- def chat():
243
- data = request.json
244
- query = data.get("query", "")
245
- dev = data.get("dev_mode", False)
246
- dev_mode["enabled"] = dev
247
- response, source, elapsed = chatbot_response(query, dev)
248
- return jsonify({"response": response, "source": source, "response_time": elapsed})
249
-
250
- @app.route("/api/feedback", methods=["POST"])
251
- def feedback():
252
- data = request.json
253
- query = data.get("query", "")
254
- response = data.get("response", "")
255
- feedback_type = data.get("feedback", "")
256
- comment = data.get("comment", None)
257
- record_feedback(feedback_type, query, response, comment)
258
- return jsonify({"status": "success"})
259
-
260
- @app.route("/", methods=["GET"])
261
- def index():
262
- return "University Inquiries AI Chatbot API. Use POST /api/chat or /api/feedback.", 200
263
-
264
- if __name__ == "__main__":
265
- app.run(host="0.0.0.0", port=7861)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from sentence_transformers import SentenceTransformer
4
+ from sklearn.metrics.pairwise import cosine_similarity
5
+ import numpy as np
6
+ from huggingface_hub import upload_file, hf_hub_download, InferenceClient
7
+ from flask import Flask, request, jsonify
8
+ import time
9
+ import tempfile
10
+
11
+
12
+ # === Setup cache directories ===
13
+ os.environ["HF_HOME"] = "/tmp/.cache"
14
+ os.environ["HF_DATASETS_CACHE"] = "/tmp/.cache"
15
+ os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/.cache"
16
+ os.makedirs("/tmp/.cache", exist_ok=True)
17
+ os.makedirs("/tmp/outputs", exist_ok=True)
18
+
19
+
20
+ # === Initialize models ===
21
+ embedding_model = SentenceTransformer('paraphrase-mpnet-base-v2')
22
+ token = os.getenv("HF_TOKEN") or os.getenv("NEW_PUP_AI_Project")
23
+ inference_client = InferenceClient(
24
+ model="mistralai/Mixtral-8x7B-Instruct-v0.1",
25
+ token=token
26
+ )
27
+
28
+
29
+ # === Load dataset ===
30
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
31
+ DATASET_PATH = os.path.join(BASE_DIR, "dataset.json")
32
+ with open(DATASET_PATH, "r") as f:
33
+ dataset = json.load(f)
34
+
35
+ questions = [item["question"] for item in dataset]
36
+ answers = [item["answer"] for item in dataset]
37
+ question_embeddings = embedding_model.encode(questions, convert_to_tensor=True)
38
+
39
+
40
+ # === Feedback system setup ===
41
+ feedback_data = []
42
+ feedback_questions = []
43
+ feedback_embeddings = None
44
+ dev_mode = {"enabled": False}
45
+ feedback_path = "/tmp/outputs/feedback.json"
46
+ COMMENTS_PATH = "/tmp/outputs/Comments.json"
47
+
48
+ if not os.path.exists(COMMENTS_PATH):
49
+ with open(COMMENTS_PATH, "w") as f:
50
+ json.dump([], f, indent=4)
51
+
52
+ try:
53
+ hf_token = os.getenv("NEW_PUP_AI_Project")
54
+ downloaded_path = hf_hub_download(
55
+ repo_id="oceddyyy/University_Inquiries_Feedback",
56
+ filename="feedback.json",
57
+ repo_type="dataset",
58
+ token=hf_token
59
+ )
60
+ with open(downloaded_path, "r") as f:
61
+ feedback_data = json.load(f)
62
+ feedback_questions = [item["question"] for item in feedback_data]
63
+ if feedback_questions:
64
+ feedback_embeddings = embedding_model.encode(feedback_questions, convert_to_tensor=True)
65
+
66
+ with open(feedback_path, "w") as f_local:
67
+ json.dump(feedback_data, f_local, indent=4)
68
+
69
+ except Exception as e:
70
+ print(f"[Startup] Feedback not loaded from Hugging Face. Using local only. Reason: {e}")
71
+ feedback_data = []
72
+
73
+
74
+ # === Helper: upload file to HF ===
75
+ def upload_file_to_hf(local_path, remote_filename):
76
+ """Upload a single file to Hugging Face dataset repo."""
77
+ hf_token = os.getenv("NEW_PUP_AI_Project")
78
+ if not hf_token:
79
+ raise ValueError("Hugging Face token not found in environment variables!")
80
+
81
+ try:
82
+ upload_file(
83
+ path_or_fileobj=local_path,
84
+ path_in_repo=remote_filename,
85
+ repo_id="oceddyyy/University_Inquiries_Feedback",
86
+ repo_type="dataset",
87
+ token=hf_token
88
+ )
89
+ print(f"[UPLOAD] {remote_filename} uploaded successfully to Hugging Face.")
90
+ except Exception as e:
91
+ print(f"[ERROR] Upload failed for {remote_filename}: {e}")
92
+
93
+
94
+ # === Chatbot core ===
95
+ def chatbot_response(query, dev_mode_flag):
96
+ query_embedding = embedding_model.encode([query], convert_to_tensor=True)
97
+
98
+ # Check feedback-based matches first
99
+ if feedback_embeddings is not None:
100
+ feedback_scores = cosine_similarity(query_embedding.cpu().numpy(), feedback_embeddings.cpu().numpy())[0]
101
+ best_idx = int(np.argmax(feedback_scores))
102
+ best_score = feedback_scores[best_idx]
103
+ matched_feedback = feedback_data[best_idx]
104
+
105
+ base_threshold = 0.8
106
+ upvotes = matched_feedback.get("upvotes", 0)
107
+ downvotes = matched_feedback.get("downvotes", 0)
108
+ adjusted_threshold = base_threshold - (0.01 * upvotes) + (0.01 * downvotes)
109
+ dynamic_threshold = min(max(adjusted_threshold, 0.4), 1.0)
110
+
111
+ if best_score >= dynamic_threshold:
112
+ return matched_feedback["response"], "Feedback", 0.0
113
+
114
+ # Otherwise, use main dataset
115
+ similarity_scores = cosine_similarity(query_embedding.cpu().numpy(), question_embeddings.cpu().numpy())[0]
116
+ top_k = 3
117
+ top_k_indices = np.argsort(similarity_scores)[-top_k:][::-1]
118
+ top_k_items = [dataset[idx] for idx in top_k_indices]
119
+ top_k_scores = [similarity_scores[idx] for idx in top_k_indices]
120
+
121
+ matched_item = top_k_items[0]
122
+ matched_a = matched_item.get("answer", "")
123
+ matched_source = matched_item.get("source", "PUP Handbook")
124
+ best_score = top_k_scores[0]
125
+
126
+ # Developer mode (LLM synthesis)
127
+ if dev_mode_flag:
128
+ context = ""
129
+ for i, item in enumerate(top_k_items):
130
+ context += f"Relevant info #{i+1} (score: {top_k_scores[i]:.2f}):\n\"{item.get('answer', '')}\"\n\n"
131
+
132
+ prompt = (
133
+ f"You are an expert university assistant. "
134
+ f"A student asked: \"{query}\"\n"
135
+ f"Here are the most relevant handbook information snippets:\n{context}"
136
+ f"Using only the information above, answer the student's question in your own words. "
137
+ f"If the handbook info is not relevant, say you don't know."
138
+ )
139
+
140
+ try:
141
+ start_time = time.time()
142
+ response = ""
143
+
144
+ if hasattr(inference_client, "chat_completion"):
145
+ conversation = [
146
+ {"role": "system", "content": "You are an expert university assistant."},
147
+ {"role": "user", "content": prompt}
148
+ ]
149
+ llm_response = inference_client.chat_completion(
150
+ messages=conversation,
151
+ model="mistralai/Mixtral-8x7B-Instruct-v0.1",
152
+ max_tokens=200,
153
+ temperature=0.7
154
+ )
155
+ if isinstance(llm_response, dict) and "choices" in llm_response:
156
+ response = llm_response["choices"][0]["message"]["content"]
157
+ elif hasattr(llm_response, "generated_text"):
158
+ response = llm_response.generated_text
159
+ else:
160
+ llm_response = inference_client.text_generation(
161
+ prompt,
162
+ max_new_tokens=200,
163
+ temperature=0.7
164
+ )
165
+ if isinstance(llm_response, dict) and "generated_text" in llm_response:
166
+ response = llm_response["generated_text"]
167
+ elif hasattr(llm_response, "generated_text"):
168
+ response = llm_response.generated_text
169
+
170
+ elapsed = time.time() - start_time
171
+
172
+ if not response.strip() or response.strip() == matched_a.strip():
173
+ if "month" in matched_item and "year" in matched_item:
174
+ response = f"As of {matched_item['month']}, {matched_item['year']}, {matched_a}"
175
+ else:
176
+ response = f"According to 2019 Proposed PUP Handbook, {matched_a}"
177
+ return response.strip(), matched_source, elapsed
178
+
179
+ except Exception as e:
180
+ error_msg = f"[ERROR] HF inference failed: {e}"
181
+ return f"(UnivAI+++ error: {error_msg})", matched_source, 0.0
182
+
183
+ # Normal retrieval mode
184
+ if best_score < 0.4:
185
+ response = "Sorry, but the PUP handbook does not contain such information."
186
+ else:
187
+ if "month" in matched_item and "year" in matched_item:
188
+ response = f"As of {matched_item['month']}, {matched_item['year']}, {matched_a}"
189
+ else:
190
+ response = f"According to 2019 Proposed PUP Handbook, {matched_a}"
191
+ return response.strip(), matched_source, 0.0
192
+
193
+
194
+ # === Improved feedback recording (uploads only new/updated entries) ===
195
+ def record_feedback(feedback_type, user_query, chatbot_response_text, comment=None):
196
+ """Records feedback and uploads only new or updated entry."""
197
+ global feedback_embeddings, feedback_questions
198
+ matched = False
199
+ new_embedding = embedding_model.encode([user_query], convert_to_tensor=True)
200
+
201
+ for item in feedback_data:
202
+ existing_embedding = embedding_model.encode([item["question"]], convert_to_tensor=True)
203
+ similarity = cosine_similarity(existing_embedding.cpu().numpy(), new_embedding.cpu().numpy())[0][0]
204
+ if similarity >= 0.8 and item["response"] == chatbot_response_text:
205
+ matched = True
206
+ votes = {"positive": "upvotes", "negative": "downvotes"}
207
+ item[votes[feedback_type]] = item.get(votes[feedback_type], 0) + 1
208
+ # Only upload the updated item (not full dataset)
209
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".json") as tempf:
210
+ json.dump([item], tempf, indent=4)
211
+ tempf_path = tempf.name
212
+ upload_file_to_hf(tempf_path, "latest_feedback_update.json")
213
+ os.remove(tempf_path)
214
+ break
215
+
216
+ if not matched:
217
+ entry = {
218
+ "question": user_query,
219
+ "response": chatbot_response_text,
220
+ "feedback": feedback_type,
221
+ "upvotes": 1 if feedback_type == "positive" else 0,
222
+ "downvotes": 1 if feedback_type == "negative" else 0
223
+ }
224
+ feedback_data.append(entry)
225
+
226
+ # Save only this new entry remotely
227
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".json") as tempf:
228
+ json.dump([entry], tempf, indent=4)
229
+ tempf_path = tempf.name
230
+ upload_file_to_hf(tempf_path, "latest_feedback_entry.json")
231
+ os.remove(tempf_path)
232
+
233
+ # Always update local JSON + embeddings
234
+ with open(feedback_path, "w") as f:
235
+ json.dump(feedback_data, f, indent=4)
236
+
237
+ feedback_questions = [item["question"] for item in feedback_data]
238
+ if feedback_questions:
239
+ feedback_embeddings = embedding_model.encode(feedback_questions, convert_to_tensor=True)
240
+
241
+ # Comment saving (optional)
242
+ if comment and comment.strip():
243
+ try:
244
+ with open(COMMENTS_PATH, "r") as f:
245
+ comments_list = json.load(f)
246
+ except json.JSONDecodeError:
247
+ comments_list = []
248
+
249
+ comment_entry = {
250
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
251
+ "question": user_query,
252
+ "response": chatbot_response_text,
253
+ "feedback": feedback_type,
254
+ "comment": comment.strip()
255
+ }
256
+ comments_list.append(comment_entry)
257
+
258
+ with open(COMMENTS_PATH, "w") as f:
259
+ json.dump(comments_list, f, indent=4)
260
+
261
+ # Upload only the latest comment
262
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".json") as tempf:
263
+ json.dump([comment_entry], tempf, indent=4)
264
+ tempf_path = tempf.name
265
+ upload_file_to_hf(tempf_path, "latest_comment_entry.json")
266
+ os.remove(tempf_path)
267
+
268
+
269
+ # === Flask API routes ===
270
+ app = Flask(__name__)
271
+
272
+ @app.route("/api/chat", methods=["POST"])
273
+ def chat():
274
+ data = request.json
275
+ query = data.get("query", "")
276
+ dev = data.get("dev_mode", False)
277
+ dev_mode["enabled"] = dev
278
+ response, source, elapsed = chatbot_response(query, dev)
279
+ return jsonify({"response": response, "source": source, "response_time": elapsed})
280
+
281
+ @app.route("/api/feedback", methods=["POST"])
282
+ def feedback():
283
+ data = request.json
284
+ user_query = data.get("query", "")
285
+ chatbot_resp = data.get("response", "")
286
+ feedback_type = data.get("feedback", "")
287
+ comment = data.get("comment", None)
288
+ record_feedback(feedback_type, user_query, chatbot_resp, comment)
289
+ return jsonify({"status": "success"})
290
+
291
+ @app.route("/", methods=["GET"])
292
+ def index():
293
+ return "University Inquiries AI Chatbot API. Use POST /api/chat or /api/feedback.", 200
294
+
295
+
296
+ if __name__ == "__main__":
297
+ app.run(host="0.0.0.0", port=7861)