DoAn / core /rag /generator.py
hungnha's picture
change commit
b91b0a5
from __future__ import annotations
from typing import Any, Dict, List, TYPE_CHECKING
if TYPE_CHECKING:
from core.rag.retrival import Retriever
# System prompt cho LLM (export để gradio/eval dùng)
SYSTEM_PROMPT = """Bạn là Trợ lý học vụ Đại học Bách khoa Hà Nội.
## NGUYÊN TẮC:
1. Chỉ được đưa ra câu trả lời dựa trên CONTEXT được cung cấp. Không suy đoán, không bổ sung thông tin ngoài CONTEXT.
2. Nếu CONTEXT chứa nhiều văn bản khác nhau, ưu tiên nội dung mới nhất, TRỪ KHI có điều khoản chuyển tiếp nói khác.
3. Nếu không tìm thấy thông tin trong CONTEXT, trả lời: "Không tìm thấy thông tin trong dữ liệu hiện có."
"""
def build_context(results: List[Dict[str, Any]], max_chars: int = 8000) -> str:
"""Xây dựng context từ kết quả retrieval để đưa vào prompt."""
parts = []
for i, r in enumerate(results, 1):
meta = r.get("metadata", {})
source = meta.get("source_file", "N/A")
header = meta.get("header_path", "")
doc_type = meta.get("document_type", "")
cohorts = meta.get("applicable_cohorts", "")
program = meta.get("program_name", "")
program_code = meta.get("program_code", "")
faculty = meta.get("faculty", "")
degree_levels = meta.get("degree_levels", [])
issued_year = meta.get("issued_year", "")
content = r.get("content", "").strip()
# Tạo dòng metadata
meta_info = f"Nguồn: {source}"
if header and header != "/":
meta_info += f" | Mục: {header}"
if doc_type:
meta_info += f" | Loại: {doc_type}"
if issued_year:
meta_info += f" | Năm: {issued_year}"
if cohorts:
meta_info += f" | Áp dụng: {cohorts}"
if program:
meta_info += f" | CTĐT: {program}"
if program_code:
meta_info += f" | Mã: {program_code}"
if faculty:
meta_info += f" | Khoa: {faculty}"
if degree_levels:
levels = ", ".join(degree_levels) if isinstance(degree_levels, list) else degree_levels
meta_info += f" | Bậc: {levels}"
parts.append(f"[TÀI LIỆU {i}]\n{meta_info}\n{content}")
context = "\n---\n".join(parts)
# Cắt ngắn nếu vượt quá giới hạn
return context[:max_chars] if len(context) > max_chars else context
def build_prompt(question: str, context: str) -> str:
"""Ghép system prompt, context và câu hỏi thành prompt hoàn chỉnh."""
return f"{SYSTEM_PROMPT}\n\n## CONTEXT:\n{context}\n\n## CÂU HỎI: {question}\n\n## TRẢ LỜI:"
class RAGContextBuilder:
"""Kết hợp retrieval và context building thành một bước."""
def __init__(self, retriever: "Retriever", max_context_chars: int = 8000):
"""Khởi tạo với retriever và giới hạn context."""
self._retriever = retriever
self._max_context_chars = max_context_chars
def retrieve_and_prepare(
self,
question: str,
k: int = 5,
initial_k: int = 20,
mode: str = "hybrid_rerank"
) -> Dict[str, Any]:
"""Retrieve documents và chuẩn bị context + prompt cho LLM."""
# Tìm kiếm documents liên quan
results = self._retriever.flexible_search(question, k=k, initial_k=initial_k, mode=mode)
# Không tìm thấy kết quả
if not results:
return {
"results": [],
"contexts": [],
"context_text": "",
"prompt": "",
}
# Xây dựng context và prompt
context_text = build_context(results, self._max_context_chars)
prompt = build_prompt(question, context_text)
return {
"results": results, # Kết quả retrieval gốc
"contexts": [r.get("content", "")[:1000] for r in results], # Context rút gọn (cho eval)
"context_text": context_text, # Context đầy đủ
"prompt": prompt, # Prompt hoàn chỉnh
}
# Alias để tương thích ngược
RAGGenerator = RAGContextBuilder