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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -43
app.py CHANGED
@@ -1,11 +1,17 @@
1
  # app.py
2
- # --- MUST BE AT THE TOP ---
3
- import os
4
- import shutil
 
 
 
 
 
 
5
 
6
- # Đặt cache vào /tmp (HF Space cho phép ghi vào /tmp)
7
  os.environ["HF_HOME"] = "/tmp/hf_home"
8
- os.environ["TRANSFORMERS_CACHE"] = "/tmp/hf_cache" # warning deprecated OK
9
  os.environ["HF_DATASETS_CACHE"] = "/tmp/hf_datasets"
10
  os.environ["XDG_CACHE_HOME"] = "/tmp/.cache"
11
  os.environ["HOME"] = "/tmp"
@@ -13,40 +19,29 @@ os.makedirs("/tmp/.cache", exist_ok=True)
13
  shutil.rmtree("/.cache", ignore_errors=True)
14
 
15
  # --- LOGIN HF HUB ---
16
- from huggingface_hub import login, hf_hub_download
17
  HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN")
18
  if HF_TOKEN:
19
  login(HF_TOKEN)
20
  else:
21
- print("Warning: HF token not found. Only public repos will be accessible.")
22
 
23
  # --- LOAD DỮ LIỆU ---
24
- HF_REPO_ID = "DrPie/eGoV_Data" # dataset repo chứa dữ liệu
25
  REPO_TYPE = "dataset"
26
 
27
- import pickle, gzip, re, json
28
- import numpy as np
29
- import faiss
30
- from sentence_transformers import SentenceTransformer
31
- from rank_bm25 import BM25Okapi
32
- import google.generativeai as genai
33
- from flask import Flask, request, jsonify
34
- from flask_cors import CORS
35
-
36
  print("--- KHỞI ĐỘNG MÁY CHỦ CHATBOT ---")
37
  try:
38
- print("Đang tải các tài nguyên cần thiết từ Hugging Face Hub...")
39
  RAW_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="toan_bo_du_lieu_final.json", repo_type=REPO_TYPE)
40
  FAISS_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="index.faiss", repo_type=REPO_TYPE)
41
  METAS_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="metas.pkl.gz", repo_type=REPO_TYPE)
42
  BM25_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="bm25.pkl.gz", repo_type=REPO_TYPE)
43
  IDMAP_PATH = hf_hub_download(repo_id=HF_REPO_ID, filename="id_to_record.pkl", repo_type=REPO_TYPE)
44
 
45
- print("✅ Tải file dữ liệu thành công!")
46
 
47
  API_KEY = os.environ.get("GOOGLE_API_KEY")
48
  if not API_KEY:
49
- raise ValueError("Lỗi: GOOGLE_API_KEY chưa được thiết lập trong Secrets của Space")
50
  genai.configure(api_key=API_KEY)
51
 
52
  generation_model = genai.GenerativeModel('gemini-2.5-flash')
@@ -62,11 +57,10 @@ try:
62
 
63
  print(f"✅ Sẵn có {faiss_index.ntotal} chunks kiến thức.")
64
  print(f"✅ Có {len(procedure_map)} thủ tục hành chính.")
65
-
66
  except Exception as e:
67
  print(f"❌ Lỗi khi tải tài nguyên: {e}")
68
 
69
- # --- LOGIC XỬ LÝ ---
70
  def classify_followup(text: str):
71
  text = text.lower().strip()
72
  score = 0
@@ -88,40 +82,52 @@ def classify_followup(text: str):
88
 
89
  def minmax_scale(arr):
90
  arr = np.array(arr, dtype="float32")
91
- if len(arr) == 0 or np.max(arr) == np.min(arr): return np.zeros_like(arr)
 
92
  return (arr - np.min(arr)) / (np.max(arr) - np.min(arr))
93
 
94
- def retrieve(query: str, top_k=3):
 
95
  qv = embedding_model.encode([query], normalize_embeddings=True).astype("float32")
96
- D, I = faiss_index.search(qv, top_k*5)
97
  vec_scores = (1 - D[0]).tolist()
98
  vec_idx = I[0].tolist()
 
99
  tokenized_query = query.split()
100
  bm25_scores_all = bm25.get_scores(tokenized_query)
101
- bm25_top_idx = np.argsort(-bm25_scores_all)[:top_k*5].tolist()
 
 
102
  union_idx = list(dict.fromkeys(vec_idx + bm25_top_idx))
103
- vec_map = {i: s for i,s in zip(vec_idx, vec_scores)}
104
- vec_list = [vec_map.get(i,0.0) for i in union_idx]
105
  bm25_list = [bm25_scores_all[i] for i in union_idx]
106
- fused = 0.7 * minmax_scale(vec_list) + 0.3 * minmax_scale(bm25_list)
 
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)
112
- if not procedure: return "Không tìm thấy thủ tục."
113
- field_map = {"ten_thu_tuc": "Tên thủ tục", "cach_thuc_thuc_hien": "Cách thức thực hiện",
114
- "thanh_phan_ho_so": "Thành phần hồ sơ", "trinh_tu_thuc_hien": "Trình tự thực hiện",
115
- "co_quan_thuc_hien": " quan thực hiện", "yeu_cau_dieu_kien": "Yêu cầu, điều kiện",
116
- "thu_tuc_lien_quan": "Thủ tục liên quan", "nguon": "Nguồn"}
117
- parts = [f"{field_map[k]}:\n{str(v).strip()}" for k,v in procedure.items() if v and k in field_map]
 
 
 
 
 
 
 
118
  return "\n\n".join(parts)
119
 
120
- # --- FLASK APP & ROUTE /chat ---
121
  app = Flask(__name__)
122
  CORS(app)
123
 
124
- # Route test
125
  @app.route('/', methods=['GET'])
126
  def home():
127
  return "eGov-Bot backend is running!", 200
@@ -135,30 +141,32 @@ def chat():
135
  session_id = data.get('session_id', 'default')
136
  if not user_query:
137
  return jsonify({"error": "Không có câu hỏi"}), 400
 
138
  if session_id not in chat_histories:
139
  chat_histories[session_id] = []
140
  current_history = chat_histories[session_id]
 
141
  context = ""
142
  if classify_followup(user_query) == 0 and current_history:
143
  context = current_history[-1].get('context', '')
144
  else:
145
- retrieved_indices = retrieve(user_query)
146
  if retrieved_indices:
147
  parent_id = metadatas[retrieved_indices[0]]["parent_id"]
148
  context = get_full_procedure_text(parent_id)
 
149
  history_str = "\n".join([f"{item['role']}: {item['content']}" for item in current_history])
150
- 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.
151
  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.
152
-
153
  Lịch sử trò chuyện:
154
  {history_str}
155
-
156
  DỮ LIỆU: --- {context} ---
157
  CÂU HỎI: {user_query}"""
 
158
  response = generation_model.generate_content(prompt)
159
  answer = response.text
160
- current_history.append({'role':'user','content':user_query})
161
- current_history.append({'role':'model','content':answer,'context':context})
162
  return jsonify({"answer": answer})
163
 
164
  if __name__ == '__main__':
 
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"
16
  os.environ["XDG_CACHE_HOME"] = "/tmp/.cache"
17
  os.environ["HOME"] = "/tmp"
 
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')
 
57
 
58
  print(f"✅ Sẵn có {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
 
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)
112
+ if not procedure:
113
+ return "Không tìm thấy thủ tục."
114
+ field_map = {
115
+ "ten_thu_tuc": "Tên thủ tục",
116
+ "cach_thuc_thuc_hien": "Cách thức thực hiện",
117
+ "thanh_phan_ho_so": "Thành phần hồ sơ",
118
+ "trinh_tu_thuc_hien": "Trình tự thực hiện",
119
+ "co_quan_thuc_hien": "Cơ quan thực hiện",
120
+ "yeu_cau_dieu_kien": "Yêu cầu, điều kiện",
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 ---
128
  app = Flask(__name__)
129
  CORS(app)
130
 
 
131
  @app.route('/', methods=['GET'])
132
  def home():
133
  return "eGov-Bot backend is running!", 200
 
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
+
145
  if session_id not in chat_histories:
146
  chat_histories[session_id] = []
147
  current_history = chat_histories[session_id]
148
+
149
  context = ""
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__':