[Tech Blog #11 | AI & GenAI] Xây Dựng Ứng Dụng RAG Mini Đầu Tiên: Kết Nối Tri Thức Nội Bộ Vào Mô Hình Ngôn Ngữ Lớn (LLM)
![[Tech Blog #11 | AI & GenAI] Xây Dựng Ứng Dụng RAG Mini Đầu Tiên: Kết Nối Tri Thức Nội Bộ Vào Mô Hình Ngôn Ngữ Lớn (LLM)](/uploads/ai-images/cover-xay-dung-ung-dung-rag.png)
1. Đặt vấn đề: Ba "điểm nghẽn" chí mạng của LLM thuần túy
Các mô hình ngôn ngữ lớn (LLM) như GPT-4, Claude hay Gemini rất thông minh, nhưng khi áp dụng vào doanh nghiệp thực tế, chúng lập tức vấp phải 3 rào cản lớn:
- Giới hạn thời gian tri thức (Knowledge Cutoff): Mô hình chỉ biết những gì đã xảy ra trước thời điểm kết thúc huấn luyện. Nó hoàn toàn không biết các tin tức, chính sách vừa ban hành sáng nay.
- Không tiếp cận được dữ liệu nội bộ: Mô hình không thể đọc được file PDF hợp đồng, sổ tay quy chế công ty, hay dữ liệu khách hàng lưu trong database riêng tư của bạn.
- Hiện tượng "Ảo giác" (Hallucination): Khi gặp câu hỏi về thông tin không có sẵn trong dữ liệu huấn luyện, thay vì nói "Tôi không biết", LLM thường có xu hướng bịa ra một câu trả lời nghe cực kỳ logic và tự tin.
Để giải quyết triệt để 3 điểm nghẽn này mà không cần tốn hàng trăm nghìn USD để huấn luyện lại mô hình, kiến trúc RAG (Retrieval-Augmented Generation - Tạo sinh tăng cường truy xuất) đã ra đời và trở thành tiêu chuẩn vàng của ngành công nghiệp GenAI.
2. RAG hoạt động như thế nào? (Quy trình 4 bước trực quan)
Hãy tưởng tượng LLM là một học sinh đi thi:
- LLM thuần túy giống như học sinh thi "gấp sách": Chỉ dựa vào trí nhớ có sẵn trong đầu, gặp câu hỏi lạ rất dễ chém gió linh tinh.
- Hệ thống RAG giống như học sinh được thi "mở sách": Khi nhận đề thi, học sinh lật đúng trang sách chứa tài liệu liên quan, đọc hiểu và viết lại câu trả lời chính xác dựa trên trang sách đó.
[1. Tài liệu nội bộ] ---> Cắt nhỏ (Chunking) ---> Tạo Vector (Embeddings) ---> Lưu vào Vector Store
|
[2. Câu hỏi người dùng] ---> Tạo Vector câu hỏi -------------------------------------+
|
v
[3. Retrieval] <--- So khớp Cosine Similarity, lấy ra 2-3 đoạn liên quan nhất <------+
|
v
[4. Generation] ---> Ghép [Context] + [Câu hỏi] ---> Gửi vào LLM ---> [Câu trả lời chính xác 100%]
Quy trình RAG tiêu chuẩn gồm 4 chặng:
- Indexing (Lập chỉ mục): Đọc văn bản thô, cắt thành các đoạn nhỏ (Chunks) dài khoảng 200–500 từ, sau đó dùng Embedding Model để chuyển thành vector và lưu trữ (đã học ở [Tech Blog #07]).
- Retrieval (Truy xuất): Khi người dùng đặt câu hỏi, hệ thống tìm các chunk văn bản có độ tương đồng ngữ nghĩa (Cosine Similarity) cao nhất với câu hỏi.
- Augmentation (Tăng cường ngữ cảnh): Nhồi các chunk vừa tìm được vào System Prompt làm tài liệu chứng cứ (
Context). - Generation (Tạo sinh): Ra lệnh cho LLM tổng hợp câu trả lời CHỈ DỰA TRÊN ngữ cảnh được cung cấp.
3. Bảng so sánh: RAG vs. Fine-tuning vs. Long Context Window
Khi muốn dạy kiến thức mới cho LLM, các kỹ sư thường cân nhắc giữa 3 phương pháp:
| Tiêu chí | RAG (Retrieval-Augmented Generation) | Fine-tuning (Tinh chỉnh trọng số) | Long Context Window (Nhồi hết file vào prompt) |
|---|---|---|---|
| Mục đích chính | Cung cấp thông tin thực tế, tài liệu nội bộ chính xác. | Dạy phong cách hành văn mới, cấu trúc trả lời hoặc học domain cực hẹp. | Đọc nhanh toàn bộ 1 cuốn sách/tài liệu dài trong 1 lần hỏi. |
| Chi phí triển khai | 🟢 Thấp (Dễ xây dựng với API có sẵn). | 🔴 Rất cao (Tốn GPU, chi phí gán nhãn dữ liệu). | 🟡 Trung bình (Tốn tiền token cho mỗi lượt hỏi). |
| Tốc độ cập nhật dữ liệu | ⚡ Tức thì (Chỉ cần ném file mới vào Vector DB). | 🐢 Rất chậm (Phải train lại mô hình từ đầu). | ⚡ Tức thì (Tải file lên là xong). |
| Khả năng triệt tiêu ảo giác | 🟢 Cực kỳ cao (Có trích dẫn nguồn rõ ràng). | 🔴 Kém (Vẫn có thể bịa thông tin). | 🟡 Khá tốt nhưng dễ bị hiện tượng "Lost in the middle". |
| Trường hợp sử dụng | Chatbot hỏi đáp chính sách nhân sự, sổ tay sản phẩm, hỗ trợ khách hàng. | Đào tạo mô hình dịch thuật chuyên ngành luật/y tế, viết code theo chuẩn riêng. | Tóm tắt nhanh một bản cáo bạch tài chính dài 300 trang. |
4. Thực hành thực chiến: Xây dựng RAG Pipeline mini hoàn chỉnh bằng Python
Dưới đây là một script Python hoàn chỉnh (sử dụng API chuẩn của OpenAI / Gemini tương thích) mô phỏng một Chatbot tra cứu Quy chế nội bộ công ty. Code được thiết kế khép kín, minh bạch và có cơ chế chống ảo giác tuyệt đối:
import os
import json
import numpy as np
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "your-api-key-here"))
# ==============================================================================
# BƯỚC 1: KHO TÀI LIỆU NỘI BỘ ĐÃ ĐƯỢC CHUNKING (INDEXING)
# ==============================================================================
raw_documents = [
{
"chunk_id": "HR_01",
"title": "Chính sách làm việc từ xa (Work From Home - WFH)",
"content": "Nhân viên chính thức được phép đăng ký WFH tối đa 2 ngày/tuần. Yêu cầu đăng ký trước trên cổng thông tin nội bộ ít nhất 24 giờ và được quản lý trực tiếp phê duyệt."
},
{
"chunk_id": "HR_02",
"title": "Quy định về ngày nghỉ phép năm",
"content": "Mỗi nhân viên có 12 ngày phép năm hưởng nguyên lương. Sau mỗi 3 năm cống hiến, nhân viên được cộng thêm 1 ngày phép năm. Phép năm chưa dùng hết chỉ được bảo lưu tối đa đến ngày 31/03 của năm kế tiếp."
},
{
"chunk_id": "SEC_01",
"title": "Bảo mật thiết bị và mật khẩu",
"content": "Mật khẩu máy tính làm việc phải có tối thiểu 12 ký tự gồm chữ hoa, chữ thường, số và ký tự đặc biệt. Mật khẩu phải được thay đổi định kỳ 90 ngày một lần. Nghiêm cấm cắm USB lạ vào máy công ty."
}
]
# ==============================================================================
# BƯỚC 2: HÀM TẠO EMBEDDING & TÍNH ĐỘ TƯƠNG ĐỒNG COSINE
# ==============================================================================
def get_embedding(text: str) -> list:
"""Tạo vector embedding bằng mô hình text-embedding-3-small của OpenAI."""
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
def cosine_similarity(a, b):
"""Tính khoảng cách góc giữa 2 vector."""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Đánh chỉ mục (Indexing): Tạo embedding cho toàn bộ tài liệu nội bộ
print("Đang mã hóa tài liệu nội bộ thành Vector Embeddings...")
for doc in raw_documents:
doc["embedding"] = get_embedding(doc["content"])
print("Hoàn tất lập chỉ mục!")
# ==============================================================================
# BƯỚC 3: HÀM TRUY XUẤT NGỮ CẢNH LIÊN QUAN NHẤT (RETRIEVAL)
# ==============================================================================
def retrieve_relevant_chunks(query: str, top_k: int = 1):
"""Tìm top K đoạn văn bản liên quan nhất tới câu hỏi của người dùng."""
query_vec = get_embedding(query)
scored_docs = []
for doc in raw_documents:
score = cosine_similarity(query_vec, doc["embedding"])
scored_docs.append((doc, score))
# Sắp xếp theo độ tương đồng giảm dần
scored_docs.sort(key=lambda x: x[1], reverse=True)
return [item[0] for item in scored_docs[:top_k]]
# ==============================================================================
# BƯỚC 4: SINH CÂU TRẢ LỜI CÓ CHỨNG CỨ (GENERATION)
# ==============================================================================
def ask_rag_assistant(question: str) -> str:
"""Quy trình RAG hoàn chỉnh: Truy xuất -> Tăng cường -> Tạo sinh."""
# 1. Truy xuất thông tin
relevant_chunks = retrieve_relevant_chunks(question, top_k=1)
matched_doc = relevant_chunks[0]
# 2. Xây dựng Context
context_text = f"Tài liệu [{matched_doc['chunk_id']} - {matched_doc['title']}]:\n{matched_doc['content']}"
# 3. System Prompt với nguyên tắc Guardrail chống ảo giác
system_instruction = """
Bạn là trợ lý AI giải đáp quy chế nội bộ công ty.
Nhiệm vụ của bạn là trả lời câu hỏi của nhân viên CHỈ DỰA TRÊN thông tin trong phần [TÀI LIỆU CUNG CẤP].
Nguyên tắc bắt buộc:
1. Nếu thông tin không có trong tài liệu, hãy trả lời chính xác: 'Xin lỗi, quy chế hiện tại không đề cập đến vấn đề này.'
2. Tuyệt đối không tự suy diễn hoặc bịa đặt ngoài tài liệu.
3. Luôn dẫn nguồn chunk_id ở cuối câu trả lời.
"""
user_prompt = f"""
[TÀI LIỆU CUNG CẤP]:
{context_text}
[CÂU HỎI]:
{question}
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_instruction},
{"role": "user", "content": user_prompt}
],
temperature=0.0 # Đặt về 0 để câu trả lời bám sát sự thật tuyệt đối
)
return response.choices[0].message.content
# ==============================================================================
# CHẠY THỬ NGHIỆM THỰC TẾ
# ==============================================================================
if __name__ == "__main__":
# Test case 1: Câu hỏi có trong tài liệu
q1 = "Tôi muốn làm việc tại nhà vào thứ 6 thì cần báo trước bao lâu?"
print(f"\n[Q1]: {q1}")
print(f"[A1]: {ask_rag_assistant(q1)}")
# Test case 2: Câu hỏi hoàn toàn KHÔNG có trong tài liệu (Kiểm tra chống ảo giác)
q2 = "Công ty có hỗ trợ tiền gửi xe hàng tháng và ăn trưa không?"
print(f"\n[Q2]: {q2}")
print(f"[A2]: {ask_rag_assistant(q2)}")
5. Câu hỏi phỏng vấn thực tế (Interview Q&A)
Câu 1 (Mức độ: Junior / Entry-level)
Câu hỏi: "Chunking (Cắt nhỏ văn bản) trong RAG là gì? Tại sao chúng ta không ném nguyên cả cuốn cẩm nang 500 trang vào Vector Database?"
Gợi ý trả lời:
*"Chunking là kỹ thuật chia một tài liệu dài thành nhiều đoạn văn bản nhỏ hơn (thường từ 200 đến 500 từ, có phần gối đầu Chunk Overlap khoảng 10-20%).
Chúng ta không thể ném nguyên cả cuốn sách dài vào vì 2 lý do chính:
- Mất độ tập trung của Vector (Embedding Dilution): Một tài liệu 500 trang nói về 50 chủ đề khác nhau khi bị nén thành một vector duy nhất sẽ làm vector đó bị 'loãng', khiến Cosine Similarity không thể tìm chính xác đoạn trả lời cho một câu hỏi cụ thể.
- Tiết kiệm Token và Ngữ cảnh: Khi chunking nhỏ, chúng ta chỉ cần trích xuất đúng 1-2 đoạn văn thực sự liên quan để nạp vào prompt cho LLM, giúp tiết kiệm chi phí gọi API và tăng tốc độ phản hồi."*
Câu 2 (Mức độ: Junior / Mid-level)
Câu hỏi: "Khi nào bạn quyết định dùng RAG và khi nào bạn quyết định Fine-tuning cho một dự án AI của công ty?"
Gợi ý trả lời:
*"Quy tắc phân định chuẩn nhất là: RAG dành cho Tri thức (Knowledge), còn Fine-tuning dành cho Phong cách và Cấu trúc (Form/Style):
- Chọn RAG khi: Dữ liệu nội bộ thay đổi liên tục (chính sách, giá bán, kho hàng), cần trích dẫn nguồn chứng cứ minh bạch, và yêu cầu chi phí triển khai thấp, cập nhật dữ liệu tức thì.
- Chọn Fine-tuning khi: Bạn cần mô hình học một văn phong đặc thù (như viết email chăm sóc khách hàng theo chuẩn thương hiệu), học một ngôn ngữ lập trình nội bộ hiếm gặp, hoặc muốn ép mô hình luôn trả về cấu trúc cú pháp cố định (JSON/SQL) mà Prompt Engineering không thể đảm bảo 100% ổn định."*
6. Key Takeaways (Ghi nhớ nhanh)
- RAG là "thi mở sách": Giúp LLM tra cứu tài liệu nội bộ và triệt tiêu hiện tượng ảo giác (Hallucination).
- Quy trình 4 chữ vàng: Indexing (Lập chỉ mục) Retrieval (Truy xuất) Augmentation (Tăng cường) Generation (Tạo sinh).
- Chunking là chìa khóa chất lượng: Cắt đoạn văn bản vừa đủ ngữ nghĩa (Chunk Size) và có phần gối đầu (Chunk Overlap) để không bị đứt gãy ý nghĩa giữa các đoạn.
- Nhiệt độ (Temperature) bằng 0: Khi làm RAG tra cứu sự thật, luôn đặt
temperature = 0.0và thiết lập Guardrail cấm mô hình suy diễn ngoài tài liệu cung cấp.