Davidtran99 commited on
Commit
9bb60b8
·
1 Parent(s): 0c2f39a

Fix: Frontend kh��ng nh���n �������c response - Th��m validation v�� error handling

Browse files

- Fix logic ��p intent: Kh��ng ��p greeting th��nh search_legal
- T���i ��u LLM: T��ng threads t��� 4���8, gi���m context t��� 8192���4096
- R��t g���n prompts: Gi���m tokens ����� t��ng t���c �����
- Th��m response validation: �����m b���o lu��n c�� message/clarification
- C���i thi���n error handling: �����m b���o response lu��n h���p l���

backend/hue_portal/chatbot/chatbot.py CHANGED
@@ -111,12 +111,32 @@ class Chatbot(CoreChatbot):
111
  intent = route_decision.forced_intent
112
 
113
  # Nếu session đã có selected_document_code (user đã chọn văn bản ở wizard)
114
- # thì luôn ép intent về search_legal route sang SEARCH,
115
- # tránh bị kẹt nhánh small-talk/off-topic do nội dung câu hỏi ban đầu.
116
- if selected_doc_code:
117
- intent = "search_legal"
118
- route_decision.route = IntentRoute.SEARCH
119
- route_decision.forced_intent = "search_legal"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  # Map tất cả intent tra cứu nội dung về search_legal
122
  domain_search_intents = {
@@ -217,19 +237,31 @@ class Chatbot(CoreChatbot):
217
  if intent == "search_legal" and not selected_doc_code and not has_doc_code_in_query:
218
  print("[WIZARD] ✅ Stage 1: Using direct semantic search from slow_path_handler")
219
  # Delegate to slow_path_handler which uses direct semantic search (no query rewrite)
220
- slow_handler = SlowPathHandler()
221
- response = slow_handler.handle(
222
- query=query,
223
- intent=intent,
224
- session_id=session_id,
225
- selected_document_code=None, # No document selected yet
226
- )
 
 
 
 
 
227
 
228
- # Ensure response has wizard metadata
229
- if response:
 
 
 
 
230
  response.setdefault("wizard_stage", "choose_document")
231
  response.setdefault("routing", "legal_wizard")
232
  response.setdefault("type", "options")
 
 
 
233
 
234
  # Update session metadata
235
  if session_id:
@@ -248,20 +280,48 @@ class Chatbot(CoreChatbot):
248
  if session_id:
249
  try:
250
  bot_message = response.get("message") or response.get("clarification", {}).get("message", "")
251
- ConversationContext.add_message(
252
- session_id=session_id,
253
- role="bot",
254
- content=bot_message,
255
- intent=intent,
256
- )
 
257
  except Exception as e:
258
  print(f"⚠️ Failed to save wizard bot message: {e}")
 
 
259
 
260
- return response if response else {
261
- "message": "Xin lỗi, lỗi xảy ra khi tìm kiếm văn bản.",
 
 
262
  "intent": intent,
263
  "results": [],
264
  "count": 0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  }
266
 
267
  # Stage 2: Choose topic/section (if document selected but no topic yet)
 
111
  intent = route_decision.forced_intent
112
 
113
  # Nếu session đã có selected_document_code (user đã chọn văn bản ở wizard)
114
+ # thì ép intent về search_legal CHỈ KHI query không phải greeting/small_talk
115
+ # Tránh ép greeting thành search_legal (gây chậm sai logic)
116
+ if selected_doc_code and route_decision.route != IntentRoute.GREETING:
117
+ # Chỉ ép intent nếu không phải greeting đơn giản
118
+ query_lower = query.lower().strip()
119
+ is_simple_greeting = (
120
+ len(query_lower.split()) <= 3 and
121
+ any(greeting in query_lower for greeting in ["xin chào", "xin chao", "chào", "chao", "hello", "hi", "hey"]) and
122
+ not any(kw in query_lower for kw in ["phạt", "mức phạt", "vi phạm", "thủ tục", "hồ sơ", "địa chỉ", "công an", "cảnh báo", "kỷ luật", "đảng"])
123
+ )
124
+ if not is_simple_greeting:
125
+ intent = "search_legal"
126
+ route_decision.route = IntentRoute.SEARCH
127
+ route_decision.forced_intent = "search_legal"
128
+ else:
129
+ # Reset selected_doc_code nếu user gửi greeting mới
130
+ if session_id:
131
+ try:
132
+ ConversationContext.update_session_metadata(
133
+ session_id,
134
+ {"selected_document_code": None, "selected_topic": None, "wizard_stage": None}
135
+ )
136
+ selected_doc_code = None
137
+ logger.info("[WIZARD] Reset selected_doc_code for new greeting")
138
+ except Exception:
139
+ pass
140
 
141
  # Map tất cả intent tra cứu nội dung về search_legal
142
  domain_search_intents = {
 
237
  if intent == "search_legal" and not selected_doc_code and not has_doc_code_in_query:
238
  print("[WIZARD] ✅ Stage 1: Using direct semantic search from slow_path_handler")
239
  # Delegate to slow_path_handler which uses direct semantic search (no query rewrite)
240
+ try:
241
+ slow_handler = SlowPathHandler()
242
+ response = slow_handler.handle(
243
+ query=query,
244
+ intent=intent,
245
+ session_id=session_id,
246
+ selected_document_code=None, # No document selected yet
247
+ )
248
+ except Exception as e:
249
+ logger.error("[WIZARD] Error in slow_path_handler: %s", e, exc_info=True)
250
+ print(f"⚠️ [WIZARD] Error in slow_path_handler: {e}")
251
+ response = None
252
 
253
+ # Ensure response has wizard metadata and required fields
254
+ if response and isinstance(response, dict):
255
+ # Ensure message field exists
256
+ if not response.get("message") and not response.get("clarification"):
257
+ response["message"] = "Tôi đã tìm thấy các văn bản pháp luật liên quan. Bạn hãy chọn văn bản muốn tra cứu:"
258
+
259
  response.setdefault("wizard_stage", "choose_document")
260
  response.setdefault("routing", "legal_wizard")
261
  response.setdefault("type", "options")
262
+ response.setdefault("intent", intent)
263
+ response.setdefault("results", [])
264
+ response.setdefault("count", 0)
265
 
266
  # Update session metadata
267
  if session_id:
 
280
  if session_id:
281
  try:
282
  bot_message = response.get("message") or response.get("clarification", {}).get("message", "")
283
+ if bot_message:
284
+ ConversationContext.add_message(
285
+ session_id=session_id,
286
+ role="bot",
287
+ content=bot_message,
288
+ intent=intent,
289
+ )
290
  except Exception as e:
291
  print(f"⚠️ Failed to save wizard bot message: {e}")
292
+
293
+ return response
294
 
295
+ # Fallback response if slow_handler failed or returned invalid response
296
+ logger.warning("[WIZARD] slow_path_handler returned invalid response, using fallback")
297
+ return {
298
+ "message": "Tôi đã tìm thấy các văn bản pháp luật liên quan. Bạn hãy chọn văn bản muốn tra cứu:",
299
  "intent": intent,
300
  "results": [],
301
  "count": 0,
302
+ "type": "options",
303
+ "wizard_stage": "choose_document",
304
+ "routing": "legal_wizard",
305
+ "clarification": {
306
+ "message": "Tôi đã tìm thấy các văn bản pháp luật liên quan. Bạn hãy chọn văn bản muốn tra cứu:",
307
+ "options": [
308
+ {
309
+ "code": "264-QD-TW",
310
+ "title": "Quyết định 264-QĐ/TW về kỷ luật đảng viên",
311
+ "reason": "Quy định chung về xử lý kỷ luật đối với đảng viên vi phạm.",
312
+ },
313
+ {
314
+ "code": "QD-69-TW",
315
+ "title": "Quy định 69-QĐ/TW về kỷ luật tổ chức đảng, đảng viên",
316
+ "reason": "Quy định chi tiết về các hành vi vi phạm và hình thức kỷ luật.",
317
+ },
318
+ {
319
+ "code": "TT-02-CAND",
320
+ "title": "Thông tư 02/2021/TT-BCA về điều lệnh CAND",
321
+ "reason": "Quy định về điều lệnh, lễ tiết, tác phong trong CAND.",
322
+ },
323
+ ],
324
+ },
325
  }
326
 
327
  # Stage 2: Choose topic/section (if document selected but no topic yet)
backend/hue_portal/chatbot/llm_integration.py CHANGED
@@ -465,9 +465,10 @@ class LLMGenerator:
465
  return
466
 
467
  # CPU-friendly defaults: smaller context/batch to reduce latency/RAM
468
- n_ctx = int(os.environ.get("LLAMA_CPP_CONTEXT", "8192"))
469
- n_threads = int(os.environ.get("LLAMA_CPP_THREADS", "4"))
470
- n_batch = int(os.environ.get("LLAMA_CPP_BATCH", "1024"))
 
471
  n_gpu_layers = int(os.environ.get("LLAMA_CPP_GPU_LAYERS", "0"))
472
  use_mmap = os.environ.get("LLAMA_CPP_USE_MMAP", "true").lower() == "true"
473
  use_mlock = os.environ.get("LLAMA_CPP_USE_MLOCK", "true").lower() == "true"
@@ -859,24 +860,11 @@ class LLMGenerator:
859
  f"Lịch sử hội thoại gần đây:\n{context_summary}\n\n"
860
  )
861
 
 
862
  prompt += (
863
- "Đây là các điều khoản/chủ đề trong văn bản có thể liên quan:\n"
864
- f"{os.linesep.join(candidate_lines)}\n\n"
865
- f"Hãy chọn tối đa {max_options} chủ đề/điều khoản quan trọng nhất cần người dùng xác nhận.\n"
866
- "Yêu cầu trả về JSON với dạng:\n"
867
- "{\n"
868
- ' "message": "Câu nhắc người dùng bằng tiếng Việt",\n'
869
- ' "options": [\n'
870
- ' {"title": "Tên chủ đề/điều khoản", "article": "Điều X", "reason": "Lý do gợi ý", "keywords": ["từ", "khóa", "tìm", "kiếm"]},\n'
871
- " ...\n"
872
- " ],\n"
873
- ' "search_keywords": ["từ", "khóa", "chính", "để", "tìm", "kiếm"]\n'
874
- "}\n"
875
- "Trong đó:\n"
876
- "- options: Danh sách chủ đề/điều khoản để người dùng chọn\n"
877
- "- search_keywords: Danh sách từ khóa quan trọng để tìm kiếm thông tin liên quan\n"
878
- "- Mỗi option nên có keywords riêng để tìm kiếm chính xác hơn\n"
879
- "Chỉ in JSON, không thêm lời giải thích khác."
880
  )
881
 
882
  raw = self._generate_from_prompt(prompt)
@@ -1053,15 +1041,11 @@ class LLMGenerator:
1053
  ]
1054
  context_text += " " + " ".join(recent_user_messages)
1055
 
 
1056
  prompt = (
1057
- "Bạn là trợ lý pháp luật. Tôi cần bạn trích xuất các từ khóa quan trọng để tìm kiếm thông tin.\n\n"
1058
- f"Ngữ cảnh: {context_text[:500]}\n\n"
1059
- "Hãy trích xuất 5-10 từ khóa quan trọng nhất (tiếng Việt) để tìm kiếm.\n"
1060
- "Yêu cầu trả về JSON với dạng:\n"
1061
- "{\n"
1062
- ' "keywords": ["từ", "khóa", "quan", "trọng"]\n'
1063
- "}\n"
1064
- "Chỉ in JSON, không thêm lời giải thích khác."
1065
  )
1066
 
1067
  raw = self._generate_from_prompt(prompt)
 
465
  return
466
 
467
  # CPU-friendly defaults: smaller context/batch to reduce latency/RAM
468
+ # Tăng threads để tăng tốc độ trên CPU (HF Spaces có nhiều cores)
469
+ n_ctx = int(os.environ.get("LLAMA_CPP_CONTEXT", "4096")) # Giảm context để nhanh hơn
470
+ n_threads = int(os.environ.get("LLAMA_CPP_THREADS", "8")) # Tăng từ 4 lên 8 threads
471
+ n_batch = int(os.environ.get("LLAMA_CPP_BATCH", "512")) # Giảm batch để giảm RAM
472
  n_gpu_layers = int(os.environ.get("LLAMA_CPP_GPU_LAYERS", "0"))
473
  use_mmap = os.environ.get("LLAMA_CPP_USE_MMAP", "true").lower() == "true"
474
  use_mlock = os.environ.get("LLAMA_CPP_USE_MLOCK", "true").lower() == "true"
 
860
  f"Lịch sử hội thoại gần đây:\n{context_summary}\n\n"
861
  )
862
 
863
+ # Tối ưu prompt: rút gọn để giảm tokens và tăng tốc độ
864
  prompt += (
865
+ f"Các điều khoản liên quan:\n{os.linesep.join(candidate_lines[:5])}\n\n"
866
+ f"Chọn {max_options} chủ đề quan trọng nhất. JSON:\n"
867
+ "{\"message\": \"Câu nhắc\", \"options\": [{\"title\": \"...\", \"article\": \"...\", \"reason\": \"...\", \"keywords\": [...]}], \"search_keywords\": [...]}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
  )
869
 
870
  raw = self._generate_from_prompt(prompt)
 
1041
  ]
1042
  context_text += " " + " ".join(recent_user_messages)
1043
 
1044
+ # Tối ưu prompt: ngắn gọn hơn để giảm tokens và tăng tốc độ
1045
  prompt = (
1046
+ "Trích xuất 5-8 từ khóa quan trọng từ:\n"
1047
+ f"{context_text[:300]}\n\n"
1048
+ "JSON: {\"keywords\": [\"từ\", \"khóa\"]}"
 
 
 
 
 
1049
  )
1050
 
1051
  raw = self._generate_from_prompt(prompt)
backend/hue_portal/chatbot/views.py CHANGED
@@ -228,10 +228,36 @@ def chat(request: Request) -> Response:
228
  chatbot = get_chatbot()
229
  response = chatbot.generate_response(message, session_id=session_id)
230
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  # Ensure session_id is in response
232
  if "session_id" not in response:
233
  response["session_id"] = session_id
234
 
 
 
 
 
 
 
 
 
 
 
235
  # Enhanced logging for search_legal queries
236
  intent = response.get("intent", "unknown")
237
  if intent == "search_legal":
 
228
  chatbot = get_chatbot()
229
  response = chatbot.generate_response(message, session_id=session_id)
230
 
231
+ # Validate response - ensure it's a dict with required fields
232
+ if not response or not isinstance(response, dict):
233
+ logger.error("[CHAT] ❌ Invalid response from chatbot.generate_response: %s", type(response))
234
+ response = {
235
+ "message": "Xin lỗi, có lỗi xảy ra khi xử lý câu hỏi của bạn. Vui lòng thử lại.",
236
+ "intent": "error",
237
+ "results": [],
238
+ "count": 0,
239
+ "session_id": session_id,
240
+ }
241
+
242
+ # Ensure required fields exist
243
+ if "message" not in response and "clarification" not in response:
244
+ logger.warning("[CHAT] ⚠️ Response missing 'message' field, adding default")
245
+ response["message"] = "Xin lỗi, không thể tìm thấy thông tin."
246
+
247
  # Ensure session_id is in response
248
  if "session_id" not in response:
249
  response["session_id"] = session_id
250
 
251
+ # Ensure intent exists
252
+ if "intent" not in response:
253
+ response["intent"] = "unknown"
254
+
255
+ # Ensure results and count exist
256
+ if "results" not in response:
257
+ response["results"] = []
258
+ if "count" not in response:
259
+ response["count"] = len(response.get("results", []))
260
+
261
  # Enhanced logging for search_legal queries
262
  intent = response.get("intent", "unknown")
263
  if intent == "search_legal":