Xây dựng hệ thống Graph RAG gọn nhẹ với LightRAG và Python

AI tutorial - IT technology blog
AI tutorial - IT technology blog

Vấn đề thực tế: Khi Vector Search bỏ lỡ bức tranh tổng thể

Vector search tiêu chuẩn xử lý các truy vấn đơn lẻ rất tốt. Khi bạn hỏi định nghĩa của một mã lỗi, nó trả về đúng đoạn trích chỉ trong chưa đầy 50 mili giây. Nhưng nó lại gặp khó khăn khi câu hỏi trải rộng trên nhiều hệ thống và dịch vụ.

Giả sử bạn hỏi bot tài liệu nội bộ: “Bản cập nhật auth Q3 ảnh hưởng thế nào đến các webhook handler của Stripe?” Một pipeline vector tiêu chuẩn sẽ embed truy vấn của bạn rồi tìm kiếm các chunk văn bản lân cận. Vấn đề là gì? Câu trả lời không nằm trong một file duy nhất. Nó nằm rải rác từ các thảo luận PR, tài liệu RFC kiến trúc cho đến các worker handler phía hạ nguồn. Độ tương đồng Cosine (Cosine similarity) lấy về ba đoạn văn rời rạc, bỏ sót các dependency chung và buộc LLM của bạn phải tự đoán các mối liên kết còn thiếu.

Nguyên nhân gốc rễ: Vì sao Flat Embedding bỏ qua các mối quan hệ hệ thống

Hầu hết các hệ thống RAG đều cắt tài liệu đầu vào thành các chunk cố định 500 token và embed từng chunk một cách độc lập. Điều này tạo ra hai điểm mù rõ rệt:

  • Mất cấu trúc liên kết thực thể (Entity topology): Việc chia nhỏ chunk cắt đứt các liên kết rõ ràng giữa các thành phần. Vector có thể cho bạn biết “JWT” và “RS256” xuất hiện trong cùng một đoạn văn. Nhưng nó không thể truy vết một cách tin cậy việc chữ ký không hợp lệ trong AuthService ảnh hưởng dây chuyền đến BillingWorker cách đó 3 bước (hop) như thế nào.
  • Điểm mù với các câu hỏi bao quát toàn bộ corpus: Flat vector rất xuất sắc với các truy vấn hẹp. Nhưng chúng lại vấp ngã trước các câu hỏi tổng hợp như “Ba nguyên nhân gây timeout phổ biến nhất trên toàn bộ 14 microservice của chúng ta là gì?” Không có chunk đơn lẻ nào chứa được cái nhìn tổng quan đó.

So sánh kiến trúc: Vector RAG vs. GraphRAG vs. LightRAG

Knowledge graph giải quyết các khoảng trống hiển thị này bằng cách ánh xạ trực tiếp các mối quan hệ vào đồ thị thực thể. Dưới đây là sự khác biệt thực tế giữa ba kiến trúc truy xuất chính:

Kiến trúc Duyệt mối quan hệ (Relationship Traversal) Chi phí Indexing (100k Token) Cập nhật gia tăng (Incremental Updates)
Vector RAG tiêu chuẩn Không (Các chunk cô lập) ~$0.002 (Chỉ embedding) Append tức thì
Microsoft GraphRAG Phân cấp (Cộng đồng Leiden) ~$8.00–$25.00+ (Nhiều lượt gọi LLM chuyên sâu) Yêu cầu phân cụm lại toàn bộ phân cấp đồ thị
LightRAG Hai cấp độ (Đồ thị thực thể + chủ đề) ~$0.15–$0.40 (Trích xuất trong một lượt) Append native ở cấp tài liệu

Microsoft GraphRAG tạo ra các bản tóm tắt cộng đồng phân cấp, toàn diện trên toàn bộ tập dữ liệu. Độ sâu này rất phù hợp cho phân tích kinh tế vĩ mô, nhưng việc chạy phân cụm đệ quy trên một wiki kỹ thuật 500 trang có thể tiêu tốn hàng triệu token chỉ trong một lần build. Đáng thất vọng hơn, việc thêm năm pull request mới thường đồng nghĩa với việc phải chạy lại bước phân cụm tốn kém đó từ đầu.

LightRAG chọn hướng tiếp cận tinh gọn hơn. Nó trích xuất các thực thể và mối quan hệ chính chỉ trong một lượt duy nhất, sau đó chia quá trình truy xuất thành hai tầng: tra cứu thực thể mức thấp và truy vấn chủ đề mức cao. Bạn sẽ nhận được các câu trả lời thấu hiểu mối quan hệ ngữ cảnh mà không làm cạn kiệt ngân sách API.

Triển khai: Xây dựng pipeline LightRAG với Python

Bạn có thể xây dựng một prototype Graph RAG hoàn chỉnh với Python và một endpoint tương thích với OpenAI. Hãy làm theo các bước sau để cấu hình lưu trữ, index dữ liệu quan hệ và kiểm thử các truy vấn hai cấp độ.

Bước 1: Cài đặt các thư viện phụ thuộc

Tạo một môi trường ảo riêng biệt và cài đặt package lightrag-hku cùng với OpenAI SDK chính thức:

python3 -m venv venv
source venv/bin/activate
pip install lightrag-hku openai

Bước 2: Khởi tạo Knowledge Graph Engine

Lưu đoạn script sau thành graph_rag.py. Chúng ta cấu hình LightRAG với một async LLM wrapper để trích xuất mối quan hệ và một hàm embedding tiêu chuẩn để đối sánh node.

import os
import asyncio
from lightrag import LightRAG, QueryParam
from lightrag.llm import openai_complete_if_cache, openai_embedding
from lightrag.utils import EmbeddingFunc

# Thiết lập API key của bạn
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "sk-your-key-here")

WORKING_DIR = "./lightrag_storage"
os.makedirs(WORKING_DIR, exist_ok=True)

async def llm_model_func(prompt, system_prompt=None, history_messages=[], **kwargs):
    return await openai_complete_if_cache(
        "gpt-4o-mini",
        prompt,
        system_prompt=system_prompt,
        history_messages=history_messages,
        **kwargs
    )

async def embedding_func(texts: list[str]):
    return await openai_embedding(
        texts,
        model="text-embedding-3-small"
    )

rag = LightRAG(
    working_dir=WORKING_DIR,
    llm_model_func=llm_model_func,
    embedding_func=EmbeddingFunc(
        embedding_dim=1536,
        max_token_size=8192,
        func=embedding_func
    )
)

Bước 3: Index văn bản liên kết

Tiếp theo, đưa tài liệu kiến trúc có tính phụ thuộc lẫn nhau vào engine. LightRAG sẽ trích xuất các thực thể như AuthService và PaymentGateway, suy luận mối quan hệ giữa chúng và lưu trữ đồ thị cục bộ.

async def index_docs():
    sample_doc = """
    AuthService xử lý các token OAuth2 và xác thực session. Trong Q3, AuthService đã được cập nhật
    để phát hành JWT ngắn hạn với chữ ký bất đối xứng RS256.
    
    PaymentGateway nhận các sự kiện webhook từ Stripe. Để xác minh nguồn gốc sự kiện, PaymentGateway
    xác thực danh tính người dùng thông qua public key của AuthService trước khi gửi các sự kiện thanh toán.
    
    BillingService lắng nghe các sự kiện từ PaymentGateway. Nếu AuthService từ chối một session,
    BillingService sẽ gắn cờ giao dịch để xem xét gian lận thủ công.
    """
    
    print("Đang index tài liệu và trích xuất các node đồ thị...")
    await rag.ainsert(sample_doc)
    print("Hoàn tất trích xuất đồ thị.")

Bước 4: Truy vấn trên ngữ cảnh Local và Global

LightRAG cung cấp bốn chế độ truy vấn riêng biệt phù hợp với từng loại câu hỏi khác nhau:

  • local: Duyệt qua các liên kết 1 bước (1-hop) trực tiếp xung quanh các thực thể cụ thể (ví dụ: “AuthService sử dụng thuật toán ký nào?”).
  • global: Quét các cụm quan hệ rộng hơn trên toàn bộ corpus (ví dụ: “Phác thảo quy trình fallback khi xác thực thất bại trong quá trình xử lý thanh toán.”).
  • hybrid: Kết hợp duyệt đồ thị thực thể với truy xuất vector tiêu chuẩn để có độ bao phủ cân bằng.
  • naive: Chạy tìm kiếm vector dựa trên chunk cơ bản để so sánh hiệu năng A/B trực tiếp.
async def query_pipeline():
    question = "Nếu AuthService từ chối một session trong quá trình thanh toán qua Stripe, điều gì sẽ xảy ra trên tất cả các dịch vụ hạ nguồn?"
    
    print("\n--- Đang truy vấn với chế độ Hybrid ---")
    response = await rag.aquery(
        question,
        param=QueryParam(mode="hybrid")
    )
    print(response)

async def main():
    await index_docs()
    await query_pipeline()

if __name__ == "__main__":
    asyncio.run(main())

Bước 5: Chạy và kiểm tra kết quả

Chạy script từ terminal của bạn:

python graph_rag.py

Trong lần chạy đầu tiên, LightRAG sẽ ghi dữ liệu đồ thị vào kv_store_*.json và graph_chunk_entity_relation.graphml bên trong thư mục làm việc đã chỉ định của bạn.

Các lưu ý khi triển khai Production

Việc chuyển từ prototype cục bộ sang môi trường production đòi hỏi phải cân nhắc một số đánh đổi về hạ tầng:

  • Giới hạn lưu trữ đồ thị: Mặc định LightRAG sử dụng đồ thị NetworkX trong bộ nhớ (in-memory) và lưu dưới dạng JSON. Khi tập dữ liệu của bạn vượt quá 50.000 thực thể, hãy chuyển sang một graph engine bên ngoài như Neo4j hoặc Memgraph kết hợp với một vector store như Qdrant hoặc Milvus.
  • Kiểm soát chi phí trích xuất: Quá trình trích xuất node chiếm hơn 85% chi phí ingestion. Sử dụng một model nhẹ như gpt-4o-mini hoặc một instance Qwen 2.5 14B tự host qua Ollama sẽ giúp chi phí index cho mỗi tài liệu chỉ tốn một phần nhỏ của 1 cent.
  • Ghi gia tăng thực sự (True incremental writes): Do LightRAG không cần re-index cấu trúc phân cấp cộng đồng, việc gọi rag.ainsert() khi có cập nhật mới sẽ hợp nhất trực tiếp các node và cạnh mới vào đồ thị hiện có mà không làm khóa pipeline của bạn.
Share: