Welcome to MikeDaDev's Blog – notes on Java, backend & system design.

Explore
MikeDaDev's Blog
in ai

graph rag & advanced rag

Tung Bui Thanh (TungDaDev)14 mins read

Graph rag.webp

Vector search giỏi tìm những thứ na ná nhau trên bề mặt, nhưng gần như mù tịt về các mối quan hệ nằm khuất giữa các thực thể.

Giai đoạn 2023 - 2024, RAG (Retrieval-Augmented Generation) là từ cửa miệng của mọi dự án AI trong doanh nghiệp. Công thức nghe rất đơn giản:

  1. Lấy tài liệu PDF/Docx của công ty.
  2. Cắt nhỏ thành từng đoạn (chunking: 500 tokens).
  3. Cho qua model embedding để ra vector.
  4. Lưu vào vector database (Pinecone, Qdrant, Milvus, PgVector).
  5. Người dùng hỏi thì tính cosine similarity, lấy top-K đoạn giống nhất, nhét vào prompt cho LLM trả lời.

Với câu hỏi tra cứu thông tin cụ thể (factoid retrieval) thì chạy rất ngon: "Chính sách nghỉ phép của công ty là bao nhiêu ngày?" hay "Quy trình xin cấp thẻ tín dụng gồm những bước nào?".

Nhưng đưa lên production với hàng triệu trang tài liệu phức tạp thì bắt đầu lòi ra vấn đề. Bạn thử hỏi:

  • "Các dự án của phòng ban X trong năm qua có mối liên hệ rủi ro nào với các đối tác chuỗi cung ứng ở thị trường Châu Á?"
  • "Nhân sự A và Nhân sự B cùng tham gia những sáng kiến công nghệ nào, và ai là người phê duyệt ngân sách cuối cùng?"

RAG kiểu cơ bản (Naive RAG) gặp mấy câu này là đuối: hoặc trả lời hời hợt, hoặc bịa (hallucination), hoặc sót những mắt xích quan trọng nằm rải rác ở nhiều tài liệu.

Từ đó mới có GraphRAG: kết hợp Knowledge Graph với vector search. Cách làm được nhắc tới nhiều nhất là của Microsoft Research, trong paper From Local to Global: A Graph RAG Approach to Query-Focused Summarization (2024) và bộ mã nguồn mở microsoft/graphrag.

Mình sẽ đi qua lý do Naive RAG hụt hơi, cách GraphRAG dựng đồ thị và truy vấn, rồi tới một kiến trúc Hybrid RAG cho doanh nghiệp cùng mấy chuyện tiền nong cần tính.


# vì sao vector search truyền thống (naive rag) thất bại?

Muốn hiểu vì sao cần GraphRAG thì phải nhìn thẳng vào 3 giới hạn có sẵn của Naive Vector Search:

# chunking làm đứt mạch ngữ cảnh (context fragmentation)

Cắt tài liệu thành từng khối 500 tokens thì một câu chuyện liền mạch sẽ bị băm nát.

  • Chunk 1: "Công ty Alpha ký hợp đồng trị giá 50 triệu USD với đối tác Beta."
  • Chunk 87 (cách đó 20 trang): "Dự án hợp tác của Beta gặp sự cố vi phạm bản quyền phần mềm và bị đình chỉ."
  • Chunk 140: "Giám đốc pháp chế của Alpha yêu cầu kiểm toán toàn bộ hợp đồng của Beta."

Người dùng hỏi: "Tại sao Alpha lại kiểm toán hợp đồng?". Cosine similarity giữa câu hỏi và chunk 140 có thể cao, nhưng chunk 140 lại chẳng có liên kết ngữ nghĩa trực tiếp nào với chunk 1 và chunk 87. Vector search không có khái niệm nối các mắt xích lại với nhau (relationship chaining).

# bó tay với câu hỏi cần tổng hợp toàn cục (global sensemaking)

Vector search dựa trên nguyên tắc tìm chỗ giống nhau cục bộ (local similarity). Nếu bạn hỏi: "Chủ đề chính xuyên suốt toàn bộ 500 bài đánh giá hiệu quả kinh doanh của tập đoàn năm 2025 là gì?"

Không có chunk nào một mình chứa được câu trả lời này. Vector search sẽ bốc top-K (giả sử 5) đoạn giống câu hỏi nhất, thường là những đoạn có cụm "hiệu quả kinh doanh", và bỏ qua 495 bài còn lại. Nó không tổng hợp được ở mức nhìn toàn cảnh. Paper của Microsoft gọi đây là bài toán query-focused summarization trên toàn bộ kho dữ liệu, và đó chính là động lực chính để họ làm GraphRAG.

# điểm mù của cosine similarity (giống bề mặt khác với đúng về cấu trúc)

Hai đoạn văn có thể có khoảng cách cosine rất gần chỉ vì cùng dùng từ ngữ pháp lý trang trọng, trong khi ý nghĩa pháp lý lại trái ngược nhau. Vector chỉ biết hai thứ gần nhau trong không gian ngữ nghĩa ẩn (latent semantic space), nó không hiểu cấu trúc sự thật: ai làm gì, quan hệ ra sao, khi nào.


# graphrag: biến tri thức thành đồ thị

Naive RAG chỉ lưu từng khối văn bản rời rạc. GraphRAG thì rút từ văn bản ra một mạng lưới thực thể và quan hệ (entity-relationship graph):

Một hệ thống GraphRAG đầy đủ chạy qua 2 giai đoạn chính:

# giai đoạn 1: indexing & graph construction (dựng đồ thị tri thức)

  1. Source Documents Chunking: cắt tài liệu thành các text unit.
  2. Entity & Relationship Extraction: cho LLM làm "người trích xuất thông tin", quét từng đoạn để lấy ra:
    • Entities: tên người, tổ chức, công nghệ, địa điểm, sự kiện...
    • Relationships: quan hệ giữa các thực thể, mỗi quan hệ có mô tả ngắn và độ mạnh; phần trích xuất "claims" (khẳng định có bằng chứng, như "A bị điều tra vì...") là tuỳ chọn riêng.
    • Mỗi thực thể/quan hệ xuất hiện ở nhiều chunk thì các mô tả được LLM gộp lại thành một. Bản của Microsoft còn cho LLM quét thêm vài lượt ("gleaning") để vớt thực thể bị sót, nên số lần gọi LLM nhiều hơn số chunk.
  3. Graph Clustering (thuật toán phân cụm Leiden):
    • Gom các thực thể liên kết chặt với nhau thành các community, xếp theo nhiều tầng (hierarchical communities: cụm lớn → cụm vừa → cụm nhỏ). Leiden tối ưu độ "modularity": cạnh bên trong cụm nhiều, cạnh nối ra ngoài ít, giống như chia một công ty thành các nhóm hay làm việc với nhau.
  4. Community Summarization:
    • LLM viết bản tóm tắt (community report) cho từng cụm, từ cụm nhỏ lên cụm lớn. Đây là thứ giúp GraphRAG trả lời được câu hỏi tổng quan, loại câu mà vector search bó tay.

Kết quả indexing của bản Microsoft mặc định là các bảng (file parquet): entity, relationship, community, community report, text unit, kèm embedding của mô tả entity. Không bắt buộc phải có graph database; Neo4j chỉ là một lựa chọn nếu bạn muốn truy vấn đồ thị trực tiếp.

Ở giai đoạn truy vấn, Global Search chạy kiểu map-reduce: mỗi community report được hỏi riêng để lấy các ý liên quan kèm điểm, rồi các ý điểm cao được gộp lại thành câu trả lời. Local Search thì tìm các entity gần câu hỏi nhất (qua embedding mô tả entity), rồi kéo theo hàng xóm, quan hệ, text unit gốc và community report liên quan để làm ngữ cảnh. Bản mới còn có DRIFT search, lai giữa hai kiểu.

# giai đoạn 2: dual search modes (hai kiểu truy vấn)


# so sánh: naive rag vs advanced rag vs graphrag

Tiêu chíNaive RAG (cơ bản)Advanced RAG (Hybrid Search)GraphRAG (kiểu Microsoft)
Lưu trữChỉ vector databaseVector DB + BM25 sparse indexBảng entity/relation (hoặc graph DB) + vector + summaries
Trả lời câu hỏi chi tiếtTốt (nếu nằm gọn trong 1 chunk)Rất tốt (nhờ reranker)Khá (thường thua RAG thường ở câu hỏi một bước)
Suy luận nhiều bước (multi-hop)Rất kém (ngữ cảnh bị đứt)Trung bình (tách câu hỏi, truy vấn nhiều vòng)Tốt (lần theo đường đi trên đồ thị)
Tổng hợp toàn cục (global summary)Thất bại hoàn toànKémTốt nhất (nhờ hierarchical community summaries)
Chi phí indexing (token)Rất rẻ (chỉ tốn tiền embedding)RẻĐắt (gọi LLM nhiều lượt để trích xuất graph)
Tốc độ truy vấn (latency)Nhanh (chỉ một lần tìm vector)Nhanh (thêm bước rerank)Chậm hơn (global search gọi LLM trên nhiều community)

Bảng trên là đánh giá định tính. Một nghiên cứu so sánh có hệ thống năm 2025 (RAG vs. GraphRAG: A Systematic Evaluation and Key Insights) cho kết quả khá tỉnh táo: hai bên bổ sung cho nhau chứ không có bên thắng tuyệt đối. RAG thường làm tốt hơn với câu hỏi một bước và câu hỏi chi tiết, GraphRAG mạnh hơn ở câu hỏi nhiều bước cần suy luận. Latency của global search còn phụ thuộc số community phải duyệt, có thể lên tới chục giây với kho lớn.


# kiến trúc thực tế: enterprise hybrid graphrag

Trong doanh nghiệp, không có công nghệ nào là "viên đạn bạc" (silver bullet). Cách chắc ăn nhất là hybrid architecture gồm 3 lớp:

RRF gộp các danh sách kết quả mà không cần chuẩn hoá điểm của từng nguồn (điểm BM25 và cosine vốn không cùng thang đo). Mỗi tài liệu được cộng điểm theo thứ hạng của nó ở từng danh sách: score(d) = Σ 1 / (k + rank_i(d)), với k thường là 60. Tài liệu đứng cao ở nhiều danh sách thì điểm cao. Kết quả từ graph thường là các fact (thực thể, quan hệ) chứ không phải đoạn văn, nên hoặc map chúng về text unit gốc trước khi đưa vào RRF, hoặc đặt chúng thành một phần riêng trong prompt như ví dụ bên dưới. Reranker (cross-encoder) đặt sau RRF để chấm lại mức liên quan thật của top vài chục ứng viên.

# ví dụ schema entity & relationship bằng typescript

// Định nghĩa cấu trúc tri thức được trích xuất từ tài liệu
export interface ExtractedEntity {
  name: string
  type: 'PERSON' | 'ORGANIZATION' | 'PROJECT' | 'TECHNOLOGY' | 'RISK'
  description: string
  sourceChunkId: string
}
 
export interface ExtractedRelationship {
  sourceEntity: string
  targetEntity: string
  relationType: 'DEVELOPS' | 'OWNS' | 'DEPENDS_ON' | 'INVESTED_IN' | 'BLOCKED_BY'
  strength: number // 1 to 10
  contextSnippet: string
}
 
export interface KnowledgeGraphSubGraph {
  entities: ExtractedEntity[]
  relationships: ExtractedRelationship[]
}

Khi người dùng hỏi một câu phức tạp, hệ thống sẽ:

  1. Rút các thực thể trong câu hỏi ra (Named Entity Recognition - NER).
  2. Query Neo4j hoặc NetworkX để lấy sub-graph lân cận trong bán kính 2-hop.
  3. Ghép các node và edge đó với các đoạn văn tương đồng lấy từ vector search.
  4. Prompt gửi cho LLM có cấu trúc rõ ràng:
    • Facts from Graph: (thực thể A liên kết với B qua quan hệ C).
    • Relevant Paragraphs: (nội dung chi tiết từ văn bản).

# bài toán chi phí & trade-off khi làm graphrag

GraphRAG mạnh thật, nhưng trước khi đưa vào hệ thống vẫn phải tính chuyện tiền nong:

# chi phí indexing ban đầu rất lớn

Để dựng knowledge graph, LLM phải đọc từng chunk, phân tích câu, gọi hàm trích xuất thực thể. Thử ước lượng thô với 100.000 trang tài liệu, giả sử mỗi trang ~500 token, tức khoảng 50 triệu token:

  • Naive RAG: chỉ tốn tiền embedding. Với giá model embedding phổ biến cỡ 0,02 - 0,13 cho 1 triệu token, tổng chỉ khoảng vài đô tới vài chục đô.
  • GraphRAG: mỗi chunk đi kèm prompt trích xuất dài, có thể thêm vài lượt gleaning, rồi còn gộp mô tả và viết community report. Tổng token đầu vào dễ gấp vài lần kho dữ liệu, cộng thêm token đầu ra. Với model cỡ lớn, hoá đơn dễ lên hàng trăm tới hàng nghìn đô, tức gấp hàng chục tới hàng trăm lần Naive RAG. Con số thật phụ thuộc nặng vào model và giá ở thời điểm bạn chạy, nên chạy thử trên một mẫu nhỏ rồi nhân lên.
  • Cách xử lý: dùng model mã nguồn mở cỡ nhỏ nhưng trích xuất tốt (dòng Qwen hay Llama cỡ 7-8B, chạy local qua vLLM) cho khâu trích xuất thực thể, chỉ dùng model lớn trả phí cho bước tóm tắt community. Đổi lại, model nhỏ trích xuất kém hơn (sót thực thể, đặt tên không thống nhất), nên cần đo chất lượng đồ thị trước khi chốt. Microsoft Research cũng có LazyGraphRAG, hoãn phần dùng LLM sang lúc truy vấn; theo công bố của họ, chi phí indexing ngang vector RAG, chỉ bằng khoảng 0,1% so với GraphRAG đầy đủ.

# cập nhật dữ liệu (graph maintenance & upsert)

Khi một tài liệu bị sửa hoặc xoá:

  • Xoá vector trong vector DB rất dễ (xoá theo document ID là xong).
  • Nhưng xoá trong graph DB thì phải dọn các cạnh (edge), tính lại trọng số và cập nhật lại bản tóm tắt community. Một thực thể có thể được gộp từ mô tả ở nhiều tài liệu, xoá một tài liệu nghĩa là phải viết lại mô tả đó. Phân cụm Leiden chạy lại cũng có thể đổi ranh giới community, kéo theo viết lại report. Nên làm một pipeline bất đồng bộ qua hàng đợi (Kafka / RabbitMQ) để xử lý ngầm phía sau, và chấp nhận đồ thị cập nhật trễ hơn vector index. Các bản gần đây của Microsoft GraphRAG có hỗ trợ cập nhật tăng dần (incremental update) để đỡ phải index lại từ đầu.

TIP

Chiến lược Hybrid RAG hợp lý: Đừng vội vứt vector search. Dùng vector DB (như Qdrant) cho câu hỏi tra cứu văn bản chính xác (exact retrieval) và graph DB (như Neo4j) cho câu hỏi cần suy luận quan hệ (relational reasoning). Một query router sẽ quyết định khi nào mới cần tra đồ thị, để đỡ tốn tiền.


# tổng kết

RAG đang thay đổi rất nhanh: từ Naive RAG (tìm văn bản đơn giản) sang Advanced RAG (tìm kiếm lai kèm reranking) và giờ là GraphRAG (suy luận dựa trên mạng lưới tri thức có liên kết).

Nếu hệ thống của bạn chỉ cần đọc mấy tài liệu FAQ ngắn, Naive RAG hoặc Hybrid Search là quá đủ. Còn nếu bạn đang làm hệ thống phân tích báo cáo tài chính, quản trị rủi ro pháp lý, nghiên cứu y khoa hay kiến trúc phần mềm cỡ lớn, nơi câu hỏi hay đòi nối nhiều mắt xích, thì GraphRAG đáng thử. Theo mình, cách đi ít rủi ro là làm Hybrid Search cho thật tốt trước, gom một bộ câu hỏi đánh giá từ người dùng thật, rồi chạy GraphRAG trên một tập tài liệu nhỏ để so xem nó có cải thiện đúng những câu đang trả lời dở hay không.


Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!

Happy coding 😎 👍🏻 🚀 🔥.