Tôi đã làm việc với hơn 20 công ty luật tại Việt Nam trong 5 năm qua, và một vấn đề mà gần như tất cả đều gặp phải là: quy trình quản lý tri thức vẫn còn rất thủ công. Một ngày nọ, một công ty luật lớn tại TP.HCM gọi cho tôi với giọng hoảng loạn — họ vừa mất 3 ngày làm việc để tìm lại một điều khoản phạt trong hợp đồng hợp tác với đối tác Nhật Bản, và khách hàng đã gửi email yêu cầu bồi thường 200 triệu đồng. Đó là lúc tôi quyết định triển khai giải pháp RAG (Retrieval-Augmented Generation) cho họ, và HolySheep AI chính là nền tảng then chốt giúp họ giải quyết bài toán này.

Bối cảnh bài toán: Tại sao律所 cần RAG ngay bây giờ?

Theo khảo sát của Legaltech Vietnam 2025, trung bình một công ty luật tại Việt Nam quản lý khoảng 50,000-200,000 tài liệu pháp lý, bao gồm hợp đồng, quy định nội bộ, án lệ, và văn bản pháp luật. Vấn đề không nằm ở việc thiếu dữ liệu, mà ở chỗ:

Trong bài viết này, tôi sẽ hướng dẫn bạn xây dựng một hệ thống RAG hoàn chỉnh cho律所, sử dụng HolySheep AI làm backbone với chi phí chỉ bằng 1/6 so với việc sử dụng OpenAI trực tiếp.

Kiến trúc hệ thống RAG cho quản lý tri thức pháp lý

Tổng quan kiến trúc 3 lớp

+------------------------------------------------------------------+
|                    PRESENTATION LAYER                              |
|  +-------------+  +-------------+  +-------------+                 |
|  | Web Portal  |  | API Gateway |  | Admin Panel |                 |
|  +-------------+  +-------------+  +-------------+                 |
+------------------------------------------------------------------+
                              |
+------------------------------------------------------------------+
|                    BUSINESS LAYER                                 |
|  +-------------+  +-------------+  +-------------+                 |
|  | Clause      |  | Risk        |  | Case Law    |                 |
|  | Extraction  |  | Annotation  |  | Retrieval   |                 |
|  | Service     |  | Service     |  | Service     |                 |
|  +-------------+  +-------------+  +-------------+                 |
|  +-------------------------------------------------------------+   |
|  |              RAG Pipeline (HolySheep AI)                     |   |
|  |  - Document Parser  - Embedding  - Vector Store              |   |
|  |  - Chunking Strategy - Re-ranking  - Context Injection       |   |
|  +-------------------------------------------------------------+   |
+------------------------------------------------------------------+
                              |
+------------------------------------------------------------------+
|                    DATA LAYER                                     |
|  +-------------+  +-------------+  +-------------+                 |
|  | Document DB |  | Vector DB   |  | Cache Layer |                 |
|  | (PostgreSQL)|  | (ChromaDB)  |  | (Redis)     |                 |
|  +-------------+  +-------------+  +-------------+                 |
+------------------------------------------------------------------+

Sơ đồ luồng dữ liệu chi tiết

┌────────────────────────────────────────────────────────────────────┐
│  LUỒNG XỬ LÝ TÀI LIỆU PHÁP LÝ                                       │
└────────────────────────────────────────────────────────────────────┘

  [Upload Contract PDF/Word]
            │
            ▼
  ┌─────────────────────┐
  │ Document Parser     │    ← PyPDF2 / python-docx
  │ (Text + Structure)  │
  └─────────────────────┘
            │
            ▼
  ┌─────────────────────┐
  │ Intelligent Chunking│    ← RecursiveCharacterTextSplitter
  │ (500-1000 tokens)   │      + semantic boundaries
  └─────────────────────┘
            │
            ▼
  ┌─────────────────────┐
  │ Embedding Generation│    ← HolySheep API
  │ (text-embedding-3)  │      Base: https://api.holysheep.ai/v1
  └─────────────────────┘
            │
            ▼
  ┌─────────────────────┐
  │ Vector Storage      │    ← ChromaDB / Qdrant
  │ (Metadata + Index)  │
  └─────────────────────┘
            │
            ▼
  ┌─────────────────────┐
  │ RAG Query Pipeline  │    ← Query → Retrieve → Rerank → Generate
  └─────────────────────┘

Triển khai thực tế: Code mẫu hoàn chỉnh

1. Cài đặt và cấu hình HolySheep Client

"""
RAG Pipeline cho Hệ thống Quản lý Tri thức Pháp lý
Sử dụng HolySheep AI làm embedding và generation backend
"""

import os
import json
import hashlib
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass, field
from datetime import datetime

Third-party imports

import requests from requests.exceptions import ConnectionError, Timeout, HTTPError import ChromaDB from chromadb from sentence_transformers import SentenceTransformer from tenacity import retry, stop_after_attempt, wait_exponential

============================================================

CẤU HÌNH HOLYSHEEP API

============================================================

HOLYSHEEP_CONFIG = { "base_url": "https://api.holysheep.ai/v1", # LUÔN SỬ DỤNG endpoint này "api_key": os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY"), "embedding_model": "text-embedding-3-small", "embedding_dim": 1536, "timeout": 30, # seconds "max_retries": 3 } @dataclass class LegalDocument: """Cấu trúc dữ liệu cho tài liệu pháp lý""" doc_id: str title: str doc_type: str # contract, regulation, case_law, legal_opinion content: str metadata: Dict = field(default_factory=dict) created_at: datetime = field(default_factory=datetime.now) def __post_init__(self): if not self.doc_id: self.doc_id = hashlib.md5( f"{self.title}{self.created_at}".encode() ).hexdigest()[:16] class HolySheepClient: """ Client wrapper cho HolySheep AI API Xử lý các lỗi kết nối và retry tự động """ def __init__(self, config: Dict = HOLYSHEEP_CONFIG): self.base_url = config["base_url"] self.api_key = config["api_key"] self.timeout = config["timeout"] self.max_retries = config["max_retries"] self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }) def _handle_response(self, response: requests.Response) -> Dict: """Xử lý phản hồi từ API và kiểm tra lỗi""" if response.status_code == 401: raise PermissionError( "401 Unauthorized: Kiểm tra API key. " "Truy cập https://www.holysheep.ai/register để lấy key mới" ) elif response.status_code == 429: raise ConnectionError( "429 Rate Limited: Đã vượt quota. Vui lòng đợi hoặc nâng cấp gói" ) elif response.status_code >= 500: raise ConnectionError( f"{response.status_code} Server Error: HolySheep đang bảo trì" ) response.raise_for_status() return response.json() @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10) ) def create_embedding(self, texts: List[str]) -> List[List[float]]: """ Tạo embedding vector sử dụng HolySheep API Args: texts: Danh sách các đoạn text cần embed Returns: List of embedding vectors (1536 dimensions) """ payload = { "model": "text-embedding-3-small", "input": texts, "encoding_format": "float" } try: response = self.session.post( f"{self.base_url}/embeddings", json=payload, timeout=self.timeout ) result = self._handle_response(response) return [item["embedding"] for item in result["data"]] except ConnectionError as e: print(f"[HolySheep] ConnectionError: {e}") raise except Timeout: print("[HolySheep] Timeout khi gọi embedding API") raise ConnectionError("Embedding request timeout") @retry(stop=stop_after_attempt(3), wait=wait_exponential(min=2, max=10)) def generate_completion( self, prompt: str, system_prompt: str = "", temperature: float = 0.3, max_tokens: int = 2000 ) -> str: """ Tạo phản hồi sử dụng HolySheep Chat Completions API Args: prompt: Prompt người dùng system_prompt: System prompt (cho vai trò AI) temperature: Độ sáng tạo (0-1) max_tokens: Số token tối đa Returns: Generated text response """ messages = [] if system_prompt: messages.append({"role": "system", "content": system_prompt}) messages.append({"role": "user", "content": prompt}) payload = { "model": "gpt-4.1", # Hoặc deepseek-v3.2 để tiết kiệm chi phí "messages": messages, "temperature": temperature, "max_tokens": max_tokens } try: response = self.session.post( f"{self.base_url}/chat/completions", json=payload, timeout=self.timeout ) result = self._handle_response(response) return result["choices"][0]["message"]["content"] except ConnectionError as e: print(f"[HolySheep] ConnectionError: {e}") raise

2. Pipeline Trích xuất Điều khoản Hợp đồng

"""
Module trích xuất điều khoản hợp đồng sử dụng RAG
Tự động nhận diện các loại điều khoản và đánh dấu rủi ro
"""

from enum import Enum
from typing import List, Dict, Optional
import re
import json

class ClauseType(Enum):
    """Các loại điều khoản pháp lý thường gặp"""
    PENALTY = "penalty"                    # Điều khoản phạt
    CONFIDENTIALITY = "confidentiality"    # Bảo mật
    TERMINATION = "termination"            # Chấm dứt hợp đồng
    LIABILITY = "liability"                # Trách nhiệm
    FORCE_MAJEURE = "force_majeure"        # Bất khả kháng
    INTELLECTUAL_PROPERTY = "ip"           # Sở hữu trí tuệ
    DISPUTE_RESOLUTION = "dispute"         # Giải quyết tranh chấp
    PAYMENT = "payment"                    # Thanh toán
    LIMITATION = "limitation"              # Giới hạn trách nhiệm
    INDEMNIFICATION = "indemnity"         # Bồi thường


CLAUSE_PATTERNS = {
    ClauseType.PENALTY: [
        r"phạt\s+(?:vi phạm|breach)",
        r"penalty\s+(?:clause|for breach)",
        r" liquidated damages",
        r"bồi\s*thường\s+(?:thiệt\s*hại|damages)",
        r"nghĩa\s*vụ\s+(?:bồi\s*thường|đền\s*bù)"
    ],
    ClauseType.CONFIDENTIALITY: [
        r"bảo\s*mật|confidential",
        r"thông\s*tin\s+(?: bí ?mật|được\s+bảo\s*vệ)",
        r"non-disclosure",
        r"tiết\s*lộ|disclose"
    ],
    ClauseType.TERMINATION: [
        r"chấm?\s*dứt|terminate|rescind",
        r"hủy\s+(?:bỏ)?\s*hợp\s*đồng",
        r"đơn\s*phương\s+(?:chấm\s*dứt|tự\s*ý)",
        r"thanh\s*lý"
    ],
    ClauseType.LIABILITY: [
        r"trách\s*nhiệm|liability",
        r"responsible\s+for",
        r"indemnif",
        r"bồi\s*thường"
    ],
    ClauseType.FORCE_MAJEURE: [
        r"bất\s*khả\s*kháng|force\s*majeure",
        r"sự\s*kiện\s+(?:bất\s*thường|không\s*thể\s*kiểm\s*soát)",
        r"Act\s+of\s+God"
    ],
    ClauseType.DISPUTE: [
        r"tranh\s*chấp|dispute",
        r"giải\s*quyết\s+(?:bằng)?\s*(?: thương ?lượng|trọng ?tài|tòa ?án)",
        r"arbitration|mediation"
    ]
}


class ContractClauseExtractor:
    """
    Trích xuất và phân loại điều khoản từ hợp đồng
    Kết hợp pattern matching và AI generation
    """
    
    def __init__(self, holysheep_client: HolySheepClient, vector_store):
        self.client = holysheep_client
        self.vector_store = vector_store
        self.embedding_model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
    
    def _classify_clause_type(self, clause_text: str) -> List[ClauseType]:
        """Phân loại loại điều khoản dựa trên patterns"""
        matched_types = []
        text_lower = clause_text.lower()
        
        for clause_type, patterns in CLAUSE_PATTERNS.items():
            for pattern in patterns:
                if re.search(pattern, text_lower, re.IGNORECASE):
                    matched_types.append(clause_type)
                    break
        
        return matched_types if matched_types else [ClauseType.LIABILITY]  # Default
    
    def _detect_risk_level(
        self, 
        clause_text: str, 
        clause_types: List[ClauseType]
    ) -> Tuple[str, str]:
        """
        Đánh giá mức độ rủi ro của điều khoản
        
        Returns:
            (risk_level, explanation)
            risk_level: "high", "medium", "low"
        """
        risk_keywords = {
            "high": [r"100%", r"toàn\s*bộ", r"không\s*giới\s*hạn", r"unlimited"],
            "medium": [r"vượt\s*quá", r"giới\s*hạn", r"không\s*quá", r"cap"],
            "low": [r"tối\s*thiểu", r"đã\s*quy\s*định", r"theo\s*luật"]
        }
        
        risk_score = 0
        text_lower = clause_text.lower()
        
        # High risk indicators
        for pattern in risk_keywords["high"]:
            if re.search(pattern, text_lower):
                risk_score += 3
        
        # Medium risk indicators
        for pattern in risk_keywords["medium"]:
            if re.search(pattern, text_lower):
                risk_score += 1
        
        # Low risk indicators
        for pattern in risk_keywords["low"]:
            if re.search(pattern, text_lower):
                risk_score -= 1
        
        if risk_score >= 3:
            return "HIGH", "Điều khoản có mức rủi ro cao, cần luật sư rà soát kỹ"
        elif risk_score >= 1:
            return "MEDIUM", "Có một số điểm cần lưu ý"
        else:
            return "LOW", "Điều khoản tiêu chuẩn, rủi ro thấp"
    
    async def extract_clauses(
        self, 
        document: LegalDocument,
        user_query: Optional[str] = None
    ) -> List[Dict]:
        """
        Trích xuất tất cả điều khoản từ tài liệu
        
        Args:
            document: Tài liệu pháp lý cần phân tích
            user_query: Query tùy chọn để tập trung vào loại điều khoản cụ thể
            
        Returns:
            List of extracted clauses với metadata
        """
        # Bước 1: Semantic chunking
        chunks = self._semantic_chunk(document.content)
        
        results = []
        for i, chunk in enumerate(chunks):
            # Bước 2: Phân loại điều khoản
            clause_types = self._classify_clause_type(chunk["text"])
            
            # Bước 3: Đánh giá rủi ro
            risk_level, risk_explanation = self._detect_risk_level(
                chunk["text"], 
                clause_types
            )
            
            # Bước 4: Tạo embedding và lưu vào vector store
            embedding = self.client.create_embedding([chunk["text"]])[0]
            self.vector_store.add(
                documents=[chunk["text"]],
                embeddings=[embedding],
                metadatas=[{
                    "doc_id": document.doc_id,
                    "clause_types": [ct.value for ct in clause_types],
                    "chunk_index": i,
                    "risk_level": risk_level
                }],
                ids=[f"{document.doc_id}_chunk_{i}"]
            )
            
            # Bước 5: Sinh mô tả tự động bằng AI
            description = await self._generate_clause_description(
                chunk["text"], 
                clause_types
            )
            
            results.append({
                "clause_id": f"{document.doc_id}_c{i+1}",
                "text": chunk["text"][:500] + "..." if len(chunk["text"]) > 500 else chunk["text"],
                "types": [ct.value for ct in clause_types],
                "risk_level": risk_level,
                "risk_explanation": risk_explanation,
                "ai_description": description,
                "page_reference": chunk.get("page", "N/A"),
                "position": chunk.get("position", i)
            })
        
        return results
    
    def _semantic_chunk(self, text: str) -> List[Dict]:
        """
        Chia nhỏ văn bản theo ranh giới ngữ nghĩa
        Ưu tiên tách theo điều, mục, khoản
        """
        # Pattern cho các loại văn bản pháp lý Việt Nam
        patterns = [
            r"Điều\s+\d+",           # Điều 1, Điều 2...
            r"Article\s+\d+",         # Article 1, Article 2...
            r"Mục\s+[IVX\d]+",       # Mục I, Mục II...
            r"Section\s+\d+",        # Section 1...
            r"^\d+\.",               # 1., 2., 3....
            r"^Khoản\s+\d+",         # Khoản 1...
        ]
        
        chunks = []
        sentences = re.split(r'(?<=[.!?])\s+', text)
        current_chunk = ""
        current_start = 0
        
        for i, sentence in enumerate(sentences):
            current_chunk += sentence + " "
            
            # Check if this sentence starts a new section
            for pattern in patterns:
                if re.match(pattern, sentence.strip(), re.IGNORECASE):
                    if len(current_chunk.strip()) > 50:  # Minimum chunk size
                        chunks.append({
                            "text": current_chunk.strip(),
                            "position": current_start,
                            "sentence_count": len(sentences[:i])
                        })
                    current_chunk = ""
                    current_start = i
                    break
        
        # Add remaining chunk
        if current_chunk.strip():
            chunks.append({
                "text": current_chunk.strip(),
                "position": current_start,
                "sentence_count": len(sentences)
            })
        
        return chunks
    
    async def _generate_clause_description(
        self, 
        clause_text: str, 
        clause_types: List[ClauseType]
    ) -> str:
        """Sinh mô tả tự động cho điều khoản"""
        
        system_prompt = """Bạn là một luật sư chuyên nghiệp với 15 năm kinh nghiệm.
Nhiệm vụ: Tóm tắt ngắn gọn điều khoản pháp lý một cách dễ hiểu.
Yêu cầu:
- Tóm tắt trong 2-3 câu
- Giải thích ý nghĩa pháp lý
- Chỉ ra các bên liên quan và nghĩa vụ của họ
- Nếu có con số cụ thể (số tiền, %, thời hạn), HÃY GIỮ LẠI
- Ngôn ngữ: Tiếng Việt, tránh thuật ngữ quá phức tạp"""
        
        prompt = f"""Hãy tóm tắt điều khoản sau:

Loại điều khoản: {', '.join([ct.value for ct in clause_types])}

Nội dung:
{clause_text}

YÊU CẦU: 
- Trả lời bằng tiếng Việt
- Giữ nguyên các con số cụ thể nếu có
- Tối đa 3 câu"""
        
        try:
            return self.client.generate_completion(
                prompt=prompt,
                system_prompt=system_prompt,
                temperature=0.3
            )
        except Exception as e:
            return f"[Lỗi AI generation: {str(e)}]"

3. Module Tra cứu Án lệ với RAG

"""
Module tra cứu án lệ sử dụng RAG pipeline
Tìm kiếm các quyết định tương tự dựa trên ngữ nghĩa
"""

from typing import List, Dict, Optional, Tuple
from datetime import datetime
import json


@dataclass
class CaseLawQuery:
    """Cấu trúc truy vấn án lệ"""
    facts: str                      # Mô tả sự kiện
    legal_issues: List[str]          # Các vấn đề pháp lý liên quan
    jurisdiction: str = "Vietnam"    # Thẩm quyền
    court_level: Optional[str] = None # Cấp tòa
    year_range: Optional[Tuple[int, int]] = None  # Khoảng năm
    case_types: Optional[List[str]] = None  # Loại vụ án


@dataclass
class CaseLawResult:
    """Kết quả tra cứu án lệ"""
    case_id: str
    case_number: str
    court_name: str
    judgment_date: str
    summary: str
    legal_basis: List[str]
    ruling_content: str
    relevance_score: float
    similar_facts: List[str]
    key_precedents: List[str]


class CaseLawRAGRetriever:
    """
    Hệ thống tra cứu án lệ thông minh
    Sử dụng hybrid search (vector + keyword)
    """
    
    def __init__(
        self, 
        holysheep_client: HolySheepClient,
        vector_store,
        reranker_model=None
    ):
        self.client = holysheep_client
        self.vector_store = vector_store
        self.reranker = reranker_model  # Cross-encoder reranker
    
    async def search(
        self, 
        query: CaseLawQuery,
        top_k: int = 10,
        min_relevance: float = 0.7
    ) -> List[CaseLawResult]:
        """
        Tìm kiếm án lệ liên quan
        
        Args:
            query: Query object chứa mô tả sự kiện và vấn đề pháp lý
            top_k: Số lượng kết quả trả về sau reranking
            min_relevance: Ngưỡng relevance tối thiểu
            
        Returns:
            List of relevant case law results
        """
        # Bước 1: Xây dựng enhanced query
        enhanced_query = self._build_enhanced_query(query)
        
        # Bước 2: Tạo embedding cho query
        query_embedding = self.client.create_embedding([enhanced_query])[0]
        
        # Bước 3: Vector similarity search
        vector_results = self.vector_store.query(
            query_embeddings=[query_embedding],
            n_results=top_k * 3,  # Lấy nhiều hơn để rerank
            where=self._build_filter(query)
        )
        
        # Bước 4: Keyword search (backup)
        keyword_results = self._keyword_search(query)
        
        # Bước 5: Merge và deduplicate
        merged_results = self._merge_results(
            vector_results, 
            keyword_results
        )
        
        # Bước 6: Reranking (nếu có model)
        if self.reranker:
            reranked = await self._rerank(query, merged_results, top_k)
        else:
            reranked = merged_results[:top_k]
        
        # Bước 7: Post-processing và định dạng
        return [
            self._format_case_result(doc, query) 
            for doc in reranked 
            if doc.get("relevance", 0) >= min_relevance
        ]
    
    def _build_enhanced_query(self, query: CaseLawQuery) -> str:
        """Xây dựng query mở rộng với context"""
        
        query_parts = [
            f"Sự kiện: {query.facts}",
            f"Vấn đề pháp lý: {', '.join(query.legal_issues)}",
            f"Thẩm quyền: {query.jurisdiction}"
        ]
        
        if query.court_level:
            query_parts.append(f"Cấp tòa: {query.court_level}")
        
        return " | ".join(query_parts)
    
    def _build_filter(self, query: CaseLawQuery) -> Dict:
        """Xây dựng filter cho vector search"""
        
        filter_conditions = {}
        
        if query.jurisdiction:
            filter_conditions["jurisdiction"] = query.jurisdiction
        
        if query.court_level:
            filter_conditions["court_level"] = query.court_level
        
        if query.year_range:
            filter_conditions["year"] = {
                "$gte": query.year_range[0],
                "$lte": query.year_range[1]
            }
        
        if query.case_types:
            filter_conditions["case_type"] = {"$in": query.case_types}
        
        return filter_conditions if filter_conditions else None
    
    async def _rerank(
        self, 
        query: CaseLawQuery,
        results: List[Dict],
        top_k: int
    ) -> List[Dict]:
        """
        Re-ranking sử dụng Cross-Encoder
        Cải thiện độ chính xác bằng cách đánh giá lại 
        mức độ liên quan của từng kết quả
        """
        
        if not results:
            return []
        
        # Tạo query-document pairs
        query_text = f"{query.facts} | {', '.join(query.legal_issues)}"
        pairs = [
            (query_text, doc.get("content", ""))
            for doc in results
        ]
        
        # Tính relevance scores bằng cross-encoder
        # Sử dụng HolySheep để rerank nếu không có local model
        reranked_results = []
        
        for i, doc in enumerate(results):
            # Prompt cho relevance scoring
            eval_prompt = f"""Đánh giá mức độ liên quan giữa câu hỏi và văn bản.
Cho điểm từ 0.0 đến 1.0 (1.0 = hoàn toàn liên quan).

Câu hỏi: {query_text}

Văn bản: {doc.get('content', '')[:1000]}

Trả lời CHỈ bằng một con số (ví dụ: 0.85)"""
            
            try:
                score_str = self.client.generate_completion(
                    prompt=eval_prompt,
                    temperature=0.1,
                    max_tokens=10
                )
                relevance = float(score_str.strip())
            except:
                relevance = doc.get("relevance", 0.5)
            
            reranked_results.append({
                **doc,
                "relevance": relevance
            })
        
        # Sắp xếp theo relevance score
        reranked_results.sort(key=lambda x: x["relevance"], reverse=True)
        
        return reranked_results[:top_k]
    
    def _format_case_result(
        self, 
        doc: Dict, 
        query: CaseLawQuery
    ) -> CaseLawResult:
        """Định dạng kết quả thành CaseLawResult object"""
        
        # Trích xuất similar facts
        similar_facts = self._extract_similar_facts(
            doc.get("content", ""),
            query.facts
        )
        
        return CaseLawResult(
            case_id=doc.get("id", ""),
            case_number=doc.get("case_number", "N/A"),
            court_name=doc.get("court", "Tòa án"),
            judgment_date=doc.get("date", "N/A"),
            summary=doc.get("summary", ""),
            legal_basis=doc.get("legal_bases", []),
            ruling_content=doc.get("ruling", ""),
            relevance_score=doc.get("relevance", 0),
            similar_facts=similar_facts,
            key_precedents=doc.get("precedents", [])
        )
    
    def _extract_similar_facts(
        self, 
        case_text: str, 
        query_facts: str
    ) -> List[str]:
        """Trích xuất các sự kiện tương