VietCat commited on
Commit
daffc5f
·
1 Parent(s): 943efe8

optimize timing

Browse files
Files changed (3) hide show
  1. app/constants.py +1 -1
  2. app/message_processor.py +74 -106
  3. app/reranker.py +3 -0
app/constants.py CHANGED
@@ -192,4 +192,4 @@ FOUND_REGULATIONS_MESSAGES = [
192
  ]
193
 
194
  SHEET_RANGE = 'chat!A2:N'
195
- VERSION_NUMBER = 123456792
 
192
  ]
193
 
194
  SHEET_RANGE = 'chat!A2:N'
195
+ VERSION_NUMBER = 123456793
app/message_processor.py CHANGED
@@ -30,6 +30,8 @@ class MessageProcessor:
30
  if field not in message_data:
31
  logger.error(f"[ERROR] Missing field {field} in message_data: {message_data}")
32
  return
 
 
33
  sender_id = message_data["sender_id"]
34
  page_id = message_data["page_id"]
35
  message_text = message_data["text"]
@@ -41,38 +43,24 @@ class MessageProcessor:
41
  logger.info(f"[DEBUG] Không có message_text và attachments, không xử lý...")
42
  return
43
 
44
- loop = asyncio.get_event_loop()
45
  sheets_client = self.channel.get_sheets_client()
46
  history = await loop.run_in_executor(
47
  None, lambda: sheets_client.get_conversation_history(sender_id, page_id)
48
  )
49
  logger.info(f"[DEBUG] history: ... {history[-3:]}")
50
 
51
- # --- SỬA LỖI LOGIC CHỐNG TRÙNG LẶP TẠI ĐÂY ---
52
- # Kiểm tra xem timestamp của sự kiện webhook này đã tồn tại trong lịch sử chưa
53
  for row in history:
54
- # Chuyển đổi an toàn sang string để so sánh
55
  sheet_timestamps = [str(ts) for ts in row.get('timestamp', [])]
56
  if str(timestamp) in sheet_timestamps:
57
  logger.warning(f"Webhook lặp lại cho sự kiện đã tồn tại (timestamp: {timestamp}). Bỏ qua.")
58
- return # Bỏ qua hoàn toàn để tránh xử lý lại
59
 
60
- # --- LUỒNG XỬ LÝ GỐC CỦA BẠN ĐƯỢC GIỮ NGUYÊN ---
61
  log_kwargs = {
62
- 'conversation_id': None,
63
- 'recipient_id': sender_id,
64
- 'page_id': page_id,
65
- 'originaltext': message_text,
66
- 'originalcommand': '',
67
- 'originalcontent': '',
68
- 'originalattachments': attachments,
69
- 'originalvehicle': '',
70
- 'originalaction': '',
71
- 'originalpurpose': '',
72
- 'originalquestion': '',
73
- 'systemresponse': '',
74
- 'timestamp': [timestamp],
75
- 'isdone': False
76
  }
77
 
78
  logger.info(f"[DEBUG] Message cơ bản: {log_kwargs}")
@@ -81,13 +69,11 @@ class MessageProcessor:
81
  logger.error("Không thể tạo conversation mới!")
82
  return
83
  logger.info(f"[DEBUG] Message history sau lần ghi đầu: {conv}")
84
-
85
- # Thêm timestamp mới nếu chưa có (logic này có thể không cần thiết nữa nhưng giữ lại để không thay đổi luồng)
86
  conv['timestamp'] = self.flatten_timestamp(conv['timestamp'])
87
  if timestamp not in conv['timestamp']:
88
  conv['timestamp'].append(timestamp)
89
 
90
- # Lần gọi thứ 2 để cập nhật thêm thông tin ban đầu (nếu có)
91
  conv_after_update1 = await loop.run_in_executor(None, lambda: sheets_client.log_conversation(**conv))
92
  if conv_after_update1:
93
  conv = conv_after_update1
@@ -98,7 +84,8 @@ class MessageProcessor:
98
  return
99
 
100
  try:
101
- await self.facebook.send_message(message=get_random_message(PROCESSING_STATUS_MESSAGES))
 
102
  except Exception as e:
103
  if "expired" in str(e).lower():
104
  logger.warning("[FACEBOOK] Token expired, invalidate and refresh")
@@ -131,127 +118,111 @@ class MessageProcessor:
131
  else:
132
  keywords = extract_keywords(message_text, VEHICLE_KEYWORDS)
133
  cau_hoi = message_text
134
- for kw in keywords:
135
- cau_hoi = cau_hoi.replace(kw, "")
136
  cau_hoi = cau_hoi.strip()
137
 
138
  logger.info(f"[DEBUG] Phương tiện: {keywords} - Hành vi: {tu_khoa} - Mục đích: {muc_dich} - Câu hỏi: {cau_hoi}")
139
 
140
- # Hợp nhất dữ liệu đã phân tích vào `conv`
141
- conv['originalcommand'] = command
142
- conv['originalcontent'] = remaining_text
143
- conv['originalvehicle'] = ','.join(keywords)
144
- conv['originalaction'] = tu_khoa
145
- conv['originalpurpose'] = muc_dich
146
- conv['originalquestion'] = cau_hoi or ""
147
 
148
  muc_dich_to_use = muc_dich or conv.get('originalpurpose')
149
  logger.info(f"[DEBUG] Định hướng mục đích xử lý: {muc_dich_to_use}")
150
-
151
  conversation_context = self.get_llm_history(history)
152
 
153
  response = None
 
 
 
 
 
 
 
 
154
  if not command:
155
- if muc_dich_to_use == "hỏi về mức phạt":
156
- response = await self.handle_muc_phat(conv, conversation_context, page_token, sender_id)
157
- elif muc_dich_to_use == "hỏi về quy tắc giao thông":
158
- response = await self.handle_quy_tac(conv, conversation_context, page_token, sender_id)
159
- elif muc_dich_to_use == "hỏi về báo hiệu đường bộ":
160
- response = await self.handle_bao_hieu(conv, conversation_context, page_token, sender_id)
161
- elif muc_dich_to_use == "hỏi về quy trình xử lý vi phạm giao thông":
162
- response = await self.handle_quy_trinh(conv, conversation_context, page_token, sender_id)
163
- elif muc_dich_to_use == "thông tin cá nhân của AI":
164
- response = await self.handle_ca_nhan(conv, conversation_context, page_token, sender_id)
165
- else:
166
- response = await self.handle_khac(conv, conversation_context, page_token, sender_id)
167
  else:
168
  if command == "xong":
169
  post_url = await self.create_facebook_post(page_token, conv['recipient_id'], [conv])
170
- if post_url:
171
- response = f"Bài viết đã được tạo thành công! Bạn có thể xem tại: {post_url}"
172
- else:
173
- response = "Đã xảy ra lỗi khi tạo bài viết. Vui lòng thử lại sau."
174
  conv['isdone'] = True
175
  else:
176
  response = "Vui lòng cung cấp thêm thông tin và gõ lệnh \\xong khi hoàn tất."
177
  conv['isdone'] = False
178
 
179
- await self.facebook.send_message(message=response)
 
180
 
181
  conv['systemresponse'] = response
182
 
183
  logger.info(f"Chuẩn bị ghi/cập nhật dữ liệu cuối cùng vào sheet: {conv}")
184
- await loop.run_in_executor(None, lambda: sheets_client.log_conversation(**conv))
 
 
 
185
  return
186
 
 
187
  def get_latest_timestamp(self,ts_value):
188
- if isinstance(ts_value, (int, float)): return int(ts_value)
189
- if isinstance(ts_value, str):
190
- try: return int(json.loads(ts_value))
191
- except:
192
- try: return int(ts_value)
193
- except: return 0
194
- if isinstance(ts_value, list):
195
- if not ts_value: return 0
196
- return max([self.get_latest_timestamp(item) for item in ts_value]) if ts_value else 0
197
- return 0
198
 
199
  def get_llm_history(self, history: List):
200
  sorted_history = sorted(history, key=lambda row: self.get_latest_timestamp(row.get('timestamp', 0)))
201
-
202
  total_chars = 0
203
  MAX_CONTEXT_CHARS = 20_000
204
  conversation_context = []
205
  for row in reversed(sorted_history):
206
  temp_blocks = []
207
-
208
- # --- SỬA LỖI THỨ TỰ LỊCH SỬ TẠI ĐÂY ---
209
- # Đảm bảo lượt nói của user luôn được thêm vào trước.
210
  if row.get('originaltext'):
211
  temp_blocks.append({"role": "user", "content": row['originaltext']})
212
- # Lượt nói của trợ lý ảo được thêm vào sau.
213
  if row.get('systemresponse'):
214
  temp_blocks.append({"role": "assistant", "content": row['systemresponse']})
215
-
216
  temp_total = sum(len(block['content']) for block in temp_blocks)
217
  if total_chars + temp_total > MAX_CONTEXT_CHARS: continue
218
-
219
- # Thêm cặp hỏi-đáp vào đầu danh sách context, duy trì thứ tự thời gian
220
  conversation_context = temp_blocks + conversation_context
221
  total_chars += temp_total
222
  return conversation_context
223
 
224
  def flatten_timestamp(self, ts):
225
  flat = []
226
- if not isinstance(ts, list):
227
- ts = [ts]
228
  for t in ts:
229
- if isinstance(t, list):
230
- flat.extend(self.flatten_timestamp(t))
231
- else:
232
- flat.append(t)
233
  return flat
234
 
235
  def normalize_vehicle_keyword(self, keyword: str) -> str:
236
  from app.constants import VEHICLE_KEYWORDS
237
  import difflib
238
- if not keyword:
239
- return ""
240
  matches = difflib.get_close_matches(keyword.lower(), [k.lower() for k in VEHICLE_KEYWORDS], n=1, cutoff=0.6)
241
  if matches:
242
  for k in VEHICLE_KEYWORDS:
243
- if k.lower() == matches[0]:
244
- return k
245
  return keyword
246
-
247
  async def format_search_results(self, conversation_context: str, question: str, matches: List[Dict[str, Any]], page_token: str, sender_id: str) -> str:
248
  if not matches:
249
  return "Không tìm thấy kết quả phù hợp."
250
- await self.facebook.send_message(message=get_random_message(FOUND_REGULATIONS_MESSAGES))
 
 
 
251
  try:
252
  reranked = await self.channel.reranker.rerank(question, matches, top_k=10)
253
- if reranked:
254
- matches = reranked
255
  except Exception as e:
256
  logger.error(f"[RERANK] Lỗi khi rerank: {e}")
257
 
@@ -265,16 +236,11 @@ class MessageProcessor:
265
  fullContent = (match.get('fullcontent') or '').strip()
266
  full_result_text += f"{fullContent}"
267
  hpbsnoidung = arr_to_str(match.get('hpbsnoidung'), sep="; ")
268
- if hpbsnoidung:
269
- full_result_text += f"\n- Hình phạt bổ sung: {hpbsnoidung}"
270
  bpkpnoidung = arr_to_str(match.get('bpkpnoidung'), sep="; ")
271
- if bpkpnoidung:
272
- full_result_text += f"\n- Biện pháp khắc phục: {bpkpnoidung}"
273
- impounding = match.get('impounding')
274
- if impounding:
275
- full_result_text += f"\n- Tạm giữ phương tiện: 07 ngày"
276
 
277
- # --- PROMPT MỚI ĐƯỢC CẬP NHẬT TẠI ĐÂY ---
278
  prompt = (
279
  "Bạn là một trợ lý pháp lý AI chuyên nghiệp. Nhiệm vụ của bạn là tổng hợp thông tin từ hai nguồn: **Lịch sử trò chuyện** và **Các đoạn luật liên quan** để đưa ra một câu trả lời duy nhất, liền mạch và tự nhiên cho người dùng.\n\n"
280
  "**QUY TẮC BẮT BUỘC:**\n"
@@ -288,7 +254,9 @@ class MessageProcessor:
288
  "### Trả lời:"
289
  )
290
 
291
- await self.facebook.send_message(message=f"{get_random_message(SUMMARY_STATUS_MESSAGES)}")
 
 
292
  try:
293
  answer = await self.channel.llm.generate_text(prompt)
294
  if answer and answer.strip():
@@ -299,13 +267,10 @@ class MessageProcessor:
299
  except Exception as e:
300
  logger.error(f"LLM không sẵn sàng: {e}\n{traceback.format_exc()}")
301
 
302
- # Fallback response
303
- return "Dựa trên thông tin bạn cung cấp, tôi đã tìm thấy một số quy định liên quan. Tuy nhiên, tôi đang gặp chút khó khăn trong việc tóm tắt. Bạn vui lòng tham khảo nội dung chi tiết trong các văn bản luật nhé."
304
 
305
  async def create_facebook_post(self, page_token: str, sender_id: str, history: List[Dict[str, Any]]) -> str:
306
  logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
307
- # In a real scenario, you would use the Facebook Graph API to create a post.
308
- # This is a mock implementation.
309
  return "https://facebook.com/mock_post_url"
310
 
311
  async def handle_muc_phat(self, conv, conversation_context, page_token, sender_id):
@@ -322,12 +287,19 @@ class MessageProcessor:
322
  embedding = await self.channel.embedder.create_embedding(search_query)
323
  logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
324
 
 
325
  match_count = get_settings().match_count
326
- matches = self.channel.supabase.match_documents(
327
- embedding,
328
- match_count=match_count,
329
- user_question=search_query
 
 
 
 
 
330
  )
 
331
  logger.info(f"[DEBUG] matches: {matches[:2]}...{matches[-2:]}")
332
  if matches:
333
  response = await self.format_search_results(conversation_context, question or action, matches, page_token, sender_id)
@@ -341,7 +313,6 @@ class MessageProcessor:
341
  return response
342
 
343
  async def _handle_general_question(self, conversation_context: str, message_text: str, topic: str) -> str:
344
- """Hàm chung để xử lý các câu hỏi kiến thức chung."""
345
  prompt = (
346
  "Bạn là một trợ lý AI am hiểu về luật giao thông Việt Nam. "
347
  "Dựa vào lịch sử trò chuyện và kiến thức của bạn, hãy trả lời câu hỏi của người dùng một cách rõ ràng, ngắn gọn và chính xác.\n"
@@ -365,17 +336,14 @@ class MessageProcessor:
365
 
366
  async def handle_quy_tac(self, conv, conversation_context, page_token, sender_id):
367
  conv['isdone'] = True
368
- # return await self._handle_general_question(conversation_context, conv['originaltext'], "quy tắc giao thông")
369
  return await self.handle_muc_phat(conv, conversation_context, page_token, sender_id)
370
 
371
  async def handle_bao_hieu(self, conv, conversation_context, page_token, sender_id):
372
  conv['isdone'] = True
373
- # return await self._handle_general_question(conversation_context, conv['originaltext'], "báo hiệu đường bộ")
374
  return await self.handle_muc_phat(conv, conversation_context, page_token, sender_id)
375
 
376
  async def handle_quy_trinh(self, conv, conversation_context, page_token, sender_id):
377
  conv['isdone'] = True
378
- # return await self._handle_general_question(conversation_context, conv['originaltext'], "quy trình xử lý vi phạm giao thông")
379
  return await self.handle_muc_phat(conv, conversation_context, page_token, sender_id)
380
 
381
  async def handle_ca_nhan(self, conv, conversation_context, page_token, sender_id):
@@ -394,4 +362,4 @@ class MessageProcessor:
394
  return answer.strip() if answer and answer.strip() else "Chào bạn, mình là WeThoong AI đây!"
395
  except Exception as e:
396
  logger.error(f"Lỗi khi xử lý câu hỏi cá nhân: {e}")
397
- return "Chào bạn, mình là WeThoong AI, trợ lý giao thông thông minh của bạn!"
 
30
  if field not in message_data:
31
  logger.error(f"[ERROR] Missing field {field} in message_data: {message_data}")
32
  return
33
+
34
+ loop = asyncio.get_event_loop()
35
  sender_id = message_data["sender_id"]
36
  page_id = message_data["page_id"]
37
  message_text = message_data["text"]
 
43
  logger.info(f"[DEBUG] Không có message_text và attachments, không xử lý...")
44
  return
45
 
 
46
  sheets_client = self.channel.get_sheets_client()
47
  history = await loop.run_in_executor(
48
  None, lambda: sheets_client.get_conversation_history(sender_id, page_id)
49
  )
50
  logger.info(f"[DEBUG] history: ... {history[-3:]}")
51
 
 
 
52
  for row in history:
 
53
  sheet_timestamps = [str(ts) for ts in row.get('timestamp', [])]
54
  if str(timestamp) in sheet_timestamps:
55
  logger.warning(f"Webhook lặp lại cho sự kiện đã tồn tại (timestamp: {timestamp}). Bỏ qua.")
56
+ return
57
 
 
58
  log_kwargs = {
59
+ 'conversation_id': None, 'recipient_id': sender_id, 'page_id': page_id,
60
+ 'originaltext': message_text, 'originalcommand': '', 'originalcontent': '',
61
+ 'originalattachments': attachments, 'originalvehicle': '', 'originalaction': '',
62
+ 'originalpurpose': '', 'originalquestion': '', 'systemresponse': '',
63
+ 'timestamp': [timestamp], 'isdone': False
 
 
 
 
 
 
 
 
 
64
  }
65
 
66
  logger.info(f"[DEBUG] Message cơ bản: {log_kwargs}")
 
69
  logger.error("Không thể tạo conversation mới!")
70
  return
71
  logger.info(f"[DEBUG] Message history sau lần ghi đầu: {conv}")
72
+
 
73
  conv['timestamp'] = self.flatten_timestamp(conv['timestamp'])
74
  if timestamp not in conv['timestamp']:
75
  conv['timestamp'].append(timestamp)
76
 
 
77
  conv_after_update1 = await loop.run_in_executor(None, lambda: sheets_client.log_conversation(**conv))
78
  if conv_after_update1:
79
  conv = conv_after_update1
 
84
  return
85
 
86
  try:
87
+ # TỐI ƯU 1: Gửi tin nhắn trạng thái và không cần chờ đợi
88
+ asyncio.create_task(self.facebook.send_message(message=get_random_message(PROCESSING_STATUS_MESSAGES)))
89
  except Exception as e:
90
  if "expired" in str(e).lower():
91
  logger.warning("[FACEBOOK] Token expired, invalidate and refresh")
 
118
  else:
119
  keywords = extract_keywords(message_text, VEHICLE_KEYWORDS)
120
  cau_hoi = message_text
121
+ for kw in keywords: cau_hoi = cau_hoi.replace(kw, "")
 
122
  cau_hoi = cau_hoi.strip()
123
 
124
  logger.info(f"[DEBUG] Phương tiện: {keywords} - Hành vi: {tu_khoa} - Mục đích: {muc_dich} - Câu hỏi: {cau_hoi}")
125
 
126
+ conv.update({
127
+ 'originalcommand': command, 'originalcontent': remaining_text, 'originalvehicle': ','.join(keywords),
128
+ 'originalaction': tu_khoa, 'originalpurpose': muc_dich, 'originalquestion': cau_hoi or ""
129
+ })
 
 
 
130
 
131
  muc_dich_to_use = muc_dich or conv.get('originalpurpose')
132
  logger.info(f"[DEBUG] Định hướng mục đích xử lý: {muc_dich_to_use}")
 
133
  conversation_context = self.get_llm_history(history)
134
 
135
  response = None
136
+ handlers = {
137
+ "hỏi về mức phạt": self.handle_muc_phat,
138
+ "hỏi về quy tắc giao thông": self.handle_quy_tac,
139
+ "hỏi về báo hiệu đường bộ": self.handle_bao_hieu,
140
+ "hỏi về quy trình xử lý vi phạm giao thông": self.handle_quy_trinh,
141
+ "thông tin cá nhân của AI": self.handle_ca_nhan
142
+ }
143
+
144
  if not command:
145
+ handler = handlers.get(muc_dich_to_use, self.handle_khac)
146
+ response = await handler(conv, conversation_context, page_token, sender_id)
 
 
 
 
 
 
 
 
 
 
147
  else:
148
  if command == "xong":
149
  post_url = await self.create_facebook_post(page_token, conv['recipient_id'], [conv])
150
+ response = f"Bài viết đã được tạo thành công! Bạn có thể xem tại: {post_url}" if post_url else "Đã xảy ra lỗi khi tạo bài viết."
 
 
 
151
  conv['isdone'] = True
152
  else:
153
  response = "Vui lòng cung cấp thêm thông tin và gõ lệnh \\xong khi hoàn tất."
154
  conv['isdone'] = False
155
 
156
+ # TỐI ƯU 2: Gửi câu trả lời cuối cùng và không cần chờ
157
+ asyncio.create_task(self.facebook.send_message(message=response))
158
 
159
  conv['systemresponse'] = response
160
 
161
  logger.info(f"Chuẩn bị ghi/cập nhật dữ liệu cuối cùng vào sheet: {conv}")
162
+ # TỐI ƯU 3: Ghi log cuối cùng và không cần chờ, cho phép webhook trả về ngay
163
+ asyncio.create_task(
164
+ loop.run_in_executor(None, lambda: sheets_client.log_conversation(**conv))
165
+ )
166
  return
167
 
168
+ # ... (các hàm get_latest_timestamp, get_llm_history, flatten_timestamp, normalize_vehicle_keyword không đổi) ...
169
  def get_latest_timestamp(self,ts_value):
170
+ if isinstance(ts_value, (int, float)): return int(ts_value)
171
+ if isinstance(ts_value, str):
172
+ try: return int(json.loads(ts_value))
173
+ except:
174
+ try: return int(ts_value)
175
+ except: return 0
176
+ if isinstance(ts_value, list):
177
+ if not ts_value: return 0
178
+ return max([self.get_latest_timestamp(item) for item in ts_value]) if ts_value else 0
179
+ return 0
180
 
181
  def get_llm_history(self, history: List):
182
  sorted_history = sorted(history, key=lambda row: self.get_latest_timestamp(row.get('timestamp', 0)))
 
183
  total_chars = 0
184
  MAX_CONTEXT_CHARS = 20_000
185
  conversation_context = []
186
  for row in reversed(sorted_history):
187
  temp_blocks = []
 
 
 
188
  if row.get('originaltext'):
189
  temp_blocks.append({"role": "user", "content": row['originaltext']})
 
190
  if row.get('systemresponse'):
191
  temp_blocks.append({"role": "assistant", "content": row['systemresponse']})
 
192
  temp_total = sum(len(block['content']) for block in temp_blocks)
193
  if total_chars + temp_total > MAX_CONTEXT_CHARS: continue
 
 
194
  conversation_context = temp_blocks + conversation_context
195
  total_chars += temp_total
196
  return conversation_context
197
 
198
  def flatten_timestamp(self, ts):
199
  flat = []
200
+ if not isinstance(ts, list): ts = [ts]
 
201
  for t in ts:
202
+ if isinstance(t, list): flat.extend(self.flatten_timestamp(t))
203
+ else: flat.append(t)
 
 
204
  return flat
205
 
206
  def normalize_vehicle_keyword(self, keyword: str) -> str:
207
  from app.constants import VEHICLE_KEYWORDS
208
  import difflib
209
+ if not keyword: return ""
 
210
  matches = difflib.get_close_matches(keyword.lower(), [k.lower() for k in VEHICLE_KEYWORDS], n=1, cutoff=0.6)
211
  if matches:
212
  for k in VEHICLE_KEYWORDS:
213
+ if k.lower() == matches[0]: return k
 
214
  return keyword
215
+
216
  async def format_search_results(self, conversation_context: str, question: str, matches: List[Dict[str, Any]], page_token: str, sender_id: str) -> str:
217
  if not matches:
218
  return "Không tìm thấy kết quả phù hợp."
219
+
220
+ # TỐI ƯU: Gửi tin nhắn trạng thái và không chờ
221
+ asyncio.create_task(self.facebook.send_message(message=get_random_message(FOUND_REGULATIONS_MESSAGES)))
222
+
223
  try:
224
  reranked = await self.channel.reranker.rerank(question, matches, top_k=10)
225
+ if reranked: matches = reranked
 
226
  except Exception as e:
227
  logger.error(f"[RERANK] Lỗi khi rerank: {e}")
228
 
 
236
  fullContent = (match.get('fullcontent') or '').strip()
237
  full_result_text += f"{fullContent}"
238
  hpbsnoidung = arr_to_str(match.get('hpbsnoidung'), sep="; ")
239
+ if hpbsnoidung: full_result_text += f"\n- Hình phạt bổ sung: {hpbsnoidung}"
 
240
  bpkpnoidung = arr_to_str(match.get('bpkpnoidung'), sep="; ")
241
+ if bpkpnoidung: full_result_text += f"\n- Biện pháp khắc phục: {bpkpnoidung}"
242
+ if match.get('impounding'): full_result_text += f"\n- Tạm giữ phương tiện: 07 ngày"
 
 
 
243
 
 
244
  prompt = (
245
  "Bạn là một trợ lý pháp lý AI chuyên nghiệp. Nhiệm vụ của bạn là tổng hợp thông tin từ hai nguồn: **Lịch sử trò chuyện** và **Các đoạn luật liên quan** để đưa ra một câu trả lời duy nhất, liền mạch và tự nhiên cho người dùng.\n\n"
246
  "**QUY TẮC BẮT BUỘC:**\n"
 
254
  "### Trả lời:"
255
  )
256
 
257
+ # TỐI ƯU: Gửi tin nhắn trạng thái và không chờ
258
+ asyncio.create_task(self.facebook.send_message(message=f"{get_random_message(SUMMARY_STATUS_MESSAGES)}"))
259
+
260
  try:
261
  answer = await self.channel.llm.generate_text(prompt)
262
  if answer and answer.strip():
 
267
  except Exception as e:
268
  logger.error(f"LLM không sẵn sàng: {e}\n{traceback.format_exc()}")
269
 
270
+ return "Dựa trên thông tin bạn cung cấp..." # Fallback
 
271
 
272
  async def create_facebook_post(self, page_token: str, sender_id: str, history: List[Dict[str, Any]]) -> str:
273
  logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
 
 
274
  return "https://facebook.com/mock_post_url"
275
 
276
  async def handle_muc_phat(self, conv, conversation_context, page_token, sender_id):
 
287
  embedding = await self.channel.embedder.create_embedding(search_query)
288
  logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
289
 
290
+ loop = asyncio.get_event_loop()
291
  match_count = get_settings().match_count
292
+
293
+ # TỐI ƯU 4: Chạy tác vụ I/O đồng bộ trong executor để không chặn event loop
294
+ matches = await loop.run_in_executor(
295
+ None,
296
+ lambda: self.channel.supabase.match_documents(
297
+ embedding,
298
+ match_count=match_count,
299
+ user_question=search_query
300
+ )
301
  )
302
+
303
  logger.info(f"[DEBUG] matches: {matches[:2]}...{matches[-2:]}")
304
  if matches:
305
  response = await self.format_search_results(conversation_context, question or action, matches, page_token, sender_id)
 
313
  return response
314
 
315
  async def _handle_general_question(self, conversation_context: str, message_text: str, topic: str) -> str:
 
316
  prompt = (
317
  "Bạn là một trợ lý AI am hiểu về luật giao thông Việt Nam. "
318
  "Dựa vào lịch sử trò chuyện và kiến thức của bạn, hãy trả lời câu hỏi của người dùng một cách rõ ràng, ngắn gọn và chính xác.\n"
 
336
 
337
  async def handle_quy_tac(self, conv, conversation_context, page_token, sender_id):
338
  conv['isdone'] = True
 
339
  return await self.handle_muc_phat(conv, conversation_context, page_token, sender_id)
340
 
341
  async def handle_bao_hieu(self, conv, conversation_context, page_token, sender_id):
342
  conv['isdone'] = True
 
343
  return await self.handle_muc_phat(conv, conversation_context, page_token, sender_id)
344
 
345
  async def handle_quy_trinh(self, conv, conversation_context, page_token, sender_id):
346
  conv['isdone'] = True
 
347
  return await self.handle_muc_phat(conv, conversation_context, page_token, sender_id)
348
 
349
  async def handle_ca_nhan(self, conv, conversation_context, page_token, sender_id):
 
362
  return answer.strip() if answer and answer.strip() else "Chào bạn, mình là WeThoong AI đây!"
363
  except Exception as e:
364
  logger.error(f"Lỗi khi xử lý câu hỏi cá nhân: {e}")
365
+ return "Chào bạn, mình là WeThoong AI, trợ lý giao thông thông minh của bạn!"
app/reranker.py CHANGED
@@ -1,4 +1,6 @@
1
  from typing import List, Dict
 
 
2
  from .config import get_settings
3
  from .gemini_client import GeminiClient
4
  from loguru import logger
@@ -203,6 +205,7 @@ class Reranker:
203
  doc['rerank_score'] = 0
204
  return doc
205
 
 
206
  async def rerank(self, query: str, docs: List[Dict], top_k: int = 5) -> List[Dict]:
207
  """
208
  Rerank docs theo độ liên quan với query, trả về top_k docs.
 
1
  from typing import List, Dict
2
+
3
+ from app.utils import timing_decorator_async
4
  from .config import get_settings
5
  from .gemini_client import GeminiClient
6
  from loguru import logger
 
205
  doc['rerank_score'] = 0
206
  return doc
207
 
208
+ @timing_decorator_async
209
  async def rerank(self, query: str, docs: List[Dict], top_k: int = 5) -> List[Dict]:
210
  """
211
  Rerank docs theo độ liên quan với query, trả về top_k docs.