davidtran999 commited on
Commit
6d3c5ac
·
verified ·
1 Parent(s): 340687c

Upload backend/hue_portal/chatbot/chatbot.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. backend/hue_portal/chatbot/chatbot.py +94 -130
backend/hue_portal/chatbot/chatbot.py CHANGED
@@ -6,7 +6,7 @@ import copy
6
  import logging
7
  import json
8
  import time
9
- from typing import Dict, Any, Optional, List
10
  from hue_portal.core.chatbot import Chatbot as CoreChatbot, get_chatbot as get_core_chatbot
11
  from hue_portal.chatbot.router import decide_route, IntentRoute, RouteDecision
12
  from hue_portal.chatbot.context_manager import ConversationContext
@@ -14,7 +14,6 @@ from hue_portal.chatbot.llm_integration import LLMGenerator
14
  from hue_portal.core.models import LegalSection
15
  from hue_portal.chatbot.exact_match_cache import ExactMatchCache
16
  from hue_portal.chatbot.slow_path_handler import SlowPathHandler
17
- from hue_portal.chatbot.document_topics import DOCUMENT_TOPICS, find_topic_by_code
18
 
19
  logger = logging.getLogger(__name__)
20
 
@@ -66,99 +65,6 @@ class Chatbot(CoreChatbot):
66
  print(f"⚠️ LLM generator not available: {e}")
67
  self.llm_generator = None
68
 
69
- def _prepare_document_candidates(self, topic_scores: Dict[str, float]) -> List[Dict[str, str]]:
70
- ordered_codes = sorted(
71
- topic_scores.keys(),
72
- key=lambda code: topic_scores[code],
73
- reverse=True,
74
- )
75
- if not ordered_codes:
76
- ordered_codes = [topic["code"] for topic in DOCUMENT_TOPICS[:3]]
77
- candidates = []
78
- for code in ordered_codes:
79
- topic = find_topic_by_code(code) or {}
80
- if not topic:
81
- continue
82
- candidates.append(
83
- {
84
- "code": topic["code"],
85
- "title": topic.get("title") or topic["code"],
86
- "summary": topic.get("summary") or "",
87
- "doc_type": topic.get("doc_type") or "",
88
- }
89
- )
90
- if len(candidates) >= 4:
91
- break
92
- return candidates
93
-
94
- def _build_router_clarification_response(
95
- self,
96
- query: str,
97
- route_decision: RouteDecision,
98
- ) -> Optional[Dict[str, Any]]:
99
- candidates = self._prepare_document_candidates(route_decision.topic_scores or {})
100
- if not candidates:
101
- return None
102
- llm_payload = None
103
- if self.llm_generator:
104
- llm_payload = self.llm_generator.suggest_clarification_topics(
105
- query,
106
- candidates,
107
- max_options=3,
108
- )
109
- default_message = (
110
- "Tôi tìm thấy một số văn bản liên quan. Bạn hãy chọn văn bản muốn tra cứu "
111
- "để tôi trả lời chính xác hơn."
112
- )
113
- message = default_message
114
- options = []
115
- if llm_payload:
116
- message = llm_payload.get("message") or default_message
117
- options = llm_payload.get("options") or []
118
- if not options:
119
- for candidate in candidates[:3]:
120
- options.append(
121
- {
122
- "code": candidate["code"].upper(),
123
- "title": candidate["title"],
124
- "reason": candidate.get("summary", ""),
125
- }
126
- )
127
- has_other = any(opt.get("code") == "__other__" for opt in options)
128
- if not has_other:
129
- options.append(
130
- {
131
- "code": "__other__",
132
- "title": "Khác",
133
- "reason": "Tôi muốn hỏi văn bản hoặc chủ đề khác",
134
- }
135
- )
136
- return {
137
- "message": message,
138
- "clarification": {
139
- "message": message,
140
- "options": options,
141
- },
142
- "intent": "search_legal",
143
- "routing": "clarification",
144
- "confidence": 0.3,
145
- "results": [],
146
- "count": 0,
147
- }
148
-
149
- def _build_plan_context(self, session_metadata: Dict[str, Any]) -> Optional[Dict[str, str]]:
150
- if not session_metadata:
151
- return None
152
- code = session_metadata.get("selected_document_code")
153
- if not code:
154
- return None
155
- topic = find_topic_by_code(code) or {}
156
- return {
157
- "selected_document_code": code,
158
- "selected_document_title": topic.get("title") or code,
159
- "selected_document_type": topic.get("doc_type") or "",
160
- }
161
-
162
  def generate_response(self, query: str, session_id: Optional[str] = None) -> Dict[str, Any]:
163
  """
164
  Generate chatbot response with session support and routing.
@@ -184,14 +90,14 @@ class Chatbot(CoreChatbot):
184
  print(f"⚠️ Failed to save user message: {e}")
185
 
186
  session_metadata: Dict[str, Any] = {}
 
187
  if session_id:
188
  try:
189
  session_metadata = ConversationContext.get_session_metadata(session_id)
 
190
  except Exception:
191
  session_metadata = {}
192
 
193
- selected_document_code = session_metadata.get("selected_document_code") if session_metadata else None
194
-
195
  # Classify intent
196
  intent, confidence = self.classify_intent(query)
197
 
@@ -202,6 +108,14 @@ class Chatbot(CoreChatbot):
202
  if route_decision.forced_intent:
203
  intent = route_decision.forced_intent
204
 
 
 
 
 
 
 
 
 
205
  # Map tất cả intent tra cứu nội dung về search_legal
206
  domain_search_intents = {
207
  "search_fine",
@@ -241,21 +155,6 @@ class Chatbot(CoreChatbot):
241
  print(f"⚠️ Failed to save cached bot message: {e}")
242
  return cached_response
243
 
244
- # Clarification request before running slow path
245
- if (
246
- intent == "search_legal"
247
- and not selected_document_code
248
- and route_decision.clarification_required
249
- ):
250
- clarification = self._build_router_clarification_response(query, route_decision)
251
- if clarification:
252
- if session_id:
253
- clarification["session_id"] = session_id
254
- plan_context = self._build_plan_context(session_metadata)
255
- if plan_context:
256
- clarification["plan"] = plan_context
257
- return clarification
258
-
259
  # Always send legal intent through Slow Path RAG
260
  if intent == "search_legal":
261
  response = self._run_slow_path_legal(
@@ -385,26 +284,64 @@ class Chatbot(CoreChatbot):
385
  is_off_topic = any(kw in query_lower for kw in off_topic_keywords)
386
 
387
  if is_off_topic:
388
- message = (
 
389
  "Xin lỗi, tôi là chatbot chuyên về tra cứu các văn bản quy định pháp luật "
390
  "về xử lí kỷ luật cán bộ đảng viên của Phòng Thanh Tra - Công An Thành Phố Huế.\n\n"
391
  "Tôi không thể trả lời các câu hỏi về nấu ăn, công thức nấu ăn hay các chủ đề khác ngoài phạm vi pháp luật.\n\n"
392
- "Bạnmuốn tra cứu thông tin về:\n"
393
- "- Các quy định về xử kỷ luật cán bộ đảng viên\n"
394
- "- Các điều khoản trong Thông tư 02 về xử lý điều lệnh trong CAND\n"
395
- "- Hoặc các văn bản pháp luật liên quan khác?"
396
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  else:
398
- message = "Tôi có thể giúp bạn tra cứu các văn bản quy định pháp luật về xử lí kỷ luật cán bộ đảng viên. Bạn muốn tìm gì?"
399
-
400
- response = {
401
- "message": message,
402
- "intent": intent,
403
- "confidence": confidence,
404
- "results": [],
405
- "count": 0,
406
- "routing": "small_talk"
407
- }
 
 
408
 
409
  else: # IntentRoute.SEARCH
410
  # Use core chatbot search for other intents
@@ -435,6 +372,12 @@ class Chatbot(CoreChatbot):
435
  except Exception:
436
  pass
437
 
 
 
 
 
 
 
438
  # Add session_id
439
  if session_id:
440
  response["session_id"] = session_id
@@ -454,9 +397,6 @@ class Chatbot(CoreChatbot):
454
 
455
  self._cache_response(query, intent, response)
456
 
457
- plan_context = self._build_plan_context(session_metadata)
458
- if plan_context:
459
- response["plan"] = plan_context
460
  return response
461
 
462
  def _run_slow_path_legal(
@@ -487,6 +427,30 @@ class Chatbot(CoreChatbot):
487
  "confidence": route_decision.confidence,
488
  },
489
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  logger.info(
491
  "[LEGAL] Slow path response - source=%s count=%s routing=%s",
492
  response.get("_source"),
 
6
  import logging
7
  import json
8
  import time
9
+ from typing import Dict, Any, Optional
10
  from hue_portal.core.chatbot import Chatbot as CoreChatbot, get_chatbot as get_core_chatbot
11
  from hue_portal.chatbot.router import decide_route, IntentRoute, RouteDecision
12
  from hue_portal.chatbot.context_manager import ConversationContext
 
14
  from hue_portal.core.models import LegalSection
15
  from hue_portal.chatbot.exact_match_cache import ExactMatchCache
16
  from hue_portal.chatbot.slow_path_handler import SlowPathHandler
 
17
 
18
  logger = logging.getLogger(__name__)
19
 
 
65
  print(f"⚠️ LLM generator not available: {e}")
66
  self.llm_generator = None
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  def generate_response(self, query: str, session_id: Optional[str] = None) -> Dict[str, Any]:
69
  """
70
  Generate chatbot response with session support and routing.
 
90
  print(f"⚠️ Failed to save user message: {e}")
91
 
92
  session_metadata: Dict[str, Any] = {}
93
+ selected_doc_code: Optional[str] = None
94
  if session_id:
95
  try:
96
  session_metadata = ConversationContext.get_session_metadata(session_id)
97
+ selected_doc_code = session_metadata.get("selected_document_code")
98
  except Exception:
99
  session_metadata = {}
100
 
 
 
101
  # Classify intent
102
  intent, confidence = self.classify_intent(query)
103
 
 
108
  if route_decision.forced_intent:
109
  intent = route_decision.forced_intent
110
 
111
+ # Nếu session đã có selected_document_code (user đã chọn văn bản ở wizard)
112
+ # thì luôn ép intent về search_legal và route sang SEARCH,
113
+ # tránh bị kẹt ở nhánh small-talk/off-topic do nội dung câu hỏi ban đầu.
114
+ if selected_doc_code:
115
+ intent = "search_legal"
116
+ route_decision.route = IntentRoute.SEARCH
117
+ route_decision.forced_intent = "search_legal"
118
+
119
  # Map tất cả intent tra cứu nội dung về search_legal
120
  domain_search_intents = {
121
  "search_fine",
 
155
  print(f"⚠️ Failed to save cached bot message: {e}")
156
  return cached_response
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  # Always send legal intent through Slow Path RAG
159
  if intent == "search_legal":
160
  response = self._run_slow_path_legal(
 
284
  is_off_topic = any(kw in query_lower for kw in off_topic_keywords)
285
 
286
  if is_off_topic:
287
+ # Ngoài phạm vi → từ chối lịch sự + gợi ý wizard với các văn bản pháp lý chính
288
+ intro_message = (
289
  "Xin lỗi, tôi là chatbot chuyên về tra cứu các văn bản quy định pháp luật "
290
  "về xử lí kỷ luật cán bộ đảng viên của Phòng Thanh Tra - Công An Thành Phố Huế.\n\n"
291
  "Tôi không thể trả lời các câu hỏi về nấu ăn, công thức nấu ăn hay các chủ đề khác ngoài phạm vi pháp luật.\n\n"
292
+ "Tuy nhiên, tôi thể giúp bạn tra cứu một số văn bản pháp luật quan trọng. "
293
+ "Bạn hãy chọn văn bản muốn xem trước:"
 
 
294
  )
295
+ clarification_options = [
296
+ {
297
+ "code": "264-QD-TW",
298
+ "title": "Quyết định 264-QĐ/TW về kỷ luật đảng viên",
299
+ "reason": "Quy định chung về xử lý kỷ luật đối với đảng viên vi phạm.",
300
+ },
301
+ {
302
+ "code": "QD-69-TW",
303
+ "title": "Quy định 69-QĐ/TW về kỷ luật tổ chức đảng, đảng viên",
304
+ "reason": "Quy định chi tiết về các hành vi vi phạm và hình thức kỷ luật.",
305
+ },
306
+ {
307
+ "code": "TT-02-CAND",
308
+ "title": "Thông tư 02/2021/TT-BCA về điều lệnh CAND",
309
+ "reason": "Quy định về điều lệnh, lễ tiết, tác phong trong CAND.",
310
+ },
311
+ {
312
+ "code": "__other__",
313
+ "title": "Khác",
314
+ "reason": "Tôi muốn hỏi văn bản hoặc chủ đề pháp luật khác.",
315
+ },
316
+ ]
317
+ response = {
318
+ "message": intro_message,
319
+ "intent": intent,
320
+ "confidence": confidence,
321
+ "results": [],
322
+ "count": 0,
323
+ "routing": "small_talk_offtopic_wizard",
324
+ "type": "options",
325
+ "wizard_stage": "choose_document",
326
+ "clarification": {
327
+ "message": intro_message,
328
+ "options": clarification_options,
329
+ },
330
+ "options": clarification_options,
331
+ }
332
  else:
333
+ message = (
334
+ "Tôi có thể giúp bạn tra cứu các văn bản quy định pháp luật về xử lí kỷ luật cán bộ đảng viên. "
335
+ "Bạn muốn tìm gì?"
336
+ )
337
+ response = {
338
+ "message": message,
339
+ "intent": intent,
340
+ "confidence": confidence,
341
+ "results": [],
342
+ "count": 0,
343
+ "routing": "small_talk",
344
+ }
345
 
346
  else: # IntentRoute.SEARCH
347
  # Use core chatbot search for other intents
 
372
  except Exception:
373
  pass
374
 
375
+ # Đánh dấu loại payload cho frontend: answer hay options (wizard)
376
+ if response.get("clarification") or response.get("type") == "options":
377
+ response.setdefault("type", "options")
378
+ else:
379
+ response.setdefault("type", "answer")
380
+
381
  # Add session_id
382
  if session_id:
383
  response["session_id"] = session_id
 
397
 
398
  self._cache_response(query, intent, response)
399
 
 
 
 
400
  return response
401
 
402
  def _run_slow_path_legal(
 
427
  "confidence": route_decision.confidence,
428
  },
429
  )
430
+
431
+ # Cập nhật metadata wizard đơn giản: nếu đang hỏi người dùng chọn văn bản
432
+ # thì đánh dấu stage = choose_document; nếu đã trả lời thì stage = answer.
433
+ if session_id:
434
+ try:
435
+ if response.get("clarification") or response.get("type") == "options":
436
+ ConversationContext.update_session_metadata(
437
+ session_id,
438
+ {
439
+ "wizard_stage": "choose_document",
440
+ },
441
+ )
442
+ else:
443
+ ConversationContext.update_session_metadata(
444
+ session_id,
445
+ {
446
+ "wizard_stage": "answer",
447
+ "last_answer_type": response.get("intent"),
448
+ },
449
+ )
450
+ except Exception:
451
+ # Không để lỗi metadata làm hỏng luồng trả lời chính
452
+ pass
453
+
454
  logger.info(
455
  "[LEGAL] Slow path response - source=%s count=%s routing=%s",
456
  response.get("_source"),