minh-4T commited on
Commit
7ec7351
·
1 Parent(s): 42ed92c

update prompt

Browse files
Files changed (2) hide show
  1. core/prompting.py +6 -8
  2. core/qa_pipeline.py +56 -60
core/prompting.py CHANGED
@@ -85,7 +85,7 @@ Về vấn đề [Chủ đề], theo **Điều [Số]**, các trường hợp ng
85
  # Lấy ví dụ phù hợp (Fallback về simple nếu không khớp)
86
  example = examples.get(question_type, examples['simple'])
87
 
88
- # 3. TOPIC INSTRUCTION: Rào chắn ngữ cảnh (Context Guardrail)
89
  if topic:
90
  topic_instr = (
91
  f"\n\n **LƯU Ý ĐẶC BIỆT VỀ CHỦ ĐỀ MỞ RỘNG:**\n"
@@ -97,19 +97,17 @@ Về vấn đề [Chủ đề], theo **Điều [Số]**, các trường hợp ng
97
  else:
98
  topic_instr = ""
99
 
100
- # [YEAR-AWARE CHANGE] Rang buoc cau tra loi theo nam hoc duoc hoi.
101
  if year_scope:
102
  year_instr = (
103
- f"\n\n **RÀNG BUỘC NĂM HỌC (BẮT BUỘC):**\n"
104
- f"- Người dùng đang hỏi trong phạm vi năm: **{year_scope}**.\n"
105
- f"- Ưu tiên các đoạnnhãn nguồn cùng năm trong context (ví dụ: [Năm 2022-2023 | ...]).\n"
106
- f"- Nếu chưa đủ bằng chứng đúng năm, được phép dùng đoạn có nhãn 'Áp dụng nhiều năm' hoặc quy định gần nhất phải ghi chú phạm vi áp dụng.\n"
107
- f"- Không kết luận 'không có dữ liệu' chỉ vì thiếu đúng nhãn năm nếu vẫn có quy định bao quát liên quan.\n"
108
  )
109
  else:
110
  year_instr = ""
111
 
112
- # 4. Gộp Prompt
113
  full_prompt = f"""{base_system}
114
  ----------------
115
  {example}
 
85
  # Lấy ví dụ phù hợp (Fallback về simple nếu không khớp)
86
  example = examples.get(question_type, examples['simple'])
87
 
88
+ # TOPIC INSTRUCTION: Rào chắn ngữ cảnh (Context Guardrail)
89
  if topic:
90
  topic_instr = (
91
  f"\n\n **LƯU Ý ĐẶC BIỆT VỀ CHỦ ĐỀ MỞ RỘNG:**\n"
 
97
  else:
98
  topic_instr = ""
99
 
 
100
  if year_scope:
101
  year_instr = (
102
+ f"\n\n **RÀNG BUỘC NĂM HỌC (LƯU Ý QUAN TRỌNG):**\n"
103
+ f"- Người dùng đang hỏi cho năm học: **{year_scope}**.\n"
104
+ f"- Nếu trong `TÀI LIỆU THAM KHẢO` nội dung khớp với năm này, hãy dùng làm đáp án chính.\n"
105
+ f"- Nếu KHÔNG nội dung đúng năm, BẮT BUỘC SỬ DỤNG tài liệu có nhãn 'Áp dụng nhiều năm' hoặc quy chế gần nhất trong context. Khi trả lời, hãy rào trước một câu thân thiện: *'Hệ thống hiện ghi nhận quy chế dùng chung/năm [Năm của tài liệu] quy định như sau...'*. TUYỆT ĐỐI KHÔNG TỪ CHỐI trả lời nếu vẫn có bản dùng chung.\n"
 
106
  )
107
  else:
108
  year_instr = ""
109
 
110
+ # Gộp Prompt
111
  full_prompt = f"""{base_system}
112
  ----------------
113
  {example}
core/qa_pipeline.py CHANGED
@@ -4,7 +4,7 @@ import logging
4
  import groq
5
  import google.generativeai as genai
6
  import json
7
-
8
  from .models import llm
9
  from .config import TOP_K_RESULTS, FINAL_TOP_K
10
  from .rerank import advanced_rerank
@@ -140,10 +140,14 @@ def sanitize_for_prompt(text: str) -> str:
140
  return text.strip()
141
 
142
 
 
 
 
 
143
  def _normalize_for_router(message: str) -> str:
144
- compact = re.sub(r"[^\w\s]", " ", (message or "").lower(), flags=re.UNICODE)
145
- compact = re.sub(r"\s+", " ", compact).strip()
146
- return compact
147
 
148
 
149
  def _quick_non_domain_reply(message: str) -> Optional[str]:
@@ -278,34 +282,25 @@ def ask_ai_improved(message: str, history: List, hybrid_retriever) -> Generator[
278
  yield full_response
279
 
280
  def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Generator[str, None, None]:
 
281
  if not message.strip():
282
- yield " Bạn chưa nhập câu hỏi."
283
  return
284
 
 
285
  quick_reply = _quick_non_domain_reply(message)
286
  if quick_reply:
287
  logger.info("Bỏ qua truy xuất tài liệu cho câu hỏi giao tiếp/ngoài phạm vi")
288
  yield quick_reply
289
  return
290
 
291
- initial_year_range, initial_mentioned_years = detect_requested_year(message)
292
- if not initial_year_range and not initial_mentioned_years:
293
- if not _was_recently_prompted_for_year(history):
294
- logger.info("Yêu cầu người dùng bổ sung năm học trước khi truy vấn")
295
- yield "Vui lòng nhập kèm năm học để tra cứu nhanh hơn (ví dụ: 2022-2023 hoặc 2023)."
296
- return
297
-
298
- logger.info("Người dùng chưa nhập năm sau khi đã được nhắc; fallback sang tìm kiếm toàn bộ")
299
-
300
- logger.info(f" CÂU HỎI GỐC: {message}")
301
  question = generate_standalone_query(message, history)
302
- # [YEAR-AWARE CHANGE] Xac dinh pham vi nam ma nguoi dung yeu cau.
303
  requested_year_range, mentioned_years = detect_requested_year(f"{message}\n{question}")
304
- if requested_year_range:
305
- logger.info(f"Lọc theo năm học yêu cầu: {requested_year_range}")
306
- elif mentioned_years:
307
- logger.info(f"Lọc theo năm được nhắc tới: {sorted(mentioned_years)}")
308
 
 
309
  processed_data = analyze_and_expand_query(question)
310
 
311
  if processed_data.get("question_type") == "normal":
@@ -317,57 +312,55 @@ def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Genera
317
  queries = processed_data['expanded_queries']
318
  logger.info(f"Các truy vấn tìm kiếm: {queries}")
319
 
320
- all_docs: List = []
321
- seen = set()
322
- year_scope_hint = requested_year_range or (", ".join(sorted(mentioned_years)) if mentioned_years else None)
323
- for query in queries:
324
- #Giữ nguyên logic alpha ngành CNTT của Minh
325
- current_alpha = 0.4 if "CNTT" in query.upper() else 0.5
326
- docs = hybrid_retriever.search(
327
- query,
328
- k=TOP_K_RESULTS,
329
- alpha=current_alpha,
330
- year_scope=year_scope_hint,
331
- )
332
- for doc in docs:
333
- content_hash = hashlib.sha256(doc.page_content.encode("utf-8")).hexdigest()
334
- if content_hash not in seen:
335
- all_docs.append(doc)
336
- seen.add(content_hash)
 
 
 
 
 
 
 
 
 
 
337
 
338
  logger.info(f"Tìm thấy tổng {len(all_docs)} documents.")
 
 
339
  if not all_docs:
340
- yield "Không tìm thấy thông tin liên quan trong tài liệu."
341
  return
342
 
343
- # [YEAR-AWARE CHANGE] Lọc theo năm nhưng vẫn fallback nếu không có tài liệu đúng năm.
344
- year_scope = None
345
- year_filter_requested = bool(requested_year_range or mentioned_years)
346
- year_filtered_docs = filter_docs_by_year(all_docs, requested_year_range, mentioned_years)
347
-
348
- if year_filter_requested:
349
- if year_filtered_docs:
350
- if len(year_filtered_docs) != len(all_docs):
351
- logger.info(f"Đã lọc theo năm: còn {len(year_filtered_docs)}/{len(all_docs)} documents")
352
- all_docs = year_filtered_docs
353
- if requested_year_range:
354
- year_scope = requested_year_range
355
- elif mentioned_years:
356
- year_scope = ", ".join(sorted(mentioned_years))
357
- else:
358
- logger.warning("Không tìm thấy tài liệu đúng năm yêu cầu, fallback sang tập tài liệu tổng quát")
359
-
360
  final_docs = advanced_rerank(question, all_docs, top_k=FINAL_TOP_K)
361
 
 
362
  context_parts = []
363
  total_chars = 0
364
  for doc in final_docs:
365
  page = doc.metadata.get('page_number', 'N/A')
366
  file_name = doc.metadata.get('source_file') or doc.metadata.get('source')
367
- # [YEAR-AWARE CHANGE] Gan nhan nam trong context de LLM bam dung nguon.
368
  doc_year = infer_doc_academic_year(doc)
369
  year_label = f"Năm {doc_year}" if doc_year != "ALL" else "Áp dụng nhiều năm"
370
  source = f"[{year_label} | {os.path.basename(file_name)} | Trang {page}]" if file_name else f"[{year_label} | Trang {page}]"
 
371
  block = f"{source}\n{doc.page_content}"
372
  if total_chars + len(block) > MAX_CONTEXT_CHARS:
373
  break
@@ -377,12 +370,14 @@ def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Genera
377
  context = "\n\n---\n\n".join(context_parts)
378
  topic_hint = processed_data.get('topic') or processed_data.get('root_question') or question
379
 
380
- prompt = create_advanced_prompt(question, context, question_type, topic_hint, year_scope=year_scope)
 
381
 
382
  logger.info("Đang tạo câu trả lời cuối cùng ...")
383
 
384
  success = False
385
- # Thử với Groq
 
386
  for _ in range(len(api_manager.groq_keys)):
387
  try:
388
  client = api_manager.get_groq_client()
@@ -404,7 +399,7 @@ def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Genera
404
  logger.error(f"Lỗi Groq: {e}")
405
  break
406
 
407
- # Dự phòng sang Gemini (nếu Groq lỗi hoặc hết key)
408
  if not success:
409
  logger.warning("Chuyển sang Gemini ...")
410
  for _ in range(max(1, len(api_manager.gemini_keys))):
@@ -421,5 +416,6 @@ def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Genera
421
  api_manager.rotate_gemini()
422
  logger.error(f"Lỗi Gemini: {e}")
423
 
 
424
  if not success:
425
- yield "Đã xảy ra lỗi hệ thống hoặc quá tải. Vui lòng thử lại sau giây lát!"
 
4
  import groq
5
  import google.generativeai as genai
6
  import json
7
+ import unicodedata
8
  from .models import llm
9
  from .config import TOP_K_RESULTS, FINAL_TOP_K
10
  from .rerank import advanced_rerank
 
140
  return text.strip()
141
 
142
 
143
+ def remove_accents(input_str: str) -> str:
144
+ s1 = unicodedata.normalize('NFKD', input_str).encode('ASCII', 'ignore').decode('utf-8')
145
+ return s1.lower()
146
+
147
  def _normalize_for_router(message: str) -> str:
148
+ compact = remove_accents(message or "")
149
+ compact = re.sub(r"[^\w\s]", " ", compact, flags=re.UNICODE)
150
+ return re.sub(r"\s+", " ", compact).strip()
151
 
152
 
153
  def _quick_non_domain_reply(message: str) -> Optional[str]:
 
282
  yield full_response
283
 
284
  def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Generator[str, None, None]:
285
+ # Kiểm tra rỗng
286
  if not message.strip():
287
+ yield "Bạn chưa nhập câu hỏi."
288
  return
289
 
290
+ # Xử lý các câu giao tiếp/xã giao nhanh (đã được sửa lỗi dấu tiếng Việt)
291
  quick_reply = _quick_non_domain_reply(message)
292
  if quick_reply:
293
  logger.info("Bỏ qua truy xuất tài liệu cho câu hỏi giao tiếp/ngoài phạm vi")
294
  yield quick_reply
295
  return
296
 
297
+ # Phân tích câu hỏi
298
+ logger.info(f"CÂU HỎI GỐC: {message}")
 
 
 
 
 
 
 
 
299
  question = generate_standalone_query(message, history)
 
300
  requested_year_range, mentioned_years = detect_requested_year(f"{message}\n{question}")
301
+ year_scope_hint = requested_year_range or (", ".join(sorted(mentioned_years)) if mentioned_years else None)
 
 
 
302
 
303
+ # Phân loại và mở rộng từ khóa
304
  processed_data = analyze_and_expand_query(question)
305
 
306
  if processed_data.get("question_type") == "normal":
 
312
  queries = processed_data['expanded_queries']
313
  logger.info(f"Các truy vấn tìm kiếm: {queries}")
314
 
315
+ def fetch_docs(year_hint):
316
+ docs_temp = []
317
+ seen_temp = set()
318
+ for query in queries:
319
+ current_alpha = 0.4 if "CNTT" in query.upper() else 0.5
320
+ retrieved = hybrid_retriever.search(
321
+ query,
322
+ k=TOP_K_RESULTS,
323
+ alpha=current_alpha,
324
+ year_scope=year_hint
325
+ )
326
+ for doc in retrieved:
327
+ content_hash = hashlib.sha256(doc.page_content.encode("utf-8")).hexdigest()
328
+ if content_hash not in seen_temp:
329
+ docs_temp.append(doc)
330
+ seen_temp.add(content_hash)
331
+ return docs_temp
332
+ # Tìm tài liệu
333
+
334
+ # Cố gắng tìm tài liệu khớp chính xác với năm học người dùng nhắc đến
335
+ all_docs = fetch_docs(year_scope_hint)
336
+
337
+ # Nếu lớp 1 tìm không ra hoặc người dùng hoàn toàn không nhập năm, hệ thống sẽ tự động hạ chuẩn, tìm trên toàn bộ cơ sở dữ liệu chung (ALL)
338
+ if not all_docs and year_scope_hint:
339
+ logger.info(f"Bộ l���c năm '{year_scope_hint}' quá gắt không ra kết quả. Tự động Fallback tìm trên bản chung...")
340
+ year_scope_hint = None # Reset lại biến hint để quét toàn bộ VectorDB
341
+ all_docs = fetch_docs(None)
342
 
343
  logger.info(f"Tìm thấy tổng {len(all_docs)} documents.")
344
+
345
+ # Xử lý lịch sự nếu Vector DB thực sự "bó tay"
346
  if not all_docs:
347
+ yield f"Dạ, hiện tại hệ thống không tìm thấy quy định nào liên quan đến vấn đề này. Bạn có thể dùng các từ khóa mang tính hành chính hơn được không ạ?"
348
  return
349
 
350
+ # Rerank lại kết quả để chống ảo giác
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  final_docs = advanced_rerank(question, all_docs, top_k=FINAL_TOP_K)
352
 
353
+ # Gắn nhãn năm học vào Context cho LLM đọc
354
  context_parts = []
355
  total_chars = 0
356
  for doc in final_docs:
357
  page = doc.metadata.get('page_number', 'N/A')
358
  file_name = doc.metadata.get('source_file') or doc.metadata.get('source')
359
+
360
  doc_year = infer_doc_academic_year(doc)
361
  year_label = f"Năm {doc_year}" if doc_year != "ALL" else "Áp dụng nhiều năm"
362
  source = f"[{year_label} | {os.path.basename(file_name)} | Trang {page}]" if file_name else f"[{year_label} | Trang {page}]"
363
+
364
  block = f"{source}\n{doc.page_content}"
365
  if total_chars + len(block) > MAX_CONTEXT_CHARS:
366
  break
 
370
  context = "\n\n---\n\n".join(context_parts)
371
  topic_hint = processed_data.get('topic') or processed_data.get('root_question') or question
372
 
373
+ # Truyền year_scope_hint vào prompt để LLM biết đường rào đón
374
+ prompt = create_advanced_prompt(question, context, question_type, topic_hint, year_scope=year_scope_hint)
375
 
376
  logger.info("Đang tạo câu trả lời cuối cùng ...")
377
 
378
  success = False
379
+
380
+ # Streaming qua Groq (Có xoay tua khi gặp lỗi 429)
381
  for _ in range(len(api_manager.groq_keys)):
382
  try:
383
  client = api_manager.get_groq_client()
 
399
  logger.error(f"Lỗi Groq: {e}")
400
  break
401
 
402
+ # Streaming dự phòng qua Gemini
403
  if not success:
404
  logger.warning("Chuyển sang Gemini ...")
405
  for _ in range(max(1, len(api_manager.gemini_keys))):
 
416
  api_manager.rotate_gemini()
417
  logger.error(f"Lỗi Gemini: {e}")
418
 
419
+ # Báo lỗi khi cả 2 API đều sập
420
  if not success:
421
+ yield "Đã xảy ra lỗi hệ thống hoặc quá tải API. Vui lòng thử lại sau giây lát!"