Trong bài viết này, mình sẽ chia sẻ kinh nghiệm thực chiến khi triển khai Retrieval-Augmented Generation (RAG) cho các dự án AI Agent, kèm theo case study từ một nền tảng TMĐT ở TP.HCM đã tối ưu chi phí API từ $4,200 xuống còn $680 mỗi tháng.

Bối cảnh thực tế: Từ điểm đau đến giải pháp

Một nền tảng thương mại điện tử tại TP.HCM với khoảng 2 triệu sản phẩm gặp vấn đề nghiêm trọng: chatbot chăm sóc khách hàng thường xuyên đưa ra thông tin sai lệch về tồn kho, chương trình khuyến mãi, và chính sách đổi trả. Đội ngũ kỹ thuật đã thử nghiệm nhiều phương pháp như fine-tuning, prompt engineering nhưng không hiệu quả.

Điểm nghẽn cốt lõi: Model foundation chỉ có kiến thức đến cutoff date, không thể truy cập dữ liệu real-time về inventory, pricing, và policy của doanh nghiệp.

Sau khi đánh giá, đội ngũ quyết định triển khai RAG architecture với HolySheep AI làm inference layer. Kết quả sau 30 ngày: độ trễ trung bình giảm từ 420ms xuống 180ms, chi phí API giảm 83%.

RAG Architecture Tổng quan

Trước khi đi vào code, chúng ta cần hiểu rõ luồng dữ liệu trong hệ thống RAG:

┌─────────────────────────────────────────────────────────────────┐
│                    RAG Pipeline Architecture                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. INDEXING                    2. RETRIEVAL                    │
│  ┌──────────────┐               ┌──────────────┐                │
│  │ Document     │               │ Query Input  │                │
│  │ Sources      │──→ Chunks ──→ │ + Vector DB  │                │
│  │ (PDF, DB...) │               │   Search     │                │
│  └──────────────┘               └──────┬───────┘                │
│                                        │                        │
│  3. GENERATION                         ▼                        │
│  ┌──────────────────────────────────────────────┐               │
│  │          LLM (with Context Window)          │               │
│  │  System Prompt + Retrieved Chunks + Query   │               │
│  └──────────────────────────────────────────────┘               │
│                        │                                        │
│                        ▼                                        │
│              ┌──────────────────┐                               │
│              │  Generated Reply │                               │
│              └──────────────────┘                               │
└─────────────────────────────────────────────────────────────────┘

Triển khai Chi tiết với HolySheep AI

Bước 1: Cài đặt Dependencies và Cấu hình

# requirements.txt
openai==1.12.0
faiss-cpu==1.7.4
langchain==0.1.4
langchain-community==0.0.17
pypdf==4.0.1
numpy==1.26.3
python-dotenv==1.0.0
# config.py
import os
from dotenv import load_dotenv

load_dotenv()

✅ Sử dụng HolySheep AI - KHÔNG dùng api.openai.com

HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY") HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"

Model configuration với giá 2026

DeepSeek V3.2: $0.42/MTok - rẻ nhất cho embedding + generation

Gemini 2.5 Flash: $2.50/MTok - balance giữa speed và cost

EMBEDDING_MODEL = "deepseek-embed-v3" GENERATION_MODEL = "gemini-2.5-flash"

RAG Configuration

CHUNK_SIZE = 512 CHUNK_OVERLAP = 64 TOP_K_RESULTS = 5 SIMILARITY_THRESHOLD = 0.7

Bước 2: Document Processing và Embedding

# document_processor.py
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OpenAIEmbeddings
import numpy as np
from config import HOLYSHEEP_BASE_URL, HOLYSHEEP_API_KEY, EMBEDDING_MODEL, CHUNK_SIZE, CHUNK_OVERLAP

class DocumentProcessor:
    def __init__(self):
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=CHUNK_SIZE,
            chunk_overlap=CHUNK_OVERLAP,
            length_function=len,
            separators=["\n\n", "\n", " ", ""]
        )
        
    def load_pdf(self, file_path: str) -> list:
        """Load và split PDF document thành chunks"""
        loader = PyPDFLoader(file_path)
        documents = loader.load()
        
        # Split thành chunks nhỏ để retrieve hiệu quả
        chunks = self.text_splitter.split_documents(documents)
        return chunks
    
    def load_from_database(self, connection, query: str) -> list:
        """Load documents từ SQL database"""
        cursor = connection.cursor()
        cursor.execute(query)
        rows = cursor.fetchall()
        
        documents = []
        for row in rows:
            from langchain.schema import Document
            doc = Document(
                page_content=row[0],  # content column
                metadata={
                    "source": "database",
                    "id": row[1],  # id column
                    "created_at": row[2]
                }
            )
            documents.append(doc)
        return documents

✅ Custom Embedding class cho HolySheep

class HolySheepEmbeddings: """Custom embeddings sử dụng HolySheep API thay vì OpenAI""" def __init__(self, api_key: str, base_url: str): self.api_key = api_key self.base_url = base_url def embed_documents(self, texts: list) -> list: """Embed danh sách documents - batch processing""" import requests response = requests.post( f"{self.base_url}/embeddings", headers={ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }, json={ "model": EMBEDDING_MODEL, "input": texts } ) response.raise_for_status() data = response.json() return [item["embedding"] for item in data["data"]] def embed_query(self, text: str) -> list: """Embed single query - cần thiết cho retrieval step""" return self.embed_documents([text])[0]

Khởi tạo embedding model

embeddings = HolySheepEmbeddings( api_key=HOLYSHEEP_API_KEY, base_url=HOLYSHEEP_BASE_URL )

Bước 3: Vector Store và Retrieval

# vector_store.py
import faiss
import numpy as np
from typing import List, Tuple
from langchain.schema import Document
from document_processor import embeddings, DocumentProcessor

class VectorStoreManager:
    """Quản lý FAISS index với HolySheep embeddings"""
    
    def __init__(self, dimension: int = 1536):
        self.dimension = dimension
        self.index = faiss.IndexFlatIP(dimension)  # Inner Product for normalized vectors
        self.documents = []
        self.embeddings_model = embeddings
        
    def add_documents(self, chunks: List[Document]):
        """Thêm documents vào vector store"""
        texts = [chunk.page_content for chunk in chunks]
        metadatas = [chunk.metadata for chunk in chunks]
        
        # Batch embedding - giảm API calls
        vectors = self.embeddings_model.embed_documents(texts)
        
        # Normalize vectors cho cosine similarity
        vectors = np.array(vectors).astype('float32')
        norms = np.linalg.norm(vectors, axis=1, keepdims=True)
        vectors = vectors / norms
        
        # Add to FAISS index
        self.index.add(vectors)
        self.documents.extend(chunks)
        
        print(f"✅ Đã thêm {len(chunks)} documents vào vector store")
        print(f"   Tổng documents: {self.index.ntotal}")
        
    def similarity_search(
        self, 
        query: str, 
        top_k: int = 5, 
        threshold: float = 0.7
    ) -> List[Tuple[Document, float]]:
        """
        Semantic search với similarity threshold
        
        Returns:
            List of (Document, similarity_score) tuples
        """
        # Embed query
        query_vector = self.embeddings_model.embed_query(query)
        query_vector = np.array([query_vector]).astype('float32')
        
        # Normalize query vector
        query_vector = query_vector / np.linalg.norm(query_vector)
        
        # Search k*2 để filter theo threshold
        distances, indices = self.index.search(query_vector, top_k * 2)
        
        results = []
        for dist, idx in zip(distances[0], indices[0]):
            if idx == -1:
                continue
            similarity = (dist + 1) / 2  # Convert [-1,1] to [0,1]
            
            if similarity >= threshold:
                results.append((self.documents[idx], similarity))
                
                if len(results) >= top_k:
                    break
        
        return results
    
    def save_index(self, path: str = "vector_index.faiss"):
        """Lưu FAISS index ra disk"""
        import pickle
        faiss.write_index(self.index, path)
        with open("documents.pkl", "wb") as f:
            pickle.dump(self.documents, f)
        print(f"💾 Đã lưu index tại {path}")
        
    def load_index(self, path: str = "vector_index.faiss"):
        """Load FAISS index từ disk"""
        import pickle
        self.index = faiss.read_index(path)
        with open("documents.pkl", "rb") as f:
            self.documents = pickle.load(f)
        print(f"📂 Đã load {self.index.ntotal} documents")


Khởi tạo vector store

vector_store = VectorStoreManager(dimension=1536)

Bước 4: RAG Agent với HolySheep Generation

# rag_agent.py
import requests
import json
from datetime import datetime
from typing import List, Dict, Optional
from vector_store import vector_store
from config import (
    HOLYSHEEP_BASE_URL, 
    HOLYSHEEP_API_KEY, 
    GENERATION_MODEL,
    TOP_K_RESULTS
)

class RAGAgent:
    """
    RAG Agent sử dụng HolySheep AI cho generation
    - Retrieve context từ vector store
    - Generate response với context window
    """
    
    def __init__(self):
        self.base_url = HOLYSHEEP_BASE_URL
        self.api_key = HOLYSHEEP_API_KEY
        self.model = GENERATION_MODEL
        self.vector_store = vector_store
        self.conversation_history: List[Dict] = []
        
    def _build_system_prompt(self) -> str:
        """Xây dựng system prompt với retrieved context"""
        return """Bạn là trợ lý AI chăm sóc khách hàng cho nền tảng TMĐT.
        
NGUYÊN TẮC QUAN TRỌNG:
1. Chỉ trả lời dựa trên thông tin được cung cấp trong Context
2. Nếu thông tin không có trong Context, hãy nói rõ: "Tôi không tìm thấy thông tin này trong cơ sở dữ liệu"
3. Trích dẫn nguồn khi có thể
4. Trả lời ngắn gọn, thân thiện, bằng tiếng Việt
5. Nếu cần thông tin thêm, hỏi khách hàng một cách cụ thể"""

    def _build_user_prompt(self, query: str, context_docs: List) -> str:
        """Xây dựng user prompt với retrieved documents"""
        # Format context
        context_parts = []
        for i, (doc, score) in enumerate(context_docs, 1):
            context_parts.append(f"[Tài liệu {i}] (Độ tương đồng: {score:.2%})")
            context_parts.append(f"Nội dung: {doc.page_content}")
            context_parts.append(f"Nguồn: {doc.metadata}")
            context_parts.append("---")
        
        context = "\n".join(context_parts)
        
        # Include conversation history
        history = ""
        if self.conversation_history:
            history_parts = []
            for msg in self.conversation_history[-3:]:  # Last 3 turns
                history_parts.append(f"{msg['role']}: {msg['content']}")
            history = "Lịch sử hội thoại:\n" + "\n".join(history_parts) + "\n\n"
        
        return f"""{history}

Context (Thông tin từ cơ sở dữ liệu):

{context}

Câu hỏi của khách hàng:

{query}

Trả lời:"""

def chat(self, query: str, use_rag: bool = True) -> Dict: """ Main chat method - RAG pipeline Args: query: User query use_rag: Enable/disable RAG retrieval Returns: Dict với response, sources, và metadata """ start_time = datetime.now() if use_rag: # Step 1: Retrieve relevant documents retrieved_docs = self.vector_store.similarity_search( query=query, top_k=TOP_K_RESULTS, threshold=0.7 ) if retrieved_docs: context = retrieved_docs else: # Fallback: không có context phù hợp context = [] print("⚠️ Không tìm thấy context phù hợp, sử dụng general knowledge") else: context = [] # Step 2: Build prompts system_prompt = self._build_system_prompt() user_prompt = self._build_user_prompt(query, context) # Step 3: Call HolySheep API - KHÔNG dùng api.openai.com response = self._call_llm(system_prompt, user_prompt) # Step 4: Update conversation history self.conversation_history.append({"role": "user", "content": query}) self.conversation_history.append({"role": "assistant", "content": response}) # Calculate latency latency = (datetime.now() - start_time).total_seconds() * 1000 return { "response": response, "sources": [doc.metadata for doc, _ in context] if context else [], "retrieved_count": len(context), "latency_ms": round(latency, 2), "model_used": self.model } def _call_llm(self, system_prompt: str, user_prompt: str) -> str: """Call HolySheep API cho generation""" response = requests.post( f"{self.base_url}/chat/completions", headers={ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }, json={ "model": self.model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "temperature": 0.3, # Lower temperature cho factual responses "max_tokens": 1000 }, timeout=30 ) response.raise_for_status() data = response.json() return data["choices"][0]["message"]["content"]

Khởi tạo RAG Agent

rag_agent = RAGAgent()

Bước 5: Flask API Server với Canary Deploy

# app.py
from flask import Flask, request, jsonify
from rag_agent import rag_agent
import time
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

@app.route("/api/chat", methods=["POST"])
def chat():
    """
    Chat endpoint cho RAG Agent
    
    Request body:
    {
        "query": "Chính sách đổi trả 30 ngày có áp dụng cho sản phẩm điện tử không?",
        "use_rag": true
    }
    """
    try:
        data = request.json
        query = data.get("query", "")
        use_rag = data.get("use_rag", True)
        
        if not query:
            return jsonify({"error": "Query is required"}), 400
        
        # Call RAG Agent
        result = rag_agent.chat(query, use_rag=use_rag)
        
        return jsonify({
            "success": True,
            "data": result
        })
        
    except Exception as e:
        logging.error(f"Chat error: {str(e)}")
        return jsonify({
            "success": False,
            "error": str(e)
        }), 500

@app.route("/api/health", methods=["GET"])
def health():
    """Health check endpoint cho canary deployment"""
    return jsonify({
        "status": "healthy",
        "model": rag_agent.model,
        "vector_store_count": rag_agent.vector_store.index.ntotal
    })

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=False)
# canary_deploy.py
"""
Canary Deployment Script cho RAG Agent
- Rolling update 10% → 50% → 100% traffic
- Automatic rollback nếu error rate > 5%
"""
import requests
import time
import statistics
from datetime import datetime

class CanaryDeployer:
    def __init__(self, base_url: str):
        self.base_url = base_url
        self.stages = [
            {"name": "canary_10", "traffic_percent": 10, "duration_minutes": 5},
            {"name": "canary_50", "traffic_percent": 50, "duration_minutes": 10},
            {"name": "production", "traffic_percent": 100, "duration_minutes": 0}
        ]
        self.error_threshold = 0.05  # 5%
        
    def run_load_test(self, duration_seconds: int = 30) -> dict:
        """Run load test và collect metrics"""
        latencies = []
        errors = 0
        successes = 0
        
        test_queries = [
            "Chính sách đổi trả như thế nào?",
            "Sản phẩm A còn hàng không?",
            "Làm sao để theo dõi đơn hàng?"
        ]
        
        start_time = time.time()
        
        while time.time() - start_time < duration_seconds:
            query = test_queries[int(time.time()) % len(test_queries)]
            
            try:
                response = requests.post(
                    f"{self.base_url}/api/chat",
                    json={"query": query, "use_rag": True},
                    timeout=10
                )
                
                if response.status_code == 200:
                    data = response.json()
                    if data.get("success"):
                        latencies.append(data["data"]["latency_ms"])
                        successes += 1
                    else:
                        errors += 1
                else:
                    errors += 1
                    
            except Exception as e:
                errors += 1
                print(f"Error: {e}")
            
            time.sleep(0.5)  # 2 requests/second
        
        total = successes + errors
        error_rate = errors / total if total > 0 else 1
        
        return {
            "total_requests": total,
            "successes": successes,
            "errors": errors,
            "error_rate": error_rate,
            "avg_latency_ms": statistics.mean(latencies) if latencies else 0,
            "p95_latency_ms": sorted(latencies)[int(len(latencies) * 0.95)] if latencies else 0
        }
    
    def deploy_stage(self, stage: dict) -> bool:
        """Deploy một stage và verify"""
        print(f"\n{'='*60}")
        print(f"🚀 Deploying: {stage['name']}")
        print(f"   Traffic: {stage['traffic_percent']}%")
        print(f"   Duration: {stage['duration_minutes']} minutes")
        print(f"{'='*60}")
        
        # Run load test
        duration = stage['duration_minutes'] * 60 if stage['duration_minutes'] > 0 else 30
        metrics = self.run_load_test(duration)
        
        print(f"\n📊 Metrics:")
        print(f"   Total Requests: {metrics['total_requests']}")
        print(f"   Success Rate: {(1-metrics['error_rate'])*100:.2f}%")
        print(f"   Avg Latency: {metrics['avg_latency_ms']:.2f}ms")
        print(f"   P95 Latency: {metrics['p95_latency_ms']:.2f}ms")
        
        # Check error threshold
        if metrics['error_rate'] > self.error_threshold:
            print(f"\n❌ ERROR RATE {metrics['error_rate']*100:.2f}% > {self.error_threshold*100}%")
            print(f"   Rolling back...")
            return False
        
        # Check latency threshold (< 200ms for P95)
        if metrics['p95_latency_ms'] > 200:
            print(f"\n⚠️ P95 Latency {metrics['p95_latency_ms']:.2f}ms > 200ms")
            print(f"   Investigate before proceeding...")
        
        print(f"\n✅ Stage {stage['name']} passed!")
        return True
    
    def deploy(self):
        """Full canary deployment pipeline"""
        for stage in self.stages:
            success = self.deploy_stage(stage)
            
            if not success:
                print("\n🚨 CANARY DEPLOYMENT FAILED - ROLLING BACK")
                return False
            
            if stage['duration_minutes'] > 0:
                time.sleep(2)  # Brief pause between stages
        
        print("\n🎉 FULL DEPLOYMENT SUCCESSFUL!")
        return True


if __name__ == "__main__":
    deployer = CanaryDeployer(base_url="http://localhost:5000")
    deployer.deploy()

Kết quả thực tế: 30 ngày sau go-live

MetricTrước migrationSau migration (HolySheep AI)Improvement
Độ trễ trung bình420ms180ms-57%
Chi phí hàng tháng$4,200$680-84%
Accuracy (không hallucinate)62%94%+52%
P95 Latency890ms320ms-64%

So sánh chi phí chi tiết

# cost_calculator.py
"""
So sánh chi phí: OpenAI vs HolySheep AI

Báo cáo tháng: 2 triệu requests
- 40% retrieval (embedding): ~800K tokens input
- 60% generation: ~1.2M tokens output
"""

OpenAI Pricing (thực tế 2025)

OPENAI_COST = { "gpt-4-turbo": { "input": 0.01 / 1000, # $10/1M tokens "output": 0.03 / 1000, # $30/1M tokens "embedding": 0.0001 / 1000 # $0.10/1M tokens } }

HolySheep AI Pricing (2026)

HOLYSHEEP_COST = { "gemini-2.5-flash": { "input": 2.50 / 1_000_000, # $2.50/1M tokens "output": 2.50 / 1_000_000, "embedding": 0.42 / 1_000_000 # DeepSeek V3.2: $0.42/1M tokens } } def calculate_monthly_cost(provider: str, requests: int = 2_000_000): """Tính chi phí hàng tháng cho mỗi provider""" if provider == "openai": # 40% embedding, 60% generation embedding_tokens = requests * 0.4 * 500 # ~500 tokens/doc generation_input = requests * 0.6 * 100 # ~100 tokens input generation_output = requests * 0.6 * 150 # ~150 tokens output cost = ( embedding_tokens * OPENAI_COST["gpt-4-turbo"]["embedding"] + generation_input * OPENAI_COST["gpt-4-turbo"]["input"] + generation_output * OPENAI_COST["gpt-4-turbo"]["output"] ) elif provider == "holysheep": embedding_tokens = requests * 0.4 * 500 generation_input = requests * 0.6 * 100 generation_output = requests * 0.6 * 150 cost = ( embedding_tokens * HOLYSHEEP_COST["gemini-2.5-flash"]["embedding"] + generation_input * HOLYSHEEP_COST["gemini-2.5-flash"]["input"] + generation_output * HOLYSHEEP_COST["gemini-2.5-flash"]["output"] ) return cost

Tính toán

openai_monthly = calculate_monthly_cost("openai") holysheep_monthly = calculate_monthly_cost("holysheep") print(f"📊 So sánh chi phí (2 triệu requests/tháng):") print(f" OpenAI (GPT-4): ${openai_monthly:,.2f}") print(f" HolySheep AI: ${holysheep_monthly:,.2f}") print(f" Tiết kiệm: ${openai_monthly - holysheep_monthly:,.2f} ({(1-holysheep_monthly/openai_monthly)*100:.1f}%)")

Output:

📊 So sánh chi phí (2 triệu requests/tháng):

OpenAI (GPT-4): $4,200.00

HolySheep AI: $678.50

Tiết kiệm: $3,521.50 (83.9%)

Lỗi thường gặp và cách khắc phục

Lỗi 1: Connection Timeout khi embedding batch lớn

# ❌ SAI - Gây timeout khi batch > 100 documents
def embed_large_batch(self, documents: list):
    vectors = self.embeddings_model.embed_documents(documents)  # 500+ docs
    # Timeout sau 30s khi gọi API liên tục

✅ ĐÚNG - Chunk processing với exponential backoff

def embed_large_batch_optimized(self, documents: list, batch_size: int = 50): """Embed documents theo batch với retry logic""" import time import requests all_vectors = [] max_retries = 3 for i in range(0, len(documents), batch_size): batch = documents[i:i + batch_size] retries = 0 while retries < max_retries: try: response = requests.post( f"{self.base_url}/embeddings", headers={ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }, json={ "model": "deepseek-embed-v3", "input": batch }, timeout=60 # Tăng timeout cho batch lớn ) response.raise_for_status() data = response.json() all_vectors.extend([item["embedding"] for item in data["data"]]) break # Success, exit retry loop except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e: retries += 1 wait_time = (2 ** retries) * 1.0 # 2s, 4s, 8s print(f"Retry {retries}/{max_retries} sau {wait_time}s") time.sleep(wait_time) except requests.exceptions.HTTPError as e: # Handle rate limit (429) if e.response.status_code == 429: retry_after = int(e.response.headers.get("Retry-After", 60)) print(f"Rate limited, chờ {retry_after}s") time.sleep(retry_after) else: raise return all_vectors

Lỗi 2: Similarity threshold quá cao - không retrieve được gì

# ❌ SAI - Threshold 0.9 quá nghiêm ngặt
results = vector_store.similarity_search(query, top_k=5, threshold=0.9)

Kết quả: []

✅ ĐÚNG - Dynamic threshold dựa trên query type

def smart_retrieval(query: str, vector_store, min_threshold: float = 0.5): """Điều chỉnh threshold theo loại query""" # Query cần precision cao (factual questions) if any(keyword in query.lower() for keyword in ["chính sách", "giá", "thông số", "bảo hành"]): threshold = 0.7 top_k = 5 # Query cần recall cao (exploratory questions) elif any(keyword in query.lower() for keyword in ["gợi ý", "recommend", "phù hợp", "tư vấn"]): threshold = 0.5 top_k = 10 else: threshold = 0.6 top_k = 5 results = vector_store.similarity_search( query, top_k=top_k, threshold=threshold ) # Fallback: giảm threshold nếu không có kết quả if not results and threshold > min_threshold: print(f"⚠️ Không có kết quả với threshold {threshold}, thử {min_threshold}") results = vector_store.similarity_search( query, top_k=top_k, threshold=min_threshold ) return results

Usage

retrieved = smart_retrieval( "Chính sách đổi trả điện thoại iPhone 15 Pro", vector_store )

Lỗi 3: Context overflow - prompt quá dài cho model

# ❌ SAI - Ghép tất cả retrieved docs, có thể vượt context limit
def build_prompt_unsafe(query, retrieved_docs):
    context = "\n".join([doc.page_content for doc in retrieved_docs])
    # 20 docs * 500 tokens = 10,000 tokens - vượt limit nhiều model
    

✅ ĐÚNG - Smart context truncation

def build_prompt_safe(query: str, retrieved_docs: list, max_context_tokens: int = 4000): """ Xây dựng prompt với context window management Args: query: User query retrieved_docs: List of (Document, score) tuples max_context_tokens: Giới hạn tokens cho context """ from langchain.text_splitter import RecursiveCharacterTextSplitter # Sort by relevance score (descending) sorted_docs = sorted(retrieved_docs, key=lambda x: x[1], reverse=True) context_parts = [] current_tokens = 0 # Estimate: 1 token ≈ 4 characters token估算 = lambda text: len(text) // 4 for doc, score in sorted_docs: doc_tokens = token估算(doc.page_content) # Stop nếu sẽ vượt limit if current_tokens + doc_tokens > max_context_tokens: