DrPie commited on
Commit
25ffee2
·
verified ·
1 Parent(s): ea8833b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -71
app.py CHANGED
@@ -1,15 +1,8 @@
1
- # app.py
2
- import os, shutil, gzip, pickle, re, json
3
- import numpy as np
4
- import faiss
5
- from sentence_transformers import SentenceTransformer
6
- from rank_bm25 import BM25Okapi
7
- import google.generativeai as genai
8
- from flask import Flask, request, jsonify
9
- from flask_cors import CORS
10
- from huggingface_hub import login, hf_hub_download
11
 
12
- # --- CACHE CONFIG (HF Spaces chỉ ghi vào /tmp) ---
13
  os.environ["HF_HOME"] = "/tmp/hf_home"
14
  os.environ["TRANSFORMERS_CACHE"] = "/tmp/hf_cache"
15
  os.environ["HF_DATASETS_CACHE"] = "/tmp/hf_datasets"
@@ -19,93 +12,86 @@ os.makedirs("/tmp/.cache", exist_ok=True)
19
  shutil.rmtree("/.cache", ignore_errors=True)
20
 
21
  # --- LOGIN HF HUB ---
 
22
  HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN")
23
  if HF_TOKEN:
24
  login(HF_TOKEN)
25
  else:
26
- print("⚠️ Warning: HF token not found. Chỉ truy cập public repo được thôi.")
27
 
28
  # --- LOAD DỮ LIỆU ---
29
- HF_REPO_ID = "DrPie/eGoV_Data"
30
  REPO_TYPE = "dataset"
31
 
 
 
 
 
 
 
 
32
  print("--- KHỞI ĐỘNG MÁY CHỦ CHATBOT ---")
33
  try:
 
34
  RAW_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="toan_bo_du_lieu_final.json", repo_type=REPO_TYPE)
35
- FAISS_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="index.faiss", repo_type=REPO_TYPE)
36
- METAS_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="metas.pkl.gz", repo_type=REPO_TYPE)
37
  BM25_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="bm25.pkl.gz", repo_type=REPO_TYPE)
38
  IDMAP_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="id_to_record.pkl", repo_type=REPO_TYPE)
39
 
40
- print("✅ Đã tải file dữ liệu từ HF Hub!")
41
 
42
  API_KEY = os.environ.get("GOOGLE_API_KEY")
43
  if not API_KEY:
44
- raise ValueError("GOOGLE_API_KEY chưa trong Secrets")
45
  genai.configure(api_key=API_KEY)
46
-
47
  generation_model = genai.GenerativeModel('gemini-2.5-flash')
48
- embedding_model = SentenceTransformer("AITeamVN/Vietnamese_Embedding")
49
 
50
- faiss_index = faiss.read_index(FAISS_PATH)
51
- with gzip.open(METAS_PATH, "rb") as f:
52
- metadatas = pickle.load(f)
53
  with gzip.open(BM25_PATH, "rb") as f:
54
  bm25 = pickle.load(f)
55
  with open(IDMAP_PATH, "rb") as f:
56
  procedure_map = pickle.load(f)
57
 
58
- print(f"✅ Sẵn {faiss_index.ntotal} chunks kiến thức.")
59
- print(f"✅ Có {len(procedure_map)} thủ tục hành chính.")
60
  except Exception as e:
61
  print(f"❌ Lỗi khi tải tài nguyên: {e}")
62
 
63
- # --- HÀM XỬ LÝ ---
64
  def classify_followup(text: str):
65
  text = text.lower().strip()
66
  score = 0
67
- strong_followup = [r"\b(nó|cái (này|đó|ấy)|thủ tục (này|đó|ấy))\b",
68
- r"\b(vừa (nói|hỏi)|trước đó|ở trên|phía trên)\b",
69
- r"\b(tiếp theo|tiếp|còn nữa|ngoài ra)\b",
70
- r"\b(thế (thì|à)|vậy (thì|à)|như vậy)\b"]
71
- detail_qs = [r"\b(mất bao lâu|thời gian|bao nhiêu tiền|chi phí|phí)\b",
72
- r"\b(ở đâu|tại đâu|chỗ nào|địa chỉ)\b",
73
- r"\b(cần (gì|những gì)|yêu cầu|điều kiện)\b"]
74
- specific_services = [r"\b(làm|cấp|gia hạn|đổi|đăng ký)\s+(căn cước|cmnd|cccd)\b",
75
- r"\b(làm|cấp|gia hạn|đổi)\s+hộ chiếu\b",
76
- r"\b(đăng ký)\s+(kết hôn|sinh|tử|hộ khẩu)\b"]
77
- if any(re.search(p, text) for p in strong_followup): score -= 3
78
- if any(re.search(p, text) for p in detail_qs): score -= 2
79
- if any(re.search(p, text) for p in specific_services): score += 3
80
- if len(text.split()) <= 4: score -= 1
 
 
 
 
 
 
 
 
 
 
81
  return 0 if score < 0 else 1
82
 
83
- def minmax_scale(arr):
84
- arr = np.array(arr, dtype="float32")
85
- if len(arr) == 0 or np.max(arr) == np.min(arr):
86
- return np.zeros_like(arr)
87
- return (arr - np.min(arr)) / (np.max(arr) - np.min(arr))
88
-
89
- def hybrid_retrieve(query: str, top_k=3, alpha=0.7):
90
- """Hybrid search: kết hợp semantic (FAISS) và lexical (BM25)."""
91
- qv = embedding_model.encode([query], normalize_embeddings=True).astype("float32")
92
- D, I = faiss_index.search(qv, top_k * 5)
93
- vec_scores = (1 - D[0]).tolist()
94
- vec_idx = I[0].tolist()
95
-
96
  tokenized_query = query.split()
97
- bm25_scores_all = bm25.get_scores(tokenized_query)
98
- bm25_top_idx = np.argsort(-bm25_scores_all)[:top_k * 5].tolist()
99
-
100
- # Gộp và chuẩn hóa điểm
101
- union_idx = list(dict.fromkeys(vec_idx + bm25_top_idx))
102
- vec_map = {i: s for i, s in zip(vec_idx, vec_scores)}
103
- vec_list = [vec_map.get(i, 0.0) for i in union_idx]
104
- bm25_list = [bm25_scores_all[i] for i in union_idx]
105
- fused = alpha * minmax_scale(vec_list) + (1 - alpha) * minmax_scale(bm25_list)
106
-
107
- order = np.argsort(-fused)
108
- return [union_idx[i] for i in order[:top_k]]
109
 
110
  def get_full_procedure_text(parent_id):
111
  procedure = procedure_map.get(parent_id)
@@ -121,7 +107,7 @@ def get_full_procedure_text(parent_id):
121
  "thu_tuc_lien_quan": "Thủ tục liên quan",
122
  "nguon": "Nguồn"
123
  }
124
- parts = [f"{field_map[k]}:\n{str(v).strip()}" for k, v in procedure.items() if v and k in field_map]
125
  return "\n\n".join(parts)
126
 
127
  # --- FLASK APP ---
@@ -139,6 +125,7 @@ def chat():
139
  data = request.json
140
  user_query = data.get('question')
141
  session_id = data.get('session_id', 'default')
 
142
  if not user_query:
143
  return jsonify({"error": "Không có câu hỏi"}), 400
144
 
@@ -150,23 +137,27 @@ def chat():
150
  if classify_followup(user_query) == 0 and current_history:
151
  context = current_history[-1].get('context', '')
152
  else:
153
- retrieved_indices = hybrid_retrieve(user_query)
154
  if retrieved_indices:
155
- parent_id = metadatas[retrieved_indices[0]]["parent_id"]
156
  context = get_full_procedure_text(parent_id)
157
 
158
  history_str = "\n".join([f"{item['role']}: {item['content']}" for item in current_history])
159
- prompt = f"""Bạn là trợ lý eGov-Bot. Trả lời tiếng Việt chính xác dựa vào DỮ LIỆU sau.
160
  Nếu thiếu dữ liệu, hãy nói "Mình chưa có thông tin" và đưa link nguồn trong dữ liệu để tham khảo.
161
- Lịch sử trò chuyện:
162
- {history_str}
163
- DỮ LIỆU: --- {context} ---
 
 
164
  CÂU HỎI: {user_query}"""
165
 
166
  response = generation_model.generate_content(prompt)
167
  answer = response.text
 
168
  current_history.append({'role': 'user', 'content': user_query})
169
  current_history.append({'role': 'model', 'content': answer, 'context': context})
 
170
  return jsonify({"answer": answer})
171
 
172
  if __name__ == '__main__':
 
1
+ # --- MUST BE AT THE TOP ---
2
+ import os
3
+ import shutil
 
 
 
 
 
 
 
4
 
5
+ # Đặt cache vào /tmp (HF Space cho phép ghi vào /tmp)
6
  os.environ["HF_HOME"] = "/tmp/hf_home"
7
  os.environ["TRANSFORMERS_CACHE"] = "/tmp/hf_cache"
8
  os.environ["HF_DATASETS_CACHE"] = "/tmp/hf_datasets"
 
12
  shutil.rmtree("/.cache", ignore_errors=True)
13
 
14
  # --- LOGIN HF HUB ---
15
+ from huggingface_hub import login, hf_hub_download
16
  HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN")
17
  if HF_TOKEN:
18
  login(HF_TOKEN)
19
  else:
20
+ print("Warning: HF token not found. Only public repos will be accessible.")
21
 
22
  # --- LOAD DỮ LIỆU ---
23
+ HF_REPO_ID = "DrPie/eGoV_Data" # dataset repo chứa dữ liệu
24
  REPO_TYPE = "dataset"
25
 
26
+ import pickle, gzip, re, json
27
+ import numpy as np
28
+ from rank_bm25 import BM25Okapi
29
+ import google.generativeai as genai
30
+ from flask import Flask, request, jsonify
31
+ from flask_cors import CORS
32
+
33
  print("--- KHỞI ĐỘNG MÁY CHỦ CHATBOT ---")
34
  try:
35
+ print("Đang tải các tài nguyên cần thiết từ Hugging Face Hub...")
36
  RAW_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="toan_bo_du_lieu_final.json", repo_type=REPO_TYPE)
 
 
37
  BM25_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="bm25.pkl.gz", repo_type=REPO_TYPE)
38
  IDMAP_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="id_to_record.pkl", repo_type=REPO_TYPE)
39
 
40
+ print("✅ Tải file dữ liệu thành công!")
41
 
42
  API_KEY = os.environ.get("GOOGLE_API_KEY")
43
  if not API_KEY:
44
+ raise ValueError("Lỗi: GOOGLE_API_KEY chưa được thiết lập trong Secrets của Space")
45
  genai.configure(api_key=API_KEY)
 
46
  generation_model = genai.GenerativeModel('gemini-2.5-flash')
 
47
 
48
+ # Không còn embedding và FAISS
 
 
49
  with gzip.open(BM25_PATH, "rb") as f:
50
  bm25 = pickle.load(f)
51
  with open(IDMAP_PATH, "rb") as f:
52
  procedure_map = pickle.load(f)
53
 
54
+ print(f"✅ BM25 loaded, tổng {len(procedure_map)} thủ tục hành chính.")
55
+
56
  except Exception as e:
57
  print(f"❌ Lỗi khi tải tài nguyên: {e}")
58
 
59
+ # --- LOGIC XỬ LÝ ---
60
  def classify_followup(text: str):
61
  text = text.lower().strip()
62
  score = 0
63
+ strong_followup = [
64
+ r"\b(nó|cái (này|đó|ấy)|thủ tục (này|đó|ấy))\b",
65
+ r"\b(vừa (nói|hỏi)|trước đó|ở trên|phía trên)\b",
66
+ r"\b(tiếp theo|tiếp|còn nữa|ngoài ra)\b",
67
+ r"\b(thế (thì|à)|vậy (thì|à)|như vậy)\b"
68
+ ]
69
+ detail_qs = [
70
+ r"\b(mất bao lâu|thời gian|bao nhiêu tiền|chi phí|phí)\b",
71
+ r"\b(ở đâu|tại đâu|chỗ nào|địa chỉ)\b",
72
+ r"\b(cần (gì|những gì)|yêu cầu|điều kiện)\b"
73
+ ]
74
+ specific_services = [
75
+ r"\b(làm|cấp|gia hạn|đổi|đăng ký)\s+(căn cước|cmnd|cccd)\b",
76
+ r"\b(làm|cấp|gia hạn|đổi)\s+hộ chiếu\b",
77
+ r"\b(đăng ký)\s+(kết hôn|sinh|tử|hộ khẩu)\b"
78
+ ]
79
+ if any(re.search(p, text) for p in strong_followup):
80
+ score -= 3
81
+ if any(re.search(p, text) for p in detail_qs):
82
+ score -= 2
83
+ if any(re.search(p, text) for p in specific_services):
84
+ score += 3
85
+ if len(text.split()) <= 4:
86
+ score -= 1
87
  return 0 if score < 0 else 1
88
 
89
+ def retrieve(query: str, top_k=3):
90
+ # Chỉ dùng BM25
 
 
 
 
 
 
 
 
 
 
 
91
  tokenized_query = query.split()
92
+ bm25_scores = bm25.get_scores(tokenized_query)
93
+ top_idx = np.argsort(-bm25_scores)[:top_k].tolist()
94
+ return top_idx
 
 
 
 
 
 
 
 
 
95
 
96
  def get_full_procedure_text(parent_id):
97
  procedure = procedure_map.get(parent_id)
 
107
  "thu_tuc_lien_quan": "Thủ tục liên quan",
108
  "nguon": "Nguồn"
109
  }
110
+ parts = [f"{field_map[k]}:\n{str(v).strip()}" for k,v in procedure.items() if v and k in field_map]
111
  return "\n\n".join(parts)
112
 
113
  # --- FLASK APP ---
 
125
  data = request.json
126
  user_query = data.get('question')
127
  session_id = data.get('session_id', 'default')
128
+
129
  if not user_query:
130
  return jsonify({"error": "Không có câu hỏi"}), 400
131
 
 
137
  if classify_followup(user_query) == 0 and current_history:
138
  context = current_history[-1].get('context', '')
139
  else:
140
+ retrieved_indices = retrieve(user_query)
141
  if retrieved_indices:
142
+ parent_id = retrieved_indices[0]
143
  context = get_full_procedure_text(parent_id)
144
 
145
  history_str = "\n".join([f"{item['role']}: {item['content']}" for item in current_history])
146
+ prompt = f"""Bạn là trợ lý eGov-Bot. Trả lời tiếng Việt, chính xác, dựa vào DỮ LIỆU sau.
147
  Nếu thiếu dữ liệu, hãy nói "Mình chưa có thông tin" và đưa link nguồn trong dữ liệu để tham khảo.
148
+ Lịch sử trò chuyện: {history_str}
149
+ DỮ LIỆU:
150
+ ---
151
+ {context}
152
+ ---
153
  CÂU HỎI: {user_query}"""
154
 
155
  response = generation_model.generate_content(prompt)
156
  answer = response.text
157
+
158
  current_history.append({'role': 'user', 'content': user_query})
159
  current_history.append({'role': 'model', 'content': answer, 'context': context})
160
+
161
  return jsonify({"answer": answer})
162
 
163
  if __name__ == '__main__':