Spaces:
Sleeping
Sleeping
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ì
|
| 115 |
-
#
|
| 116 |
-
if selected_doc_code:
|
| 117 |
-
intent
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 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 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
|
|
|
| 257 |
except Exception as e:
|
| 258 |
print(f"⚠️ Failed to save wizard bot message: {e}")
|
|
|
|
|
|
|
| 259 |
|
| 260 |
-
|
| 261 |
-
|
|
|
|
|
|
|
| 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 và 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 |
-
|
| 469 |
-
|
| 470 |
-
|
|
|
|
| 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 |
-
"
|
| 864 |
-
f"{
|
| 865 |
-
|
| 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 |
-
"
|
| 1058 |
-
f"
|
| 1059 |
-
"
|
| 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":
|