0

Lượn một vòng RAG để xem mình outdate thế nào rồi nào (P1)

Mở đầu

  • RAG là một công nghệ mình đã đi cùng nó khá lâu, từ RAG truyền thống, GraphRAG rồi AgenticRAG. Các dự án về chatbot cũng không phải là ít nên thời điểm này từ góc độ mình nó vẫn là một công nghệ tiềm năng, chẳng qua là mình cải tiến nó đủ sâu đến đâu, mang nó đi được tới đâu trong hệ thống của khách hàng.
  • Nhân dịp May Fest + sắp tới phát triển hơn về RAG + đã lui về ở ẩn khá lâu, mình nghĩ đây là dịp mình quay trở lại học hỏi và chia sẻ kiến thức. Không dài dòng nữa, lét gô!

1. 🤖 Agentic RAG

Reference:

Singh et al., "Agentic Retrieval-Augmented Generation: A Survey on Agentic RAG", arXiv:2501.09136, Jan 2025 (cập nhật Apr 2026)


Ý tưởng cốt lõi

RAG truyền thống hoạt động theo pipeline cứng nhắc, tuyến tính:

Câu hỏi → Truy xuất top-k docs (Semantic search, text search, hybrid search) → Đưa vào LLM → Trả lời

Vấn đề: Nếu lần truy xuất đó không đủ thông tin, LLM vẫn phải cố trả lời với dữ liệu thiếu. Không có cơ chế để "hỏi lại" hay "tìm thêm". Nói chung bài toán đơn giản, dữ liệu rõ ràng và muốn nhanh thì RAG truyền thống ổn.

Agentic RAG thay đổi hoàn toàn paradigm này bằng cách nhúng AI agent tự chủ vào pipeline. Agent được trang bị 4 pattern thiết kế cốt lõi:

Pattern Mô tả
Reflection Agent tự đánh giá kết quả retrieval và generation, quyết định có cần làm lại không
Planning Phân rã câu hỏi phức tạp thành các sub-task, lên kế hoạch thực thi
Tool Use Gọi nhiều công cụ: vector search, web search, SQL query, code execution...
Multi-agent Nhiều agent chuyên biệt cộng tác — agent tìm kiếm, agent tổng hợp, agent kiểm tra...

Luồng xử lý điển hình/cơ bản của Agentic RAG:

1. Nhận câu hỏi phức tạp
2. Agent Planning: phân rã thành 3-4 sub-questions
3. Agent Retrieval: với mỗi sub-question, chọn nguồn phù hợp (vector DB, web, SQL...)
4. Agent Critique: đánh giá kết quả → nếu thiếu → quay lại bước 3
5. Agent Synthesis: tổng hợp tất cả kết quả thành câu trả lời cuối
6. Agent Verification: kiểm tra tính nhất quán và factual accuracy

Điểm sáng chính

So sánh với RAG truyền thống:

Khía cạnh RAG Truyền thống Agentic RAG
Số lần retrieval 1 lần cố định Nhiều lần, động
Nguồn dữ liệu Vector DB duy nhất Đa nguồn: DB, web, API, SQL...
Xử lý câu hỏi phức tạp Kém (không phân rã được) Tốt (multi-step planning)
Phát hiện lỗi Không có Tự phát hiện & sửa qua reflection
Latency Thấp (1 round trip) Cao hơn (nhiều vòng lặp)
Chi phí Thấp Cao hơn (nhiều LLM calls)

Kiến trúc phân loại theo số agent:

  • Single-agent: 1 agent đảm nhiệm tất cả — phù hợp tác vụ vừa phức tạp
  • Multi-agent parallel: nhiều agent chạy song song, mỗi agent tìm 1 khía cạnh → merge kết quả
  • Multi-agent hierarchical: 1 orchestrator agent điều phối các sub-agents chuyên biệt
  • Multi-agent collaborative: các agent trao đổi, phản biện nhau trước khi ra kết quả cuối

💡 Insight thực tế: Agentic RAG không phải lúc nào cũng tốt hơn. Với câu hỏi đơn giản, overhead của planning + nhiều LLM calls làm latency tăng gấp 3-5x. Best practice: dùng router để phân loại câu hỏi đơn giản vs. phức tạp, chỉ dùng Agentic cho câu phức tạp.

Ứng dụng thực tế

  • Microsoft Copilot (Office 365, Azure AI): dùng Agentic RAG cho "Deep Research" feature — tự động tìm kiếm web, đọc nhiều nguồn, tổng hợp báo cáo
  • Perplexity AI Deep Research: orchestrate nhiều retrieval rounds, tự viết outline → tìm từng mục → tổng hợp
  • LangGraph (LangChain): framework open-source phổ biến nhất để build Agentic RAG với state machine
  • AutoGen (Microsoft): multi-agent framework, dùng trong enterprise RAG systems

2. 🧠 ContextRAG — Extraction-Free Hierarchical Graph

Reference:

Prosvirnin et al., "ContextRAG: Extraction-Free Hierarchical Graph Construction for Retrieval-Augmented Generation", arXiv:2605.19735, May 2026


Ý tưởng cốt lõi

Graph-based RAG thường gặp bottleneck lớn: cần một pipeline NLP riêng để extract entities, relations trước khi xây graph. Pipeline này:

  • Tốn kém (cần NER model, relation extraction model)
  • Dễ lỗi với domain chuyên biệt (medical, legal, kỹ thuật)
  • Không linh hoạt khi data thay đổi

(Thực ra GraphRAG của Microsoft đang dùng LLM để extract entity nhưng vẫn là danh sách entity types fix cứng, đúng là khó linh hoạt khi dữ liệu đa dạ domain. Mình cũng gặp vấn đề này với dự án khách hàng, có nhiều phương án cải thiện có thể thử như tạo ra summary của document sau đó dùng summary + chunk text để LLM tự quyết định entity types nào là phù hợp chẳng hạn)

ContextRAG giải quyết bằng cách bỏ hoàn toàn bước extraction riêng biệt. Thay vào đó, dùng chính LLM để trực tiếp xây dựng đồ thị phân cấp từ ngữ nghĩa của văn bản

Quy trình xây dựng graph:

Văn bản thô
    ↓
[LLM hiểu cấu trúc ngữ nghĩa] ← không cần NER/NLP riêng
    ↓
Tạo nodes theo ngữ cảnh (không chỉ entities bề mặt)
    ↓
Tạo edges theo mối quan hệ ngữ nghĩa sâu
    ↓
Tổ chức thành đồ thị phân cấp nhiều tầng:
  Tầng 0: Chi tiết cụ thể (facts, events)
  Tầng 1: Mối quan hệ giữa các facts
  Tầng 2: Patterns, themes tổng quát
  Tầng 3: Meta-context, discourse structure

Ví dụ

Bước 1 — Đọc và phân tích văn bản bằng LLM (thay cho NER pipeline):

Thay vì dùng NER model để detect [PERSON: Elon Musk], [ORG: Tesla], ContextRAG gửi đoạn văn trực tiếp cho LLM với prompt kiểu:

"Đọc đoạn văn sau. Hãy xác định:
 1. Các concepts/entities quan trọng (không chỉ tên riêng — bao gồm cả ý tưởng, sự kiện, trạng thái)
 2. Mối quan hệ giữa chúng (nhân quả, thời gian, phụ thuộc, đối lập...)
 3. Ngữ cảnh bao quanh mỗi quan hệ (điều kiện, thời điểm, hoàn cảnh)
 
 Đoạn văn: [TEXT]"

LLM trả về dạng structured — ví dụ với đoạn văn về Tesla:

{
  "nodes": [
    {"id": "n1", "label": "Tesla Model 3 launch", "type": "event"},
    {"id": "n2", "label": "mass market EV adoption", "type": "concept"},
    {"id": "n3", "label": "battery cost reduction", "type": "causal_factor"}
  ],
  "edges": [
    {"from": "n1", "to": "n2", "relation": "enables", "context": "when priced under $40k"},
    {"from": "n3", "to": "n1", "relation": "prerequisite_for", "context": "2017-2020 supply chain improvements"}
  ]
}

Điểm khác với NER: NER chỉ nhận ra "Tesla" là ORG, "Elon Musk" là PERSON. LLM nhận ra "battery cost reduction" là một causal factor — thứ NER không thể detect vì nó là khái niệm trừu tượng, không phải named entity.

Bước 2 — Tổ chức nodes/edges vào đồ thị phân cấp 4 tầng:

Tầng 0 — FACTS (chi tiết cụ thể, atomic):
  Ví dụ: "Tesla Model 3 ra mắt tháng 7/2017 với giá $35,000"
  → Node rất cụ thể, ít kết nối

Tầng 1 — RELATIONS (mối quan hệ giữa facts):
  Ví dụ: "Model 3 launch → triggered → mass EV adoption in US"
  → Node là edge của tầng 0, được "nâng cấp" thành node riêng

Tầng 2 — PATTERNS (xu hướng, chủ đề chung):
  Ví dụ: "EV market disruption pattern: cost reduction → mass adoption"
  → Abstraction từ nhiều Tầng 1 nodes

Tầng 3 — META-CONTEXT (bức tranh toàn cảnh):
  Ví dụ: "Tesla's role in accelerating global energy transition"
  → Abstraction từ toàn bộ corpus liên quan đến Tesla

Bước 3 — Xây dựng cấu trúc phân cấp tự động:

LLM không chỉ tạo nodes/edges mà còn tự phân loại tầng cho từng node dựa trên độ trừu tượng:

Độ cụ thể cao → Tầng 0
Mối quan hệ giữa facts → Tầng 1
Pattern/theme từ nhiều facts → Tầng 2
Big picture / discourse → Tầng 3

Đây là quá trình lặp nhiều lần: LLM đọc tầng dưới → tổng hợp → tạo node tầng trên — tương tự như RAPTOR nhưng theo cấu trúc graph thay vì tree.

Khi query:

  • Traverse graph từ tầng phù hợp tùy độ phức tạp của câu hỏi.
  • LLM phân tích câu hỏi → xác định độ phức tạp → traverse từ tầng phù hợp. Ví dụ:
    • "Giá Tesla Model 3 năm 2017 là bao nhiêu?" → Tầng 0 (cần fact cụ thể)
    • "Điều gì thúc đẩy EV adoption?" → Tầng 1-2 (cần quan hệ nhân quả)
    • "Tesla ảnh hưởng như thế nào đến năng lượng toàn cầu?" → Tầng 3 (cần big picture)

Điểm sáng chính

  • Graph theo ngữ cảnh, không chỉ theo entities: Ví dụ: "sự kiện A dẫn đến hệ quả B trong hoàn cảnh C" — mối quan hệ nhân quả này không thể capture bằng NER thông thường
  • Phân cấp đa tầng: câu hỏi cần chi tiết → truy vấn tầng 0; câu hỏi cần tổng quát → truy vấn tầng 2-3
  • Dễ update: khi có tài liệu mới, chỉ cần insert nodes/edges mới mà không cần rebuild toàn bộ

3. ⚖️ BalanceRAG — Joint Risk Calibration

Reference:

Jia et al., "BalanceRAG: Joint Risk Calibration for Cascaded Retrieval-Augmented Generation", arXiv:2605.20084, May 2026


Ý tưởng cốt lõi

Vấn đề thực tế phổ biến: Nhiều team đã tune RAG pipeline rất kỹ từng bước — retrieval accuracy cao, reranker tốt — nhưng kết quả cuối vẫn có hallucination. Tại sao?

Lý do: khi optimize từng module độc lập, các lỗi và thiên lệch của từng module tích lũy và tương tác theo cách khó đoán:

Retrieval lấy docs tốt 95% → Reranker chọn lại 90% → Generator vẫn hallucinate 15%

Vấn đề nằm ở sự tương tác giữa các stage, không phải từng stage. (Chúng ta có thể tuning từng steps nếu build các metrics đánh giá các module, export kết quả đánh giá và tuning từ trái sang phải, tiền đề là metrics chuẩn chỉnh, có golden data,....)

BalanceRAG giải quyết bài toán này bằng cách thiết kế lại thành Cascaded RAG — hệ thống 2 nhánh với routing thông minh, và calibrate đồng thời cả 2 ngưỡng thay vì từng nhánh riêng.


🔍 Cascaded RAG là gì? Tại sao lại thiết kế như vậy?

Ý tưởng gốc: Không phải câu hỏi nào cũng cần RAG. Nếu LLM đã biết câu trả lời (câu hỏi phổ thông, kiến thức cơ bản), retrieve thêm docs vừa tốn chi phí vừa có thể làm nhiễu. Cascade RAG giải quyết bằng cách dùng LLM như tuyến phòng thủ đầu tiên, chỉ gọi RAG khi LLM thực sự không chắc.

Cơ chế "uncertainty score" — LLM tự đánh giá độ tự tin:

LLM không chỉ sinh ra câu trả lời mà còn sinh ra xác suất token cho mỗi từ. Uncertainty score u(x) được tính từ các xác suất này — thường dùng negative log-likelihood hoặc entropy của output:

Ví dụ LLM sinh: "Thủ đô của Pháp là Paris"
  → Token "Paris": xác suất 0.97 → LLM rất tự tin → u1 thấp → ACCEPT

Ví dụ LLM sinh: "Người đầu tiên đặt chân lên Sao Hỏa là... Armstrong?"  
  → Token "Armstrong": xác suất 0.31 → LLM không tự tin → u1 cao → ESCALATE

⚠️ Uncertainty score không phải LLM tự nói "tôi không chắc" bằng lời — mà là con số kỹ thuật tính từ phân phối xác suất output, hoàn toàn tự động, không cần prompt đặc biệt (Cái này sẽ cần điều tra kỹ hơn sau)

Kiến trúc Cascaded RAG:

Query x
    ↓
[Nhánh 1: LLM-only]
  → LLM sinh câu trả lời y1
  → Tính uncertainty u1(x) từ token probabilities
    │
    ├── u1(x) ≤ t1 (LLM tự tin)?
    │      → ACCEPT y1: trả lời ngay, không retrieve gì cả
    │        (tiết kiệm cost + latency, tránh RAG làm nhiễu)
    │
    └── u1(x) > t1 (LLM không chắc)?
           → ESCALATE: chuyển sang nhánh 2
                    ↓
           [Nhánh 2: LLM + RAG]
             → Retriever tìm top-k docs liên quan
             → LLM đọc docs + sinh câu trả lời y2 mới
             → Tính uncertainty u2(x) từ output mới
                    │
                    ├── u2(x) ≤ t2 (RAG giúp LLM tự tin hơn)?
                    │      → ACCEPT y2: trả lời bằng kết quả RAG
                    │
                    └── u2(x) > t2 (RAG vẫn không đủ)?
                           → ABSTAIN: từ chối trả lời
                             (thay vì hallucinate, hệ thống nói "tôi không biết")

Tại sao thiết kế 2 nhánh, không phải 1 nhánh duy nhất?

Vì thực nghiệm (và logic) cho thấy: RAG không phải lúc nào cũng tốt hơn LLM-only. Có những câu LLM biết rõ, nhưng khi retrieve thêm docs lại retrieve được tài liệu không liên quan → LLM bị "confuse" → trả lời sai. Ví dụ từ paper:

  • LLM-only trả lời đúng "Charleston là thủ đô West Virginia"
  • RAG retrieve được bài viết về "Charleston, South Carolina" (thành phố lớn hơn nhưng không phải thủ đô) → LLM đọc xong lại trả lời sai

Cascade design giải quyết: nếu LLM đã tự tin, đừng retrieve — hãy tin LLM.

Vấn đề với cách calibrate cũ (stage-by-stage):

  • Cách thông thường: calibrate t1 trên nhánh 1 trước → lấy phần còn lại calibrate t2.
  • Vấn đề: phải chia budget thống kê δ ra làm 2 (δ/2 cho mỗi stage) → quá thận trọng → nhiều câu hỏi bị từ chối không cần thiết.
    • Budget thống kê δ là ngưỡng sai số cho phép. Ví dụ δ = 0.1 nghĩa là: "tôi chấp nhận xác suất 10% là hệ thống sẽ vi phạm mức error rate mục tiêu α".
    • Giải thích dễ hiểu hơn: Ví dụ δ = 0.1
      • Calibrate t1 trước → dùng hết δ/2 = 0.05 cho nhánh 1
      • Calibrate t2 sau → chỉ còn δ/2 = 0.05 cho nhánh 2
      • Mỗi nhánh phải "tiết kiệm" budget → chọn ngưỡng rất thấp để đảm bảo an toàn
      • Ngưỡng thấp = yêu cầu LLM phải rất chắc chắn mới accept → nhiều câu bị loại dù thực ra có thể trả lời được

Giải pháp của BalanceRAG — thử tất cả cặp ngưỡng cùng lúc:

Thay vì chọn t1 rồi mới chọn t2, BalanceRAG thử đồng thời hàng trăm tổ hợp (t1, t2) trên một bộ dữ liệu kiểm tra (calibration set), rồi tìm tổ hợp vừa an toàn vừa chấp nhận được nhiều câu nhất.

📌 Calibration set là gì? Là một tập câu hỏi có sẵn đáp án đúng (labeled data), được tách riêng ra — không dùng để train model, chỉ dùng để tìm ngưỡng tối ưu sau khi model đã train xong. Giống như sau khi thi xong mới dùng đáp án để chấm điểm và xác định ngưỡng đỗ/trượt — chứ không dùng đáp án đó để ôn thi. Trong paper, calibration set chiếm 50% tổng dữ liệu, phần còn lại là test set để đánh giá kết quả cuối.

Ví dụ cụ thể để hình dung:

Giả sử có 1000 câu hỏi trong calibration set (đã biết câu trả lời đúng). Mục tiêu: error rate ≤ 10% (α = 0.1), tức là trong 100 câu được hệ thống trả lời, tối đa 10 câu sai.

BalanceRAG thử lần lượt các tổ hợp (t1, t2) và với mỗi tổ hợp, đếm:

Thử (t1=0.3, t2=0.6) với 1000 câu hỏi:
  → 700 câu: LLM-only đủ tự tin (u1 ≤ 0.3) → accept ngay
  → 300 câu: LLM-only không chắc → escalate sang RAG
      → 200 câu: RAG đủ tự tin (u2 ≤ 0.6) → accept
      →  100 câu: RAG cũng không chắc → abstain (từ chối)
  
  Tổng: 900 câu được trả lời (700 + 200), 100 câu bị từ chối
  Trong 900 câu đó: kiểm tra xem có bao nhiêu câu SAI?
  → Nếu ≤ 90 câu sai (≤ 10%) → tổ hợp này AN TOÀN ✅
  → Nếu > 90 câu sai → tổ hợp này KHÔNG an toàn ❌

Tại sao t1 và t2 cao hơn thì accept được nhiều câu hơn?

  • t1 cao = LLM-only được phép trả lời dù ít tự tin hơn → nhiều câu qua nhánh 1 hơn
  • t2 cao = RAG được phép trả lời dù ít tự tin hơn → ít câu bị abstain hơn
  • Nhưng ngưỡng quá cao → chấp nhận cả câu sai → error rate vượt 10% → không an toàn

Cách chọn tổ hợp cuối cùng:

Trong tất cả các tổ hợp (t1, t2) đã kiểm tra và AN TOÀN:
→ Chọn tổ hợp nào cho phép trả lời được NHIỀU CÂU NHẤT
  (vì nhiều câu được trả lời = hữu ích hơn cho người dùng)

Điểm đặc sắc của SGT — tại sao không chỉ thử brute force?

Vì số tổ hợp rất lớn (ví dụ 20×20 = 400 tổ hợp) mà budget thống kê δ có hạn. SGT phân phối budget thông minh: bắt đầu từ 1 tổ hợp có vẻ hứa hẹn → nếu an toàn, chuyển phần budget thừa sang các tổ hợp lân cận (t1 hoặc t2 cao hơn một chút) → test được nhiều tổ hợp hơn với cùng mức đảm bảo thống kê, thay vì chia đều budget cho tất cả (Bonferroni) dẫn đến mỗi tổ hợp chỉ được test với budget rất nhỏ → bỏ sót nhiều tổ hợp tốt.

Ví dụ cụ thể (từ paper — qualitative cases):

  • Câu hỏi: "Charleston là thủ đô của bang nào?"
    → LLM-only trả lời đúng "West Virginia", u1 thấp → accept ngay, không cần retrieve
    → Nếu bắt buộc dùng RAG: RAG lại trả lời sai "Charleston không phải thủ đô bang nào" (bị mislead bởi retrieved docs)
  • "Who sailed to the bong tree?"
    → LLM-only sai (hallucinate tên khác), u1 cao → escalate sang RAG
    → RAG retrieve đúng "The Owl and the Pussycat" → accept

Điểm sáng chính

  • Nhìn toàn cục: Không chỉ optimize từng bước mà optimize toàn bộ chain — giải quyết vấn đề "local optima" của từng module
  • Phát hiện "error propagation": Detect khi lỗi nhỏ ở retrieval được khuếch đại qua các stage
  • Calibrated confidence: Output kèm theo uncertainty estimate được calibrate, không chỉ là câu trả lời đơn thuần
  • Actionable: Không chỉ nói "risk cao" mà còn có thể đề xuất hành động cụ thể (retrieve thêm, rerank khác, từ chối trả lời...)

Summary

  • Bài viết cũng khá dài rồi nên mình xin phép dừng phần 1 ở đây, hẹn mọi người ở phần 2 nhé ^^. Cảm ơn mọi người đã đọc tới đây

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí