Xây dựng hệ thống RAG không có cơ sở dữ liệu vector: Ứng dụng DuckDB làm Full-text Search siêu nhẹ trên VPS
Giới thiệu: Lối đi riêng cho RAG hiệu năng cao, chi phí thấp
Trong kỷ nguyên của Trí tuệ nhân tạo (AI), Retrieval-Augmented Generation (RAG) đã trở thành kiến trúc tiêu chuẩn để tích hợp dữ liệu nội bộ của doanh nghiệp với các mô hình ngôn ngữ lớn (LLM). Thông thường, khi nghĩ đến RAG, các kỹ sư thường hướng tới các cơ sở dữ liệu vector (Vector Database) chuyên dụng như Pinecone, Milvus, Qdrant hay pgvector. Tuy nhiên, đối với các doanh nghiệp vừa và nhỏ (SMEs) hoặc các dự án MVP (Minimum Viable Product), việc vận hành một hạ tầng Vector DB đi kèm các mô hình Embedding tốn kém tài nguyên phần cứng là một bài toán chi phí đau đầu.
Liệu có giải pháp nào giúp triển khai RAG trực tiếp trên các cấu hình VPS cấu hình thấp (1-2 Core CPU, 2-4GB RAM) mà vẫn đảm bảo độ chính xác và tốc độ phản hồi? Câu trả lời nằm ở một cách tiếp cận mang tính thực tế: Full-text Search truyền thống kết hợp với DuckDB. Bài viết này sẽ hướng dẫn bạn cách xây dựng một hệ thống RAG siêu nhẹ, không cần Vector Database, tối ưu hóa triệt để tài nguyên trên VPS thông qua DuckDB.
Tại sao lại là RAG không có Vector Database?
Sự thật về Vector Search trong môi trường Production
Vector Search (Semantic Search) rất mạnh mẽ trong việc hiểu ngữ nghĩa của câu hỏi. Tuy nhiên, nó đi kèm với những thách thức không nhỏ:
- Tốn tài nguyên RAM/CPU: Việc lưu trữ các index như HNSW yêu cầu một lượng lớn bộ nhớ RAM để đảm bảo tốc độ truy vấn thấp.
- Chi phí Embedding: Mỗi khi dữ liệu thay đổi hoặc có truy vấn mới, hệ thống phải gọi đến các mô hình Embedding (như OpenAI text-embedding-3 hoặc các mô hình mã nguồn mở trên HuggingFace), làm tăng chi phí và độ trễ (latency).
- Yếu điểm với từ khóa chính xác: Vector Search đôi khi hoạt động kém hiệu quả khi người dùng tìm kiếm chính xác mã sản phẩm, số điện thoại, tên riêng hoặc thuật ngữ chuyên ngành viết tắt.
Sức mạnh của Full-text Search (BM25)
Ngược lại, thuật toán BM25 (Best Matching 25) - nền tảng của các hệ thống Full-text Search - hoạt động dựa trên tần suất xuất hiện của từ khóa. Đối với nhiều bài toán nghiệp vụ của doanh nghiệp (ví dụ: tra cứu tài liệu hướng dẫn sử dụng, điều khoản hợp đồng, chính sách nội bộ), một hệ thống tìm kiếm từ khóa chính xác và có trọng số tốt thường mang lại kết quả RAG cô đọng và chính xác hơn, đồng thời tiết kiệm đến 90% tài nguyên so với Vector Search.
DuckDB - "SQLite của giới phân tích dữ liệu"
Để triển khai Full-text Search siêu nhẹ, DuckDB là ứng cử viên sáng giá nhất hiện nay. Được mệnh danh là "SQLite cho OLAP", DuckDB là một hệ quản trị cơ sở dữ liệu nhúng (embedded database) dạng cột (columnar-oriented), được tối ưu hóa cho các truy vấn phân tích tốc độ cao.
DuckDB không chạy như một tiến trình (process) độc lập mà được nhúng trực tiếp vào ứng dụng của bạn (Python, Node.js, Go), giúp loại bỏ hoàn toàn độ trễ kết nối mạng (network overhead) và đơn giản hóa việc triển khai trên VPS xuống mức tối đa.
Đặc biệt, kể từ các phiên bản gần đây, DuckDB tích hợp sẵn extension FTS (Full-Text Search), cho phép tạo index BM25 trên các cột văn bản chỉ với vài dòng lệnh SQL đơn giản.
Kiến trúc hệ thống RAG siêu nhẹ trên VPS
Hệ thống RAG sử dụng DuckDB sẽ vận hành theo luồng quy trình (workflow) tinh gọn sau:
- Data Ingestion: Đọc dữ liệu từ các file tài liệu (PDF, Markdown, JSON), phân tách thành các đoạn văn bản nhỏ (chunks).
- Indexing: Lưu các chunk văn bản vào một bảng trong DuckDB và xây dựng index FTS (BM25). Toàn bộ dữ liệu được lưu thành một file duy nhất trên đĩa cứng của VPS.
- Retrieval: Khi người dùng đặt câu hỏi, ứng dụng thực hiện truy vấn FTS trên DuckDB để lấy ra top-K đoạn văn bản có điểm số BM25 cao nhất.
- Generation: Gửi câu hỏi kèm theo các đoạn văn bản ngữ cảnh (context) vừa tìm được tới LLM API (như OpenAI, Anthropic hoặc các mô hình chạy local dạng Ollama) để tạo ra câu trả lời cuối cùng.
Hướng dẫn triển khai chi tiết với Python
Dưới đây là hướng dẫn từng bước bằng mã nguồn Python để bạn có thể hiện thực hóa ý tưởng này ngay trên VPS của mình.
Bước 1: Cài đặt thư viện cần thiết
Trước tiên, hãy cài đặt các thư viện cần thiết. Chúng ta chỉ cần DuckDB và thư viện gọi LLM (ở đây ví dụ là OpenAI):
pip install duckdb openaiBước 2: Khởi tạo cơ sở dữ liệu và nạp dữ liệu vào DuckDB
Chúng ta sẽ tạo một file cơ sở dữ liệu có tên
rag_knowledge.db, tạo bảng và nạp một số dữ liệu mẫu.import duckdb # Kết nối đến file DuckDB (tự động tạo mới nếu chưa có) con = duckdb.connect('rag_knowledge.db') # Tạo bảng lưu trữ tài liệu con.execute(""" CREATE TABLE IF NOT EXISTS documents ( id INTEGER PRIMARY KEY, title VARCHAR, content VARCHAR ); """) # Thêm dữ liệu mẫu data = [ (1, 'Chính sách bảo hành', 'Các sản phẩm điện tử của công ty được bảo hành 12 tháng kể từ ngày mua. Không bảo hành cho trường hợp rơi vỡ hoặc vào nước.'), (2, 'Quy định hoàn tiền', 'Khách hàng có quyền yêu cầu hoàn tiền trong vòng 7 ngày nếu sản phẩm gặp lỗi kỹ thuật từ nhà sản xuất và còn nguyên hộp.'), (3, 'Thời gian làm việc', 'Văn phòng làm việc từ thứ Hai đến thứ Sáu, khung giờ từ 8:00 sáng đến 5:30 chiều. Nghỉ thứ Bảy và Chủ Nhật.') ] con.executemany("INSERT OR IGNORE INTO documents VALUES (?, ?, ?)", data) print("Đã nạp dữ liệu thành công!")Bước 3: Tạo Index Full-text Search (BM25)
Kích hoạt extension FTS của DuckDB và xây dựng chỉ mục tìm kiếm trên cột
content:# Tải và kích hoạt extension FTS con.execute("LOAD fts;") # Tạo index FTS trên bảng documents cho cột content con.execute(""" PRAGMA create_fts_index( 'documents', 'id', 'content', overwrite=1 ); """) print("Đã xây dựng thành công chỉ mục FTS!")Bước 4: Hàm truy vấn ngữ cảnh (Retrieval)
Khi có câu hỏi, chúng ta sẽ dùng hàm FTS của DuckDB để tìm kiếm các đoạn văn bản liên quan dựa trên thuật toán BM25:
def retrieve_context(query, top_k=2): # Thực hiện truy vấn FTS result = con.execute(f""" SELECT id, content, score FROM ( SELECT * FROM fts_main_documents.match_bm25(id, '{query}') ) AS fts JOIN documents ON fts.id = documents.id ORDER BY score DESC LIMIT {top_k}; """).fetchall() return [row[1] for row in result] # Chạy thử nghiệm tìm kiếm query = "Chính sách bảo hành sản phẩm vào nước" contexts = retrieve_context(query) print("Ngữ cảnh tìm được:", contexts)Bước 5: Kết hợp với LLM để hoàn thiện RAG
Cuối cùng, đóng gói ngữ cảnh vào Prompt và gửi tới LLM:
from openai import OpenAI client = OpenAI(api_key="YOUR_OPENAI_API_KEY") def r_a_g_pipeline(user_query): # 1. Tìm ngữ cảnh liên quan từ DuckDB contexts = retrieve_context(user_query) context_str = "\n".join(contexts) # 2. Xây dựng Prompt prompt = f""" Bạn là một trợ lý ảo thông minh. Hãy trả lời câu hỏi của người dùng dựa TRỰC TIẾP và CHỈ DỰA VÀO ngữ cảnh được cung cấp dưới đây. Nếu ngữ cảnh không chứa thông tin, hãy trả lời 'Tôi không biết'. Ngữ cảnh: {context_str} Câu hỏi: {user_query} Trả lời: """ # 3. Gọi LLM response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0 ) return response.choices[0].message.content # Thực thi thử nghiệm hệ thống RAG answer = r_a_g_pipeline("Sản phẩm bị vào nước có được bảo hành không?") print("Kết quả từ RAG:", answer)Đánh giá hiệu năng và chi phí trên VPS
Giải pháp sử dụng DuckDB làm Full-text Search mang lại những ưu điểm vượt trội về mặt vận hành kinh tế:
Tiêu chí Vector Database truyền thống DuckDB Full-text Search Tiêu thụ RAM Tối thiểu 1GB - 4GB+ (luôn thường trực) Chỉ tốn vài chục MB khi thực thi truy vấn Dung lượng lưu trữ Lớn (do lưu thêm ma trận vector mật độ cao) Cực nhỏ (file DB nén dạng cột tối ưu) Độ phức tạp hạ tầng Cao (Cần cài đặt Docker, cấu hình Cluster) Không (Chỉ là 1 file single-file database) Chi phí vận hành Phải thuê VPS cấu hình cao hoặc Cloud DB đắt đỏ Chạy mượt mà trên VPS $5/tháng Kết luận và Khuyến nghị
Phương pháp xây dựng RAG không có cơ sở dữ liệu vector bằng cách tận dụng DuckDB Full-text Search là một giải pháp "ngon - bổ - rẻ" đúng nghĩa cho các kỹ sư và doanh nghiệp muốn tối ưu chi phí hạ tầng VPS. Hệ thống này đặc biệt phù hợp với các kho dữ liệu dạng văn bản hành chính, quy trình kỹ thuật, tài liệu FAQ nội bộ.
Tất nhiên, để đạt hiệu quả cao nhất trong thực tế, bạn có thể cân nhắc hướng tiếp cận Hybrid Search: sử dụng Full-text Search của DuckDB cho các truy vấn từ khóa chính xác và kết hợp thêm một thư viện Vector Search siêu nhẹ dạng nhúng như FAISS hoặc USearch nếu cần hiểu sâu về ngữ nghĩa câu hỏi. Chúc các bạn ứng dụng thành công cho dự án của mình!
