Membangun Sistem Retrieval-Augmented Generation (RAG) Menggunakan LLM Lokal dan Qdrant Vector Database
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap enterprise modern, kecerdasan buatan generatif (GenAI) dan Large Language Models (LLM) telah bergeser dari sekadar eksperimen main-main menjadi tulang punggung operasional bisnis. Namun, adopsi LLM komersial seperti OpenAI GPT-4 atau Anthropic Claude menghadapi tembok besar yang tidak bisa ditawar: privasi data, latensi jaringan publik, dan biaya operasional per token yang membengkak secara eksponensial.
Perusahaan keuangan, layanan kesehatan, dan infrastruktur kritis tidak dapat mentransmisikan dokumen internal mereka—yang berisi kekayaan intelektual, catatan medis pasien, atau transaksi finansial—ke server pihak ketiga. Di sinilah arsitektur Retrieval-Augmented Generation (RAG) lokal lahir sebagai penyelamat mutlak. RAG menggabungkan presisi pencarian vektor (vector search) dengan kemampuan penalaran generatif LLM, semuanya berjalan di atas infrastruktur mandiri tanpa kabel data yang keluar dari perimeter jaringan internal perusahaan.
Namun, membangun sistem RAG lokal di dunia nyata bukan sekadar menjalankan skrip Python sederhana dari notebook Jupyter. Bottleneck performa sering kali menghantam pada tiga titik krusial:
- Inersia Ingesti Dokumen: Proses chunking yang buruk memecah konteks semantik, menyebabkan LLM gagal memahami relasi antar-paragraf.
- Keterbatasan Memori Vector Database: Qdrant atau milvus yang salah konfigurasi pada sistem berkas berbasis HDD alih-alih NVMe akan mengalami I/O bottleneck saat melakukan cosine similarity search skala besar.
- Latensi Inference Model Lokal: Menjalankan model seperti Llama 3 atau Mistral secara lokal membutuhkan orkestrasi resource GPU yang presisi melalui engine seperti Ollama atau vLLM.
Perbandingan Kondisi: Sebelum vs Sesudah Implementasi RAG Lokal
| Metrik / Parameter | Sebelum RAG Lokal (Cloud-based / Out-of-the-Box) | Sesudah RAG Lokal Mandiri (Qdrant + LLM Lokal) |
|---|---|---|
| Keamanan & Privasi Data | Risiko tinggi data leakage, data ditransmisikan ke API eksternal. | 100% Air-gapped, data tidak pernah keluar dari server perusahaan. |
| Biaya Operasional (OPEX) | Berbayar per token, membengkak drastis saat high concurrency. | Fixed cost (biaya hardware/server), tanpa biaya tambahan per request. |
| Latensi Jaringan | Bergantung pada stabilitas WAN/Internet publik (RTLT tinggi). | Latensi rendah berbasis Local Area Network (LAN) atau Inter-process communication. |
| Kustomisasi & Kontrol | Terikat pada batasan kebijakan penyedia layanan pihak ketiga. | Kontrol penuh atas embedding model, chunk size, dan prompt engineering. |
2. Konsep Inti & Deep Dive Arsitektur
Sistem RAG berbasis Qdrant dan LLM lokal beroperasi melalui dua fase utama yang terisolasi namun terintegrasi secara asinkron: Fase Ingesti Data (Offline Pipeline) dan Fase Query (Online Pipeline).
[Dokumen PDF/TXT] ---> [Parser & Chunking] ---> [Embedding Model (HuggingFace)]
|
v
[Qdrant Vector DB]
^
[User Query] -------> [Query Embedding] --------------+|
|
v
[Top-K Similarity Search]
|
v
[LLM Lokal (Ollama)] <--- [Context + Prompt] <----------+
|
v
[Final Response ke User]
Alur Kerja di Bawah Tenda (Under the Hood)
- Fase Ingesti (Data Preparation): Dokumen sumber mentah (PDF, Markdown, HTML) dibaca oleh parser. Teks dipecah menjadi potongan-potongan kecil (chunks) dengan ukuran optimal (misalnya 500 token) menggunakan teknik recursive character splitting untuk menjaga integritas struktur kalimat. Setiap chunk dikirim ke model embedding lokal (seperti
bge-large-en-v1.5atauall-minilm-l6-v2) untuk diterjemahkan menjadi vektor numerik berdimensi tinggi (misalnya 1024 dimensi). - Penyimpanan di Qdrant: Vektor-vektor ini beserta metadata aslinya (nama file, nomor halaman, teks asli) disimpan ke dalam koleksi Qdrant. Qdrant menggunakan struktur indeks HNSW (Hierarchical Navigable Small World) yang dioptimalkan untuk pencarian tetangga terdekat (approximate nearest neighbor atau ANN) secara ultra-cepat.
- Fase Query (Runtime): Ketika pengguna mengirimkan pertanyaan melalui antarmuka web, pertanyaan tersebut diubah menjadi vektor menggunakan model embedding yang sama. Qdrant kemudian melakukan pemindaian vektor untuk menemukan $K$ dokumen paling relevan (Top-K retrieval).
- Augmentasi & Generasi: Teks dari dokumen hasil retrieval digabungkan ke dalam system prompt LLM sebagai konteks latar belakang (context injection). LLM lokal (yang berjalan di atas Ollama atau vLLM) membaca konteks tersebut dan menyusun jawaban yang akurat, terhindar dari halusinasi, dan menyertakan referensi sumber dokumen asli.
3. Perbandingan Komparatif / Analisis Pendekatan
Memilih komponen yang tepat untuk stack RAG lokal sangat menentukan performa jangka panjang. Berikut adalah analisis mendalam mengenai trade-off dari berbagai opsi komponen utama:
| Komponen | Opsi A: Qdrant + Ollama + LangChain | Opsi B: Milvus + vLLM + LlamaIndex | Opsi C: ChromaDB + Transformers Murni |
|---|---|---|---|
| Vector Database | Qdrant (Rust-based, efisien RAM, HNSW indexing unggul) | Milvus (Distributed, sangat cocok untuk skala miliaran vektor) | ChromaDB (In-memory/SQLite, bagus untuk prototyping cepat) |
| LLM Inference Engine | Ollama (Sangat mudah di-setup, mendukung manajemen model CLI) | vLLM (Paging memory PagedAttention, throughput tinggi) | HuggingFace Transformers (Fleksibel, namun berat untuk produksi) |
| Orchestration Framework | LangChain (Ekosistem luas, integrasi melimpah) | LlamaIndex (Spesialisasi tinggi untuk indeks data dokumen) | Custom Python Script (Kontrol penuh, tanpa bloatware) |
| Konsumsi Resource (RAM/GPU) | Menengah ke Atas (Stabil pada RAM 16GB+, VRAM 8GB+) | Sangat Tinggi (Direkomendasikan untuk cluster enterprise) | Rendah (Cocok untuk environment dengan resource terbatas) |
| Latensi Query Rata-rata | 150ms - 400ms (Bergantung ukuran model & hardware) | 100ms - 300ms (Dioptimalkan untuk skala besar) | 300ms - 800ms (Menurun seiring bertambahnya ukuran koleksi) |
[!WARNING] Jangan pernah menggunakan ChromaDB dengan default persistence SQLite untuk production environment dengan konkurensi tinggi karena risiko database locking. Gunakan Qdrant atau Milvus untuk arsitektur enterprise yang membutuhkan skalabilitas tinggi dan konkurensi I/O yang handal.
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum melangkah ke implementasi teknis, pastikan infrastruktur server atau mesin lokal Anda memenuhi spesifikasi minimum berikut agar sistem tidak mengalami OutOfMemory (OOM) crash saat melakukan embedding dokumen berukuran besar.
Spesifikasi Minimum Hardware
- CPU: Minimal 8 Core (Disarankan Intel Xeon / AMD EPYC / Apple Silicon M-series).
- RAM: Minimal 32 GB (Direkomendasikan 64 GB untuk menjalankan LLM berukuran 13B parameter secara bersamaan).
- Storage: 100 GB NVMe SSD (Kecepatan I/O sangat kritikal untuk operasi Qdrant HNSW graph traversal).
- GPU (Opsional tapi Sangat Disarankan): NVIDIA GPU dengan VRAM minimum 12 GB (misalnya RTX 3060/4060 atau RTX 3090/4090 untuk performa maksimal).
Software Dependencies
- Ubuntu 22.04 LTS / Debian 12 / macOS Sonoma
- Docker Engine (v24.0+) & Docker Compose (v2.20+)
- Python 3.10 atau 3.11
- CUDA Toolkit 12.1+ (Jika menggunakan akselerasi GPU NVIDIA)
Lakukan verifikasi awal pada sistem operasi Anda untuk memastikan Docker berjalan dengan benar:
# Verifikasi versi Docker dan Docker Compose
docker --version
docker compose version
# Verifikasi ketersediaan GPU NVIDIA (jika ada)
nvidia-smi
5. Panduan Implementasi Langkah demi Langkah
Langkah 1: Setup Qdrant Vector Database via Docker Compose
Buat direktori kerja baru di server Anda, lalu buat file docker-compose.yml untuk menjalankan Qdrant dan Ollama dalam satu stack jaringan internal.
version: '3.8'
services:
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant_vector_db
restart: unless-stopped
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_data:/qdrant/storage
environment:
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
networks:
- rag_net
ollama:
image: ollama/ollama:latest
container_name: ollama_llm
restart: unless-stopped
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
networks:
- rag_net
volumes:
qdrant_data:
ollama_data:
networks:
rag_net:
driver: bridge
Jalankan stack Docker di latar belakang:
docker compose up -d
Langkah 2: Download Model Lokal via Ollama
Masuk ke dalam container Ollama untuk mengunduh model LLM (llama3:8b) dan model embedding (bge-large atau gunakan HuggingFace embeddings melalui Python).
# Masuk ke container ollama
docker exec -it ollama_llm ollama run llama3:8b
# Setelah masuk, ketik /bye untuk keluar, atau unduh model tambahan
ollama pull mxbai-embed-large
Langkah 3: Setup Virtual Environment Python & Install Dependencies
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install langchain langchain-community langchain-qdrant qdrant-client sentence-transformers pypdf fastapi uvicorn
Langkah 4: Script Pipeline Ingesti Dokumen (ingest.py)
Buat file Python untuk memproses dokumen PDF dari direktori lokal, memecahnya menjadi chunks, membuat embeddings, dan menyimpannya ke Qdrant.
import os
from langchain_community.document_loaders import PyPDFDirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_qdrant import QdrantVectorStore
from qdrant_client import QdrantClient
from qdrant_client.http.models import Distance, VectorParams
# Konfigurasi koneksi
QDRANT_HOST = "localhost"
QDRANT_PORT = 6333
COLLECTION_NAME = "enterprise_docs"
# Direktori dokumen
DOCS_DIR = "./documents"
def ingest_documents():
print("[+] Memulai proses ingesti dokumen...")
# 1. Load dokumen PDF
if not os.path.exists(DOCS_DIR):
os.makedirs(DOCS_DIR)
print(f"[!] Harap letakkan file PDF di dalam folder {DOCS_DIR}")
return
loader = PyPDFDirectoryLoader(DOCS_DIR)
raw_documents = loader.load()
print(f"[+] Berhasil memuat {len(raw_documents)} halaman dokumen.")
# 2. Chunking dokumen
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len,
)
docs = text_splitter.split_documents(raw_documents)
print(f"[+] Dokumen dipecah menjadi {len(docs)} chunks.")
# 3. Inisialisasi Model Embedding Lokal
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-en-v1.5",
model_kwargs={"device": "cuda\