Upload backend/hue_portal/core/query_reformulation.py with huggingface_hub
Browse files
backend/hue_portal/core/query_reformulation.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Query reformulation strategies for handling difficult queries.
|
| 3 |
+
"""
|
| 4 |
+
from typing import List, Optional, Dict, Any
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def simplify_query(query: str) -> str:
|
| 9 |
+
"""
|
| 10 |
+
Simplify query by removing stopwords and keeping only key terms.
|
| 11 |
+
|
| 12 |
+
Args:
|
| 13 |
+
query: Original query string.
|
| 14 |
+
|
| 15 |
+
Returns:
|
| 16 |
+
Simplified query string.
|
| 17 |
+
"""
|
| 18 |
+
# Vietnamese stopwords
|
| 19 |
+
stopwords = {
|
| 20 |
+
"là", "gì", "bao nhiêu", "như thế nào", "ở đâu", "của", "và", "hoặc",
|
| 21 |
+
"tôi", "bạn", "có", "không", "được", "một", "các", "với", "cho",
|
| 22 |
+
"theo", "thì", "sao", "như", "về", "trong", "nào", "để", "mà"
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
words = query.lower().split()
|
| 26 |
+
key_words = [w for w in words if w not in stopwords and len(w) > 2]
|
| 27 |
+
|
| 28 |
+
return " ".join(key_words) if key_words else query
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def extract_key_terms(query: str) -> List[str]:
|
| 32 |
+
"""
|
| 33 |
+
Extract key terms from query (document codes, numbers, important nouns).
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
query: Original query string.
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
List of key terms.
|
| 40 |
+
"""
|
| 41 |
+
key_terms = []
|
| 42 |
+
|
| 43 |
+
# Extract document codes
|
| 44 |
+
doc_code_patterns = [
|
| 45 |
+
r'QD[-\s]?69',
|
| 46 |
+
r'QD[-\s]?264',
|
| 47 |
+
r'264[-\s]?QD',
|
| 48 |
+
r'TT[-\s]?02',
|
| 49 |
+
r'QUYET[-\s]?DINH[-\s]?69',
|
| 50 |
+
r'QUYET[-\s]?DINH[-\s]?264',
|
| 51 |
+
r'THONG[-\s]?TU[-\s]?02',
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
for pattern in doc_code_patterns:
|
| 55 |
+
matches = re.findall(pattern, query.upper())
|
| 56 |
+
key_terms.extend(matches)
|
| 57 |
+
|
| 58 |
+
# Extract numbers (likely article numbers)
|
| 59 |
+
numbers = re.findall(r'\d+', query)
|
| 60 |
+
key_terms.extend(numbers)
|
| 61 |
+
|
| 62 |
+
# Extract important legal terms
|
| 63 |
+
legal_terms = [
|
| 64 |
+
"kỷ luật", "đảng viên", "cán bộ", "xử lý", "hình thức",
|
| 65 |
+
"điều lệnh", "quy định", "quyết định", "thông tư"
|
| 66 |
+
]
|
| 67 |
+
|
| 68 |
+
query_lower = query.lower()
|
| 69 |
+
for term in legal_terms:
|
| 70 |
+
if term in query_lower:
|
| 71 |
+
key_terms.append(term)
|
| 72 |
+
|
| 73 |
+
return list(set(key_terms))
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def reformulate_query_multiple_ways(query: str) -> List[str]:
|
| 77 |
+
"""
|
| 78 |
+
Generate multiple reformulations of the query.
|
| 79 |
+
|
| 80 |
+
Args:
|
| 81 |
+
query: Original query string.
|
| 82 |
+
|
| 83 |
+
Returns:
|
| 84 |
+
List of reformulated queries.
|
| 85 |
+
"""
|
| 86 |
+
reformulations = [query] # Always include original
|
| 87 |
+
|
| 88 |
+
# 1. Simplified version (remove stopwords)
|
| 89 |
+
simplified = simplify_query(query)
|
| 90 |
+
if simplified != query and len(simplified) > 3:
|
| 91 |
+
reformulations.append(simplified)
|
| 92 |
+
|
| 93 |
+
# 2. Key terms only
|
| 94 |
+
key_terms = extract_key_terms(query)
|
| 95 |
+
if key_terms:
|
| 96 |
+
key_terms_query = " ".join(key_terms)
|
| 97 |
+
if key_terms_query not in reformulations:
|
| 98 |
+
reformulations.append(key_terms_query)
|
| 99 |
+
|
| 100 |
+
# 3. Remove question words
|
| 101 |
+
question_words = ["là gì", "như thế nào", "bao nhiêu", "ở đâu", "sao", "thế nào"]
|
| 102 |
+
query_lower = query.lower()
|
| 103 |
+
for qw in question_words:
|
| 104 |
+
if qw in query_lower:
|
| 105 |
+
reformulated = query_lower.replace(qw, "").strip()
|
| 106 |
+
if reformulated and reformulated not in reformulations:
|
| 107 |
+
reformulations.append(reformulated)
|
| 108 |
+
|
| 109 |
+
# 4. Expand abbreviations
|
| 110 |
+
abbreviations = {
|
| 111 |
+
"qd": "quyết định",
|
| 112 |
+
"tt": "thông tư",
|
| 113 |
+
"cand": "công an nhân dân",
|
| 114 |
+
}
|
| 115 |
+
expanded = query_lower
|
| 116 |
+
for abbr, full in abbreviations.items():
|
| 117 |
+
expanded = expanded.replace(abbr, full)
|
| 118 |
+
if expanded != query_lower and expanded not in reformulations:
|
| 119 |
+
reformulations.append(expanded)
|
| 120 |
+
|
| 121 |
+
return reformulations
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def create_fallback_queries(query: str, intent: str) -> List[str]:
|
| 125 |
+
"""
|
| 126 |
+
Create fallback queries for when primary search fails.
|
| 127 |
+
|
| 128 |
+
Args:
|
| 129 |
+
query: Original query string.
|
| 130 |
+
intent: Detected intent.
|
| 131 |
+
|
| 132 |
+
Returns:
|
| 133 |
+
List of fallback queries ordered by priority.
|
| 134 |
+
"""
|
| 135 |
+
fallbacks = []
|
| 136 |
+
|
| 137 |
+
# Strategy 1: Extract only document codes and key legal terms
|
| 138 |
+
key_terms = extract_key_terms(query)
|
| 139 |
+
if key_terms:
|
| 140 |
+
fallbacks.append(" ".join(key_terms))
|
| 141 |
+
|
| 142 |
+
# Strategy 2: Simplified query
|
| 143 |
+
simplified = simplify_query(query)
|
| 144 |
+
if simplified != query:
|
| 145 |
+
fallbacks.append(simplified)
|
| 146 |
+
|
| 147 |
+
# Strategy 3: Intent-specific keywords
|
| 148 |
+
if intent == "search_legal":
|
| 149 |
+
# Extract document code if present
|
| 150 |
+
doc_codes = []
|
| 151 |
+
if "69" in query or "quyết định 69" in query.lower():
|
| 152 |
+
doc_codes.append("QD-69-TW")
|
| 153 |
+
if "264" in query or "quyết định 264" in query.lower():
|
| 154 |
+
doc_codes.append("264-QD-TW")
|
| 155 |
+
if "thông tư 02" in query.lower() or "tt 02" in query.lower():
|
| 156 |
+
doc_codes.append("TT-02-CAND")
|
| 157 |
+
|
| 158 |
+
# Add legal keywords
|
| 159 |
+
legal_keywords = []
|
| 160 |
+
if "kỷ luật" in query.lower():
|
| 161 |
+
legal_keywords.append("kỷ luật")
|
| 162 |
+
if "đảng viên" in query.lower():
|
| 163 |
+
legal_keywords.append("đảng viên")
|
| 164 |
+
if "xử lý" in query.lower():
|
| 165 |
+
legal_keywords.append("xử lý")
|
| 166 |
+
|
| 167 |
+
if doc_codes or legal_keywords:
|
| 168 |
+
fallback = " ".join(doc_codes + legal_keywords)
|
| 169 |
+
if fallback not in fallbacks:
|
| 170 |
+
fallbacks.append(fallback)
|
| 171 |
+
|
| 172 |
+
return fallbacks
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def reformulate_with_llm(query: str, intent: str, llm_generator=None) -> List[str]:
|
| 176 |
+
"""
|
| 177 |
+
Use LLM to reformulate complex queries into simpler, more searchable forms.
|
| 178 |
+
|
| 179 |
+
Args:
|
| 180 |
+
query: Original query string.
|
| 181 |
+
intent: Detected intent.
|
| 182 |
+
llm_generator: Optional LLM generator instance.
|
| 183 |
+
|
| 184 |
+
Returns:
|
| 185 |
+
List of reformulated queries.
|
| 186 |
+
"""
|
| 187 |
+
if not llm_generator:
|
| 188 |
+
return []
|
| 189 |
+
|
| 190 |
+
try:
|
| 191 |
+
# Create prompt for query reformulation
|
| 192 |
+
reformulation_prompt = f"""Bạn là trợ lý tìm kiếm văn bản pháp luật. Nhiệm vụ của bạn là chuyển đổi câu hỏi phức tạp thành các câu hỏi đơn giản hơn, dễ tìm kiếm hơn.
|
| 193 |
+
|
| 194 |
+
Câu hỏi gốc: "{query}"
|
| 195 |
+
|
| 196 |
+
Hãy tạo 3-5 phiên bản đơn giản hóa của câu hỏi này, tập trung vào:
|
| 197 |
+
1. Mã văn bản (nếu có): QD-69-TW, 264-QD-TW, TT-02-CAND, TT-02-BIEN-SOAN
|
| 198 |
+
2. Từ khóa chính: kỷ luật, đảng viên, xử lý, hình thức, quy định
|
| 199 |
+
3. Số điều/khoản (nếu có)
|
| 200 |
+
|
| 201 |
+
Trả về mỗi câu hỏi trên một dòng, không đánh số, không giải thích thêm.
|
| 202 |
+
Chỉ trả về các câu hỏi, không có tiêu đề hay format khác."""
|
| 203 |
+
|
| 204 |
+
response = llm_generator.generate_answer(
|
| 205 |
+
reformulation_prompt,
|
| 206 |
+
context=None,
|
| 207 |
+
documents=[]
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
if response:
|
| 211 |
+
# Parse response into list of queries
|
| 212 |
+
reformulated = [
|
| 213 |
+
line.strip()
|
| 214 |
+
for line in response.split('\n')
|
| 215 |
+
if line.strip() and not line.strip().startswith(('#', '-', '*', '1.', '2.', '3.'))
|
| 216 |
+
]
|
| 217 |
+
# Filter out queries that are too similar to original or too short
|
| 218 |
+
reformulated = [
|
| 219 |
+
q for q in reformulated
|
| 220 |
+
if len(q) > 5 and q.lower() != query.lower()
|
| 221 |
+
]
|
| 222 |
+
return reformulated[:5] # Limit to 5 reformulations
|
| 223 |
+
except Exception as e:
|
| 224 |
+
print(f"[Query Reformulation] ⚠️ LLM reformulation failed: {e}", flush=True)
|
| 225 |
+
|
| 226 |
+
return []
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def suggest_query_improvements(query: str, intent: str, found_documents: int = 0) -> str:
|
| 230 |
+
"""
|
| 231 |
+
Generate helpful suggestions for users when query is too difficult.
|
| 232 |
+
|
| 233 |
+
Args:
|
| 234 |
+
query: Original query string.
|
| 235 |
+
intent: Detected intent.
|
| 236 |
+
found_documents: Number of documents found.
|
| 237 |
+
|
| 238 |
+
Returns:
|
| 239 |
+
Suggestion message for user.
|
| 240 |
+
"""
|
| 241 |
+
suggestions = []
|
| 242 |
+
|
| 243 |
+
if intent == "search_legal":
|
| 244 |
+
if found_documents == 0:
|
| 245 |
+
suggestions.append("• Thử sử dụng mã văn bản cụ thể (ví dụ: QD-69-TW, 264-QD-TW)")
|
| 246 |
+
suggestions.append("• Nhắc đến số điều/khoản nếu bạn biết (ví dụ: Điều 5, Khoản 2)")
|
| 247 |
+
suggestions.append("• Sử dụng từ khóa chính: kỷ luật, đảng viên, xử lý, hình thức")
|
| 248 |
+
|
| 249 |
+
# Check if query has document code
|
| 250 |
+
has_code = any(code in query.upper() for code in ["QD-69", "264-QD", "TT-02", "QUYET DINH 69", "QUYET DINH 264"])
|
| 251 |
+
if not has_code:
|
| 252 |
+
suggestions.append("• Thêm mã văn bản vào câu hỏi để tìm kiếm chính xác hơn")
|
| 253 |
+
|
| 254 |
+
elif intent == "search_fine":
|
| 255 |
+
if found_documents == 0:
|
| 256 |
+
suggestions.append("• Mô tả rõ loại vi phạm (ví dụ: vượt đèn đỏ, không đội mũ bảo hiểm)")
|
| 257 |
+
suggestions.append("• Sử dụng từ khóa: mức phạt, vi phạm, xử phạt")
|
| 258 |
+
|
| 259 |
+
elif intent == "search_procedure":
|
| 260 |
+
if found_documents == 0:
|
| 261 |
+
suggestions.append("• Nêu rõ tên thủ tục hành chính bạn cần")
|
| 262 |
+
suggestions.append("• Sử dụng từ khóa: thủ tục, hồ sơ, giấy tờ")
|
| 263 |
+
|
| 264 |
+
if suggestions:
|
| 265 |
+
return "\n".join(suggestions)
|
| 266 |
+
|
| 267 |
+
return "• Thử diễn đạt câu hỏi theo cách khác\n• Sử dụng từ khóa cụ thể hơn"
|
| 268 |
+
|
| 269 |
+
|