Từ Model-Centric đến Data-Centric: Xây dựng hệ thống RAG '"Production-Ready" cho doanh nghiệp
Bạn đã bao giờ build một Chatbot RAG chạy mượt mà trên máy cá nhân, nhưng vừa deploy lên production thì nó lại trả lời sai bét? Không phải mình bạn đâu. Đó là một bài toán kinh điểm của AI Engineering năm 2026 - và hôm nay hãy cùng mình mổ sẻ no.
1. Tại sao RAG demo thì tốt, Production thì tệ?
Câu trả lời ngắn gọn là Bạn đang tập trung sai chỗ.
Hầu hết mọi người khi làm RAG đều dành 80% thời gian chọn model (GPT-4 hay Claude), còn 20% còn lại mới lo đến dữ liệu. Thực tết thì phải ngược lại hoàn toàn.
Paper "RAG: Automated Evaluation of Retrieval Augmented Generation" (Es, et al., 2023) chỉ ra rằng: Chất lượng retrieval ảnh hưởng đến kết quả cuối nhiều hơn việc chọn model đến 60%. Nghĩa là dùng GPT-3.5 với retrieval tốt vẫn thắng GPT-4 với retrieval tệ.
Mindset shift: Đừng hỏi 'Dùng model nào?" mà hãy hỏi "Dữ liệu của mình có sạch không? Chunk có hợp lý không? Tìm kiếm có chính xác không?"
2. Kiến trúc RAG nâng cao (Advanced RAG)
RAG cơ bản chỉ có 3 bước: Chunk -> Embed -> Retrieval. Đó là lý do nó thất bại ở Production.
graph TD
subgraph "Naive RAG - Hay thất bại"
A1[Tài liệu] --> |Fixed Chunking| B1[Vector DB]
Q1[Câu hỏi] --> |Semantic Search| B1
B1 --> C1[LLM] --> R1[Câu trả lời]
end
subgraph "Advanced RAG - Production-Ready"
A2[Tài liệu] --> |Semantic chunking + Metadata| B2[Vector DB]
Q2[Câu hỏi gốc] --> QT[Query Transformer / Viết lại/ Mở rộng query]
QT[Hybrid Search / Vector Search + Keyword Search]
B2 --> HS
HS --> RR[Re-ranker / Top-k]
RR --> LLM[LLM] --> R2[Câu trả lời chính xác]
end
- 3 Kỹ thuật cốt lõi
-
Query Transformer - Viết lại câu hỏi trước khi tìm
Người dùng hỏi: Chính sách nghỉ phép?"
-> AI viết lại thành 3 query: "Số ngày nghỉ phép hàng năm", "Quy trình xin nghỉ phép", "Nghỉ phép có lương"
-> Tìm kiếm cả 3, lấy union kết quả
-
Hybrid Search — Kết hợp 2 loại tìm kiếm**
Semantic Search (Vector) Keyword Search (BM25) Mạnh Tìm theo ý nghĩa Tìm theo từ khóa chính xác Yếu Bỏ sót thuật ngữ kỹ thuật Không hiểu ngữ nghĩa Hybrid Kết hợp cả hai → tốt nhất -
Re-ranking — Lọc lại trước khi đưa cho LLM
Lấy top 20 kết quả từ search → dùng Cross-encoder model chấm điểm lại → chỉ giữ top 3-5 thực sự liên quan
3. Code thực tế
# advanced_rag.py
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain.retrievers import ContextualCompressionRetriever
# ── 1. HYBRID SEARCH ──────────────────────────────────
def build_hybrid_retriever(docs, vectorstore):
# Semantic search
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 20})
# Keyword search (BM25)
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 20
# Kết hợp 50/50
hybrid = EnsembleRetriever(
retrievers=[vector_retriever, bm25_retriever],
weights=[0.5, 0.5]
)
return hybrid
# ── 2. RE-RANKING ─────────────────────────────────────
def add_reranker(hybrid_retriever):
reranker_model = HuggingFaceCrossEncoder(
model_name="BAAI/bge-reranker-v2-m3" # Hỗ trợ tiếng Việt tốt
)
compressor = CrossEncoderReranker(model=reranker_model, top_n=5)
return ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=hybrid_retriever
)
# ── 3. QUERY TRANSFORMATION ───────────────────────────
def multi_query_search(question: str, retriever, llm) -> list:
# Tạo 3 phiên bản query khác nhau
response = llm.invoke(f"""Tạo 3 câu hỏi khác nhau cùng ý nghĩa với: "{question}"
Mỗi câu trên một dòng, không đánh số.""")
queries = [question] + response.content.strip().split('\n')
# Tìm kiếm với tất cả queries, dedup kết quả
all_docs = []
seen_ids = set()
for q in queries:
docs = retriever.invoke(q)
for doc in docs:
doc_id = hash(doc.page_content)
if doc_id not in seen_ids:
all_docs.append(doc)
seen_ids.add(doc_id)
return all_docs
# ── 4. RAG PIPELINE HOÀN CHỈNH ────────────────────────
def advanced_rag_answer(question: str, docs, vectorstore) -> str:
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
hybrid_retriever = build_hybrid_retriever(docs, vectorstore)
final_retriever = add_reranker(hybrid_retriever)
relevant_docs = multi_query_search(question, final_retriever, llm)
context = "\n\n".join([d.page_content for d in relevant_docs[:5]])
response = llm.invoke(f"""Dựa vào tài liệu sau, trả lời câu hỏi.
Nếu không có thông tin, hãy nói "Tôi không tìm thấy thông tin này."
Tài liệu:
{context}
Câu hỏi: {question}""")
return response.content
4. Đánh giá hệ thống với RAGAS
Bạn không thể cải thiện thứ bạn không đo được. Dùng RAGAS để đo 3 chỉ số quan trọng nhất:
graph LR
RAG[Hệ thống RAG] --> F["Faithfulness\nCâu trả lời có\ntrung thực với tài liệu?"]
RAG --> AR["Answer Relevance\nCó đúng trọng tâm\ncâu hỏi không?"]
RAG --> CP["Context Precision\nTài liệu lấy ra có\nthực sự hữu ích không?"]
F & AR & CP --> Score["RAG Score\nMục tiêu: > 0.8"]
# evaluate_rag.py
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
from datasets import Dataset
# Chuẩn bị test dataset
test_data = {
"question": ["Chính sách nghỉ phép là gì?", ...],
"answer": ["Nhân viên được 12 ngày/năm...", ...],
"contexts": [["Đoạn tài liệu liên quan..."], ...],
"ground_truth": ["Câu trả lời đúng mẫu...", ...]
}
dataset = Dataset.from_dict(test_data)
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_precision])
print(result)
# {'faithfulness': 0.87, 'answer_relevancy': 0.82, 'context_precision': 0.79}
"ground_truth": ["Câu trả lời đúng mẫu...", ...]
}
dataset = Dataset.from_dict(test_data)
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_precision])
print(result)
{'faithfulness': 0.87, 'answer_relevancy': 0.82, 'context_precision': 0.79}
"ground_truth": ["Câu trả lời đúng mẫu...", ...]
}
dataset = Dataset.from_dict(test_data)
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_precision])
print(result)
{'faithfulness': 0.87, 'answer_relevancy': 0.82, 'context_precision': 0.79}
"ground_truth": ["Câu trả lời đúng mẫu...", ...]
}
dataset = Dataset.from_dict(test_data)
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_precision])
print(result)
{'faithfulness': 0.87, 'answer_relevancy': 0.82, 'context_precision': 0.79}
### 5. Chunking — Chi tiết nhỏ, ảnh hưởng lớn
Đây là bước mà 90% người làm RAG xử lý qua loa nhất, nhưng lại ảnh hưởng nhiều nhất đến chất lượng.
```python
# semantic_chunking.py
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
# Fixed chunking — Cắt bừa bãi, bị đứt ý giữa chừng
# text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
# Semantic chunking — Cắt theo ranh giới ý nghĩa
splitter = SemanticChunker(
embeddings=OpenAIEmbeddings(),
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=95
)
docs = splitter.create_documents([your_text])
# Thêm metadata cho mỗi chunk để filter sau này
for i, doc in enumerate(docs):
doc.metadata.update({
"chunk_id": i,
"department": "HR", # Filter theo phòng ban
"doc_type": "policy", # Filter theo loại tài liệu
"created_at": "2026-01-01" # Filter theo ngày
})
5. Chunking — Chi tiết nhỏ, ảnh hưởng lớn
Đây là bước mà 90% người làm RAG xử lý qua loa nhất, nhưng lại ảnh hưởng nhiều nhất đến chất lượng.
# semantic_chunking.py
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
# Fixed chunking — Cắt bừa bãi, bị đứt ý giữa chừng
# text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
# Semantic chunking — Cắt theo ranh giới ý nghĩa
splitter = SemanticChunker(
embeddings=OpenAIEmbeddings(),
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=95
)
docs = splitter.create_documents([your_text])
# Thêm metadata cho mỗi chunk để filter sau này
for i, doc in enumerate(docs):
doc.metadata.update({
"chunk_id": i,
"department": "HR", # Filter theo phòng ban
"doc_type": "policy", # Filter theo loại tài liệu
"created_at": "2026-01-01" # Filter theo ngày
})
6. Kết luận
RAG không khó về mặt code. Nó khó về mặt tư duy:
- Dừng tập trung vào model → Tập trung vào dữ liệu
- Dừng dùng fixed chunking → Dùng semantic chunking
- Dừng dùng vector search đơn thuần → Dùng hybrid + reranker
- Dừng deploy mà không đo → Đo liên tục với RAGAS
Đây không phải optimization — đây là tư duy đúng đắn của một AI Engineer xây sản phẩm thật.
Anh em đang gặp vấn đề gì với RAG của mình? Comment bên dưới nhé, mình sẽ cùng debug!
Nếu thấy hữu ích, Upvote + Bookmark để dành đọc khi cần. Hẹn gặp lại! 🚀
Tài liệu tham khảo
- Es, S. et al. (2023). RAGAS: Automated Evaluation of Retrieval Augmented Generation. arXiv.
- Lewis, P. et al. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS. (Facebook AI)
- Ma, X. et al. (2023). Fine-Tuning LLaMA for Multi-Stage Text Retrieval. arXiv.
- Nogueira, R. & Cho, K. (2019). Passage Re-ranking with BERT. arXiv.
All Rights Reserved