IAUCourseExp commited on
Commit
27e86ed
·
verified ·
1 Parent(s): 6d64952

Upload 3 files

Browse files
Files changed (4) hide show
  1. .gitattributes +1 -0
  2. iau_metadata.json +0 -0
  3. iau_reviews_index.faiss +3 -0
  4. my_logic.py +145 -10
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ iau_reviews_index.faiss filter=lfs diff=lfs merge=lfs -text
iau_metadata.json ADDED
The diff for this file is too large to render. See raw diff
 
iau_reviews_index.faiss ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:abf65d8eea4185c36a23cf1fcc39661a8c0f918633d444395103529424777b93
3
+ size 1276461
my_logic.py CHANGED
@@ -1,16 +1,115 @@
1
  import pandas as pd
2
  from collections import defaultdict
3
  from difflib import SequenceMatcher
 
 
 
 
 
 
4
 
 
 
 
 
 
 
 
 
 
 
 
5
  # Load reviews CSV
6
- metadata = pd.read_csv("cleaned_iau_reviews.csv").to_dict(orient="records")
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  def similar(a, b):
9
  return SequenceMatcher(None, a, b).ratio()
10
 
 
11
  def keyword_match_reviews(query, metadata):
12
  query = query.strip().replace("؟", "")
13
  keywords = set(query.split())
 
14
  results = []
15
  for row in metadata:
16
  prof = str(row["professor"])
@@ -21,6 +120,7 @@ def keyword_match_reviews(query, metadata):
21
  break
22
  return results
23
 
 
24
  def relevance_score(row, query):
25
  score = 0
26
  if row["professor"] in query:
@@ -33,9 +133,11 @@ def relevance_score(row, query):
33
  score += 1
34
  return score
35
 
 
36
  def build_strict_context(reviews, user_question):
37
  prof_match_scores = defaultdict(int)
38
  course_match_scores = defaultdict(int)
 
39
  for r in reviews:
40
  prof_sim = similar(user_question, r["professor"])
41
  course_sim = similar(user_question, r["course"])
@@ -48,7 +150,10 @@ def build_strict_context(reviews, user_question):
48
  best_course = max(course_match_scores, key=course_match_scores.get, default="")
49
 
50
  if best_prof and best_course:
51
- filtered = [r for r in reviews if similar(best_prof, r["professor"]) > 0.85 and similar(best_course, r["course"]) > 0.85]
 
 
 
52
  elif best_course:
53
  filtered = [r for r in reviews if similar(best_course, r["course"]) > 0.85]
54
  elif best_prof:
@@ -56,11 +161,13 @@ def build_strict_context(reviews, user_question):
56
  else:
57
  filtered = reviews
58
 
 
59
  result = f"👨‍🏫 استاد: {best_prof or '[نامشخص]'} — 📚 درس: {best_course or '[نامشخص]'}\n💬 نظرات:\n"
60
  for i, r in enumerate(filtered, 1):
61
  result += f"{i}. {r['comment'].strip()}\n🔗 لینک: {r['link']}\n\n"
62
  return result
63
 
 
64
  def truncate_reviews_to_fit(reviews, max_chars=127000):
65
  total = 0
66
  final = []
@@ -72,17 +179,39 @@ def truncate_reviews_to_fit(reviews, max_chars=127000):
72
  total += size
73
  return final
74
 
75
- def answer_question(user_question, model):
 
76
  print(f"\n🧠 Starting debug for question: {user_question}")
77
- retrieved = keyword_match_reviews(user_question, metadata)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  if not retrieved:
79
  return "❌ هیچ تجربه‌ای در مورد سوال شما در داده‌های کانال یافت نشد."
80
 
81
  retrieved.sort(key=lambda r: relevance_score(r, user_question), reverse=True)
82
  retrieved = truncate_reviews_to_fit(retrieved)
 
 
83
  context = build_strict_context(retrieved, user_question)
 
84
 
85
- prompt = f"""شما یک دستیار هوشمند انتخاب واحد هستید که فقط و فقط بر اساس نظرات واقعی دانشجویان از کانال @IAUCourseExp پاسخ می‌دهید.
86
 
87
  ❗ قوانین مهم:
88
  - فقط از داده‌های همین نظرات استفاده کن. هیچ اطلاعات اضافی، حدسی یا اینترنتی استفاده نکن.
@@ -92,18 +221,24 @@ def answer_question(user_question, model):
92
  • مقایسه چند استاد برای یک درس
93
  • معرفی بهترین یا بدترین استادهای یک درس
94
  • تحلیل نظر کلی دانشجویان درمورد یک درس خاص
95
- - همه‌ی نظرات مربوطه را تحلیل کن و لینکشان را هم بنویس.
96
- - نتیجه‌گیری نهایی در انتها اضافه کن.
 
 
 
 
97
 
98
  🔎 سوال دانشجو:
99
  {user_question}
100
 
101
- 📄 نظرات دانشجویان:
102
  {context}
103
 
104
  📘 پاسخ نهایی:
105
- 📊 این پاسخ بر اساس بررسی {len(retrieved)} نظر دانشجویی نوشته شده است.
106
  """
107
 
108
- response = model.generate_content(prompt)
 
 
 
109
  return response.text
 
1
  import pandas as pd
2
  from collections import defaultdict
3
  from difflib import SequenceMatcher
4
+ from sentence_transformers import SentenceTransformer
5
+ import faiss
6
+ import json
7
+ import torch
8
+ import numpy as np
9
+ from transformers import AutoTokenizer, AutoModel
10
 
11
+
12
+ # Load CSV
13
+
14
+ # Load FAISS index and metadata
15
+ index = faiss.read_index("iau_reviews_index.faiss")
16
+ with open("iau_metadata.json", "r", encoding="utf-8") as f:
17
+ metadata = json.load(f)
18
+
19
+
20
+
21
+ model = SentenceTransformer("HooshvareLab/bert-fa-zwnj-base")
22
  # Load reviews CSV
 
23
 
24
+
25
+ # Load Persian tokenizer and model
26
+ tokenizer = AutoTokenizer.from_pretrained("HooshvareLab/bert-fa-zwnj-base")
27
+ model = AutoModel.from_pretrained("HooshvareLab/bert-fa-zwnj-base").eval()
28
+
29
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
30
+ model.to(device)
31
+
32
+ # Load FAISS index and metadata
33
+ index = faiss.read_index("iau_reviews_index.faiss")
34
+ with open("iau_metadata.json", "r", encoding="utf-8") as f:
35
+ metadata = json.load(f)
36
+
37
+ def mean_pooling(model_output, attention_mask):
38
+ token_embeddings = model_output[0]
39
+ input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size())
40
+ return (token_embeddings * input_mask_expanded).sum(1) / input_mask_expanded.sum(1)
41
+
42
+ def encode_texts(texts, batch_size=16):
43
+ embeddings = []
44
+ with torch.no_grad():
45
+ for i in range(0, len(texts), batch_size):
46
+ batch = texts[i:i+batch_size]
47
+ encoded_input = tokenizer(batch, padding=True, truncation=True, return_tensors='pt', max_length=128).to(device)
48
+ model_output = model(**encoded_input)
49
+ sentence_embeddings = mean_pooling(model_output, encoded_input['attention_mask'])
50
+ sentence_embeddings = sentence_embeddings.cpu().numpy()
51
+ embeddings.append(sentence_embeddings)
52
+ return np.vstack(embeddings)
53
+
54
+ def search_reviews(query, top_k=5):
55
+
56
+ keywords = query.strip().split()
57
+
58
+ candidate_rows = [
59
+ r for r in metadata
60
+ if any(kw in r["professor"] or kw in r["course"] for kw in keywords)
61
+ ]
62
+
63
+ if not candidate_rows:
64
+ return []
65
+
66
+ texts = [r["course"] + " " + r["professor"] + " " + r["comment"] for r in candidate_rows]
67
+ vectors = encode_texts(texts)
68
+ vectors = vectors / np.linalg.norm(vectors, axis=1, keepdims=True)
69
+
70
+ query_vec = encode_texts([query])
71
+ query_vec = query_vec / np.linalg.norm(query_vec, axis=1, keepdims=True)
72
+
73
+
74
+ local_index = faiss.IndexFlatIP(vectors.shape[1])
75
+ local_index.add(vectors)
76
+
77
+ D, I = local_index.search(query_vec, min(top_k, len(candidate_rows)))
78
+
79
+ return [candidate_rows[i] for i in I[0]]
80
+
81
+
82
+ def filter_relevant(results, query):
83
+ query = query.replace("؟", "").strip()
84
+ query_tokens = set(query.split())
85
+
86
+ def is_strict_match(row):
87
+ # Normalize and tokenize professor and course
88
+ prof_tokens = set(str(row["professor"]).strip().split())
89
+ course_tokens = set(str(row["course"]).strip().split())
90
+
91
+ # Match only if full token overlap exists (not substrings)
92
+ match_prof = prof_tokens & query_tokens
93
+ match_course = course_tokens & query_tokens
94
+
95
+ return bool(match_prof or match_course)
96
+
97
+ # Return all matching results
98
+ return [r for r in results if is_strict_match(r)]
99
+
100
+
101
+
102
+
103
+
104
+ # ---- Fuzzy similarity score ----
105
  def similar(a, b):
106
  return SequenceMatcher(None, a, b).ratio()
107
 
108
+ # ---- Enhanced keyword fallback ----
109
  def keyword_match_reviews(query, metadata):
110
  query = query.strip().replace("؟", "")
111
  keywords = set(query.split())
112
+
113
  results = []
114
  for row in metadata:
115
  prof = str(row["professor"])
 
120
  break
121
  return results
122
 
123
+ # ---- Sort by relevance ----
124
  def relevance_score(row, query):
125
  score = 0
126
  if row["professor"] in query:
 
133
  score += 1
134
  return score
135
 
136
+ # ---- Strict context builder (best prof+course only) ----
137
  def build_strict_context(reviews, user_question):
138
  prof_match_scores = defaultdict(int)
139
  course_match_scores = defaultdict(int)
140
+
141
  for r in reviews:
142
  prof_sim = similar(user_question, r["professor"])
143
  course_sim = similar(user_question, r["course"])
 
150
  best_course = max(course_match_scores, key=course_match_scores.get, default="")
151
 
152
  if best_prof and best_course:
153
+ filtered = [
154
+ r for r in reviews
155
+ if similar(best_prof, r["professor"]) > 0.85 and similar(best_course, r["course"]) > 0.85
156
+ ]
157
  elif best_course:
158
  filtered = [r for r in reviews if similar(best_course, r["course"]) > 0.85]
159
  elif best_prof:
 
161
  else:
162
  filtered = reviews
163
 
164
+
165
  result = f"👨‍🏫 استاد: {best_prof or '[نامشخص]'} — 📚 درس: {best_course or '[نامشخص]'}\n💬 نظرات:\n"
166
  for i, r in enumerate(filtered, 1):
167
  result += f"{i}. {r['comment'].strip()}\n🔗 لینک: {r['link']}\n\n"
168
  return result
169
 
170
+ # ---- Truncation helper ----
171
  def truncate_reviews_to_fit(reviews, max_chars=127000):
172
  total = 0
173
  final = []
 
179
  total += size
180
  return final
181
 
182
+ # ---- Main answer function ----
183
+ def answer_question(user_question):
184
  print(f"\n🧠 Starting debug for question: {user_question}")
185
+
186
+ retrieved = search_reviews(user_question, top_k=100)
187
+ print(f"🔍 FAISS returned {len(retrieved)} raw rows")
188
+
189
+ retrieved = filter_relevant(retrieved, user_question)
190
+ print(f"✅ After filter_relevant(): {len(retrieved)} rows")
191
+
192
+ keyword_hits = keyword_match_reviews(user_question, metadata)
193
+ print(f"🔠 Keyword hits found: {len(keyword_hits)}")
194
+
195
+ existing_links = set(r["link"] for r in retrieved)
196
+ added = 0
197
+ for r in keyword_hits:
198
+ if r["link"] not in existing_links:
199
+ retrieved.append(r)
200
+ added += 1
201
+ print(f"➕ Added {added} unique fallback keyword rows")
202
+ print(f"📊 Total before truncation: {len(retrieved)}")
203
+
204
  if not retrieved:
205
  return "❌ هیچ تجربه‌ای در مورد سوال شما در داده‌های کانال یافت نشد."
206
 
207
  retrieved.sort(key=lambda r: relevance_score(r, user_question), reverse=True)
208
  retrieved = truncate_reviews_to_fit(retrieved)
209
+ print(f"✂️ After truncation: {len(retrieved)} rows")
210
+
211
  context = build_strict_context(retrieved, user_question)
212
+ print("📝 Sample context sent to GPT:\n", context[:100000], "\n...")
213
 
214
+ prompt = f"""شما یک دستیار هوشمند انتخاب واحد هستید که فقط و فقط بر اساس نظرات واقعی دانشجویان از کانال @IAUCourseExp پاسخ می‌دهید. کار شما کمک به دانشجویان برای انتخاب استاد و درس، بر اساس تجربیات ثبت‌شده در این کانال است.
215
 
216
  ❗ قوانین مهم:
217
  - فقط از داده‌های همین نظرات استفاده کن. هیچ اطلاعات اضافی، حدسی یا اینترنتی استفاده نکن.
 
221
  • مقایسه چند استاد برای یک درس
222
  • معرفی بهترین یا بدترین استادهای یک درس
223
  • تحلیل نظر کلی دانشجویان درمورد یک درس خاص
224
+ بنابراین آماده باش که با توجه به داده‌ها به هر نوع سوال، دقیق و قابل اعتماد پاسخ بدهی.
225
+ - همه‌ی نظرات مربوط به سوال را بررسی کن (نه فقط یکی یا دو تا) و به‌صورت فهرست‌وار یا خلاصه‌شده تحلیلشان کن.
226
+ - برای هر نظر، لینک تلگرام مربوطه را نیز حتماً ذکر کن.
227
+ - در پایان پاسخ، نتیجه‌گیری نهایی خود را بنویس: آیا این استاد برای این درس توصیه می‌شود یا نه — فقط بر اساس همین نظرات.
228
+ - در انتها حتماً بنویس:
229
+ 📊 این پاسخ بر اساس بررسی {len(retrieved)} نظر دانشجویی نوشته شده است.
230
 
231
  🔎 سوال دانشجو:
232
  {user_question}
233
 
234
+ 📄 نظرات دانشجویان (برگرفته از کانال تجربیات انتخاب واحد):
235
  {context}
236
 
237
  📘 پاسخ نهایی:
 
238
  """
239
 
240
+
241
+ # NEW (Gemini)
242
+
243
+ response = gemini_model.generate_content(prompt)
244
  return response.text