Upload backend/hue_portal/chatbot/chatbot.py with huggingface_hub
Browse files
backend/hue_portal/chatbot/chatbot.py
CHANGED
|
@@ -13,7 +13,7 @@ from hue_portal.core.chatbot import Chatbot as CoreChatbot, get_chatbot as get_c
|
|
| 13 |
from hue_portal.chatbot.router import decide_route, IntentRoute, RouteDecision, DOCUMENT_CODE_PATTERNS
|
| 14 |
from hue_portal.chatbot.context_manager import ConversationContext
|
| 15 |
from hue_portal.chatbot.llm_integration import LLMGenerator
|
| 16 |
-
from hue_portal.core.models import LegalSection
|
| 17 |
from hue_portal.chatbot.exact_match_cache import ExactMatchCache
|
| 18 |
from hue_portal.chatbot.slow_path_handler import SlowPathHandler
|
| 19 |
|
|
@@ -172,33 +172,118 @@ class Chatbot(CoreChatbot):
|
|
| 172 |
# - Nếu user chưa chọn và không có mã trong query → bật wizard để user chọn
|
| 173 |
# - Nếu có mã trong query → không bật wizard, đi thẳng vào slow_path
|
| 174 |
if intent == "search_legal" and not selected_doc_code and not has_doc_code_in_query:
|
| 175 |
-
print("[WIZARD] ✅ Chatbot layer wizard triggered,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
intro_message = (
|
| 177 |
"Tôi tìm thấy một số nhóm văn bản có thể liên quan đến câu hỏi của bạn.\n\n"
|
| 178 |
"Bạn hãy chọn văn bản muốn tra cứu trước, sau đó tôi sẽ trả lời chi tiết hơn:"
|
| 179 |
)
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
response = {
|
| 203 |
"message": intro_message,
|
| 204 |
"intent": intent,
|
|
|
|
| 13 |
from hue_portal.chatbot.router import decide_route, IntentRoute, RouteDecision, DOCUMENT_CODE_PATTERNS
|
| 14 |
from hue_portal.chatbot.context_manager import ConversationContext
|
| 15 |
from hue_portal.chatbot.llm_integration import LLMGenerator
|
| 16 |
+
from hue_portal.core.models import LegalSection, LegalDocument
|
| 17 |
from hue_portal.chatbot.exact_match_cache import ExactMatchCache
|
| 18 |
from hue_portal.chatbot.slow_path_handler import SlowPathHandler
|
| 19 |
|
|
|
|
| 172 |
# - Nếu user chưa chọn và không có mã trong query → bật wizard để user chọn
|
| 173 |
# - Nếu có mã trong query → không bật wizard, đi thẳng vào slow_path
|
| 174 |
if intent == "search_legal" and not selected_doc_code and not has_doc_code_in_query:
|
| 175 |
+
print("[WIZARD] ✅ Chatbot layer wizard triggered, using AI to generate options")
|
| 176 |
+
# Load canonical documents từ DB
|
| 177 |
+
canonical_candidates = []
|
| 178 |
+
try:
|
| 179 |
+
canonical_docs = list(
|
| 180 |
+
LegalDocument.objects.filter(
|
| 181 |
+
code__in=["264-QD-TW", "QD-69-TW", "TT-02-CAND"]
|
| 182 |
+
)
|
| 183 |
+
)
|
| 184 |
+
for doc in canonical_docs:
|
| 185 |
+
summary = getattr(doc, "summary", "") or ""
|
| 186 |
+
metadata = getattr(doc, "metadata", {}) or {}
|
| 187 |
+
if not summary and isinstance(metadata, dict):
|
| 188 |
+
summary = metadata.get("summary", "")
|
| 189 |
+
canonical_candidates.append(
|
| 190 |
+
{
|
| 191 |
+
"code": doc.code,
|
| 192 |
+
"title": getattr(doc, "title", "") or doc.code,
|
| 193 |
+
"summary": summary,
|
| 194 |
+
"doc_type": getattr(doc, "doc_type", "") or "",
|
| 195 |
+
"section_title": "",
|
| 196 |
+
}
|
| 197 |
+
)
|
| 198 |
+
except Exception as exc:
|
| 199 |
+
logger.warning("[WIZARD] Failed to load canonical documents: %s", exc)
|
| 200 |
+
|
| 201 |
+
# Fallback nếu không load được từ DB
|
| 202 |
+
if not canonical_candidates:
|
| 203 |
+
canonical_candidates = [
|
| 204 |
+
{
|
| 205 |
+
"code": "264-QD-TW",
|
| 206 |
+
"title": "Quyết định 264-QĐ/TW về kỷ luật đảng viên",
|
| 207 |
+
"summary": "Quy định chung về xử lý kỷ luật đối với đảng viên vi phạm.",
|
| 208 |
+
"doc_type": "",
|
| 209 |
+
"section_title": "",
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
"code": "QD-69-TW",
|
| 213 |
+
"title": "Quy định 69-QĐ/TW về kỷ luật tổ chức đảng, đảng viên",
|
| 214 |
+
"summary": "Quy định chi tiết về các hành vi vi phạm và hình thức kỷ luật.",
|
| 215 |
+
"doc_type": "",
|
| 216 |
+
"section_title": "",
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"code": "TT-02-CAND",
|
| 220 |
+
"title": "Thông tư 02/2021/TT-BCA về điều lệnh CAND",
|
| 221 |
+
"summary": "Quy định về điều lệnh, lễ tiết, tác phong trong CAND.",
|
| 222 |
+
"doc_type": "",
|
| 223 |
+
"section_title": "",
|
| 224 |
+
},
|
| 225 |
+
]
|
| 226 |
+
|
| 227 |
+
# Dùng LLM để đề xuất options dựa trên câu hỏi
|
| 228 |
+
clarification_options = []
|
| 229 |
intro_message = (
|
| 230 |
"Tôi tìm thấy một số nhóm văn bản có thể liên quan đến câu hỏi của bạn.\n\n"
|
| 231 |
"Bạn hãy chọn văn bản muốn tra cứu trước, sau đó tôi sẽ trả lời chi tiết hơn:"
|
| 232 |
)
|
| 233 |
+
|
| 234 |
+
if self.llm_generator:
|
| 235 |
+
try:
|
| 236 |
+
llm_payload = self.llm_generator.suggest_clarification_topics(
|
| 237 |
+
query,
|
| 238 |
+
canonical_candidates,
|
| 239 |
+
max_options=3,
|
| 240 |
+
)
|
| 241 |
+
if llm_payload:
|
| 242 |
+
intro_message = llm_payload.get("message") or intro_message
|
| 243 |
+
raw_options = llm_payload.get("options")
|
| 244 |
+
if isinstance(raw_options, list) and len(raw_options) > 0:
|
| 245 |
+
clarification_options = [
|
| 246 |
+
{
|
| 247 |
+
"code": (opt.get("code") or candidate.get("code", "")).upper(),
|
| 248 |
+
"title": opt.get("title") or opt.get("document_title") or candidate.get("title", ""),
|
| 249 |
+
"reason": opt.get("reason")
|
| 250 |
+
or opt.get("summary")
|
| 251 |
+
or candidate.get("summary")
|
| 252 |
+
or candidate.get("section_title")
|
| 253 |
+
or "",
|
| 254 |
+
}
|
| 255 |
+
for opt, candidate in zip(
|
| 256 |
+
raw_options,
|
| 257 |
+
canonical_candidates[: len(raw_options)],
|
| 258 |
+
)
|
| 259 |
+
if (opt.get("code") or candidate.get("code"))
|
| 260 |
+
and (opt.get("title") or opt.get("document_title") or candidate.get("title"))
|
| 261 |
+
]
|
| 262 |
+
print(f"[WIZARD] ✅ LLM generated {len(clarification_options)} options")
|
| 263 |
+
except Exception as exc:
|
| 264 |
+
logger.warning("[WIZARD] LLM suggestion failed: %s, using fallback", exc)
|
| 265 |
+
|
| 266 |
+
# Fallback nếu LLM không trả về options hợp lệ
|
| 267 |
+
if not clarification_options:
|
| 268 |
+
clarification_options = [
|
| 269 |
+
{
|
| 270 |
+
"code": candidate["code"].upper(),
|
| 271 |
+
"title": candidate["title"],
|
| 272 |
+
"reason": candidate.get("summary") or candidate.get("section_title") or "",
|
| 273 |
+
}
|
| 274 |
+
for candidate in canonical_candidates[:3]
|
| 275 |
+
]
|
| 276 |
+
print("[WIZARD] Using fallback options (LLM unavailable or failed)")
|
| 277 |
+
|
| 278 |
+
# Thêm option "Khác" nếu chưa có
|
| 279 |
+
if not any(opt.get("code") == "__other__" for opt in clarification_options):
|
| 280 |
+
clarification_options.append(
|
| 281 |
+
{
|
| 282 |
+
"code": "__other__",
|
| 283 |
+
"title": "Khác",
|
| 284 |
+
"reason": "Tôi muốn hỏi văn bản hoặc chủ đề pháp luật khác.",
|
| 285 |
+
}
|
| 286 |
+
)
|
| 287 |
response = {
|
| 288 |
"message": intro_message,
|
| 289 |
"intent": intent,
|