Membangun Sistem Retrieval-Augmented Generation (RAG) Skala Enterprise dengan LLM dan Vektorisasi Lokal
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap enterprise modern, integrasi kecerdasan buatan, khususnya Large Language Models (LLM), sering kali terhalang oleh keterbatasan domain pengetahuan model (knowledge cutoff) dan risiko halusinasi data. Perusahaan enterprise membutuhkan sistem tanya-jawab (Q&A) yang akurat, real-time, dan sepenuhnya mematuhi regulasi privasi data internal tanpa harus mengekspos dokumen rahasia ke penyedia LLM publik pihak ketiga secara mentah-mentah.
Sebelum penerapan Retrieval-Augmented Generation (RAG) berbasis vektor lokal:
- Model LLM sering memberikan jawaban spekulatif atau salah saat ditanya mengenai SOP internal, arsitektur infrastruktur, atau dokumentasi finansial perusahaan.
- Proses pencarian dokumen internal menggunakan mesin pencari konvensional (Keyword-based Search seperti LIKE query di MySQL atau Elasticsearch standar) gagal menangkap kedekatan semantik (semantic similarity).
- Ketergantungan penuh pada API cloud eksternal menimbulkan risiko kepatuhan terhadap regulasi seperti GDPR, HIPAA, dan UU PDP.
Sesudah penerapan RAG Skala Enterprise:
- Sistem mampu menggabungkan kekuatan penalaran LLM dengan pencarian berbasis vektor semantik yang sangat presisi terhadap repositori dokumen internal.
- Pengolahan data dilakukan secara lokal menggunakan enkripsi transit dan at-rest, menjaga kedaulatan data perusahaan.
- Latensi sistem dapat ditekan secara drastis melalui arsitektur caching berlapis menggunakan Redis dan optimasi koneksi database vektor.
2. Konsep Inti & Deep Dive Arsitektur
Sistem RAG bekerja dengan cara mengambil query pengguna, mengubahnya menjadi representasi vektor numerik (embedding), mencari dokumen paling relevan di dalam vector database, lalu menyuntikkan dokumen tersebut ke dalam prompt LLM sebagai konteks tambahan.
+-----------------------------------------------------------------------------------------+
| PIPELINE INGESTION |
| |
| [Dokumen PDF/MD] ---> [Text Splitter & Chunking] ---> [Embedding Model] ---> [Vector DB] |
+-----------------------------------------------------------------------------------------+
+-----------------------------------------------------------------------------------------+
| PIPELINE QUERY RAG |
| |
| [User Query] ---> [HyDE / Query Expansion] ---> [Embedding Model] |
| | |
| v |
| [LLM Generation] <--- [Context Injection] <--- [Vector Search (Cosine/HNSW)] |
+-----------------------------------------------------------------------------------------+
Komponen Utama Arsitektur:
- Ingestion Engine: Mengambil dokumen dari penyimpanan S3/MinIO, memecahnya menjadi potongan kecil (chunking), lalu menghasilkan vektor menggunakan model embedding lokal (misalnya BGE-m3 atau E5).
- Vector Database Engine: Menyimpan dan melakukan pengindeksan vektor berdimensi tinggi menggunakan algoritma pencarian tetangga terdekat (Approximate Nearest Neighbors - ANN) seperti HNSW.
- Retrieval Layer: Menerima query, menerapkan teknik Hypothetical Document Embeddings (HyDE) untuk meningkatkan kualitas pencarian, dan mengambil top-k chunk relevan.
- Generation Layer: Mengirimkan konteks dan query ke LLM lokal (misalnya Llama 3 atau Mistral via Ollama/vLLM) untuk menghasilkan jawaban akhir.
3. Perbandingan Komparatif / Analisis Pendekatan
Pemilihan komponen infrastruktur sangat menentukan performa dan biaya operasional sistem RAG enterprise.
| Komponen / Solusi | Kelebihan | Kekurangan | Konsumsi Sumber Daya (CPU/RAM/Latensi) |
|---|---|---|---|
| Milvus | Skalabilitas tinggi, mendukung miliaran vektor, arsitektur terdistribusi | Kompleksitas tinggi dalam deployment dan maintenance cluster | Tinggi (Membutuhkan minimal 16GB RAM, Kubernetes direkomendasikan) |
| Qdrant | Ditulis dengan Rust, performa tinggi, fitur filtering payload kaya, mudah di-deploy | Skalabilitas horizontal lebih menantang dibanding Milvus pada skala masif | Sedang-Tinggi (Optimal pada 8GB-16GB RAM, latensi < 15ms) |
| pgvector | Integrasi mulus dengan PostgreSQL eksisting, mendukung ACID, backup mudah | Performa HNSW index sedikit di bawah dedicated vector db pada skala ekstrem | Tergantung beban PostgreSQL (CPU & RAM shared dengan database transaksional) |
| Ollama (LLM Lokal) | Privasi data mutlak, bebas biaya API per token, mudah di-setup | Membutuhkan GPU dedicated (NVIDIA A10/L40/RTX) untuk throughput tinggi | Tinggi pada VRAM GPU (12GB - 24GB VRAM) |
[!WARNING] Hindari menggunakan
pgvectorpada tabel transaksional utama yang memiliki beban write (INSERT/UPDATE) tinggi tanpa pemisahan read replica, karena pembuatan index HNSW dapat memblokir operasi tulis.
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum memulai instalasi, pastikan infrastruktur memenuhi spesifikasi minimal berikut:
- OS: Ubuntu 22.04 LTS / Debian 12 Bookworm
- CPU: Minimal 8 Core
- RAM: 32 GB
- Storage: 100 GB NVMe SSD
- GPU (Opsional tapi Direkomendasikan): NVIDIA GPU dengan VRAM minimum 16GB (Driver CUDA 12.x terinstal)
Lakukan pembaruan sistem dan instalasi dependensi esensial:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git build-essential software-properties-common ufw fail2ban nginx
# Instalasi Docker dan Docker Compose untuk Vector DB & Ollama
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --arch) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Konfigurasi Firewall (UFW) untuk mengamankan port manajemen:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
5. Panduan Implementasi Langkah demi Langkah
Buat struktur direktori proyek di /opt/enterprise-rag:
sudo mkdir -p /opt/enterprise-rag/{data,config,logs,app}
cd /opt/enterprise-rag
Buat file docker-compose.yml untuk menjalankan Qdrant sebagai Vector Database dan Ollama sebagai LLM Server:
version: '3.8'
services:
qdrant:
image: qdrant/qdrant:v1.9.0
container_name: enterprise-qdrant
restart: always
ports:
- "6333:6333"
- "6334:6334"
volumes:
- /opt/enterprise-rag/data/qdrant_storage:/qdrant/storage
environment:
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
networks:
- rag-net
ollama:
image: ollama/ollama:latest
container_name: enterprise-ollama
restart: always
ports:
- "11434:11434"
volumes:
- /opt/enterprise-rag/data/ollama_models:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
networks:
- rag-net
networks:
rag-net:
driver: bridge
Jalankan layanan container:
docker compose up -d
Unduh model LLM dan Embedding ke dalam container Ollama:
docker exec -it enterprise-ollama ollama pull llama3
docker exec -it enterprise-ollama ollama pull bge-m3
Buat script aplikasi Python (/opt/enterprise-rag/app/main.py) untuk menangani proses Ingestion dan Retrieval:
import os
from typing import List
import requests
from qdrant_client import QdrantClient
from qdrant_client.http import models
QDRANT_HOST = "localhost"
QDRANT_PORT = 6333
OLLAMA_URL = "http://localhost:11434/api"
client = QdrantClient(host=QDRANT_HOST, port=QDRANT_PORT)
COLLECTION_NAME = "enterprise_knowledge"
def init_collection():
if not client.collection_exists(COLLECTION_NAME):
client.create_collection(
collection_name=COLLECTION_NAME,
vectors_config=models.VectorParams(size=1024, distance=models.Distance.COSINE)
)
print(f"Koleksi {COLLECTION_NAME} berhasil dibuat.")
def get_embedding(text: str) -> List[float]:
response = requests.post(
f"{OLLAMA_URL}/embeddings",
json={"model": "bge-m3", "prompt": text}
)
response.raise_for_status()
return response.json()["embedding"]
def add_document(doc_id: int, text: str, metadata: dict):
vector = get_embedding(text)
client.upsert(
collection_name=COLLECTION_NAME,
points=[
models.PointStruct(
id=doc_id,
vector=vector,
payload={"text": text, **metadata}
)
]
)
print(f"Dokumen ID {doc_id} berhasil diindeks.")
def search_documents(query: str, limit: int = 3) -> List[str]:
query_vector = get_embedding(query)
search_result = client.search(
collection_name=COLLECTION_NAME,
query_vector=query_vector,
limit=limit
)
return [hit.payload["text"] for hit in search_result]
def generate_answer(query: str) -> str:
context_chunks = search_documents(query)
context = "\n".join(context_chunks)
prompt = f"""Gunakan konteks berikut untuk menjawab pertanyaan di bawah ini dengan akurat.
Konteks:
{context}
Pertanyaan: {query}
Jawaban:"""
response = requests.post(
f"{OLLAMA_URL}/generate",
json={"model": "llama3", "prompt": prompt, "stream": False}
)
response.raise_for_status()
return response.json()["response"]
if __name__ == "__main__":
init_collection()
# Contoh Ingestion
add_document(1, "Kebijakan cuti tahunan karyawan adalah 12 hari kerja setelah masa kerja 1 tahun.", {"category": "HR"})
# Contoh Query
question = "Berapa hari hak cuti saya setelah bekerja selama setahun?"
answer = generate_answer(question)
print(f"Q: {question}\nA: {answer}")
6. Bedah Kode & Penjelasan Detail Baris Kunci
models.VectorParams(size=1024, distance=models.Distance.COSINE): Menentukan dimensi vektor sebesar 1024 (sesuai dengan output model embeddingbge-m3) dan menggunakan jarak Cosine Similarity untuk mengukur kedekatan semantik antar dokumen.client.upsert(...): Melakukan operasi penyimpanan atau pembaruan (idempotent) pada Qdrant, sangat penting untuk mencegah duplikasi data saat pipeline ingestion berjalan berulang kali.requests.post(..., json=...): Komunikasi REST API langsung dengan instance Ollama lokal, memastikan tidak ada data teks keluar dari batas infrastruktur perusahaan.- Penyusunan
promptpada fungsigenerate_answer: Mengarahkan LLM agar membatasi pengetahuannya hanya berdasarkan konteks yang diambil dari Vector Database, sehingga menekan potensi halusinasi.
7. Skenario Produksi & Studi Kasus Dunia Nyata
Dalam lingkungan produksi berbeban tinggi (misalnya 500 concurrent users), menjalankan skrip Python sinkron akan menyebabkan bottleneck pada I/O network dan proses embedding.
Solusi Arsitektur Produksi:
- Backend API menggunakan FastAPI dengan worker asynchronous (
uvicorn). - Pipeline Ingestion dokumen besar di-offload menggunakan Celery dengan Redis sebagai broker antrean (message queue).
- Nginx dipasang sebagai Reverse Proxy di depan FastAPI dengan konfigurasi rate limiting dan SSL/TLS menggunakan Let's Encrypt.
Konfigurasi Nginx (/etc/nginx/sites-available/rag-api):
limit_req_zone $binary_remote_addr zone=rag_limit:10m rate=10r/s;
server {
listen 80;
server_name rag.internal.enterprise.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name rag.internal.enterprise.com;
ssl_certificate /etc/letsencrypt/live/rag.internal.enterprise.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rag.internal.enterprise.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 50M;
location / {
limit_req zone=rag_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
}
}
8. Common Pitfalls, Anti-Patterns & Solusi
- Anti-Pattern 1: Ukuran Chunk Terlalu Besar (Over-chunking)
- Masalah: Menyimpan satu dokumen utuh sebagai satu vektor merusak presisi pencarian karena informasi spesifik terendam dalam teks umum.
- Solusi: Terapkan chunking dengan ukuran 512 token dan overlap 64 token menggunakan recursive character text splitter.
- Anti-Pattern 2: Mengabaikan Normalisasi Vektor
- Masalah: Perhitungan Cosine Similarity pada vektor yang tidak dinormalisasi dapat memakan waktu komputasi lebih lama.
- Solusi: Pastikan model embedding secara native menghasilkan vektor ternormalisasi (unit vector).
- Anti-Pattern 3: Tidak Mengamankan Port Vector Database
- Masalah: Membuka port 6333 Qdrant ke publik tanpa autentikasi token.
- Solusi: Aktifkan API Key di Qdrant dan pastikan port hanya dapat diakses melalui internal Docker network atau localhost.
9. Benchmark, Metrik Kinerja & Tuning Performa
Lakukan pengujian beban (load testing) menggunakan tool k6 untuk menguji ketahanan API RAG terhadap lonjakan trafik.
Script pengujian load-test.js:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '1m', target: 0 },
],
};
export default function () {
const url = 'https://rag.internal.enterprise.com/api/v1/query';
const payload = JSON.stringify({
query: 'Bagaimana prosedur klaim asuransi kesehatan karyawan?'
});
const params = {
headers: { 'Content-Type': 'application/json' },
};
const res = http.post(url, payload, params);
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 2000ms': (r) => r.timings.duration < 2000,
});
sleep(1);
}
Tuning Parameter Qdrant untuk Performa Maksimal:
- Sesuaikan parameter
m(jumlah koneksi per node di HNSW graph, misal 16-64) danef_construct(ukuran dynamic candidate list saat konstruksi index) untuk menyeimbangkan antara kecepatan pencarian dan akurasi (Recall).
10. Checklist Penerapan Produksi & Kesimpulan
- Pastikan seluruh komunikasi antar container menggunakan Docker internal network yang terisolasi.
- Konfigurasikan rotasi log otomatis untuk Docker container dan Nginx menggunakan
logrotate. - Terapkan enkripsi at-rest pada direktori penyimpanan Qdrant (
/opt/enterprise-rag/data/qdrant_storage). - Set up backup otomatis harian untuk data vektor menggunakan cron job dan sinkronisasi ke S3 bucket terenkripsi.
- Monitor penggunaan VRAM GPU dan RAM sistem menggunakan Prometheus dan Grafana.
Dengan mengikuti arsitektur dan panduan implementasi ini, organisasi Anda dapat membangun sistem Retrieval-Augmented Generation (RAG) kelas enterprise yang aman, berkinerja tinggi, dan sepenuhnya berada di bawah kendali infrastruktur lokal.