fisherman611 commited on
Commit
322af2a
·
verified ·
1 Parent(s): 610f3f3

Update utils/question_refiner.py

Browse files
Files changed (1) hide show
  1. utils/question_refiner.py +1110 -976
utils/question_refiner.py CHANGED
@@ -1,976 +1,1110 @@
1
- import re
2
- import json
3
- from typing import Dict, List, Tuple, Optional, Any
4
- from langchain_google_genai import ChatGoogleGenerativeAI
5
- from langchain.prompts import PromptTemplate
6
- from config import Config
7
-
8
- class VietnameseLegalQuestionRefiner:
9
- """
10
- Refines Vietnamese legal questions for better search and understanding
11
- """
12
-
13
- def __init__(self):
14
- # Initialize LLM for question refinement
15
- self.llm = None
16
- if Config.GOOGLE_API_KEY:
17
- self.llm = ChatGoogleGenerativeAI(
18
- model=Config.MODEL_REFINE,
19
- google_api_key=Config.GOOGLE_API_KEY,
20
- temperature=0.1
21
- )
22
-
23
- # Vietnamese legal terminology mappings
24
- self.legal_abbreviations = {
25
- # Common legal abbreviations
26
- "dn": "doanh nghiệp",
27
- "dntn": "doanh nghiệp tư nhân",
28
- "tnhh": "trách nhiệm hữu hạn",
29
- "cp": "cổ phần",
30
- "hđ": "hợp đồng",
31
- "hđlđ": "hợp đồng lao động",
32
- "tclđ": "tai cạnh lao động",
33
- "bhxh": "bảo hiểm xã hội",
34
- "bhyt": "bảo hiểm y tế",
35
- "bhtn": "bảo hiểm thất nghiệp",
36
- "qsd": "quyền sử dụng",
37
- "qsdđ": "quyền sử dụng đất",
38
- "gcn": "giấy chứng nhận",
39
- "gpkd": "giấy phép kinh doanh",
40
- "gpđkkd": "giấy phép đăng ký kinh doanh",
41
- "mst": "mã số thuế",
42
- "tncn": "thuế thu nhập cá nhân",
43
- "tndn": "thuế thu nhập doanh nghiệp",
44
- "gtgt": "giá trị gia tăng",
45
- "vat": "thuế giá trị gia tăng",
46
- "nld": "người lao động",
47
- "ntd": "người sử dụng lao động",
48
- "tc": "tài chính",
49
- "kt": "kế toán",
50
- "tl": "tài liệu",
51
- "vb": "văn bản",
52
- "qđ": "quyết định",
53
- "tt": "thông tư",
54
- "nđ": "nghị định",
55
- "dl": "dự luật",
56
- "qh": "quốc hội",
57
- "cp": "chính phủ",
58
- "btc": "bộ tài chính",
59
- "blđtbxh": "bộ lao động thương binh và xã hội",
60
- "btp": "bộ tư pháp",
61
- "btn": "bộ tài nguyên",
62
- "khdn": "kế hoạch doanh nghiệp"
63
- }
64
-
65
- # Legal context keywords
66
- self.legal_contexts = {
67
- "business": ["doanh nghiệp", "kinh doanh", "công ty", "thành lập", "giải thể", "vốn điều lệ"],
68
- "labor": ["lao động", "nhân viên", "hợp đồng lao động", "lương", "nghỉ phép", "sa thải"],
69
- "tax": ["thuế", "kê khai", "miễn thuế", "giảm thuế", "mức thuế", "thuế suất"],
70
- "real_estate": ["bất động sản", "đất đai", "nhà ở", "chuyển nhượng", "sổ đỏ", "quyền sử dụng"],
71
- "family": ["gia đình", "hôn nhân", "ly hôn", "thừa kế", "con cái", "nuôi dưỡng"],
72
- "criminal": ["hình sự", "vi phạm", "tội danh", "án phạt", "bồi thường"],
73
- "civil": ["dân sự", "tranh chấp", "khiếu nại", "tố cáo", "bồi thường"]
74
- }
75
-
76
- # Common misspellings and corrections
77
- self.common_corrections = {
78
- "doanh nghiep": "doanh nghiệp",
79
- "hop dong": "hợp đồng",
80
- "lao dong": "lao động",
81
- "tai chinh": "tài chính",
82
- "ke toan": "kế toán",
83
- "thue": "thuế",
84
- "quyen": "quyền",
85
- "nghia vu": "nghĩa vụ",
86
- "dat dai": "đất đai",
87
- "nha o": "nhà ở",
88
- "gia dinh": "gia đình",
89
- "hon nhan": "hôn nhân",
90
- "ly hon": "ly hôn"
91
- }
92
-
93
- def refine_question(self, question: str, use_llm: bool = True) -> Dict[str, str]:
94
- """
95
- Main method to refine a Vietnamese legal question
96
-
97
- Args:
98
- question: Original user question
99
- use_llm: Whether to use LLM for advanced refinement
100
-
101
- Returns:
102
- Dictionary containing original and refined questions with metadata
103
- """
104
- result = {
105
- "original_question": question,
106
- "refined_question": question,
107
- "refinement_steps": [],
108
- "detected_context": [],
109
- "expanded_terms": [],
110
- "corrections_made": []
111
- }
112
-
113
- # Step 1: Basic cleaning and normalization
114
- cleaned_question = self._basic_cleaning(question)
115
- if cleaned_question != question:
116
- result["refinement_steps"].append("basic_cleaning")
117
- result["refined_question"] = cleaned_question
118
-
119
- # Step 2: Correct common misspellings
120
- corrected_question = self._correct_spelling(cleaned_question)
121
- if corrected_question != cleaned_question:
122
- result["refinement_steps"].append("spelling_correction")
123
- result["corrections_made"] = self._get_corrections_made(cleaned_question, corrected_question)
124
- result["refined_question"] = corrected_question
125
-
126
- # Step 3: Expand abbreviations
127
- expanded_question = self._expand_abbreviations(corrected_question)
128
- if expanded_question != corrected_question:
129
- result["refinement_steps"].append("abbreviation_expansion")
130
- result["expanded_terms"] = self._get_expanded_terms(corrected_question, expanded_question)
131
- result["refined_question"] = expanded_question
132
-
133
- # Step 4: Advanced LLM-based context detection (if enabled)
134
- if use_llm and self.llm:
135
- context = self._llm_detect_legal_context(expanded_question)
136
- result["llm_context_detection"] = True
137
- else:
138
- context = self._detect_legal_context(expanded_question)
139
- result["llm_context_detection"] = False
140
-
141
- result["detected_context"] = context
142
-
143
- # Step 5: LLM-based intent analysis (if enabled)
144
- intent_analysis = {}
145
- if use_llm and self.llm:
146
- intent_analysis = self._llm_analyze_question_intent(expanded_question)
147
- result["intent_analysis"] = intent_analysis
148
- result["refinement_steps"].append("intent_analysis")
149
-
150
- # Step 6: Add context keywords
151
- context_enhanced_question = self._add_context_keywords(expanded_question, context)
152
- if context_enhanced_question != expanded_question:
153
- result["refinement_steps"].append("context_enhancement")
154
- result["refined_question"] = context_enhanced_question
155
-
156
- # Step 7: Advanced LLM-based refinement (if enabled and available)
157
- if use_llm and self.llm and len(result["refined_question"].strip()) > 10:
158
- best_refined = result["refined_question"]
159
- refinement_method = None
160
-
161
- # Try chain-of-thought for complex questions
162
- if (Config.ENABLE_CHAIN_OF_THOUGHT and
163
- intent_analysis.get("complexity") == "complex"):
164
- cot_refined = self._llm_chain_of_thought_refinement(
165
- result["refined_question"], context, intent_analysis
166
- )
167
- if cot_refined:
168
- best_refined = cot_refined
169
- refinement_method = "chain_of_thought"
170
- result["refinement_steps"].append("chain_of_thought")
171
-
172
- # Try iterative refinement for moderate complexity
173
- elif (Config.ENABLE_ITERATIVE_REFINEMENT and
174
- intent_analysis.get("complexity") in ["moderate", "complex"]):
175
- iterative_refined = self._llm_iterative_refinement(
176
- result["refined_question"], context, Config.MAX_REFINEMENT_ITERATIONS
177
- )
178
- if iterative_refined:
179
- best_refined = iterative_refined
180
- refinement_method = "iterative"
181
- result["refinement_steps"].append("iterative_refinement")
182
-
183
- # Fallback to standard advanced refinement
184
- if not refinement_method:
185
- llm_refined = self._llm_refine_question_advanced(
186
- result["refined_question"],
187
- context,
188
- intent_analysis
189
- )
190
- if llm_refined:
191
- best_refined = llm_refined
192
- refinement_method = "advanced"
193
- result["refinement_steps"].append("llm_enhancement")
194
-
195
- # Apply the best refinement if found
196
- if best_refined != result["refined_question"]:
197
- # Validate the refinement with LLM (if enabled)
198
- if Config.ENABLE_LLM_VALIDATION:
199
- if self._llm_validate_refinement(result["refined_question"], best_refined, context):
200
- result["refined_question"] = best_refined
201
- result["llm_validation_passed"] = True
202
- result["refinement_method"] = refinement_method
203
- else:
204
- result["llm_validation_passed"] = False
205
- print(f"LLM refinement ({refinement_method}) rejected by validation")
206
- else:
207
- # Apply without validation
208
- result["refined_question"] = best_refined
209
- result["llm_validation_passed"] = None
210
- result["refinement_method"] = refinement_method
211
-
212
- return result
213
-
214
- def _basic_cleaning(self, question: str) -> str:
215
- """Basic text cleaning and normalization"""
216
- # Remove extra whitespace
217
- question = re.sub(r'\s+', ' ', question.strip())
218
-
219
- # Remove special characters except Vietnamese diacritics and basic punctuation
220
- question = re.sub(r'[^\w\s\u00C0-\u017F\u1EA0-\u1EF9\?\.\,\!\-\(\)]', ' ', question)
221
-
222
- # Normalize question marks
223
- question = re.sub(r'\?+', '?', question)
224
-
225
- # Ensure question ends with appropriate punctuation
226
- if not question.endswith(('?', '.', '!')):
227
- question += '?'
228
-
229
- return question.strip()
230
-
231
- def _correct_spelling(self, question: str) -> str:
232
- """Correct common Vietnamese legal term misspellings"""
233
- corrected = question.lower()
234
-
235
- for misspelling, correction in self.common_corrections.items():
236
- # Use word boundaries to avoid partial matches
237
- pattern = r'\b' + re.escape(misspelling) + r'\b'
238
- corrected = re.sub(pattern, correction, corrected, flags=re.IGNORECASE)
239
-
240
- return corrected
241
-
242
- def _expand_abbreviations(self, question: str) -> str:
243
- """Expand common Vietnamese legal abbreviations"""
244
- expanded = question.lower()
245
-
246
- for abbrev, full_form in self.legal_abbreviations.items():
247
- # Match abbreviations with word boundaries
248
- pattern = r'\b' + re.escape(abbrev) + r'\b'
249
- # Replace with both abbreviated and full form for better search
250
- replacement = f"{abbrev} {full_form}"
251
- expanded = re.sub(pattern, replacement, expanded, flags=re.IGNORECASE)
252
-
253
- return expanded
254
-
255
- def _detect_legal_context(self, question: str) -> List[str]:
256
- """Detect the legal context/domain of the question"""
257
- detected_contexts = []
258
- question_lower = question.lower()
259
-
260
- for context, keywords in self.legal_contexts.items():
261
- if any(keyword in question_lower for keyword in keywords):
262
- detected_contexts.append(context)
263
-
264
- return detected_contexts
265
-
266
- def _add_context_keywords(self, question: str, contexts: List[str]) -> str:
267
- """Add relevant context keywords to improve search"""
268
- if not contexts:
269
- return question
270
-
271
- # Add general legal keywords
272
- enhanced = question
273
-
274
- # Add context-specific keywords
275
- context_keywords = []
276
- for context in contexts:
277
- if context == "business":
278
- context_keywords.extend(["luật doanh nghiệp", "đăng ký kinh doanh"])
279
- elif context == "labor":
280
- context_keywords.extend(["bộ luật lao động", "quyền lao động"])
281
- elif context == "tax":
282
- context_keywords.extend(["luật thuế", "nghĩa vụ thuế"])
283
- elif context == "real_estate":
284
- context_keywords.extend(["luật đất đai", "quyền sở hữu"])
285
- elif context == "family":
286
- context_keywords.extend(["luật hôn nhân gia đình"])
287
-
288
- # Add keywords that aren't already in the question
289
- question_lower = question.lower()
290
- new_keywords = [kw for kw in context_keywords if kw not in question_lower]
291
-
292
- if new_keywords:
293
- enhanced = f"{question} {' '.join(new_keywords[:2])}" # Add max 2 keywords
294
-
295
- return enhanced
296
-
297
- def _llm_detect_legal_context(self, question: str) -> List[str]:
298
- """Use LLM to detect legal context more accurately"""
299
- if not self.llm:
300
- return self._detect_legal_context(question)
301
-
302
- prompt = PromptTemplate(
303
- template="""Bạn là chuyên gia phân loại câu hỏi pháp luật Việt Nam. Hãy phân tích câu hỏi và xác định lĩnh vực pháp lý liên quan.
304
-
305
- Các lĩnh vực pháp lý chính:
306
- - business: Doanh nghiệp, kinh doanh, thành lập công ty, giải thể, vốn điều lệ
307
- - labor: Lao động, hợp đồng lao động, sa thải, lương, nghỉ phép, bảo hiểm xã hội
308
- - tax: Thuế, kê khai thuế, miễn thuế, thuế thu nhập, VAT
309
- - real_estate: Bất động sản, đất đai, nhà ở, chuyển nhượng, sở hữu
310
- - family: Hôn nhân, ly hôn, thừa kế, nuôi con, quyền con cái
311
- - criminal: Hình sự, tội phạm, vi phạm, án phạt, truy tố
312
- - civil: Dân sự, hợp đồng, tranh chấp, bồi thường, quyền sở hữu
313
- - administrative: Hành chính, thủ tục, giấy tờ, cơ quan nhà nước
314
- - constitutional: Hiến pháp, quyền công dân, nghĩa vụ, cơ cấu nhà nước
315
-
316
- Câu hỏi: {question}
317
-
318
- Hãy trả về tối đa 3 lĩnh vực phù hợp nhất, cách nhau bởi dấu phẩy (ví dụ: business, tax):""",
319
- input_variables=["question"]
320
- )
321
-
322
- try:
323
- response = self.llm.invoke(prompt.format(question=question))
324
- contexts = [ctx.strip() for ctx in response.content.strip().split(",")]
325
- # Validate contexts
326
- valid_contexts = ["business", "labor", "tax", "real_estate", "family", "criminal", "civil", "administrative", "constitutional"]
327
- return [ctx for ctx in contexts if ctx in valid_contexts][:3]
328
- except Exception as e:
329
- print(f"Error in LLM context detection: {e}")
330
- return self._detect_legal_context(question)
331
-
332
- def _llm_analyze_question_intent(self, question: str) -> Dict[str, Any]:
333
- """Use LLM to analyze question intent and structure"""
334
- if not self.llm:
335
- return self._get_fallback_intent_analysis(question)
336
-
337
- # Simplified prompt for more reliable JSON response
338
- prompt = PromptTemplate(
339
- template="""Analyze this Vietnamese legal question and return ONLY a JSON object.
340
-
341
- Question: {question}
342
-
343
- Return JSON with these exact fields:
344
- - intent: "procedural" OR "definition" OR "comparison" OR "calculation" OR "advice" OR "specific_case"
345
- - complexity: "simple" OR "moderate" OR "complex"
346
- - keywords: array of 3-5 Vietnamese keywords
347
- - ambiguity_level: "low" OR "medium" OR "high"
348
- - requires_clarification: true OR false
349
-
350
- Example: {{"intent": "procedural", "complexity": "simple", "keywords": ["thành lập", "doanh nghiệp"], "ambiguity_level": "low", "requires_clarification": false}}
351
-
352
- ONLY return the JSON object, no other text:""",
353
- input_variables=["question"]
354
- )
355
-
356
- try:
357
- response = self.llm.invoke(prompt.format(question=question))
358
-
359
- # Check if response exists and has content
360
- if not response or not hasattr(response, 'content'):
361
- print("Empty or invalid response from LLM")
362
- return self._get_fallback_intent_analysis(question)
363
-
364
- content = response.content.strip()
365
-
366
- # Debug: print raw response
367
- print(f"Raw LLM response: '{content[:100]}...'")
368
-
369
- if not content:
370
- print("Empty content from LLM")
371
- return self._get_fallback_intent_analysis(question)
372
-
373
- # Clean up the response to extract JSON
374
- json_content = self._extract_json_from_response(content)
375
-
376
- if not json_content:
377
- print("No JSON found in response")
378
- return self._get_fallback_intent_analysis(question)
379
-
380
- # Parse JSON
381
- analysis = json.loads(json_content)
382
-
383
- # Validate the analysis fields
384
- validated_analysis = self._validate_intent_analysis(analysis)
385
- print(f"Validated analysis: {validated_analysis}")
386
- return validated_analysis
387
-
388
- except json.JSONDecodeError as e:
389
- print(f"JSON decode error: {e}")
390
- print(f"Attempted to parse: '{json_content if 'json_content' in locals() else 'N/A'}'")
391
- return self._get_fallback_intent_analysis(question)
392
- except Exception as e:
393
- print(f"Error in LLM intent analysis: {e}")
394
- # Try a simplified backup approach
395
- return self._simple_llm_intent_analysis(question)
396
-
397
- def _extract_json_from_response(self, content: str) -> Optional[str]:
398
- """Extract JSON from LLM response that might contain extra text"""
399
- if not content or not content.strip():
400
- return None
401
-
402
- content = content.strip()
403
-
404
- # Remove markdown code blocks if present
405
- content = re.sub(r'```json\s*', '', content, flags=re.IGNORECASE)
406
- content = re.sub(r'```\s*$', '', content)
407
- content = re.sub(r'^```\s*', '', content)
408
-
409
- # Remove common prefixes
410
- prefixes_to_remove = [
411
- "here is the json:",
412
- "here's the json:",
413
- "json:",
414
- "response:",
415
- "analysis:",
416
- ]
417
-
418
- content_lower = content.lower()
419
- for prefix in prefixes_to_remove:
420
- if content_lower.startswith(prefix):
421
- content = content[len(prefix):].strip()
422
- break
423
-
424
- # If content already looks like JSON (starts with {), try to use it directly
425
- if content.startswith('{') and content.endswith('}'):
426
- return content
427
-
428
- # Try to find JSON object in the response using regex
429
- json_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
430
- matches = re.findall(json_pattern, content, re.DOTALL)
431
-
432
- if matches:
433
- # Return the first (hopefully only) JSON match
434
- return matches[0].strip()
435
-
436
- # If no JSON pattern found, try to extract between first { and last }
437
- start_idx = content.find('{')
438
- end_idx = content.rfind('}')
439
-
440
- if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
441
- extracted = content[start_idx:end_idx + 1].strip()
442
- # Basic validation - should have at least one : for key-value pairs
443
- if ':' in extracted:
444
- return extracted
445
-
446
- return None
447
-
448
- def _simple_llm_intent_analysis(self, question: str) -> Dict[str, Any]:
449
- """Simplified LLM analysis with basic prompts"""
450
- if not self.llm:
451
- return self._get_fallback_intent_analysis(question)
452
-
453
- try:
454
- # Very simple approach - ask for specific fields one by one
455
- intent_prompt = f"What type of legal question is this? Answer only: procedural, definition, comparison, calculation, advice, or specific_case\n\nQuestion: {question}\n\nAnswer:"
456
- complexity_prompt = f"How complex is this question? Answer only: simple, moderate, or complex\n\nQuestion: {question}\n\nAnswer:"
457
-
458
- intent_response = self.llm.invoke(intent_prompt)
459
- complexity_response = self.llm.invoke(complexity_prompt)
460
-
461
- # Extract simple responses
462
- intent = intent_response.content.strip().lower()
463
- complexity = complexity_response.content.strip().lower()
464
-
465
- # Validate responses
466
- valid_intents = ["procedural", "definition", "comparison", "calculation", "advice", "specific_case"]
467
- valid_complexity = ["simple", "moderate", "complex"]
468
-
469
- if intent not in valid_intents:
470
- intent = "procedural" # default
471
- if complexity not in valid_complexity:
472
- complexity = "simple" # default
473
-
474
- # Extract keywords using simple approach
475
- keywords = []
476
- words = question.lower().split()
477
- important_words = [w for w in words if len(w) > 3 and w not in ["thế", "nào", "như", "thì", "này", "được", "có", "của", "để", "cho", "với", "trong", "từ", "về"]]
478
- keywords = important_words[:3]
479
-
480
- return {
481
- "intent": intent,
482
- "complexity": complexity,
483
- "keywords": keywords,
484
- "ambiguity_level": "medium" if complexity == "complex" else "low",
485
- "requires_clarification": complexity == "complex"
486
- }
487
-
488
- except Exception as e:
489
- print(f"Error in simple LLM analysis: {e}")
490
- return self._get_fallback_intent_analysis(question)
491
-
492
- def _validate_intent_analysis(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
493
- """Validate and clean up intent analysis results"""
494
- validated = {}
495
-
496
- # Validate intent
497
- valid_intents = ["procedural", "definition", "comparison", "calculation", "advice", "specific_case"]
498
- validated["intent"] = analysis.get("intent", "unknown")
499
- if validated["intent"] not in valid_intents:
500
- validated["intent"] = "unknown"
501
-
502
- # Validate complexity
503
- valid_complexity = ["simple", "moderate", "complex"]
504
- validated["complexity"] = analysis.get("complexity", "simple")
505
- if validated["complexity"] not in valid_complexity:
506
- validated["complexity"] = "simple"
507
-
508
- # Validate keywords
509
- keywords = analysis.get("keywords", [])
510
- if isinstance(keywords, list):
511
- validated["keywords"] = [str(k).strip() for k in keywords[:5] if k and str(k).strip()]
512
- else:
513
- validated["keywords"] = []
514
-
515
- # Validate ambiguity level
516
- valid_ambiguity = ["low", "medium", "high"]
517
- validated["ambiguity_level"] = analysis.get("ambiguity_level", "low")
518
- if validated["ambiguity_level"] not in valid_ambiguity:
519
- validated["ambiguity_level"] = "low"
520
-
521
- # Validate requires_clarification
522
- validated["requires_clarification"] = bool(analysis.get("requires_clarification", False))
523
-
524
- # Validate suggested_clarifications
525
- clarifications = analysis.get("suggested_clarifications", [])
526
- if isinstance(clarifications, list):
527
- validated["suggested_clarifications"] = [str(c).strip() for c in clarifications[:3] if c and str(c).strip()]
528
- else:
529
- validated["suggested_clarifications"] = []
530
-
531
- return validated
532
-
533
- def _get_fallback_intent_analysis(self, question: str) -> Dict[str, Any]:
534
- """Get fallback intent analysis using rule-based approach"""
535
- # Simple rule-based fallback
536
- question_lower = question.lower()
537
-
538
- # Determine intent based on keywords
539
- if any(word in question_lower for word in ["thủ tục", "cách", "làm thế nào", "quy trình", "bước"]):
540
- intent = "procedural"
541
- elif any(word in question_lower for word in ["là gì", "định nghĩa", "khái niệm", "nghĩa là"]):
542
- intent = "definition"
543
- elif any(word in question_lower for word in ["so sánh", "khác nhau", "giống", "khác biệt"]):
544
- intent = "comparison"
545
- elif any(word in question_lower for word in ["tính", "tính toán", "phí", "lệ phí", "thuế"]):
546
- intent = "calculation"
547
- elif any(word in question_lower for word in ["nên", "có thể", "được không", "có được"]):
548
- intent = "advice"
549
- else:
550
- intent = "specific_case"
551
-
552
- # Determine complexity based on length and question marks
553
- word_count = len(question.split())
554
- if word_count < 8:
555
- complexity = "simple"
556
- elif word_count < 20:
557
- complexity = "moderate"
558
- else:
559
- complexity = "complex"
560
-
561
- # Extract simple keywords
562
- keywords = []
563
- for word in question.split():
564
- word_clean = re.sub(r'[^\w]', '', word).lower()
565
- if len(word_clean) > 3 and word_clean not in ["thế", "nào", "như", "thì", "này", "được", "có", "của"]:
566
- keywords.append(word_clean)
567
- if len(keywords) >= 3:
568
- break
569
-
570
- return {
571
- "intent": intent,
572
- "complexity": complexity,
573
- "keywords": keywords,
574
- "ambiguity_level": "medium" if complexity == "complex" else "low",
575
- "requires_clarification": complexity == "complex",
576
- "suggested_clarifications": []
577
- }
578
-
579
- def _llm_refine_question_advanced(self, question: str, contexts: List[str], intent_analysis: Dict[str, Any]) -> Optional[str]:
580
- """Advanced LLM-based question refinement with context and intent awareness"""
581
- if not self.llm:
582
- return None
583
-
584
- context_str = ", ".join(contexts) if contexts else "tổng quát"
585
- intent = intent_analysis.get("intent", "unknown")
586
- complexity = intent_analysis.get("complexity", "simple")
587
- keywords = intent_analysis.get("keywords", [])
588
-
589
- # Choose refinement strategy based on intent
590
- if intent == "procedural":
591
- strategy_prompt = """
592
- Đây là câu hỏi về thủ tục pháp lý. Hãy:
593
- - Làm rõ loại thủ tục cụ thể
594
- - Thêm từ khóa về quy trình, bước thực hiện
595
- - Đề cập đến cơ quan có thẩm quyền nếu phù hợp"""
596
- elif intent == "definition":
597
- strategy_prompt = """
598
- Đây là câu hỏi định nghĩa khái niệm. Hãy:
599
- - Làm rõ khái niệm cần định nghĩa
600
- - Thêm ngữ cảnh pháp lý liên quan
601
- - Đề cập đến văn bản luật có liên quan"""
602
- elif intent == "comparison":
603
- strategy_prompt = """
604
- Đây là câu hỏi so sánh. Hãy:
605
- - Làm rõ các đối tượng được so sánh
606
- - Thêm tiêu chí so sánh cụ thể
607
- - Đảm bảo tính khách quan"""
608
- else:
609
- strategy_prompt = """
610
- Hãy cải thiện câu hỏi theo nguyên tắc chung:
611
- - Làm rõ ý định của câu hỏi
612
- - Thêm ngữ cảnh pháp lý phù hợp
613
- - Sử dụng thuật ngữ chuẩn mực"""
614
-
615
- prompt = PromptTemplate(
616
- template="""Bạn là chuyên gia pháp lý Việt Nam có 20 năm kinh nghiệm. Hãy cải thiện câu hỏi pháp lý sau để tối ưu hóa việc tìm kiếm thông tin.
617
-
618
- THÔNG TIN PHÂN TÍCH:
619
- - Lĩnh vực pháp lý: {context}
620
- - Loại câu hỏi: {intent}
621
- - Độ phức tạp: {complexity}
622
- - Từ khóa chính: {keywords}
623
-
624
- CHIẾN LƯỢC CẢI THIỆN:
625
- {strategy}
626
-
627
- NGUYÊN TẮC CHUNG:
628
- 1. Giữ nguyên ý nghĩa gốc của câu hỏi
629
- 2. Sử dụng thuật ngữ pháp lý chính xác và chuẩn mực
630
- 3. Làm rõ các khái niệm mơ hồ
631
- 4. Thêm ngữ cảnh pháp lý cần thiết
632
- 5. Tối ưu hóa cho tìm kiếm trong cơ sở dữ liệu pháp luật
633
- 6. Đảm bảo câu hỏi ngắn gọn nhưng đầy đủ thông tin
634
- 7. Ưu tiên các từ khóa xuất hiện trong văn bản pháp luật Việt Nam
635
-
636
- Câu hỏi gốc: {question}
637
-
638
- Câu hỏi được cải thiện (chỉ trả về câu hỏi, không giải thích):""",
639
- input_variables=["question", "context", "intent", "complexity", "keywords", "strategy"]
640
- )
641
-
642
- try:
643
- response = self.llm.invoke(prompt.format(
644
- question=question,
645
- context=context_str,
646
- intent=intent,
647
- complexity=complexity,
648
- keywords=", ".join(keywords),
649
- strategy=strategy_prompt
650
- ))
651
-
652
- refined = response.content.strip()
653
-
654
- # Advanced validation
655
- if self._validate_refined_question(question, refined, intent_analysis):
656
- return refined
657
-
658
- except Exception as e:
659
- print(f"Error in advanced LLM refinement: {e}")
660
-
661
- return None
662
-
663
- def _llm_validate_refinement(self, original: str, refined: str, contexts: List[str]) -> bool:
664
- """Use LLM to validate if the refinement maintains original intent"""
665
- if not self.llm:
666
- return True
667
-
668
- prompt = PromptTemplate(
669
- template="""Bạn là chuyên gia đánh giá chất lượng câu hỏi pháp lý. Hãy đánh giá xem câu hỏi đã được cải thiện có giữ nguyên ý nghĩa gốc và có tốt hơn cho việc tìm kiếm thông tin pháp luật không.
670
-
671
- Câu hỏi gốc: {original}
672
- Câu hỏi đã cải thiện: {refined}
673
- Lĩnh vực: {contexts}
674
-
675
- Tiêu chí đánh giá:
676
- 1. Giữ nguyên ý nghĩa gốc (có/không)
677
- 2. Cải thiện khả năng tìm kiếm (có/không)
678
- 3. Sử dụng thuật ngữ pháp lý phù hợp (có/không)
679
- 4. Độ dài hợp lý (có/không)
680
- 5. Rõ ràng và dễ hiểu (có/không)
681
-
682
- Kết luận: CHẤP_NHẬN hoặc TỪ_CHỐI
683
-
684
- Chỉ trả về kết luận:""",
685
- input_variables=["original", "refined", "contexts"]
686
- )
687
-
688
- try:
689
- response = self.llm.invoke(prompt.format(
690
- original=original,
691
- refined=refined,
692
- contexts=", ".join(contexts)
693
- ))
694
-
695
- return "CHẤP_NHẬN" in response.content.strip().upper()
696
- except Exception as e:
697
- print(f"Error in LLM validation: {e}")
698
- return True
699
-
700
- def _validate_refined_question(self, original: str, refined: str, intent_analysis: Dict[str, Any]) -> bool:
701
- """Validate refined question with multiple criteria"""
702
- if not refined or not refined.strip():
703
- return False
704
-
705
- # Basic length check
706
- if len(refined) < 10 or len(refined) > 500:
707
- return False
708
-
709
- # Should contain question mark for questions
710
- if intent_analysis.get("intent") in ["procedural", "definition"] and "?" not in refined:
711
- return False
712
-
713
- # Shouldn't start with meta phrases
714
- meta_phrases = ["câu hỏi", "tôi muốn hỏi", "xin hỏi", "cho tôi biết"]
715
- if any(refined.lower().startswith(phrase) for phrase in meta_phrases):
716
- return False
717
-
718
- # Should be different from original (some improvement made)
719
- if refined.strip().lower() == original.strip().lower():
720
- return False
721
-
722
- return True
723
-
724
- def _get_corrections_made(self, original: str, corrected: str) -> List[Dict[str, str]]:
725
- """Get list of spelling corrections that were made"""
726
- corrections = []
727
- for misspelling, correction in self.common_corrections.items():
728
- if misspelling in original.lower() and correction in corrected.lower():
729
- corrections.append({"from": misspelling, "to": correction})
730
- return corrections
731
-
732
- def _get_expanded_terms(self, original: str, expanded: str) -> List[Dict[str, str]]:
733
- """Get list of abbreviations that were expanded"""
734
- expansions = []
735
- for abbrev, full_form in self.legal_abbreviations.items():
736
- if abbrev in original.lower() and full_form in expanded.lower():
737
- expansions.append({"abbreviation": abbrev, "full_form": full_form})
738
- return expansions
739
-
740
- def get_refinement_summary(self, refinement_result: Dict) -> str:
741
- """Generate a human-readable summary of refinements made"""
742
- if not refinement_result["refinement_steps"]:
743
- return "Không có cải thiện nào được thực hiện."
744
-
745
- summary_parts = []
746
-
747
- if "basic_cleaning" in refinement_result["refinement_steps"]:
748
- summary_parts.append("làm sạch văn bản")
749
-
750
- if "spelling_correction" in refinement_result["refinement_steps"]:
751
- corrections = refinement_result["corrections_made"]
752
- if corrections:
753
- summary_parts.append(f"sửa {len(corrections)} lỗi chính tả")
754
-
755
- if "abbreviation_expansion" in refinement_result["refinement_steps"]:
756
- expansions = refinement_result["expanded_terms"]
757
- if expansions:
758
- summary_parts.append(f"mở rộng {len(expansions)} từ viết tắt")
759
-
760
- if "intent_analysis" in refinement_result["refinement_steps"]:
761
- intent_analysis = refinement_result.get("intent_analysis", {})
762
- intent = intent_analysis.get("intent", "unknown")
763
- complexity = intent_analysis.get("complexity", "simple")
764
- summary_parts.append(f"phân tích ý định ({intent}, độ phức tạp: {complexity})")
765
-
766
- if "context_enhancement" in refinement_result["refinement_steps"]:
767
- contexts = refinement_result["detected_context"]
768
- if contexts:
769
- context_method = "AI" if refinement_result.get("llm_context_detection") else "quy tắc"
770
- summary_parts.append(f"thêm từ khóa cho lĩnh vực {', '.join(contexts)} ({context_method})")
771
-
772
- # LLM enhancements
773
- llm_methods = []
774
- if "chain_of_thought" in refinement_result["refinement_steps"]:
775
- llm_methods.append("suy luận từng bước")
776
- if "iterative_refinement" in refinement_result["refinement_steps"]:
777
- llm_methods.append("cải thiện lặp")
778
- if "llm_enhancement" in refinement_result["refinement_steps"]:
779
- llm_methods.append("cải thiện tiêu chuẩn")
780
-
781
- if llm_methods:
782
- validation_status = ""
783
- if refinement_result.get("llm_validation_passed") is not None:
784
- validation_status = " (đã xác thực)" if refinement_result["llm_validation_passed"] else " (chưa xác thực)"
785
-
786
- method_str = ", ".join(llm_methods)
787
- summary_parts.append(f"cải thiện bằng AI ({method_str}){validation_status}")
788
-
789
- return f"Đã {', '.join(summary_parts)}."
790
-
791
- def get_detailed_analysis(self, refinement_result: Dict) -> str:
792
- """Get detailed analysis of the refinement process"""
793
- if not refinement_result.get("intent_analysis"):
794
- return ""
795
-
796
- intent_analysis = refinement_result["intent_analysis"]
797
- analysis_parts = []
798
-
799
- # Intent information
800
- intent = intent_analysis.get("intent", "unknown")
801
- intent_map = {
802
- "procedural": "Thủ tục",
803
- "definition": "Định nghĩa",
804
- "comparison": "So sánh",
805
- "calculation": "Tính toán",
806
- "advice": "Tư vấn",
807
- "specific_case": "Trường hợp cụ thể"
808
- }
809
- analysis_parts.append(f"Loại câu hỏi: {intent_map.get(intent, intent)}")
810
-
811
- # Complexity
812
- complexity = intent_analysis.get("complexity", "simple")
813
- complexity_map = {"simple": "Đơn giản", "moderate": "Trung bình", "complex": "Phức tạp"}
814
- analysis_parts.append(f"Độ phức tạp: {complexity_map.get(complexity, complexity)}")
815
-
816
- # Keywords
817
- keywords = intent_analysis.get("keywords", [])
818
- if keywords:
819
- analysis_parts.append(f"Từ khóa chính: {', '.join(keywords[:3])}")
820
-
821
- # Ambiguity level
822
- ambiguity = intent_analysis.get("ambiguity_level", "low")
823
- ambiguity_map = {"low": "Thấp", "medium": "Trung bình", "high": "Cao"}
824
- analysis_parts.append(f"Độ mơ hồ: {ambiguity_map.get(ambiguity, ambiguity)}")
825
-
826
- return " | ".join(analysis_parts)
827
-
828
- def _llm_chain_of_thought_refinement(self, question: str, contexts: List[str], intent_analysis: Dict[str, Any]) -> Optional[str]:
829
- """Use chain-of-thought reasoning for complex question refinement"""
830
- if not self.llm or intent_analysis.get("complexity") != "complex":
831
- return None
832
-
833
- prompt = PromptTemplate(
834
- template="""Bạn là chuyên gia pháp lý Việt Nam với 25 năm kinh nghiệm. Hãy sử dụng phương pháp suy luận từng bước để cải thiện câu hỏi pháp lý phức tạp sau.
835
-
836
- THÔNG TIN PHÂN TÍCH:
837
- - Câu hỏi gốc: {question}
838
- - Lĩnh vực pháp lý: {contexts}
839
- - Độ phức tạp: {complexity}
840
- - Độ mơ hồ: {ambiguity}
841
-
842
- BƯỚC 1: PHÂN TÍCH VẤN ĐỀ
843
- Hãy xác định:
844
- - Vấn đề pháp lý cốt lõi là gì?
845
- - Có những khái niệm nào cần làm rõ?
846
- - Thiếu thông tin gì để trả lời đầy đủ?
847
-
848
- BƯỚC 2: XÁC ĐỊNH NGỮ CẢNH PHÁP LÝ
849
- Hãy xác định:
850
- - Văn bản pháp luật nào có khả năng liên quan?
851
- - Cơ quan có thẩm quyền nào cần đề cập?
852
- - Thủ tục hoặc quy trình nào cần nêu rõ?
853
-
854
- BƯỚC 3: TỐI ƯU HÓA TỪ KHÓA
855
- Hãy xác định:
856
- - Thuật ngữ pháp lý chính xác cần sử dụng
857
- - Từ khóa tìm kiếm hiệu quả
858
- - Cụm từ thường xuất hiện trong văn bản pháp luật
859
-
860
- BƯỚC 4: XÂY DỰNG CÂU HỎI TỐI ƯU
861
- Dựa trên 3 bước trên, hãy xây dựng câu hỏi mới:
862
- - Rõ ràng và cụ thể
863
- - Sử dụng thuật ngữ pháp lý chuẩn
864
- - Tối ưu cho tìm kiếm
865
-
866
- ĐỊNH DẠNG TRẢ LỜI JSON:
867
- {{
868
- "analysis": {{
869
- "core_legal_issue": "vấn đề pháp lý cốt lõi",
870
- "unclear_concepts": ["khái niệm 1", "khái niệm 2"],
871
- "missing_information": ["thông tin thiếu 1", "thông tin thiếu 2"]
872
- }},
873
- "legal_context": {{
874
- "relevant_laws": ["luật 1", "luật 2"],
875
- "authorities": ["cơ quan 1", "cơ quan 2"],
876
- "procedures": ["thủ tục 1", "thủ tục 2"]
877
- }},
878
- "keywords": {{
879
- "legal_terms": ["thuật ngữ 1", "thuật ngữ 2"],
880
- "search_keywords": ["từ khóa 1", "từ khóa 2"],
881
- "legal_phrases": ["cụm từ 1", "cụm từ 2"]
882
- }},
883
- "refined_question": "câu hỏi được cải thiện",
884
- "confidence_score": 0.95,
885
- "reasoning": "lý do tại sao câu hỏi này tốt hơn"
886
- }}
887
-
888
- Chỉ trả về JSON hợp lệ:""",
889
- input_variables=["question", "contexts", "complexity", "ambiguity"]
890
- )
891
-
892
- try:
893
- response = self.llm.invoke(prompt.format(
894
- question=question,
895
- contexts=", ".join(contexts),
896
- complexity=intent_analysis.get("complexity", "complex"),
897
- ambiguity=intent_analysis.get("ambiguity_level", "high")
898
- ))
899
-
900
- result = json.loads(response.content.strip())
901
- refined_question = result.get("refined_question", "")
902
- confidence = result.get("confidence_score", 0.0)
903
-
904
- # Only return if confidence is high enough
905
- if refined_question and confidence > Config.MIN_CONFIDENCE_SCORE:
906
- return refined_question
907
-
908
- except Exception as e:
909
- print(f"Error in chain-of-thought refinement: {e}")
910
-
911
- return None
912
-
913
- def _llm_iterative_refinement(self, question: str, contexts: List[str], max_iterations: int = 3) -> Optional[str]:
914
- """Use iterative refinement to progressively improve the question"""
915
- if not self.llm:
916
- return None
917
-
918
- current_question = question
919
-
920
- for iteration in range(max_iterations):
921
- prompt = PromptTemplate(
922
- template="""Bạn là chuyên gia cải thiện câu hỏi pháp lý. Đây là lần cải thiện thứ {iteration} của câu hỏi.
923
-
924
- Câu hỏi hiện tại: {current_question}
925
- Lĩnh vực pháp lý: {contexts}
926
-
927
- Hãy phân tích và cải thiện thêm câu hỏi theo các tiêu chí:
928
-
929
- LẦN 1: Tập trung vào thuật ngữ pháp lý và cấu trúc câu
930
- LẦN 2: Tập trung vào ngữ cảnh và từ khóa tìm kiếm
931
- LẦN 3: Tập trung vào tính rõ ràng và độ chính xác
932
-
933
- Nguyên tắc cải thiện:
934
- 1. Mỗi lần cải thiện phải có tiến bộ rõ rệt
935
- 2. Giữ nguyên ý nghĩa gốc
936
- 3. Tăng cường khả năng tìm kiếm
937
- 4. Sử dụng thuật ngữ chuẩn mực
938
-
939
- Trả về định dạng JSON:
940
- {{
941
- "improved_question": "câu hỏi được cải thiện",
942
- "improvements_made": ["cải thiện 1", "cải thiện 2"],
943
- "quality_score": 0.85,
944
- "needs_further_improvement": true/false
945
- }}
946
-
947
- Chỉ trả về JSON:""",
948
- input_variables=["current_question", "contexts", "iteration"]
949
- )
950
-
951
- try:
952
- response = self.llm.invoke(prompt.format(
953
- current_question=current_question,
954
- contexts=", ".join(contexts),
955
- iteration=iteration + 1
956
- ))
957
-
958
- result = json.loads(response.content.strip())
959
- improved_question = result.get("improved_question", "")
960
- quality_score = result.get("quality_score", 0.0)
961
- needs_improvement = result.get("needs_further_improvement", False)
962
-
963
- if improved_question and improved_question != current_question:
964
- current_question = improved_question
965
-
966
- # Stop if quality is high enough or no further improvement needed
967
- if quality_score > 0.9 or not needs_improvement:
968
- break
969
- else:
970
- break
971
-
972
- except Exception as e:
973
- print(f"Error in iterative refinement iteration {iteration + 1}: {e}")
974
- break
975
-
976
- return current_question if current_question != question else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Any, Optional
2
+ import re
3
+ import json
4
+ from langchain_google_genai import ChatGoogleGenerativeAI
5
+ from langchain.prompts import PromptTemplate
6
+ from config import Config
7
+
8
+ class VietnameseLegalQuestionRefiner:
9
+ """
10
+ Refines Vietnamese legal questions for better search and understanding
11
+ """
12
+
13
+ def __init__(self):
14
+ # Initialize LLM for question refinement
15
+ self.llm = None
16
+ if Config.GOOGLE_API_KEY:
17
+ self.llm = ChatGoogleGenerativeAI(
18
+ model=Config.MODEL_REFINE,
19
+ google_api_key=Config.GOOGLE_API_KEY,
20
+ temperature=0.1
21
+ )
22
+
23
+ # Vietnamese legal terminology mappings
24
+ self.legal_abbreviations = {
25
+ # Common legal abbreviations
26
+ "dn": "doanh nghiệp",
27
+ "dntn": "doanh nghiệp tư nhân",
28
+ "tnhh": "trách nhiệm hữu hạn",
29
+ "cp": "cổ phần",
30
+ "hđ": "hợp đồng",
31
+ "hđlđ": "hợp đồng lao động",
32
+ "tclđ": "tai cạnh lao động",
33
+ "bhxh": "bảo hiểm xã hội",
34
+ "bhyt": "bảo hiểm y tế",
35
+ "bhtn": "bảo hiểm thất nghiệp",
36
+ "qsd": "quyền sử dụng",
37
+ "qsdđ": "quyền sử dụng đất",
38
+ "gcn": "giấy chứng nhận",
39
+ "gpkd": "giấy phép kinh doanh",
40
+ "gpđkkd": "giấy phép đăng ký kinh doanh",
41
+ "mst": "mã số thuế",
42
+ "tncn": "thuế thu nhập cá nhân",
43
+ "tndn": "thuế thu nhập doanh nghiệp",
44
+ "gtgt": "giá trị gia tăng",
45
+ "vat": "thuế giá trị gia tăng",
46
+ "nld": "người lao động",
47
+ "ntd": "người sử dụng lao động",
48
+ "tc": "tài chính",
49
+ "kt": "kế toán",
50
+ "tl": "tài liệu",
51
+ "vb": "văn bản",
52
+ "qđ": "quyết định",
53
+ "tt": "thông tư",
54
+ "nđ": "nghị định",
55
+ "dl": "dự luật",
56
+ "qh": "quốc hội",
57
+ "cp": "chính phủ",
58
+ "btc": "bộ tài chính",
59
+ "blđtbxh": "bộ lao động thương binh và xã hội",
60
+ "btp": "bộ tư pháp",
61
+ "btn": "bộ tài nguyên",
62
+ "khdn": "kế hoạch doanh nghiệp"
63
+ }
64
+
65
+ # Legal context keywords
66
+ self.legal_contexts = {
67
+ "business": ["doanh nghiệp", "kinh doanh", "công ty", "thành lập", "giải thể", "vốn điều lệ"],
68
+ "labor": ["lao động", "nhân viên", "hợp đồng lao động", "lương", "nghỉ phép", "sa thải"],
69
+ "tax": ["thuế", "kê khai", "miễn thuế", "giảm thuế", "mức thuế", "thuế suất"],
70
+ "real_estate": ["bất động sản", "đất đai", "nhà ở", "chuyển nhượng", "sổ đỏ", "quyền sử dụng"],
71
+ "family": ["gia đình", "hôn nhân", "ly hôn", "thừa kế", "con cái", "nuôi dưỡng"],
72
+ "criminal": ["hình sự", "vi phạm", "tội danh", "án phạt", "bồi thường"],
73
+ "civil": ["dân sự", "tranh chấp", "khiếu nại", "tố cáo", "bồi thường"]
74
+ }
75
+
76
+ # Common misspellings and corrections
77
+ self.common_corrections = {
78
+ "doanh nghiep": "doanh nghiệp",
79
+ "hop dong": "hợp đồng",
80
+ "lao dong": "lao động",
81
+ "tai chinh": "tài chính",
82
+ "ke toan": "kế toán",
83
+ "thue": "thuế",
84
+ "quyen": "quyền",
85
+ "nghia vu": "nghĩa vụ",
86
+ "dat dai": "đất đai",
87
+ "nha o": "nhà ở",
88
+ "gia dinh": "gia đình",
89
+ "hon nhan": "hôn nhân",
90
+ "ly hon": "ly hôn"
91
+ }
92
+
93
+ def refine_question(self, question: str, use_llm: bool = True) -> Dict[str, str]:
94
+ """
95
+ Main method to refine a Vietnamese legal question
96
+
97
+ Args:
98
+ question: Original user question
99
+ use_llm: Whether to use LLM for advanced refinement
100
+
101
+ Returns:
102
+ Dictionary containing original and refined questions with metadata
103
+ """
104
+ result = {
105
+ "original_question": question,
106
+ "refined_question": question,
107
+ "refinement_steps": [],
108
+ "detected_context": [],
109
+ "expanded_terms": [],
110
+ "corrections_made": []
111
+ }
112
+
113
+ # Step 1: Basic cleaning and normalization
114
+ cleaned_question = self._basic_cleaning(question)
115
+ if cleaned_question != question:
116
+ result["refinement_steps"].append("basic_cleaning")
117
+ result["refined_question"] = cleaned_question
118
+
119
+ # Step 2: Correct common misspellings
120
+ corrected_question = self._correct_spelling(cleaned_question)
121
+ if corrected_question != cleaned_question:
122
+ result["refinement_steps"].append("spelling_correction")
123
+ result["corrections_made"] = self._get_corrections_made(cleaned_question, corrected_question)
124
+ result["refined_question"] = corrected_question
125
+
126
+ # Step 3: Expand abbreviations
127
+ expanded_question = self._expand_abbreviations(corrected_question)
128
+ if expanded_question != corrected_question:
129
+ result["refinement_steps"].append("abbreviation_expansion")
130
+ result["expanded_terms"] = self._get_expanded_terms(corrected_question, expanded_question)
131
+ result["refined_question"] = expanded_question
132
+
133
+ # Step 4: Advanced LLM-based context detection (if enabled)
134
+ if use_llm and self.llm:
135
+ context = self._llm_detect_legal_context(expanded_question)
136
+ result["llm_context_detection"] = True
137
+ else:
138
+ context = self._detect_legal_context(expanded_question)
139
+ result["llm_context_detection"] = False
140
+
141
+ result["detected_context"] = context
142
+
143
+ # Step 5: LLM-based intent analysis (if enabled)
144
+ intent_analysis = {}
145
+ if use_llm and self.llm:
146
+ intent_analysis = self._llm_analyze_question_intent(expanded_question)
147
+ result["intent_analysis"] = intent_analysis
148
+ result["refinement_steps"].append("intent_analysis")
149
+
150
+ # Step 6: Add context keywords
151
+ context_enhanced_question = self._add_context_keywords(expanded_question, context)
152
+ if context_enhanced_question != expanded_question:
153
+ result["refinement_steps"].append("context_enhancement")
154
+ result["refined_question"] = context_enhanced_question
155
+
156
+ # Step 7: Advanced LLM-based refinement (if enabled and available)
157
+ if use_llm and self.llm and len(result["refined_question"].strip()) > 10:
158
+ best_refined = result["refined_question"]
159
+ refinement_method = None
160
+
161
+ # Try chain-of-thought for complex questions
162
+ if (Config.ENABLE_CHAIN_OF_THOUGHT and
163
+ intent_analysis.get("complexity") == "complex"):
164
+ cot_refined = self._llm_chain_of_thought_refinement(
165
+ result["refined_question"], context, intent_analysis
166
+ )
167
+ if cot_refined:
168
+ best_refined = cot_refined
169
+ refinement_method = "chain_of_thought"
170
+ result["refinement_steps"].append("chain_of_thought")
171
+
172
+ # Try iterative refinement for moderate complexity
173
+ elif (Config.ENABLE_ITERATIVE_REFINEMENT and
174
+ intent_analysis.get("complexity") in ["moderate", "complex"]):
175
+ iterative_refined = self._llm_iterative_refinement(
176
+ result["refined_question"], context, Config.MAX_REFINEMENT_ITERATIONS
177
+ )
178
+ if iterative_refined:
179
+ best_refined = iterative_refined
180
+ refinement_method = "iterative"
181
+ result["refinement_steps"].append("iterative_refinement")
182
+
183
+ # Fallback to standard advanced refinement
184
+ if not refinement_method:
185
+ llm_refined = self._llm_refine_question_advanced(
186
+ result["refined_question"],
187
+ context,
188
+ intent_analysis
189
+ )
190
+ if llm_refined:
191
+ best_refined = llm_refined
192
+ refinement_method = "advanced"
193
+ result["refinement_steps"].append("llm_enhancement")
194
+
195
+ # Apply the best refinement if found
196
+ if best_refined != result["refined_question"]:
197
+ # Validate the refinement with LLM (if enabled)
198
+ if Config.ENABLE_LLM_VALIDATION:
199
+ if self._llm_validate_refinement(result["refined_question"], best_refined, context):
200
+ result["refined_question"] = best_refined
201
+ result["llm_validation_passed"] = True
202
+ result["refinement_method"] = refinement_method
203
+ else:
204
+ result["llm_validation_passed"] = False
205
+ print(f"LLM refinement ({refinement_method}) rejected by validation")
206
+ else:
207
+ # Apply without validation
208
+ result["refined_question"] = best_refined
209
+ result["llm_validation_passed"] = None
210
+ result["refinement_method"] = refinement_method
211
+
212
+ return result
213
+
214
+ def _basic_cleaning(self, question: str) -> str:
215
+ """Basic text cleaning and normalization"""
216
+ # Remove extra whitespace
217
+ question = re.sub(r'\s+', ' ', question.strip())
218
+
219
+ # Remove special characters except Vietnamese diacritics and basic punctuation
220
+ question = re.sub(r'[^\w\s\u00C0-\u017F\u1EA0-\u1EF9\?\.\,\!\-\(\)]', ' ', question)
221
+
222
+ # Normalize question marks
223
+ question = re.sub(r'\?+', '?', question)
224
+
225
+ # Ensure question ends with appropriate punctuation
226
+ if not question.endswith(('?', '.', '!')):
227
+ question += '?'
228
+
229
+ return question.strip()
230
+
231
+ def _correct_spelling(self, question: str) -> str:
232
+ """Correct common Vietnamese legal term misspellings"""
233
+ corrected = question.lower()
234
+
235
+ for misspelling, correction in self.common_corrections.items():
236
+ # Use word boundaries to avoid partial matches
237
+ pattern = r'\b' + re.escape(misspelling) + r'\b'
238
+ corrected = re.sub(pattern, correction, corrected, flags=re.IGNORECASE)
239
+
240
+ return corrected
241
+
242
+ def _expand_abbreviations(self, question: str) -> str:
243
+ """Expand common Vietnamese legal abbreviations"""
244
+ expanded = question.lower()
245
+
246
+ for abbrev, full_form in self.legal_abbreviations.items():
247
+ # Match abbreviations with word boundaries
248
+ pattern = r'\b' + re.escape(abbrev) + r'\b'
249
+ # Replace with both abbreviated and full form for better search
250
+ replacement = f"{abbrev} {full_form}"
251
+ expanded = re.sub(pattern, replacement, expanded, flags=re.IGNORECASE)
252
+
253
+ return expanded
254
+
255
+ def _detect_legal_context(self, question: str) -> List[str]:
256
+ """Detect the legal context/domain of the question"""
257
+ detected_contexts = []
258
+ question_lower = question.lower()
259
+
260
+ for context, keywords in self.legal_contexts.items():
261
+ if any(keyword in question_lower for keyword in keywords):
262
+ detected_contexts.append(context)
263
+
264
+ return detected_contexts
265
+
266
+ def _add_context_keywords(self, question: str, contexts: List[str]) -> str:
267
+ """Add relevant context keywords to improve search"""
268
+ if not contexts:
269
+ return question
270
+
271
+ # Add general legal keywords
272
+ enhanced = question
273
+
274
+ # Add context-specific keywords
275
+ context_keywords = []
276
+ for context in contexts:
277
+ if context == "business":
278
+ context_keywords.extend(["luật doanh nghiệp", "đăng ký kinh doanh"])
279
+ elif context == "labor":
280
+ context_keywords.extend(["bộ luật lao động", "quyền lao động"])
281
+ elif context == "tax":
282
+ context_keywords.extend(["luật thuế", "nghĩa vụ thuế"])
283
+ elif context == "real_estate":
284
+ context_keywords.extend(["luật đất đai", "quyền sở hữu"])
285
+ elif context == "family":
286
+ context_keywords.extend(["luật hôn nhân gia đình"])
287
+
288
+ # Add keywords that aren't already in the question
289
+ question_lower = question.lower()
290
+ new_keywords = [kw for kw in context_keywords if kw not in question_lower]
291
+
292
+ if new_keywords:
293
+ enhanced = f"{question} {' '.join(new_keywords[:2])}" # Add max 2 keywords
294
+
295
+ return enhanced
296
+
297
+ def _llm_detect_legal_context(self, question: str) -> List[str]:
298
+ """Use LLM to detect legal context more accurately"""
299
+ if not self.llm:
300
+ return self._detect_legal_context(question)
301
+
302
+ prompt = PromptTemplate(
303
+ template="""Bạn là chuyên gia phân loại câu hỏi pháp luật Việt Nam. Hãy phân tích câu hỏi và xác định lĩnh vực pháp lý liên quan.
304
+
305
+ Các lĩnh vực pháp lý chính:
306
+ - business: Doanh nghiệp, kinh doanh, thành lập công ty, giải thể, vốn điều lệ
307
+ - labor: Lao động, hợp đồng lao động, sa thải, lương, nghỉ phép, bảo hiểm xã hội
308
+ - tax: Thuế, kê khai thuế, miễn thuế, thuế thu nhập, VAT
309
+ - real_estate: Bất động sản, đất đai, nhà ở, chuyển nhượng, sở hữu
310
+ - family: Hôn nhân, ly hôn, thừa kế, nuôi con, quyền con cái
311
+ - criminal: Hình sự, tội phạm, vi phạm, án phạt, truy tố
312
+ - civil: Dân sự, hợp đồng, tranh chấp, bồi thường, quyền sở hữu
313
+ - administrative: Hành chính, thủ tục, giấy tờ, cơ quan nhà nước
314
+ - constitutional: Hiến pháp, quyền công dân, nghĩa vụ, cơ cấu nhà nước
315
+
316
+ Câu hỏi: {question}
317
+
318
+ Hãy trả về tối đa 3 lĩnh vực phù hợp nhất, cách nhau bởi dấu phẩy (ví dụ: business, tax):""",
319
+ input_variables=["question"]
320
+ )
321
+
322
+ try:
323
+ response = self.llm.invoke(prompt.format(question=question))
324
+ contexts = [ctx.strip() for ctx in response.content.strip().split(",")]
325
+ # Validate contexts
326
+ valid_contexts = ["business", "labor", "tax", "real_estate", "family", "criminal", "civil", "administrative", "constitutional"]
327
+ return [ctx for ctx in contexts if ctx in valid_contexts][:3]
328
+ except Exception as e:
329
+ print(f"Error in LLM context detection: {e}")
330
+ return self._detect_legal_context(question)
331
+
332
+ def _llm_analyze_question_intent(self, question: str) -> Dict[str, Any]:
333
+ """Use LLM to analyze question intent and structure"""
334
+ if not self.llm:
335
+ return self._get_fallback_intent_analysis(question)
336
+
337
+ # Simplified prompt for more reliable JSON response
338
+ prompt = PromptTemplate(
339
+ template="""Analyze this Vietnamese legal question and return ONLY a JSON object.
340
+
341
+ Question: {question}
342
+
343
+ Return JSON with these exact fields:
344
+ - intent: "procedural" OR "definition" OR "comparison" OR "calculation" OR "advice" OR "specific_case"
345
+ - complexity: "simple" OR "moderate" OR "complex"
346
+ - keywords: array of 3-5 Vietnamese keywords
347
+ - ambiguity_level: "low" OR "medium" OR "high"
348
+ - requires_clarification: true OR false
349
+
350
+ Example: {{"intent": "procedural", "complexity": "simple", "keywords": ["thành lập", "doanh nghiệp"], "ambiguity_level": "low", "requires_clarification": false}}
351
+
352
+ ONLY return the JSON object, no other text:""",
353
+ input_variables=["question"]
354
+ )
355
+
356
+ try:
357
+ response = self.llm.invoke(prompt.format(question=question))
358
+
359
+ # Check if response exists and has content
360
+ if not response or not hasattr(response, 'content'):
361
+ print("Empty or invalid response from LLM")
362
+ return self._get_fallback_intent_analysis(question)
363
+
364
+ content = response.content.strip()
365
+
366
+ # Debug: print raw response
367
+ print(f"Raw LLM response: '{content[:100]}...'")
368
+
369
+ if not content:
370
+ print("Empty content from LLM")
371
+ return self._get_fallback_intent_analysis(question)
372
+
373
+ # Clean up the response to extract JSON
374
+ json_content = self._extract_json_from_response(content)
375
+
376
+ if not json_content:
377
+ print("No JSON found in response")
378
+ return self._get_fallback_intent_analysis(question)
379
+
380
+ # Parse JSON
381
+ analysis = json.loads(json_content)
382
+
383
+ # Validate the analysis fields
384
+ validated_analysis = self._validate_intent_analysis(analysis)
385
+ print(f"Validated analysis: {validated_analysis}")
386
+ return validated_analysis
387
+
388
+ except json.JSONDecodeError as e:
389
+ print(f"JSON decode error: {e}")
390
+ print(f"Attempted to parse: '{json_content if 'json_content' in locals() else 'N/A'}'")
391
+ return self._get_fallback_intent_analysis(question)
392
+ except Exception as e:
393
+ print(f"Error in LLM intent analysis: {e}")
394
+ # Try a simplified backup approach
395
+ return self._simple_llm_intent_analysis(question)
396
+
397
+ def _extract_json_from_response(self, content: str) -> Optional[str]:
398
+ """Extract JSON from LLM response that might contain extra text"""
399
+ if not content or not content.strip():
400
+ return None
401
+
402
+ content = content.strip()
403
+
404
+ # Remove markdown code blocks if present
405
+ content = re.sub(r'```json\s*', '', content, flags=re.IGNORECASE)
406
+ content = re.sub(r'```\s*$', '', content)
407
+ content = re.sub(r'^```\s*', '', content)
408
+
409
+ # Remove common prefixes
410
+ prefixes_to_remove = [
411
+ "here is the json:",
412
+ "here's the json:",
413
+ "json:",
414
+ "response:",
415
+ "analysis:",
416
+ ]
417
+
418
+ content_lower = content.lower()
419
+ for prefix in prefixes_to_remove:
420
+ if content_lower.startswith(prefix):
421
+ content = content[len(prefix):].strip()
422
+ break
423
+
424
+ # If content already looks like JSON (starts with {), try to use it directly
425
+ if content.startswith('{') and content.endswith('}'):
426
+ return content
427
+
428
+ # Try to find JSON object in the response using regex
429
+ json_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
430
+ matches = re.findall(json_pattern, content, re.DOTALL)
431
+
432
+ if matches:
433
+ # Return the first (hopefully only) JSON match
434
+ return matches[0].strip()
435
+
436
+ # If no JSON pattern found, try to extract between first { and last }
437
+ start_idx = content.find('{')
438
+ end_idx = content.rfind('}')
439
+
440
+ if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
441
+ extracted = content[start_idx:end_idx + 1].strip()
442
+ # Basic validation - should have at least one : for key-value pairs
443
+ if ':' in extracted:
444
+ return extracted
445
+
446
+ return None
447
+
448
+ def _simple_llm_intent_analysis(self, question: str) -> Dict[str, Any]:
449
+ """Simplified LLM analysis with basic prompts"""
450
+ if not self.llm:
451
+ return self._get_fallback_intent_analysis(question)
452
+
453
+ try:
454
+ # Very simple approach - ask for specific fields one by one
455
+ intent_prompt = f"What type of legal question is this? Answer only: procedural, definition, comparison, calculation, advice, or specific_case\n\nQuestion: {question}\n\nAnswer:"
456
+ complexity_prompt = f"How complex is this question? Answer only: simple, moderate, or complex\n\nQuestion: {question}\n\nAnswer:"
457
+
458
+ intent_response = self.llm.invoke(intent_prompt)
459
+ complexity_response = self.llm.invoke(complexity_prompt)
460
+
461
+ # Extract simple responses
462
+ intent = intent_response.content.strip().lower()
463
+ complexity = complexity_response.content.strip().lower()
464
+
465
+ # Validate responses
466
+ valid_intents = ["procedural", "definition", "comparison", "calculation", "advice", "specific_case"]
467
+ valid_complexity = ["simple", "moderate", "complex"]
468
+
469
+ if intent not in valid_intents:
470
+ intent = "procedural" # default
471
+ if complexity not in valid_complexity:
472
+ complexity = "simple" # default
473
+
474
+ # Extract keywords using simple approach
475
+ keywords = []
476
+ words = question.lower().split()
477
+ important_words = [w for w in words if len(w) > 3 and w not in ["thế", "nào", "như", "thì", "này", "được", "có", "của", "để", "cho", "với", "trong", "từ", "về"]]
478
+ keywords = important_words[:3]
479
+
480
+ return {
481
+ "intent": intent,
482
+ "complexity": complexity,
483
+ "keywords": keywords,
484
+ "ambiguity_level": "medium" if complexity == "complex" else "low",
485
+ "requires_clarification": complexity == "complex"
486
+ }
487
+
488
+ except Exception as e:
489
+ print(f"Error in simple LLM analysis: {e}")
490
+ return self._get_fallback_intent_analysis(question)
491
+
492
+ def _validate_intent_analysis(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
493
+ """Validate and clean up intent analysis results"""
494
+ validated = {}
495
+
496
+ # Validate intent
497
+ valid_intents = ["procedural", "definition", "comparison", "calculation", "advice", "specific_case"]
498
+ validated["intent"] = analysis.get("intent", "unknown")
499
+ if validated["intent"] not in valid_intents:
500
+ validated["intent"] = "unknown"
501
+
502
+ # Validate complexity
503
+ valid_complexity = ["simple", "moderate", "complex"]
504
+ validated["complexity"] = analysis.get("complexity", "simple")
505
+ if validated["complexity"] not in valid_complexity:
506
+ validated["complexity"] = "simple"
507
+
508
+ # Validate keywords
509
+ keywords = analysis.get("keywords", [])
510
+ if isinstance(keywords, list):
511
+ validated["keywords"] = [str(k).strip() for k in keywords[:5] if k and str(k).strip()]
512
+ else:
513
+ validated["keywords"] = []
514
+
515
+ # Validate ambiguity level
516
+ valid_ambiguity = ["low", "medium", "high"]
517
+ validated["ambiguity_level"] = analysis.get("ambiguity_level", "low")
518
+ if validated["ambiguity_level"] not in valid_ambiguity:
519
+ validated["ambiguity_level"] = "low"
520
+
521
+ # Validate requires_clarification
522
+ validated["requires_clarification"] = bool(analysis.get("requires_clarification", False))
523
+
524
+ # Validate suggested_clarifications
525
+ clarifications = analysis.get("suggested_clarifications", [])
526
+ if isinstance(clarifications, list):
527
+ validated["suggested_clarifications"] = [str(c).strip() for c in clarifications[:3] if c and str(c).strip()]
528
+ else:
529
+ validated["suggested_clarifications"] = []
530
+
531
+ return validated
532
+
533
+ def _get_fallback_intent_analysis(self, question: str) -> Dict[str, Any]:
534
+ """Get fallback intent analysis using rule-based approach"""
535
+ # Simple rule-based fallback
536
+ question_lower = question.lower()
537
+
538
+ # Determine intent based on keywords
539
+ if any(word in question_lower for word in ["thủ tục", "cách", "làm thế nào", "quy trình", "bước"]):
540
+ intent = "procedural"
541
+ elif any(word in question_lower for word in ["là gì", "định nghĩa", "khái niệm", "nghĩa là"]):
542
+ intent = "definition"
543
+ elif any(word in question_lower for word in ["so sánh", "khác nhau", "giống", "khác biệt"]):
544
+ intent = "comparison"
545
+ elif any(word in question_lower for word in ["tính", "tính toán", "phí", "lệ phí", "thuế"]):
546
+ intent = "calculation"
547
+ elif any(word in question_lower for word in ["nên", "có thể", "được không", "có được"]):
548
+ intent = "advice"
549
+ else:
550
+ intent = "specific_case"
551
+
552
+ # Determine complexity based on length and question marks
553
+ word_count = len(question.split())
554
+ if word_count < 8:
555
+ complexity = "simple"
556
+ elif word_count < 20:
557
+ complexity = "moderate"
558
+ else:
559
+ complexity = "complex"
560
+
561
+ # Extract simple keywords
562
+ keywords = []
563
+ for word in question.split():
564
+ word_clean = re.sub(r'[^\w]', '', word).lower()
565
+ if len(word_clean) > 3 and word_clean not in ["thế", "nào", "như", "thì", "này", "được", "có", "của"]:
566
+ keywords.append(word_clean)
567
+ if len(keywords) >= 3:
568
+ break
569
+
570
+ return {
571
+ "intent": intent,
572
+ "complexity": complexity,
573
+ "keywords": keywords,
574
+ "ambiguity_level": "medium" if complexity == "complex" else "low",
575
+ "requires_clarification": complexity == "complex",
576
+ "suggested_clarifications": []
577
+ }
578
+
579
+ def _llm_refine_question_advanced(self, question: str, contexts: List[str], intent_analysis: Dict[str, Any]) -> Optional[str]:
580
+ """Advanced LLM-based question refinement with context and intent awareness"""
581
+ if not self.llm:
582
+ return None
583
+
584
+ context_str = ", ".join(contexts) if contexts else "tổng quát"
585
+ intent = intent_analysis.get("intent", "unknown")
586
+ complexity = intent_analysis.get("complexity", "simple")
587
+ keywords = intent_analysis.get("keywords", [])
588
+
589
+ # Choose refinement strategy based on intent
590
+ if intent == "procedural":
591
+ strategy_prompt = """
592
+ Đây là câu hỏi về thủ tục pháp lý. Hãy:
593
+ - Làm rõ loại thủ tục cụ thể
594
+ - Thêm từ khóa về quy trình, bước thực hiện
595
+ - Đề cập đến cơ quan có thẩm quyền nếu phù hợp"""
596
+ elif intent == "definition":
597
+ strategy_prompt = """
598
+ Đây là câu hỏi định nghĩa khái niệm. Hãy:
599
+ - Làm rõ khái niệm cần định nghĩa
600
+ - Thêm ngữ cảnh pháp lý liên quan
601
+ - Đề cập đến văn bản luật có liên quan"""
602
+ elif intent == "comparison":
603
+ strategy_prompt = """
604
+ Đây là câu hỏi so sánh. Hãy:
605
+ - Làm rõ các đối tượng được so sánh
606
+ - Thêm tiêu chí so sánh cụ thể
607
+ - Đảm bảo tính khách quan"""
608
+ else:
609
+ strategy_prompt = """
610
+ Hãy cải thiện câu hỏi theo nguyên tắc chung:
611
+ - Làm rõ ý định của câu hỏi
612
+ - Thêm ngữ cảnh pháp lý phù hợp
613
+ - Sử dụng thuật ngữ chuẩn mực"""
614
+
615
+ prompt = PromptTemplate(
616
+ template="""Bạn là chuyên gia pháp lý Việt Nam có 20 năm kinh nghiệm. Hãy cải thiện câu hỏi pháp lý sau để tối ưu hóa việc tìm kiếm thông tin.
617
+
618
+ THÔNG TIN PHÂN TÍCH:
619
+ - Lĩnh vực pháp lý: {context}
620
+ - Loại câu hỏi: {intent}
621
+ - Độ phức tạp: {complexity}
622
+ - Từ khóa chính: {keywords}
623
+
624
+ CHIẾN LƯỢC CẢI THIỆN:
625
+ {strategy}
626
+
627
+ NGUYÊN TẮC CHUNG:
628
+ 1. Giữ nguyên ý nghĩa gốc của câu hỏi
629
+ 2. Sử dụng thuật ngữ pháp lý chính xác và chuẩn mực
630
+ 3. Làm rõ các khái niệm mơ hồ
631
+ 4. Thêm ngữ cảnh pháp lý cần thiết
632
+ 5. Tối ưu hóa cho tìm kiếm trong cơ sở dữ liệu pháp luật
633
+ 6. Đảm bảo câu hỏi ngắn gọn nhưng đầy đủ thông tin
634
+ 7. Ưu tiên các từ khóa xuất hiện trong văn bản pháp luật Việt Nam
635
+
636
+ Câu hỏi gốc: {question}
637
+
638
+ Câu hỏi được cải thiện (chỉ trả về câu hỏi, không giải thích):""",
639
+ input_variables=["question", "context", "intent", "complexity", "keywords", "strategy"]
640
+ )
641
+
642
+ try:
643
+ response = self.llm.invoke(prompt.format(
644
+ question=question,
645
+ context=context_str,
646
+ intent=intent,
647
+ complexity=complexity,
648
+ keywords=", ".join(keywords),
649
+ strategy=strategy_prompt
650
+ ))
651
+
652
+ refined = response.content.strip()
653
+
654
+ # Advanced validation
655
+ if self._validate_refined_question(question, refined, intent_analysis):
656
+ return refined
657
+
658
+ except Exception as e:
659
+ print(f"Error in advanced LLM refinement: {e}")
660
+
661
+ return None
662
+
663
+ def _llm_validate_refinement(self, original: str, refined: str, contexts: List[str]) -> bool:
664
+ """Use LLM to validate if the refinement maintains original intent"""
665
+ if not self.llm:
666
+ return True
667
+
668
+ prompt = PromptTemplate(
669
+ template="""Bạn là chuyên gia đánh giá chất lượng câu hỏi pháp lý. Hãy đánh giá xem câu hỏi đã được cải thiện có giữ nguyên ý nghĩa gốc và có tốt hơn cho việc tìm kiếm thông tin pháp luật không.
670
+
671
+ Câu hỏi gốc: {original}
672
+ Câu hỏi đã cải thiện: {refined}
673
+ Lĩnh vực: {contexts}
674
+
675
+ Tiêu chí đánh giá:
676
+ 1. Giữ nguyên ý nghĩa gốc (có/không)
677
+ 2. Cải thiện khả năng tìm kiếm (có/không)
678
+ 3. Sử dụng thuật ngữ pháp lý phù hợp (có/không)
679
+ 4. Độ dài hợp lý (có/không)
680
+ 5. Rõ ràng và dễ hiểu (có/không)
681
+
682
+ Kết luận: CHẤP_NHẬN hoặc TỪ_CHỐI
683
+
684
+ Chỉ trả về kết luận:""",
685
+ input_variables=["original", "refined", "contexts"]
686
+ )
687
+
688
+ try:
689
+ response = self.llm.invoke(prompt.format(
690
+ original=original,
691
+ refined=refined,
692
+ contexts=", ".join(contexts)
693
+ ))
694
+
695
+ return "CHẤP_NHẬN" in response.content.strip().upper()
696
+ except Exception as e:
697
+ print(f"Error in LLM validation: {e}")
698
+ return True
699
+
700
+ def _validate_refined_question(self, original: str, refined: str, intent_analysis: Dict[str, Any]) -> bool:
701
+ """Validate refined question with multiple criteria"""
702
+ if not refined or not refined.strip():
703
+ return False
704
+
705
+ # Basic length check
706
+ if len(refined) < 10 or len(refined) > 500:
707
+ return False
708
+
709
+ # Should contain question mark for questions
710
+ if intent_analysis.get("intent") in ["procedural", "definition"] and "?" not in refined:
711
+ return False
712
+
713
+ # Shouldn't start with meta phrases
714
+ meta_phrases = ["câu hỏi", "tôi muốn hỏi", "xin hỏi", "cho tôi biết"]
715
+ if any(refined.lower().startswith(phrase) for phrase in meta_phrases):
716
+ return False
717
+
718
+ # Should be different from original (some improvement made)
719
+ if refined.strip().lower() == original.strip().lower():
720
+ return False
721
+
722
+ return True
723
+
724
+ def _get_corrections_made(self, original: str, corrected: str) -> List[Dict[str, str]]:
725
+ """Get list of spelling corrections that were made"""
726
+ corrections = []
727
+ for misspelling, correction in self.common_corrections.items():
728
+ if misspelling in original.lower() and correction in corrected.lower():
729
+ corrections.append({"from": misspelling, "to": correction})
730
+ return corrections
731
+
732
+ def _get_expanded_terms(self, original: str, expanded: str) -> List[Dict[str, str]]:
733
+ """Get list of abbreviations that were expanded"""
734
+ expansions = []
735
+ for abbrev, full_form in self.legal_abbreviations.items():
736
+ if abbrev in original.lower() and full_form in expanded.lower():
737
+ expansions.append({"abbreviation": abbrev, "full_form": full_form})
738
+ return expansions
739
+
740
+ def get_refinement_summary(self, refinement_result: Dict) -> str:
741
+ """Generate a human-readable summary of refinements made"""
742
+ if not refinement_result["refinement_steps"]:
743
+ return "Không có cải thiện nào được thực hiện."
744
+
745
+ summary_parts = []
746
+
747
+ if "basic_cleaning" in refinement_result["refinement_steps"]:
748
+ summary_parts.append("làm sạch văn bản")
749
+
750
+ if "spelling_correction" in refinement_result["refinement_steps"]:
751
+ corrections = refinement_result["corrections_made"]
752
+ if corrections:
753
+ summary_parts.append(f"sửa {len(corrections)} lỗi chính tả")
754
+
755
+ if "abbreviation_expansion" in refinement_result["refinement_steps"]:
756
+ expansions = refinement_result["expanded_terms"]
757
+ if expansions:
758
+ summary_parts.append(f"mở rộng {len(expansions)} từ viết tắt")
759
+
760
+ if "intent_analysis" in refinement_result["refinement_steps"]:
761
+ intent_analysis = refinement_result.get("intent_analysis", {})
762
+ intent = intent_analysis.get("intent", "unknown")
763
+ complexity = intent_analysis.get("complexity", "simple")
764
+ summary_parts.append(f"phân tích ý định ({intent}, độ phức tạp: {complexity})")
765
+
766
+ if "context_enhancement" in refinement_result["refinement_steps"]:
767
+ contexts = refinement_result["detected_context"]
768
+ if contexts:
769
+ context_method = "AI" if refinement_result.get("llm_context_detection") else "quy tắc"
770
+ summary_parts.append(f"thêm từ khóa cho lĩnh vực {', '.join(contexts)} ({context_method})")
771
+
772
+ # LLM enhancements
773
+ llm_methods = []
774
+ if "chain_of_thought" in refinement_result["refinement_steps"]:
775
+ llm_methods.append("suy luận từng bước")
776
+ if "iterative_refinement" in refinement_result["refinement_steps"]:
777
+ llm_methods.append("cải thiện lặp")
778
+ if "llm_enhancement" in refinement_result["refinement_steps"]:
779
+ llm_methods.append("cải thiện tiêu chuẩn")
780
+
781
+ if llm_methods:
782
+ validation_status = ""
783
+ if refinement_result.get("llm_validation_passed") is not None:
784
+ validation_status = " (đã xác thực)" if refinement_result["llm_validation_passed"] else " (chưa xác thực)"
785
+
786
+ method_str = ", ".join(llm_methods)
787
+ summary_parts.append(f"cải thiện bằng AI ({method_str}){validation_status}")
788
+
789
+ return f"Đã {', '.join(summary_parts)}."
790
+
791
+ def get_detailed_analysis(self, refinement_result: Dict) -> str:
792
+ """Get detailed analysis of the refinement process"""
793
+ if not refinement_result.get("intent_analysis"):
794
+ return ""
795
+
796
+ intent_analysis = refinement_result["intent_analysis"]
797
+ analysis_parts = []
798
+
799
+ # Intent information
800
+ intent = intent_analysis.get("intent", "unknown")
801
+ intent_map = {
802
+ "procedural": "Thủ tục",
803
+ "definition": "Định nghĩa",
804
+ "comparison": "So sánh",
805
+ "calculation": "Tính toán",
806
+ "advice": "Tư vấn",
807
+ "specific_case": "Trường hợp cụ thể"
808
+ }
809
+ analysis_parts.append(f"Loại câu hỏi: {intent_map.get(intent, intent)}")
810
+
811
+ # Complexity
812
+ complexity = intent_analysis.get("complexity", "simple")
813
+ complexity_map = {"simple": "Đơn giản", "moderate": "Trung bình", "complex": "Phức tạp"}
814
+ analysis_parts.append(f"Độ phức tạp: {complexity_map.get(complexity, complexity)}")
815
+
816
+ # Keywords
817
+ keywords = intent_analysis.get("keywords", [])
818
+ if keywords:
819
+ analysis_parts.append(f"Từ khóa chính: {', '.join(keywords[:3])}")
820
+
821
+ # Ambiguity level
822
+ ambiguity = intent_analysis.get("ambiguity_level", "low")
823
+ ambiguity_map = {"low": "Thấp", "medium": "Trung bình", "high": "Cao"}
824
+ analysis_parts.append(f"Độ mơ hồ: {ambiguity_map.get(ambiguity, ambiguity)}")
825
+
826
+ return " | ".join(analysis_parts)
827
+
828
+ def _llm_chain_of_thought_refinement(self, question: str, contexts: List[str], intent_analysis: Dict[str, Any]) -> Optional[str]:
829
+ """Use chain-of-thought reasoning for complex question refinement"""
830
+ if not self.llm or intent_analysis.get("complexity") != "complex":
831
+ return None
832
+
833
+ prompt = PromptTemplate(
834
+ template="""Bạn là chuyên gia pháp lý Việt Nam với 25 năm kinh nghiệm. Hãy sử dụng phương pháp suy luận từng bước để cải thiện câu hỏi pháp lý phức tạp sau.
835
+
836
+ THÔNG TIN PHÂN TÍCH:
837
+ - Câu hỏi gốc: {question}
838
+ - Lĩnh vực pháp lý: {contexts}
839
+ - Độ phức tạp: {complexity}
840
+ - Độ mơ hồ: {ambiguity}
841
+
842
+ BƯỚC 1: PHÂN TÍCH VẤN ĐỀ
843
+ Hãy xác định:
844
+ - Vấn đề pháp lý cốt lõi là gì?
845
+ - Có những khái niệm nào cần làm rõ?
846
+ - Thiếu thông tin gì để trả lời đầy đủ?
847
+
848
+ BƯỚC 2: XÁC ĐỊNH NGỮ CẢNH PHÁP LÝ
849
+ Hãy xác định:
850
+ - Văn bản pháp luật nào có khả năng liên quan?
851
+ - Cơ quan có thẩm quyền nào cần đề cập?
852
+ - Thủ tục hoặc quy trình nào cần nêu rõ?
853
+
854
+ BƯỚC 3: TỐI ƯU HÓA TỪ KHÓA
855
+ Hãy xác định:
856
+ - Thuật ngữ pháp lý chính xác cần sử dụng
857
+ - Từ khóa tìm kiếm hiệu quả
858
+ - Cụm từ thường xuất hiện trong văn bản pháp luật
859
+
860
+ BƯỚC 4: XÂY DỰNG CÂU HỎI TỐI ƯU
861
+ Dựa trên 3 bước trên, hãy xây dựng câu hỏi mới:
862
+ - Rõ ràng và cụ thể
863
+ - Sử dụng thuật ngữ pháp lý chuẩn
864
+ - Tối ưu cho tìm kiếm
865
+
866
+ ĐỊNH DẠNG TRẢ LỜI JSON:
867
+ {{
868
+ "analysis": {{
869
+ "core_legal_issue": "vấn đề pháp lý cốt lõi",
870
+ "unclear_concepts": ["khái niệm 1", "khái niệm 2"],
871
+ "missing_information": ["thông tin thiếu 1", "thông tin thiếu 2"]
872
+ }},
873
+ "legal_context": {{
874
+ "relevant_laws": ["luật 1", "luật 2"],
875
+ "authorities": ["cơ quan 1", "cơ quan 2"],
876
+ "procedures": ["thủ tục 1", "thủ tục 2"]
877
+ }},
878
+ "keywords": {{
879
+ "legal_terms": ["thuật ngữ 1", "thuật ngữ 2"],
880
+ "search_keywords": ["từ khóa 1", "từ khóa 2"],
881
+ "legal_phrases": ["cụm từ 1", "cụm từ 2"]
882
+ }},
883
+ "refined_question": "câu hỏi được cải thiện",
884
+ "confidence_score": 0.95,
885
+ "reasoning": "lý do tại sao câu hỏi này tốt hơn"
886
+ }}
887
+
888
+ Chỉ trả về JSON hợp lệ:""",
889
+ input_variables=["question", "contexts", "complexity", "ambiguity"]
890
+ )
891
+
892
+ try:
893
+ response = self.llm.invoke(prompt.format(
894
+ question=question,
895
+ contexts=", ".join(contexts),
896
+ complexity=intent_analysis.get("complexity", "complex"),
897
+ ambiguity=intent_analysis.get("ambiguity_level", "high")
898
+ ))
899
+
900
+ result = json.loads(response.content.strip())
901
+ refined_question = result.get("refined_question", "")
902
+ confidence = result.get("confidence_score", 0.0)
903
+
904
+ # Only return if confidence is high enough
905
+ if refined_question and confidence > Config.MIN_CONFIDENCE_SCORE:
906
+ return refined_question
907
+
908
+ except Exception as e:
909
+ print(f"Error in chain-of-thought refinement: {e}")
910
+
911
+ return None
912
+
913
+ def _llm_iterative_refinement(self, question: str, contexts: List[str], max_iterations: int = 3) -> Optional[str]:
914
+ """Use iterative refinement to progressively improve the question"""
915
+ if not self.llm:
916
+ return None
917
+
918
+ current_question = question
919
+
920
+ for iteration in range(max_iterations):
921
+ prompt = PromptTemplate(
922
+ template="""Bạn là chuyên gia cải thiện câu hỏi pháp lý. Đây là lần cải thiện thứ {iteration} của câu hỏi.
923
+
924
+ Câu hỏi hiện tại: {current_question}
925
+ Lĩnh vực pháp lý: {contexts}
926
+
927
+ Hãy phân tích và cải thiện thêm câu hỏi theo các tiêu chí:
928
+
929
+ LẦN 1: Tập trung vào thuật ngữ pháp lý và cấu trúc câu
930
+ LẦN 2: Tập trung vào ngữ cảnh và từ khóa tìm kiếm
931
+ LẦN 3: Tập trung vào tính rõ ràng và độ chính xác
932
+
933
+ Nguyên tắc cải thiện:
934
+ 1. Mỗi lần cải thiện phải có tiến bộ rõ rệt
935
+ 2. Giữ nguyên ý nghĩa gốc
936
+ 3. Tăng cường khả năng tìm kiếm
937
+ 4. Sử dụng thuật ngữ chuẩn mực
938
+
939
+ Trả về định dạng JSON:
940
+ {{
941
+ "improved_question": "câu hỏi được cải thiện",
942
+ "improvements_made": ["cải thiện 1", "cải thiện 2"],
943
+ "quality_score": 0.85,
944
+ "needs_further_improvement": true/false
945
+ }}
946
+
947
+ Chỉ trả về JSON:""",
948
+ input_variables=["current_question", "contexts", "iteration"]
949
+ )
950
+
951
+ try:
952
+ response = self.llm.invoke(prompt.format(
953
+ current_question=current_question,
954
+ contexts=", ".join(contexts),
955
+ iteration=iteration + 1
956
+ ))
957
+
958
+ result = json.loads(response.content.strip())
959
+ improved_question = result.get("improved_question", "")
960
+ quality_score = result.get("quality_score", 0.0)
961
+ needs_improvement = result.get("needs_further_improvement", False)
962
+
963
+ if improved_question and improved_question != current_question:
964
+ current_question = improved_question
965
+
966
+ # Stop if quality is high enough or no further improvement needed
967
+ if quality_score > 0.9 or not needs_improvement:
968
+ break
969
+ else:
970
+ break
971
+
972
+ except Exception as e:
973
+ print(f"Error in iterative refinement iteration {iteration + 1}: {e}")
974
+ break
975
+
976
+ return current_question if current_question != question else None
977
+
978
+ def is_legal_question(self, question: str, use_llm: bool = True) -> Dict[str, Any]:
979
+ """
980
+ Determine if a question is related to legal matters
981
+
982
+ Args:
983
+ question: The user's question
984
+ use_llm: Whether to use LLM for more accurate detection
985
+
986
+ Returns:
987
+ Dictionary containing detection results and confidence
988
+ """
989
+ result = {
990
+ "is_legal": False,
991
+ "confidence": 0.0,
992
+ "detected_contexts": [],
993
+ "legal_keywords_found": [],
994
+ "method_used": "keyword_based"
995
+ }
996
+
997
+ # Basic keyword-based detection
998
+ question_lower = question.lower().strip()
999
+
1000
+ # Check for direct legal keywords
1001
+ legal_keywords_found = []
1002
+
1003
+ # General legal terms that strongly indicate legal questions
1004
+ strong_legal_indicators = [
1005
+ "luật", "điều luật", "quy định", "nghị định", "thông tư", "quyết định",
1006
+ "bộ luật", "hiến pháp", "pháp luật", "pháp lệnh", "văn bản pháp lý",
1007
+ "quyền", "nghĩa vụ", "vi phạm", "xử phạt", "trách nhiệm pháp lý",
1008
+ "tòa án", "kiện", "khiếu nại", "tố cáo", "luật sư", "công chứng",
1009
+ "chế tài", "bồi thường", "án phạt", "hình phạt", "tranh chấp pháp lý"
1010
+ ]
1011
+
1012
+ # Check for strong legal indicators
1013
+ for keyword in strong_legal_indicators:
1014
+ if keyword in question_lower:
1015
+ legal_keywords_found.append(keyword)
1016
+
1017
+ # Check for context-specific legal terms
1018
+ context_score = 0
1019
+ detected_contexts = self._detect_legal_context(question)
1020
+
1021
+ # If any legal context is detected, it's likely a legal question
1022
+ if detected_contexts:
1023
+ result["detected_contexts"] = detected_contexts
1024
+ context_score = len(detected_contexts) * 0.3
1025
+
1026
+ # Check for legal abbreviations
1027
+ for abbr, full_form in self.legal_abbreviations.items():
1028
+ if abbr in question_lower or full_form in question_lower:
1029
+ legal_keywords_found.append(f"{abbr} ({full_form})")
1030
+
1031
+ # Calculate confidence based on findings
1032
+ keyword_score = min(len(legal_keywords_found) * 0.2, 0.8)
1033
+ base_confidence = keyword_score + context_score
1034
+
1035
+ result["legal_keywords_found"] = legal_keywords_found
1036
+
1037
+ # Use LLM for more sophisticated detection if available
1038
+ if use_llm and self.llm and base_confidence < 0.7:
1039
+ try:
1040
+ llm_result = self._llm_detect_legal_domain(question)
1041
+ if llm_result:
1042
+ result["is_legal"] = llm_result["is_legal"]
1043
+ result["confidence"] = llm_result["confidence"]
1044
+ result["method_used"] = "llm_enhanced"
1045
+ result["llm_reasoning"] = llm_result.get("reasoning", "")
1046
+ return result
1047
+ except Exception as e:
1048
+ print(f"Error in LLM legal domain detection: {e}")
1049
+
1050
+ # Final decision based on keyword and context analysis
1051
+ result["confidence"] = min(base_confidence, 1.0)
1052
+ result["is_legal"] = result["confidence"] >= 0.3 or len(legal_keywords_found) > 0
1053
+
1054
+ return result
1055
+
1056
+ def _llm_detect_legal_domain(self, question: str) -> Optional[Dict[str, Any]]:
1057
+ """Use LLM to detect if question is in legal domain"""
1058
+ if not self.llm:
1059
+ return None
1060
+
1061
+ prompt = PromptTemplate(
1062
+ template="""Bạn là chuyên gia phân loại câu hỏi. Hãy xác định xem câu hỏi sau có liên quan đến pháp luật Việt Nam hay không.
1063
+
1064
+ Câu hỏi pháp luật thường bao gồm:
1065
+ - Hỏi về quyền và nghĩa vụ theo pháp luật
1066
+ - Các thủ tục, quy trình pháp lý
1067
+ - Quy định, điều luật, nghị định, thông tư
1068
+ - Tranh chấp, vi phạm, xử phạt
1069
+ - Hợp đồng, giao dịch có tính pháp lý
1070
+ - Doanh nghiệp, lao động, thuế, bất động sản, gia đình (theo luật)
1071
+ - Tòa án, luật sư, công chứng
1072
+ - Hình sự, dân sự, hành chính
1073
+
1074
+ Câu hỏi KHÔNG phải pháp luật:
1075
+ - Câu hỏi về kỹ thuật, công nghệ
1076
+ - Toán học, khoa học
1077
+ - Y tế (trừ khi hỏi về quy định y tế)
1078
+ - Nấu ăn, du lịch, giải trí
1079
+ - Lịch sử, địa lý (trừ khi liên quan luật)
1080
+ - Thể thao, văn học, nghệ thuật
1081
+
1082
+ Câu hỏi: "{question}"
1083
+
1084
+ Trả lời theo định dạng JSON:
1085
+ {{"is_legal": true/false, "confidence": 0.0-1.0, "reasoning": "lý do ngắn gọn"}}
1086
+
1087
+ Chỉ trả về JSON, không có text khác:""",
1088
+ input_variables=["question"]
1089
+ )
1090
+
1091
+ try:
1092
+ response = self.llm.invoke(prompt.format(question=question))
1093
+ content = response.content.strip()
1094
+
1095
+ # Extract JSON from response
1096
+ json_content = self._extract_json_from_response(content)
1097
+ if json_content:
1098
+ result = json.loads(json_content)
1099
+ # Validate the result
1100
+ if isinstance(result.get("is_legal"), bool) and isinstance(result.get("confidence"), (int, float)):
1101
+ return {
1102
+ "is_legal": result["is_legal"],
1103
+ "confidence": float(result["confidence"]),
1104
+ "reasoning": result.get("reasoning", "")
1105
+ }
1106
+
1107
+ except Exception as e:
1108
+ print(f"Error in LLM legal domain detection: {e}")
1109
+
1110
+ return None