$ cat ./blog/gen-ai/kien-truc-rag.md
seriesRAG từ cơ bản đến nâng caobài 1

Kiến trúc RAG: toàn bộ đường đi của một câu hỏi

Bài mở đầu series RAG từ cơ bản đến nâng cao: vì sao RAG tồn tại, hai pipeline tạo nên một hệ RAG, từng khâu parsing, chunking, metadata, index, query transformation, hybrid retrieval, fusion, reranking, context engineering và grounded generation làm gì, các kiến trúc Advanced, Modular, Agentic và GraphRAG khác nhau ở đâu, và một hệ RAG cần thêm gì để sống được ở production.

~/diagrams/kien-truc-rag/rag-two-pipelines.htmltương tác
mở riêng ↗
HìnhHàng trên chạy offline: tài liệu thô được parse, cắt thành chunk, gắn metadata rồi đánh index. Hàng dưới chạy khi có người hỏi: câu hỏi được xử lý, đem đi truy xuất, xếp hạng lại, ghép thành context rồi mới tới LLM. Hai nửa chỉ gặp nhau đúng một chỗ là knowledge index - và mọi mũi tên trong hình đều là một chỗ có thể hỏng.
đường đi chínhchặng offlinechặng online

Có một cách giải thích RAG rất phổ biến:

Chia tài liệu thành các chunk
    ->  tạo embedding
    ->  lưu vào vector database
    ->  tìm chunk gần với câu hỏi
    ->  đưa cho LLM

Cách giải thích này không sai. Nhưng nếu dùng nó làm mental model chính, chúng ta rất dễ xây một hệ thống chạy đẹp trong demo rồi vỡ khi gặp dữ liệu thật.

Một hệ Retrieval-Augmented Generation trong thực tế không phải là Vector Database + LLM. Nó là một chuỗi xử lý gồm rất nhiều quyết định: parsing, chunking, metadata, indexing, query transformation, lexical retrieval, dense retrieval, fusion, reranking, context construction, prompting, generation, citation, security, observability và evaluation. Mỗi quyết định ở đó đều có thể là nguyên nhân khiến câu trả lời cuối cùng sai.

Bài viết này đi xuyên qua toàn bộ pipeline đó. Mục tiêu không phải đào sâu từng thuật toán — chunking, embedding, hybrid search, reranking hay query transformation đều xứng đáng có một bài riêng — mà là dựng một mental model hoàn chỉnh: mỗi khâu nhận gì, xử lý gì, trả ra gì, vì sao nó tồn tại, và nó liên hệ với các khâu còn lại như thế nào.


01Vì sao chúng ta cần RAG

Một Large Language Model sau khi pretrain đã hấp thụ một lượng lớn pattern và kiến thức từ dữ liệu huấn luyện. Phần kiến thức đó có thể xem là parametric memory: thông tin được mã hoá gián tiếp trong chính các tham số của model.

Cách lưu trữ này tạo ra bốn giới hạn khá cụ thể.

Thứ nhất, model không tự nhiên biết tài liệu riêng của tổ chức nếu chúng chưa từng nằm trong training data. Thứ hai, cập nhật một tài liệu trong database không hề cập nhật kiến thức bên trong model. Thứ ba, ngay cả khi model từng nhìn thấy một sự kiện lúc training, việc yêu cầu nó truy xuất chính xác một chi tiết cụ thể vẫn không giống một lần database lookup. Thứ tư, nếu chỉ dựa vào parametric knowledge, ta gần như không thể chỉ ra nguồn nào đã hỗ trợ cho một câu trả lời.

Paper RAG của Lewis và cộng sự năm 2020 đặt vấn đề đúng theo hướng này: kết hợp parametric memory của mô hình sinh với một non-parametric memory bên ngoài, có thể truy xuất khi cần.

~/diagrams/kien-truc-rag/parametric-vs-rag.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi khi hệ thống có một kênh tri thức bên ngoài: câu hỏi được đem đi truy xuất trước, evidence được nạp vào context, rồi LLM mới viết câu trả lời. Hàng dưới là ba hệ quả khi bỏ kênh đó đi - model chỉ có những gì nằm trong weights, sửa tài liệu không sửa được kiến thức, và câu trả lời không còn nguồn để trỏ về.
đường đi khi có RAGchỗ evidence được nạphệ quả khi thiếu

Điểm quan trọng nhất, và cũng là điểm hay bị hiểu nhầm nhất:

RAG không cập nhật weights của model. RAG thay đổi thông tin có sẵn tại thời điểm inference.

Model vẫn là model đó. Nhưng trước khi yêu cầu nó trả lời, hệ thống đi tìm những thông tin có khả năng liên quan từ nguồn bên ngoài rồi đưa chúng vào context. Vì vậy:

RAG = Retrieval + Context Augmentation + Generation

Bốn nhóm vấn đề RAG thường giải quyết

Tri thức nằm ngoài training data. Một chatbot nội bộ cần trả lời “quy trình deploy production của team hiện tại là gì”. Thông tin đó nằm trong Notion, Confluence, Google Drive, git repository, runbook hay ticket. Không có lý do gì để kỳ vọng một foundation model đã biết chúng.

Tri thức thay đổi theo thời gian. Nếu chính sách công ty đổi hôm nay, ta cập nhật knowledge source và re-index, thay vì fine-tune lại model chỉ để sửa một đoạn thông tin.

Grounding. Thay vì bảo model trả lời bằng kiến thức của nó, ta đưa evidence cụ thể kèm chỉ dẫn chỉ được dùng evidence đó. RAG không đảm bảo tuyệt đối rằng model sẽ không hallucinate, nhưng nó tạo ra một kênh grounding để câu trả lời có chỗ bám.

Provenance. Nếu retrieval layer giữ được document_id, title, url, page, section và chunk_id, hệ thống có thể đưa source metadata sang generation layer và trả về câu trả lời kèm nguồn. Citation không tự nhiên xuất hiện chỉ vì ta dùng vector database — nó phải được bảo toàn xuyên suốt từ ingestion đến response cuối.

Khi nào RAG không phải là đáp án

RAG phù hợp khi vấn đề là model cần truy cập đúng tri thức tại runtime. Nhưng nếu mục tiêu là thay đổi phong cách trả lời, cấu trúc output, hành vi ổn định, khả năng thực hiện một task hay domain adaptation ở mức model, thì fine-tuning hoặc instruction tuning thường hợp hơn.

~/diagrams/kien-truc-rag/rag-vs-finetuning.htmltương tác
mở riêng ↗
HìnhCâu hỏi phân loại không nằm ở kỹ thuật mà ở chỗ model đang thiếu gì. Thiếu tri thức thì sửa ở index, và một lần re-index là đủ. Thiếu hành vi thì phải sửa ở chính weights. Thiếu cả hai thì dùng cả hai - đây không phải hai lựa chọn loại trừ nhau, và hệ production lớn thường có cả hai ở hai tầng khác nhau.
nhánh hành vichỗ được sửanhánh tri thức

02RAG là một hệ thống, không phải một model

Một trong những sai lầm phổ biến nhất là đồng nhất RAG với vector search. Sơ đồ quen thuộc:

Document  ->  Embedding  ->  Vector DB
Query     ->  Embedding  ->  Top-k  ->  LLM

Đây là Naive RAG, rất hữu ích để học, nhưng chưa phải cách tốt để suy nghĩ về một hệ thống thật. Một RAG application đầy đủ nên được tách thành ít nhất hai flow lớn, như Hình 1 ở đầu bài.

Data pipeline trả lời câu hỏi: làm thế nào biến tri thức thô thành một biểu diễn có thể search tốt? File này parse ra sao, có giữ được heading, bảng và caption không, chunk theo token hay theo cấu trúc, metadata nào phải giữ, lưu dense vector hay cả sparse, tài liệu đổi thì re-index thế nào, permission metadata có được bảo toàn không.

Query pipeline trả lời câu hỏi: khi user hỏi, hệ thống phải làm gì để tìm được evidence tốt nhất và dùng evidence đó hiệu quả? Raw query có đủ rõ không, có cần rewrite hay decomposition, search bằng keyword hay vector hay cả hai, lấy bao nhiêu candidate, fuse thế nào, có cần rerank, bao nhiêu evidence thực sự nên đưa vào LLM, sắp xếp theo thứ tự nào, khi evidence không đủ thì có nên trả lời.

Điều này dẫn tới một nguyên tắc rất thực dụng khi debug:

Nếu câu trả lời cuối cùng sai, chưa chắc LLM là nơi sai.

~/diagrams/kien-truc-rag/failure-cascade.htmltương tác
mở riêng ↗
HìnhHàng trên là đường dữ liệu đi qua các khâu, từ tài liệu gốc tới câu trả lời. Hàng dưới là nguyên nhân thật sự tương ứng với từng khâu - parser làm phẳng bảng, chunk cắt mất heading, reranker loại nhầm evidence đúng, context quá nhiễu. Nhìn vào riêng câu trả lời cuối thì cả bốn trường hợp trông giống hệt nhau, và đó là lý do đổi sang một model lớn hơn thường không sửa được gì.
đường dữ liệukhâu biến đổinguyên nhân thật

Chất lượng cuối cùng của RAG gần với một phép nhân hơn là phép cộng: mỗi khâu đều có thể kéo kết quả về gần không, và không khâu nào bù được cho khâu trước nó.


03Data pipeline: từ dữ liệu thô đến knowledge index

Một RAG system chỉ tốt bằng biểu diễn tri thức mà nó search trên đó. Ta bắt đầu từ PDF, DOCX, PPTX, HTML, Markdown, wiki, email, database, API, ảnh, transcript — và cần đi tới những đơn vị tri thức search được.

~/diagrams/kien-truc-rag/data-pipeline.htmltương tác
mở riêng ↗
HìnhHàng trên là bốn khâu biến đổi: parse để khôi phục cấu trúc, clean để bỏ phần không có giá trị ngữ nghĩa, chunk kèm metadata để tạo đơn vị retrieval, rồi mới embed và đánh index. Hàng dưới là thứ mất đi nếu khâu tương ứng làm ẩu - mất bảng, mất heading, mất chủ ngữ, và cuối cùng là mất tính mới khi index không đuổi kịp nguồn.
chuỗi biến đổiđơn vị retrievalthứ có thể mất

Điểm đáng chú ý nhất trong hình này là hàng dưới. Mỗi khâu ingestion không chỉ thêm cấu trúc, nó còn có thể phá cấu trúc. Và cái đã mất ở khâu này thì không khâu nào sau đó khôi phục lại được.


04Parsing và chuẩn hoá: vấn đề thường bị đánh giá thấp

Giả sử tài liệu là một PDF có heading, đoạn văn, bảng, hình, footnote và layout nhiều cột. Nếu parser biến tất cả thành một khối text phẳng thì rất nhiều cấu trúc đã mất trước khi RAG kịp bắt đầu.

Lấy một bảng giá làm ví dụ. Một parser kém trả về:

Plan Price Limit Basic $10 10 GB Pro $30 100 GB

Một biểu diễn tốt hơn giữ lại được quan hệ hàng-cột:

## Pricing

| Plan  | Price | Limit  |
| Basic | $10   | 10 GB  |
| Pro   | $30   | 100 GB |

Khác biệt này ảnh hưởng trực tiếp tới chunking, embedding, retrieval và cả khả năng LLM đọc được context. Các document parser hiện đại như Docling hướng tới việc giữ hierarchy và metadata của tài liệu, đồng thời xuất chunk trực tiếp cho RAG pipeline.

Parsing không chỉ là extract text. Parsing là khôi phục semantic structure của tài liệu.

Sau parsing là chuẩn hoá. Header lặp trên mỗi trang, footer, số trang, navigation text, cookie notice, boilerplate, nội dung trùng, whitespace vỡ, artifact OCR — nếu không kiểm soát, tất cả những đoạn không có giá trị ngữ nghĩa đó sẽ được embed, được index, được retrieve, và cuối cùng ăn mất token trong context.

Nhưng có một lỗi ngược lại cũng nguy hiểm không kém: clean quá mạnh. Bỏ hết ký tự đặc biệt có thể làm vỡ bảng. Gộp hết xuống dòng có thể xoá ranh giới mục. Chuẩn hoá quá tay thường mất chính thứ giúp retrieval hoạt động.


05Chunking: đơn vị retrieval thực sự là gì

Search trên cả document thường quá thô. Một handbook 200 trang mà người dùng chỉ hỏi “thời gian thử việc của nhân viên kỹ thuật là bao lâu” thì ta không muốn nhét cả cuốn vào model. Ta muốn một đoạn đủ nhỏ để chính xác, nhưng đủ lớn để giữ ngữ cảnh.

Có nhiều chiến lược: fixed-size, recursive, sentence-based, semantic, structure-aware, parent-child, hierarchical, sliding window. Điều quan trọng ở bài kiến trúc chưa phải chọn thuật toán nào, mà là hiểu trade-off.

~/diagrams/kien-truc-rag/chunking-tradeoff.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi từ tài liệu tới một chunk retrieve được: cắt nhỏ, sinh đơn vị, bổ sung vài dòng context rồi mới embed. Hàng dưới là hai đầu của trade-off - cắt quá tay thì chunk mất chủ ngữ và mốc thời gian, cắt quá thô thì embedding trộn nhiều chủ đề và context tốn token cho phần nhiễu.
đường đi của chunkbước bổ sung contexthai đầu trade-off

Một chunk quá nhỏ như "The company grew by 18%." làm mất công ty nào, quý nào, chỉ số nào, so với mốc nào. Một chunk 3.000 token thì ngược lại: nó có thể chứa câu trả lời, nhưng embedding bị pha trộn nhiều chủ đề, retrieval kém chính xác, context tốn token, và LLM phải đọc rất nhiều nhiễu.

Anthropic từng chỉ ra một failure mode rất điển hình: chunk sau khi tách khỏi document có thể mất thông tin về chính document, về entity hoặc về thời gian. Contextual Retrieval giải quyết theo hướng bổ sung một đoạn context ngắn vào mỗi chunk trước khi tạo embedding và biểu diễn BM25. Một chunk như "Revenue increased 3% over the previous quarter." trở thành một đơn vị giàu ngữ cảnh hơn khi được gắn thêm "ACME Corp, Q2 2023 financial report.".

Điều rút ra không phải là mọi hệ thống đều phải dùng Contextual Retrieval, mà là một phép thử rất đơn giản:

Đọc riêng một chunk mà không nhìn tài liệu gốc. Nếu bạn không hiểu nó nói về ai, cái gì, thời điểm nào, thì retrieval cũng không hiểu.


06Metadata và index: một chunk sinh ra ba biểu diễn

Mỗi chunk thường cần cả content lẫn metadata:

{
  "chunk_id": "postgres-runbook#auth#0007",
  "document_id": "postgres-runbook",
  "content": "To rotate the database password...",
  "document_title": "PostgreSQL Production Runbook",
  "section_path": ["Authentication", "Password Rotation"],
  "page": 14,
  "source_url": "https://...",
  "version": "2026-08-14",
  "updated_at": "2026-08-14T11:25:00Z",
  "tenant_id": "platform-team",
  "access_groups": ["platform", "sre"]
}

Schema này quyết định rất nhiều khả năng ở cuối pipeline:

citation       <-  source_url / page
filter         <-  tenant / team / type
freshness      <-  version / updated_at
deduplication  <-  document_id / chunk_id
ACL            <-  access_groups
context        <-  title / section_path

Cách phân công công việc giữa vector và metadata khá rõ ràng: quan hệ ngữ nghĩa thuộc về vector và lexical ranking, còn ràng buộc chính xác thuộc về metadata filtering. Với câu hỏi “tìm policy engineering được cập nhật sau tháng 6/2026”, semantic search lo phần nội dung nào liên quan, còn filter lo phần department = engineering và updated_at >= 2026-06-01.

~/diagrams/kien-truc-rag/index-anatomy.htmltương tác
mở riêng ↗
HìnhCùng một chunk đi vào ba nhánh: nguyên văn cho inverted index để khớp đúng mặt chữ, dense vector cho ANN index để khớp nghĩa, và metadata cho filter index để khớp điều kiện cứng. Đó là lý do nên nghĩ về một knowledge store thay vì mặc định nghĩ tới một vector database duy nhất - và cũng là lý do metadata vừa là tín hiệu relevance vừa là security primitive.
nhánh ngữ nghĩaloại truy vấn phục vụnhánh chữ và điều kiện

Ngoài metadata có sẵn, ingestion pipeline còn có thể tạo thêm field bằng enrichment: title, summary, keywords, entities, category, section path, hoặc những câu hỏi mà chunk này có thể trả lời. Enrichment giúp retrieval nhưng có cái giá của nó — thêm inference lúc index, có thể sinh dữ liệu sai, tăng complexity, và bắt buộc re-index mỗi khi logic enrichment thay đổi. Vì vậy nó nên phục vụ một giả thuyết retrieval cụ thể, chứ không phải thêm field vì “có thể hữu ích”.

Indexing là một vòng đời

Tri thức trong production luôn thay đổi: tài liệu được tạo, sửa, xoá, đổi quyền, đổi phiên bản, đổi chỗ. Pipeline phải trả lời được: có incremental indexing không, khi document sửa thì chunk cũ bị xoá thế nào, dedup ra sao, re-embedding khi đổi model thế nào, có giữ version không, permission đổi thì bao lâu index mới cập nhật, có cơ chế phát hiện index cũ không.

Nếu source of truth đã đổi mà search index chưa đổi, câu trả lời của RAG sẽ khác sự thật hiện tại. Đây là một dạng lỗi hoàn toàn độc lập với chất lượng LLM.


07Query processing: câu người dùng gõ chưa phải câu nên đem đi search

Người dùng nói chuyện với ứng dụng, không nói chuyện với search engine. Trong một hội thoại:

User:       So sánh PostgreSQL và MySQL cho workload analytics.
Assistant:  ...
User:       Còn chi phí thì sao?

Nếu embed thẳng câu thứ hai, retrieval mất gần như toàn bộ chủ thể. Thứ ta muốn đem đi search là “so sánh chi phí vận hành PostgreSQL và MySQL cho workload analytics”. Đó là query rewriting.

~/diagrams/kien-truc-rag/query-transformation.htmltương tác
mở riêng ↗
HìnhHàng trên là bốn phép biến đổi có thể đứng trước retrieval: rewrite để ghép lại ngữ cảnh hội thoại, decompose để tách câu hỏi nhiều vế, expand hoặc HyDE để phủ nhiều cách diễn đạt, rồi mới ra retrieval query thật. Hàng dưới là thứ xảy ra nếu bỏ qua từng phép - mất chủ ngữ, chỉ đáp được một vế, hoặc câu hỏi nằm quá xa passage về mặt biểu diễn.
chuỗi biến đổitruy vấn đem đi searchnếu bỏ qua

Query expansion xuất phát từ chỗ một ý có nhiều cách diễn đạt. "SSO login error" có thể liên quan tới tài liệu chứa single sign-on, identity provider, SAML authentication hay federated login. Sinh nhiều biến thể rồi retrieve nhiều lần và fusion lại có thể tăng recall, nhưng đổi lại là thêm search call và thêm nhiễu.

Query decomposition xử lý câu hỏi nhiều vế. “So sánh SLA của hệ thống tại East US và West Europe, và cho biết region nào phù hợp hơn với workload của team” thực chất là ba câu hỏi. Microsoft phân biệt khá rõ giữa decomposition theo một flow cố định do orchestrator định nghĩa, và agentic RAG nơi agent tự quyết định cách tách dựa trên reasoning lúc chạy.

HyDE đi theo hướng khác hẳn. Thay vì embed câu hỏi, nó để LLM viết trước một tài liệu giả định trả lời câu hỏi đó, rồi embed tài liệu giả định để tìm hàng xóm phù hợp trong corpus. Cách này hữu ích khi khoảng cách biểu diễn giữa câu hỏi và passage quá lớn, nhưng không miễn phí: nội dung giả định có thể sai chi tiết, và nó thêm một model call vào latency.


08Dense, sparse và hybrid retrieval

Dense retrieval ánh xạ cả query lẫn chunk vào cùng một không gian rồi tìm láng giềng gần nhất. Nó đặc biệt tốt khi query và document dùng từ ngữ khác nhau nhưng nghĩa gần nhau — "How do I terminate my account?" và "Steps for closing a customer subscription" gần như không trùng chữ nào.

Lexical retrieval như BM25 mạnh ở hướng ngược lại: khi token chính xác mới là thứ quan trọng. Mã sản phẩm, mã lỗi, tên riêng, endpoint API, tên class, số hiệu điều khoản, version, acronym hiếm. Với một định danh gần như tuỳ ý kiểu ERR_CONNECTION_4821, embedding không nhất thiết là biểu diễn tốt nhất, còn inverted index thì được thiết kế đúng để tận dụng độ hiếm của term.

Vì dense và sparse hỏng theo hai kiểu khác nhau, rất nhiều kiến trúc RAG chạy cả hai rồi hợp nhất kết quả.

~/diagrams/kien-truc-rag/hybrid-fusion.htmltương tác
mở riêng ↗
HìnhCùng một truy vấn chạy song song qua hai đường: dense trên embedding và BM25 trên inverted index. Hai ranked list đó không cùng thang đo nên không cộng thẳng điểm được, mà được hợp nhất theo thứ hạng bằng Reciprocal Rank Fusion. Tài liệu đứng cao ở nhiều list sẽ được đẩy lên trong candidate set cuối cùng.
nhánh densebước hợp nhấtnhánh lexical

Chi tiết đáng nhớ ở đây là vì sao phải fuse theo rank chứ không theo score. Cosine similarity và điểm BM25 không cùng thang đo, nên cộng thẳng chúng không có ý nghĩa thống kê. RRF bỏ điểm thô đi và chỉ dùng thứ hạng:

RRF(d) = tổng theo từng list của  1 / (k + rank_i(d))

Azure AI Search hiện thực hybrid search bằng cách chạy full-text và vector query song song rồi hợp nhất bằng RRF, và tài liệu của họ mô tả k thường dùng quanh 60. Anthropic cũng mô tả pipeline dùng BM25 cộng embeddings cộng rank fusion trước khi đưa top candidate sang bước sau.

Song song với đó, metadata filtering thu hẹp không gian tìm kiếm theo những ràng buộc mà similarity không nên đụng tới: tenant_id, language, region, product, version, khoảng ngày, loại tài liệu, access group. Filter không chỉ tăng relevance — nó liên quan trực tiếp tới security boundary, và ta sẽ quay lại điểm này ở phần cuối.


09Rerank và top-k: hai bài toán khác nhau

Retriever và reranker tối ưu hai thứ khác nhau. Retriever tối ưu recall và tốc độ: nó phải quét hàng triệu chunk và trả về vài chục candidate thật nhanh. Reranker tối ưu precision: nó đọc query và passage cùng nhau thay vì so hai vector độc lập, nên đắt hơn nhiều nhưng chính xác hơn nhiều.

~/diagrams/kien-truc-rag/retrieve-then-rerank.htmltương tác
mở riêng ↗
HìnhHàng trên là hình phễu quen thuộc: quét nhanh trên toàn index để lấy rộng vài chục candidate, rồi chấm kỹ để chọn hẹp còn năm tới mười evidence. Hàng dưới là ba cái giá - reranker không tạo ra evidence mới nên recall thiếu ở chặng trước là hết cứu, chi phí rerank tăng theo số candidate, và lấy top-k quá lớn thì evidence tốt lại chìm giữa một context dài.
hai chặng chínhchặng precisioncái giá đi kèm

Anthropic báo cáo trong thí nghiệm Contextual Retrieval của họ rằng reranking tiếp tục giảm tỉ lệ retrieval thất bại so với contextual BM25 cộng contextual embeddings mà không rerank, đồng thời nhấn mạnh trade-off về latency và chi phí khi rerank nhiều candidate hơn.

Có một trực giác rất phổ biến và rất nguy hiểm ở chặng này:

retrieve nhiều hơn  =  LLM có nhiều thông tin hơn  =  answer tốt hơn

Thực tế, nhiều context hơn cũng đồng nghĩa với nhiều nội dung lạc đề hơn, nhiều mâu thuẫn hơn, nhiều token hơn, và latency cùng chi phí cao hơn. Paper Lost in the Middle cho thấy khả năng sử dụng long context của LLM không đồng đều theo vị trí: hiệu năng có thể giảm khi thông tin liên quan nằm ở giữa một context dài.

Điều đó dẫn tới một cách phát biểu lại mục tiêu của retrieval:

Mục tiêu không phải là đưa thật nhiều thứ vào context, mà là đưa đúng evidence, với đủ ngữ cảnh, theo một cấu trúc mà model dùng được hiệu quả.

Và đó chính là lúc context engineering bắt đầu.


10Context engineering: tầng phải tự viết

Sau reranking ta có một danh sách evidence. Cách làm ngây thơ là nối chúng bằng hai dòng trống rồi dán vào prompt. Một pipeline production thường cần hẳn một context builder riêng.

~/diagrams/kien-truc-rag/context-builder.htmltương tác
mở riêng ↗
HìnhHàng trên là bốn việc context builder phải làm: lọc bỏ phần dưới ngưỡng và gộp phần trùng, mở rộng sang chunk cha rồi nén phần thừa, cắt theo ngân sách token, và cuối cùng sắp xếp rồi ghép thành prompt kèm source_id. Hàng dưới là bốn cái bẫy tương ứng nếu bỏ qua từng bước - trả lời khi không đủ evidence, một fact được đếm hai lần chỉ vì trùng đoạn, thứ tự bị bỏ mặc, và mất ánh xạ về nguồn nên không dựng lại được citation.
chuỗi xử lýràng buộc tokenbẫy tương ứng

Lọc và ngưỡng. Không phải candidate nào cũng phải vào prompt. Dưới ngưỡng thì bỏ. Và nếu không còn evidence nào đủ mạnh, hệ thống nên được phép nói rằng chưa tìm thấy đủ thông tin, thay vì ép LLM hoàn thành mọi query.

Khử trùng lặp. Hybrid và multi-query retrieval hay trả về nhiều chunk gần giống nhau, hoặc cùng nội dung xuất hiện ở nhiều nguồn. Đưa hết vào context thì vừa lãng phí token, vừa khiến một fact được “bỏ phiếu” nhiều lần chỉ vì nó bị trùng.

Mở rộng ngữ cảnh. Đôi khi chunk retrieve được rất chính xác nhưng thiếu ngữ cảnh: "Thời hạn là 30 ngày." là một retrieval đúng, nhưng generation layer chưa biết thời hạn của cái gì. Một chiến lược phổ biến là retrieve chunk con rồi mở rộng sang chunk cha hoặc chunk lân cận. Nói cách khác, ta tách retrieval unit khỏi generation context unit. Đây là một insight quan trọng: chunk tối ưu để search chưa chắc là chunk tối ưu để LLM đọc.

Thứ tự. Vì model dùng thông tin không đồng đều theo vị trí trong context dài, thứ tự evidence không phải chi tiết vô nghĩa. Context builder có thể ưu tiên điểm cao, đa dạng nguồn, trình tự thời gian, nhóm logic hay thứ bậc nguồn. Không có quy tắc đúng cho mọi ứng dụng, nhưng thứ tự phải được xem như một tham số có thể đánh giá, không phải hành vi ngẫu nhiên.

Nén. Một chunk 800 token có thể chỉ có hai câu liên quan. Context compression cố biến tài liệu đã retrieve thành đoạn liên quan hoặc bản tóm tắt để giảm nhiễu. Rủi ro là bản nén do model sinh ra có thể mất chi tiết hoặc bóp méo fact, nên vẫn phải giữ ánh xạ về evidence gốc để citation trỏ đúng chỗ.

Ngân sách token. Context window là tài nguyên hữu hạn và phải chia cho nhiều thứ:

available_context_budget
  =  model_context_window
   -  system_tokens
   -  conversation_tokens
   -  query_tokens
   -  reserved_output_tokens
   -  other_tool_tokens

Từ ngân sách đó, context builder mới quyết định giữ evidence nào, mỗi nguồn bao nhiêu token, và bao nhiêu nguồn.


11Grounded generation và citation

Sau khi context được dựng, prompt thường có dạng:

SYSTEM

Bạn là trợ lý hỏi đáp trên tài liệu nội bộ.

Quy tắc:
1. Chỉ dùng thông tin trong EVIDENCE cho mọi factual claim.
2. Không suy đoán nếu evidence không đủ.
3. Khi evidence mâu thuẫn, nêu rõ sự mâu thuẫn.
4. Trích dẫn source_id của evidence đã dùng.

EVIDENCE

[source: policy-2026, section: 4.2]
...

[source: handbook-2026, page: 27]
...

QUESTION

...

Chất lượng RAG vì vậy phụ thuộc đồng thời vào ba thứ:

retrieval quality  x  context quality  x  generation behavior

Retriever tìm đúng nhưng prompt cho phép model bỏ qua evidence thì hệ thống vẫn hallucinate như thường.

Cũng cần nói rõ: grounding không có nghĩa là model không được biết gì khác. Tuỳ use case, ta có thể chọn strict grounding chỉ dùng evidence đã retrieve, grounding-first ưu tiên evidence và chỉ dùng kiến thức chung để giải thích, hay open synthesis coi evidence là một nguồn trong nhiều nguồn. Với legal, finance, compliance hay quy trình kỹ thuật, strict grounding thường quan trọng hơn; với một trợ lý học tập thì kiến thức chung có thể được phép nhiều hơn. Đây là product requirement, không phải thuộc tính tự động của RAG.

Citation đáng tin cậy đòi hỏi source identity được giữ từ đầu tới cuối:

Original Document
  ->  Chunk + document_id + page + section + url
  ->  Retriever
  ->  Context Builder
  ->  LLM / citation mapper
  ->  Answer + Source

Nếu ingestion chỉ lưu "text": "..." và bỏ hết source metadata thì rất khó dựng lại provenance chính xác ở cuối pipeline. Đây là một trong những quyết định rẻ nhất lúc đầu và đắt nhất khi phải sửa lại sau.


12Từ Naive RAG đến các kiến trúc hiện đại

Không có một kiến trúc duy nhất tên là “modern RAG”. RAG đã phát triển từ một pipeline tuyến tính đơn giản thành nhiều pattern khác nhau, và chúng chồng lấn lên nhau chứ không phải một taxonomy tuyệt đối.

~/diagrams/kien-truc-rag/rag-architectures.htmltương tác
mở riêng ↗
HìnhHàng trên là hướng tiến hoá: Naive RAG thêm các lớp trước và sau retrieval để thành Advanced, thêm router trên nhiều nguồn để thành Modular, rồi trao quyền quyết định cho agent. Hàng dưới là thứ đi kèm mỗi bước - thêm chỗ có thể hỏng, nhu cầu về nhiều loại nguồn ngoài vector database, và GraphRAG như một nhánh riêng chứ không phải bước kế tiếp.
hướng tiến hoábước trao quyềnthứ đi kèm

Naive RAG đơn giản, dễ debug, latency và chi phí thấp, rất tốt để dựng baseline. Hạn chế là raw query có thể retrieve kém, keyword và định danh chính xác dễ bị bỏ qua, top-k theo vector similarity chưa chắc là ranking tốt nhất, context dễ nhiễu, và multi-hop query gần như không xử lý được.

Advanced RAG bổ sung các stage trước và sau retrieval: query rewrite, multi-query, decomposition, HyDE, routing ở phía trước; fusion, reranking, filtering, compression, dedup, parent expansion, ordering ở phía sau. Ở đây “advanced” không đồng nghĩa với “luôn tốt hơn”: mỗi component thêm vào là thêm latency, chi phí, moving part, failure mode và bề mặt cần đánh giá.

Hãy bắt đầu bằng standard RAG đủ đơn giản để đo được. Chỉ thêm complexity khi evaluation chỉ ra một failure cụ thể.

Modular RAG xuất hiện khi retrieval không còn là một hàm duy nhất. Câu hỏi “doanh thu tháng này của khách hàng X và điều khoản thanh toán trong hợp đồng là gì” có một nửa nằm trong SQL và một nửa nằm trong index hợp đồng. Lúc đó cần một router điều phối giữa vector search, SQL, web search, knowledge graph và internal API.

Agentic RAG chuyển một phần quyết định từ code cố định sang reasoning lúc chạy.

~/diagrams/kien-truc-rag/agentic-rag-loop.htmltương tác
mở riêng ↗
HìnhHàng trên là năm bước của một lượt: nhận câu hỏi, lập kế hoạch, chọn nguồn, xem evidence đã đủ chưa, rồi mới trả lời. Mũi tên quay ngược là chỗ khác biệt thật sự - agent tự quyết định có cần thêm một vòng retrieval nữa không. Hàng dưới là cái giá của vòng lặp đó: mỗi vòng thêm token và latency, và cùng một câu hỏi có thể đi những đường khác nhau nên kết quả khó tái lập.
một lượt quyết địnhvòng lặp quay lạicái giá phải trả

Microsoft phân biệt standard RAG — orchestrator chạy một chuỗi cố định — với agentic RAG, nơi agent quyết định search nguồn nào, khi nào search, và có cần thêm bước retrieval nữa không. Điểm quan trọng:

Agentic RAG không phải là “RAG nhưng viết bằng agent framework”. Khác biệt nằm ở dynamic decision-making trong retrieval workflow.

Và cũng vì vậy, nếu phần lớn query chỉ cần một câu hỏi, một index, một lần retrieval, thì agent loop chỉ thêm latency, token cost, tính bất định và độ khó khi debug. Tài liệu kiến trúc của Microsoft cũng nhấn mạnh standard RAG phù hợp hơn nếu một lần search giải quyết được query.

GraphRAG phục vụ một loại câu hỏi khác hẳn. Vector RAG rất mạnh với “đoạn nào gần nghĩa nhất với câu hỏi này”, nhưng yếu với “những nhóm chủ đề lớn nhất trong toàn bộ bộ tài liệu là gì” hay “entity A liên hệ với entity B qua những sự kiện và tổ chức nào”. Với những câu đó, không tồn tại một chunk duy nhất chứa toàn bộ câu trả lời. GraphRAG dựng thêm một biểu diễn graph gồm entity, quan hệ, community và community report, rồi cung cấp nhiều chiến lược truy vấn:

Local Search   kết hợp graph data và raw text quanh một entity cụ thể
Global Search  dùng community report để reasoning trên cả dataset
DRIFT Search   kết hợp community context rộng với khám phá local tiếp theo

Chi phí dựng graph đáng kể, nên GraphRAG không nên được coi là mặc định thay thế cho hybrid retrieval thông thường.


13Một hệ tham chiếu hoàn chỉnh

Ghép tất cả lại cho một use case cụ thể: trợ lý hỏi đáp trên bộ tài liệu kỹ thuật nội bộ gồm PDF, Markdown và runbook. Hình dạng tổng thể chính là Hình 1 ở đầu bài — offline dựng index, online phục vụ câu hỏi.

Offline pipeline, dưới dạng pseudo-code:

documents = load_sources()

parsed = [parse(document) for document in documents]
normalized = [normalize(document) for document in parsed]

chunks = chunk(normalized)

enriched = attach_metadata(
    chunks,
    fields=[
        "document_id", "title", "section", "page",
        "version", "source_url", "access_groups",
    ],
)

vectors = embed([c.text for c in enriched])

dense_index.upsert(chunks=enriched, vectors=vectors)
text_index.upsert(chunks=enriched)

Đây chỉ là hình dạng. Pipeline production còn phải xử lý retry, idempotency, xoá, cập nhật, versioning, dedup, batching, rate limit, dead-letter queue và thay đổi permission.

Online pipeline:

def answer(user, query, conversation):
    retrieval_query = rewrite_query(query=query, conversation=conversation)
    filters = build_filters(user=user, query=retrieval_query)

    dense_hits = dense_search(retrieval_query, filters=filters, top_k=30)
    lexical_hits = bm25_search(retrieval_query, filters=filters, top_k=30)

    candidates = rrf(dense_hits, lexical_hits)
    evidence = rerank(query=retrieval_query, candidates=candidates, top_k=8)

    context = build_context(evidence, max_tokens=6000)
    if not context.is_sufficient:
        return abstain()

    return generate_grounded_answer(
        query=query,
        context=context,
        include_citations=True,
    )

Nhìn vào hàm này, có thể thấy RAG không còn là vector_db.similarity_search(query) mà là một orchestrated information pipeline.

Tech stack map

Không có một stack duy nhất tốt nhất. Một bản đồ tham khảo:

Layer Nhiệm vụ Ví dụ lựa chọn
Source connectors Đọc tri thức custom connector, filesystem, DB/API connector
Parsing Trích cấu trúc và nội dung Docling, Unstructured, PyMuPDF, parser riêng
Chunking Tạo retrieval unit splitter của framework, structure-aware chunker
Embedding Text sang dense vector hosted API, Sentence Transformers, model domain
Lexical search Truy xuất chính xác Elasticsearch/OpenSearch, engine hỗ trợ BM25
Vector search Truy xuất ngữ nghĩa pgvector, Qdrant, Milvus, Weaviate, Pinecone
Metadata Filter, bảo mật, ngữ cảnh field của search engine, payload của vector store
Fusion Hợp nhất ranked list RRF, weighted rank fusion
Reranking Candidate sang evidence cross-encoder, hosted reranker, LLM reranking
Orchestration Ghép pipeline Python, LangChain, LlamaIndex, Haystack
Generation Sinh câu trả lời LLM hosted hoặc self-hosted
Observability Trace pipeline tracing tương thích OpenTelemetry, telemetry riêng
Evaluation Chất lượng và regression DeepEval, benchmark riêng

Nhưng quan trọng hơn tên tool là interface giữa các layer:

Parser           ->  Structured Document
Chunker          ->  Chunk + Metadata
Retriever        ->  Candidate + Score + Source
Reranker         ->  Evidence + Relevance
Context Builder  ->  Grounded Context
Generator        ->  Answer + Citation

Nếu các interface này rõ ràng, việc thay một vector database hay một embedding model không đòi hỏi viết lại cả hệ thống.


14Từ demo RAG đến production RAG

Một demo có thể thành công chỉ với upload PDF, hỏi vài câu, thấy câu trả lời khá đúng. Production phải trả lời nhiều câu hỏi khó hơn nhiều.

Đánh giá không chỉ ở câu trả lời cuối

Vì RAG là hệ multi-component, evaluation cũng nên tách theo tầng:

Query processing   độ rõ của truy vấn sau biến đổi
Retrieval          recall, precision, relevance
Context            context relevance, evidence coverage
Generation         faithfulness, answer relevancy, task correctness

Microsoft và Databricks đều nhấn mạnh việc đánh giá ở mức component song song với end-to-end, bởi thay đổi ở parsing, chunking hay retrieval đều có thể lan xuống câu trả lời cuối. Toàn bộ kiến trúc evaluation và các metric cụ thể là nội dung của bài về đánh giá RAG với DeepEval trong series này. Ở bài kiến trúc, chỉ cần giữ một ý:

Evaluation không phải bước cuối của RAG. Nó là feedback loop bao quanh toàn pipeline.

Observability: phải trace được các bước trung gian

Khi một người dùng báo “câu trả lời này sai”, ta cần dựng lại được toàn bộ đường đi.

~/diagrams/kien-truc-rag/rag-trace.htmltương tác
mở riêng ↗
HìnhMột request đi qua bốn chặng có thể đo riêng: query processing, hybrid retrieval chạy song song hai đường, reranking, và generation. Chỉ log câu hỏi vào và câu trả lời ra thì không dựng lại được rank, score, filter hay prompt version đã dùng - mà đó chính là những thứ cần để root-cause. Các con số thời gian ở đây là ví dụ minh hoạ hình dạng của latency budget.
chặng chínhhai đường song songđường trả về

Một trace hữu ích cần trả lời được: query gốc là gì, rewrite thành gì, filter nào được áp, dense trả gì, BM25 trả gì, fusion xếp thế nào, reranker chấm bao nhiêu, context cuối gồm chunk nào, prompt version nào, model nào, latency từng chặng và chi phí token bao nhiêu.

Bảo mật, độ mới, chi phí và độ tin cậy

~/diagrams/kien-truc-rag/production-concerns.htmltương tác
mở riêng ↗
HìnhQuanh core pipeline còn năm mối lo không nằm trong chất lượng câu trả lời: quyền đọc phải được thực thi ngay trong retrieval chứ không ở tầng hiển thị, độ mới của index so với nguồn, ngân sách latency và chi phí cho từng chặng, hành vi khi một thành phần chết, và vòng lặp evaluation bao quanh tất cả.
core pipelineràng buộc bảo mậtmối lo vận hành

Security. Một lỗi cực kỳ nghiêm trọng là user không được đọc tài liệu nhưng retriever vẫn lấy chunk về, LLM nhìn thấy chunk đó, và câu trả lời rò rỉ thông tin. Permission không nên chỉ áp dụng ở tầng UI sau retrieval — security boundary phải nằm trước hoặc bên trong retrieval. Microsoft khuyến nghị document-level access control bằng permission metadata được index và enforce ngay tại query time. Với SaaS đa khách hàng, tuỳ nền tảng có thể dùng index riêng, namespace, collection, partition hay metadata filter nghiêm ngặt; Pinecone chẳng hạn mô tả namespace-based isolation như một pattern multitenancy, còn metadata filtering thì có trade-off về hiệu năng và không phải lúc nào cũng là ranh giới cô lập tốt nhất.

Freshness. Knowledge system cần một SLA rõ ràng cho câu hỏi “nguồn cập nhật xong thì bao lâu RAG nhìn thấy”. Một trang FAQ có thể chấp nhận index hằng ngày; một incident runbook thì cần gần realtime.

Latency và cost. Một request có thể tiêu 200 ms cho rewrite, 50 ms cho dense, 30 ms cho BM25, 180 ms cho rerank và 1500 ms cho generation. Thêm một agent loop với planning, ba subquery, vài tool call và một vòng retrieval nữa thì con số tổng có thể nhân lên đáng kể. Chi phí cũng không chỉ nằm ở lần generation cuối: parsing, OCR, enrichment và embedding lúc index đều tốn tiền. Một tối ưu thường gặp là chuyển chi phí từ online sang offline — ví dụ precompute contextual enrichment lúc indexing thay vì sinh nó tại query time.

Caching. Có nhiều tầng cache khác nhau: parsed document, embedding, retrieval, reranker, prompt/context và câu trả lời cuối. Mỗi tầng có bài toán invalidation riêng, và cache câu trả lời cuối là nguy hiểm nhất nếu tài liệu nguồn đã cập nhật. Cache key ở production thường phải phụ thuộc vào query, phạm vi quyền của user, index version, prompt version và model version.

Reliability. Embedding API chết, vector store timeout, reranker timeout, LLM rate limit, agent loop vượt ngân sách, nguồn không truy cập được — hệ thống cần biết trước sẽ retry, fallback, bỏ reranker, chỉ dùng lexical, trả lời một phần hay từ chối. Production RAG không chỉ tối ưu chất lượng ở trường hợp tốt nhất, nó cần một cách hỏng đoán trước được.


RAG là một information system quanh LLM

Nếu chỉ giữ lại một điều sau bài này:

RAG  !=  Vector Database + LLM

RAG  =  Knowledge Preparation
      +  Information Retrieval
      +  Context Engineering
      +  Grounded Generation
      +  Evaluation / Operations

Giá trị thật của mental model này lộ ra khi hệ thống hỏng. Thay vì đổi model rồi hy vọng, ta có một danh sách câu hỏi đi đúng thứ tự của pipeline:

Parser có làm mất thông tin không?
    ->  Chunk có thiếu ngữ cảnh không?
    ->  Embedding có retrieve được entity đó không?
    ->  BM25 có cần thiết ở đây không?
    ->  Filter có loại nhầm evidence không?
    ->  Fusion có hợp lý không?
    ->  Reranker có chọn đúng không?
    ->  Context có quá nhiễu không?
    ->  Prompt có ép grounding đủ mạnh không?
    ->  LLM có thực sự dùng evidence không?

Những gì nên mang theo:

  • RAG không sửa weights, nó sửa thông tin có sẵn tại thời điểm inference;
  • chất lượng cuối gần với phép nhân của các khâu hơn là phép cộng, nên một khâu hỏng sớm thì không khâu nào sau bù được;
  • parsing là khôi phục cấu trúc chứ không chỉ trích text, và cái đã mất ở đó không lấy lại được;
  • chunk phải đứng vững khi tách khỏi tài liệu gốc, nếu không thì retrieval cũng không hiểu nó;
  • metadata vừa là tín hiệu relevance vừa là security primitive, và là thứ dựng nên citation;
  • dense và sparse hỏng theo hai kiểu khác nhau, nên hybrid tồn tại vì lý do kiến trúc chứ không phải vì mốt;
  • retriever tối ưu recall, reranker tối ưu precision, và reranker không tạo ra evidence mà retriever đã bỏ sót;
  • nhiều context hơn không đồng nghĩa với câu trả lời tốt hơn;
  • đơn vị tối ưu để search khác đơn vị tối ưu để LLM đọc;
  • “advanced” không đồng nghĩa với “tốt hơn”, mỗi lớp thêm vào là thêm một bề mặt cần đánh giá.

Bài tiếp theo trong series sẽ đi sâu vào phần quan trọng nhất của nửa đầu pipeline: chunking, embedding, vector search, BM25, hybrid retrieval, fusion và reranking thực sự hoạt động như thế nào, và nên tối ưu chúng ra sao. Nếu bạn muốn nhảy thẳng sang nửa sau — làm sao biết hệ thống của mình tốt hay tệ ở đâu — thì bài về đánh giá RAG với DeepEval là chỗ bắt đầu.


Tài liệu tham khảo

Trương Đức Dũng

AI Engineer - Hà Nội

Tập trung vào LLM, RAG và các hệ thống AI cần chạy ổn định trong môi trường vận hành thật.