Hướng dẫn triển khai Contextual Retrieval của Anthropic với Python

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

Vì sao chatbot RAG hay trả lời trật lất?

Bạn đã bao giờ hỏi chatbot nội bộ một đằng, nhưng nó lại trả lời một nẻo chưa? Vấn đề hiếm khi nằm ở mô hình AI kém thông minh. Thủ phạm chính thường là bước cắt nhỏ tài liệu (chunking).

Hãy tưởng tượng bạn cầm báo cáo tài chính dày 50 trang. Bạn lấy kéo cắt vụn thành từng mẩu 200 chữ rồi thả vào hộp. Khi sếp hỏi: "Doanh thu mảng bán lẻ quý này tăng bao nhiêu?", bạn bốc trúng mẩu giấy ghi vỏn vẹn: "Bộ phận này đạt mức tăng trưởng 18% so với cùng kỳ năm trước nhờ mở rộng đại lý."

Mẩu giấy không hề có tên công ty. Không có năm tài chính. Cũng chẳng ai biết "bộ phận này" là bán lẻ hay bán sỉ. Hệ thống RAG truyền thống hoạt động y hệt như vậy. Mất ngữ cảnh gốc, LLM buộc phải đoán mò hoặc lấy râu ông nọ cắm cằm bà kia.

Contextual Retrieval xử lý bài toán này thế nào?

Tháng 9/2024, Anthropic công bố kỹ thuật Contextual Retrieval để xử lý tận gốc vấn đề này. Ý tưởng rất trực quan: không bao giờ lưu một đoạn văn trơ trọi.

Trước khi đẩy dữ liệu vào vector database, ta dùng một mô hình nhỏ như Claude 3 Haiku đọc toàn bộ tài liệu gốc. Sau đó, mô hình tự tạo một đoạn giải thích ngắn (khoảng 50 – 100 từ) gắn vào đầu mỗi chunk.

Nhờ vậy, mẩu giấy cụt ngủn ở ví dụ trước sẽ trở thành:

"Đoạn trích nằm trong Báo cáo tài chính Q3/2024 của Công ty Cổ phần Bán lẻ FPT, mục kết quả kinh doanh chuỗi FPT Shop: Bộ phận này đạt mức tăng trưởng 18% so với cùng kỳ năm trước…"

Đột nhiên mọi thứ sáng tỏ. Cả thuật toán tìm kiếm vector (Embedding) lẫn tìm kiếm từ khóa (BM25) đều dễ dàng quét trúng đoạn này. Theo benchmark của Anthropic, kết hợp Contextual Embeddings và BM25 giúp giảm 49% tỷ lệ lấy sai tài liệu. Khi bổ sung thêm bước Reranking (như Cohere Rerank), tỷ lệ lỗi giảm tới 67%.

Thực hành: Triển khai Contextual Retrieval với Python

Sau khi thử nghiệm trên hệ thống tài liệu hợp đồng nội bộ, độ chính xác của khâu retrieval tăng rõ rệt. Dưới đây là code mẫu hoàn chỉnh sử dụng Anthropic SDK và ChromaDB.

Bước 1: Cài đặt môi trường

pip install anthropic chromadb

Bước 2: Tạo hàm sinh ngữ cảnh kèm Prompt Caching

Nếu tài liệu dài hàng chục trang mà chunk nào cũng gửi lại toàn bộ văn bản, chi phí API sẽ đội lên rất nhanh. Bí quyết ở đây là Prompt Caching. Ta đánh dấu tài liệu gốc để Anthropic lưu vào cache, giảm 90% chi phí đọc lại cho các chunk tiếp theo.

import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))

def generate_context_prefix(document_text: str, chunk_text: str) -> str:
    """
    Dùng Claude 3 Haiku tạo 1-2 câu ngữ cảnh tóm lược vị trí của chunk.
    Tận dụng Prompt Caching với cache_control để tối ưu chi phí và độ trễ.
    """
    response = client.messages.create(
        model="claude-3-haiku-20240307",
        max_tokens=150,
        temperature=0.0,
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": f"<document>\n{document_text}\n</document>",
                        # Đánh dấu cache tài liệu gốc để tái sử dụng qua các chunk
                        "cache_control": {"type": "ephemeral"}
                    },
                    {
                        "type": "text",
                        "text": (
                            f"Dưới đây là một đoạn trích từ tài liệu:\n<chunk>\n{chunk_text}\n</chunk>\n\n"
                            "Hãy cung cấp ngữ cảnh ngắn gọn (1-2 câu) nêu rõ đoạn này thuộc phần nào "
                            "và đề cập chủ thể gì. Chỉ trả về phần ngữ cảnh, không thêm lời mở đầu."
                        )
                    }
                ]
            }
        ]
    )
    return response.content[0].text.strip()

Bước 3: Ghép tiền tố ngữ cảnh và nạp vào ChromaDB

Sau khi có prefix, bạn nối trực tiếp vào trước chunk gốc rồi mới tạo vector embedding.

import chromadb

chroma_client = chromadb.Client()
collection = chroma_client.create_collection(name="kb_alpha")

document = """
CÔNG TY CỔ PHẦN CÔNG NGHỆ ALPHA - BÁO CÁO THƯỜNG NIÊN 2024
Phần 1: Tổng quan kinh doanh
Công ty Alpha tập trung phát triển 2 mảng chính: Điện toán đám mây và Giải pháp Trí tuệ nhân tạo (AI).
Phần 2: Kết quả mảng AI
Mảng này ghi nhận mức tăng trưởng doanh thu 45% trong năm 2024, đạt mốc 120 tỷ đồng.
Phần 3: Kế hoạch nhân sự năm 2025
Dự kiến tuyển thêm 50 kỹ sư AI tại chi nhánh Đà Nẵng.
"""

# 2 chunk được trích từ tài liệu gốc
chunks = [
    "Mảng này ghi nhận mức tăng trưởng doanh thu 45% trong năm 2024, đạt mốc 120 tỷ đồng.",
    "Dự kiến tuyển thêm 50 kỹ sư AI tại chi nhánh Đà Nẵng."
]

for i, chunk in enumerate(chunks):
    prefix = generate_context_prefix(document_text=document, chunk_text=chunk)
    contextual_chunk = f"{prefix}\n\n{chunk}"
    
    print(f"=== CHUNK {i+1} SAU KHI GẮN NGỮ CẢNH ===")
    print(contextual_chunk)
    print("-" * 40)
    
    collection.add(
        documents=[contextual_chunk],
        metadatas=[{"original_text": chunk, "source": "Bao_cao_Alpha_2024.pdf"}],
        ids=[f"chunk_{i}"]
    )

Bước 4: Kiểm tra kết quả truy vấn

Thử nghiệm ngay với một câu hỏi thực tế:

query = "Tăng trưởng doanh thu mảng AI của Alpha đạt bao nhiêu?"
results = collection.query(query_texts=[query], n_results=1)

print("Chunk khớp nhất tìm được:")
print(results["documents"][0][0])

Nếu không có prefix, câu trích gốc chỉ chứa từ "mảng này". Bộ lọc vector rất dễ bỏ sót vì thiếu hẳn hai từ khóa sống còn: "AI" và "Alpha".

Khi nào nên áp dụng?

Điểm cộng lớn của giải pháp này là không yêu cầu đập đi xây lại hệ thống. Bạn chỉ cần chèn thêm một bước enrich dữ liệu vào pipeline ingestion.

  • Chi phí thực tế: Nhờ Prompt Caching của Anthropic, việc xử lý 1 triệu token đầu vào bằng Claude 3 Haiku chỉ tốn khoảng 0.30 USD (chưa đến 8.000 VNĐ). Khoản chi này quá nhỏ so với công sức debug lỗi ảo giác của chatbot.
  • Nên dùng khi nào: Đặc biệt hiệu quả với hợp đồng kinh tế, tài liệu API, hồ sơ y tế và báo cáo tài chính. Đây là những dạng văn bản chứa đầy đại từ chỉ định mơ hồ như "bên A", "điều khoản này", "nó".
  • Trường hợp chưa cần: Nếu kho dữ liệu gồm các mục Q&A ngắn, định nghĩa FAQ độc lập hoặc bài viết wiki phân đoạn rõ ràng từng ý, chunking truyền thống vẫn đáp ứng tốt.
Share: