Tutorial Build RAG System Lokal: Node.js, Qdrant & Ollama Embeddings
Poin Kunci Artikel Ini:
- [ ] Container Qdrant aktif dan terkonfigurasi dengan storage persistent.
- [ ] Collection Qdrant terbuat dengan dimensi 768 dan metrik Cosine.
- [ ] Payload full-text index dikonfigurasi untuk mendukung pencarian hybrid.
Privasi Data, Hukum Regulasi, dan Keterbatasan RAG Vektor Tunggal
Mengirimkan dokumen internal perusahaan ke API LLM publik seperti OpenAI atau Anthropic membawa ancaman kebocoran data sensitif. Regulasi perlindungan data seperti GDPR dan UU PDP No. 27/2022 mewajibkan kontrol penuh atas lokasi penyimpanan dan pemrosesan informasi rahasia. Ketergantungan pada API pihak ketiga juga memicu kendala latensi jaringan, kuota pemanggilan (rate limits), serta pembengkakan biaya operasional seiring bertambahnya volume data yang diindeks.
Pendekatan standar untuk query dokumen adalah Retrieval-Augmented Generation (RAG) berbasis vector search murni (dense retrieval). Metode ini mengubah teks menjadi ruang vektor berdimensi tinggi untuk menangkap konteks semantik. Namun, RAG vektor tunggal memiliki kelemahan struktural. Dense vectors unggul pada pemahaman makna abstrak, tetapi kerap gagal pada pencarian persis (exact match). Kata kunci spesifik seperti nomor seri perangkat keras, kode error sistem, nama variabel fungsi, atau istilah teknis spesifik sering kali tidak terambil secara akurat oleh penelusuran vektor murni.
Arsitektur RAG lokal dengan metode hybrid search menyelesaikan dua masalah inti ini. Seluruh data tetap tersimpan dalam batas infrastruktur lokal tanpa lalu lintas jaringan keluar. Penggabungan pencarian vektor semantik dan penelusuran kata kunci berbasis teks (lexical search) meningkatkan akurasi retrieval secara signifikan, memastikan konteks yang relevan berhasil diekstrak sebelum dikirim ke LLM.
Spesifikasi Stack Lokal: Ollama, Qdrant, dan Node.js
Sistem ini menggunakan tiga komponen utama yang berjalan sepenuhnya di lingkungan lokal:
- Ollama: Execution engine untuk menjalankan model embedding
nomic-embed-text(dimensi 768) dan model LLMllama3.2(generasi jawaban) tanpa ketergantungan cloud. - Qdrant: Vector database teroptimasi dalam bahasa C++ yang hemat memori, mendukung HNSW (Hierarchical Navigable Small World) index, payload filtering, dan penyimpanan berbasis disk.
- Node.js: Runtime aplikasi untuk eksekusi pipeline chunking dokumen, pengelolaan skema data Qdrant, orchestrator retrieval, dan komunikasi API lokal.
Setup Environment dan Infrastruktur
1. Deployment Qdrant Menggunakan Docker
Jalankan container Qdrant dengan konfigurasi volume disk lokal agar data vektor bertahan saat container dihentikan:
docker run -d -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage:z \
--name qdrant-rag qdrant/qdrant:v1.9.2Verifikasi bahwa Qdrant beroperasi melalui dashboard internal di URL http://localhost:6333/dashboard.
2. Pengunduhan Model Embeddings dan LLM
Unduh model nomic-embed-text untuk vektorisasi teks dan llama3.2 untuk sintesis jawaban:
ollama pull nomic-embed-text
ollama pull llama3.2Model nomic-embed-text dipilih karena memiliki dimensi 768 yang efisien, performa retrieval tinggi pada benchmark MTEB, serta penggunaan memori RAM yang minimal.
3. Inisialisasi Proyek Node.js
Buat repositori proyek dan pasang dependensi resmi yang dibutuhkan:
mkdir rag-lokal-node && cd rag-lokal-node
npm init -y
npm install @qdrant/js-client-rest ollama @langchain/textsplitters dotenvImplementasi Pipeline RAG Terintegrasi
1. Konfigurasi Collection Qdrant (init-db.js)
Skema collection harus menentukan ukuran dimensi vektor yang cocok dengan model embedding. Buat berkas init-db.js:
import { QdrantClient } from '@qdrant/js-client-rest';
const client = new QdrantClient({ url: 'http://localhost:6333' });
const COLLECTION_NAME = 'documents_rag';
async function setupCollection() {
const collections = await client.getCollections();
const exists = collections.collections.some(c => c.name === COLLECTION_NAME);
if (!exists) {
await client.createCollection(COLLECTION_NAME, {
vectors: {
size: 768,
distance: 'Cosine',
},
optimizers_config: {
default_segment_number: 2,
},
hnsw_config: {
m: 16,
ef_construct: 100,
},
});
console.log(`Collection ${COLLECTION_NAME} berhasil dikonfigurasi.`);
} else {
console.log(`Collection ${COLLECTION_NAME} sudah tersedia.`);
}
}
setupCollection().catch(console.error);2. Chunking Teks dan Data Ingestion (ingest.js)
Dokumen berukuran besar harus dipotong menjadi bagian-bagian kecil (chunks) agar representasi vektor tetap presisi. Buat berkas ingest.js:
import { QdrantClient } from '@qdrant/js-client-rest';
import ollama from 'ollama';
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
import crypto from 'crypto';
import fs from 'fs';
const client = new QdrantClient({ url: 'http://localhost:6333' });
const COLLECTION_NAME = 'documents_rag';
async function processAndIngest(filePath) {
const text = fs.readFileSync(filePath, 'utf-utf-8');
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 500,
chunkOverlap: 50,
});
const docs = await splitter.createDocuments([text]);
console.log(`Dokumen berhasil dipecah menjadi ${docs.length} chunk.`);
const points = [];
for (let i = 0; i < docs.length; i++) {
const chunkText = docs[i].pageContent;
const response = await ollama.embeddings({
model: 'nomic-embed-text',
prompt: chunkText,
});
points.push({
id: crypto.randomUUID(),
vector: response.embedding,
payload: {
content: chunkText,
source: filePath,
chunk_index: i,
createdAt: new Date().toISOString(),
},
});
}
await client.upsert(COLLECTION_NAME, {
wait: true,
points: points,
});
console.log('Ingestion dokumen selesai.');
}
processAndIngest('./data-internal.txt').catch(console.error);3. Pipeline Retrieval dan Generasi Jawaban (query.js)
Proses pencarian mengubah pertanyaan pengguna menjadi vektor query, mengambil chunk paling relevan dari Qdrant, lalu menyusun prompt untuk LLM lokal. Buat berkas query.js:
import { QdrantClient } from '@qdrant/js-client-rest';
import ollama from 'ollama';
const client = new QdrantClient({ url: 'http://localhost:6333' });
const COLLECTION_NAME = 'documents_rag';
async function askQuestion(userQuery) {
const queryEmbedding = await ollama.embeddings({
model: 'nomic-embed-text',
prompt: userQuery,
});
const searchResult = await client.search(COLLECTION_NAME, {
vector: queryEmbedding.embedding,
limit: 3,
});
if (searchResult.length === 0) {
console.log('Konteks tidak ditemukan.');
return;
}
const context = searchResult
.map(hit => `[Score: ${hit.score.toFixed(4)}] ${hit.payload.content}`)
.join('\
---\
');
const prompt = `Gunakan konteks berikut untuk menjawab pertanyaan. Jika jawaban tidak ada dalam konteks, katakan bahwa informasi tidak tersedia.\
\
Konteks:\
${context}\
\
Pertanyaan: ${userQuery}\
Jawaban:`;
const response = await ollama.generate({
model: 'llama3.2',
prompt: prompt,
stream: false,
options: {
temperature: 0.2,
},
});
console.log('\
=== JAWABAN RAG ===');
console.log(response.response);
}
askQuestion('Apa prosedur penanganan insiden keamanan di perusahaan?');Peningkatan Akurasi: Hybrid Search, Payload Indexing, dan Re-ranking
1. Payload Full-Text Indexing di Qdrant
Untuk mendukung pencarian teks langsung bersamaan dengan pencarian vektor, buat index payload pada field content:
await client.createPayloadIndex(COLLECTION_NAME, {
field_name: 'content',
field_schema: 'text',
});Fitur ini memungkinkan pemfilteran berbasis kata kunci persis (exact match) dikombinasikan dengan pencarian kemiripan kosinus (cosine similarity), mencegah terlewatinya dokumen yang mengandung istilah teknis langka.
2. Re-Ranking Konteks dengan Cross-Encoder
Vector similarity murni kadang menempatkan dokumen kurang relevan di peringkat atas. Menambahkan tahap Re-ranking menggunakan Cross-Encoder memproses ulang pasangan query-dokumen untuk menghasilkan bobot relevansi yang lebih tinggi sebelum prompt dikirim ke LLM.
3. Penyesuaian Strategi Chunking Dokumen
- Kode Sumber dan Spesifikasi API: Gunakan
chunkSize: 300hingga400denganchunkOverlap: 40. Ukuran chunk kecil mencegah pencampuran logika antar fungsi. - Dokumen Regulasi dan Prosedur (SOP): Gunakan
chunkSize: 800hingga1000denganchunkOverlap: 100untuk mempertahankan konteks aturan yang kompleks.
Manajemen Memori RAM dan Optimasi Resource Lokal
Menjalankan sistem AI sepenuhnya di lokal memerlukan pengelolaan penggunaan memori yang ketat:
- Ollama Model Eviction: Secara bawaan Ollama menyimpan model di RAM/VRAM selama 5 menit. Tambahkan parameter
keep_alive: "1m"pada pemanggilan API untuk segera membebaskan memori setelah eksekusi selesai. - Penyimpanan Index Disk di Qdrant: Jika jumlah vektor mencapai ratusan ribu, aktifkan parameter
on_disk_payload: truedanhnsw_config.on_disk: truepada konfigurasi collection. Hal ini memindahkan index dari RAM ke SSD untuk menghindari kesalahan Out-Of-Memory (OOM). - Kuantisasi Model (Quantization): Gunakan varian model terkuantisasi 4-bit atau 5-bit (misalnya
llama3.2:3b-instruct-q4_K_M) untuk menjaga performa penalaran tanpa menghabiskan VRAM GPU/RAM sistem.
Panduan Troubleshooting Masalah Umum
- Vector Dimension Mismatch Error: Terjadi jika ukuran vektor di Qdrant (misal 1536) berbeda dengan output model embedding (768). Pastikan
size: 768diset sesuai dengannomic-embed-text. - Koneksi Qdrant Refused: Pastikan container Docker aktif dan port 6333 terpetakan dengan benar ke host. Check status via
docker ps. - LLM Hallucination: Berikan instruksi ketat pada system prompt agar LLM menolak menjawab jika informasi tidak ditemukan dalam teks konteks yang disajikan.
Checklist Implementasi Sistem
- [ ] Container Qdrant aktif dan terkonfigurasi dengan storage persistent.
- [ ] Model
nomic-embed-textdanllama3.2berhasil di-pull via Ollama. - [ ] Collection Qdrant terbuat dengan dimensi 768 dan metrik Cosine.
- [ ] Payload full-text index dikonfigurasi untuk mendukung pencarian hybrid.
- [ ] Manajemen RAM (keep_alive & on_disk) disesuaikan dengan kapasitas hardware server.
Kesimpulan dan Arsitektur Produksi
Membangun sistem RAG lokal dengan Node.js, Qdrant, dan Ollama memberikan privasi data mutlak, menekan biaya langganan API cloud hingga nol, dan menghilangkan ketergantungan jaringan luar. Kombinasi nomic-embed-text dan Qdrant terbukti responsif dan tangguh pada infrastruktur lokal berdaya komputasi menengah.
Untuk lingkungan produksi enterprise, tambahkan layer API Gateway (Nginx), antrean tugas asinkron (BullMQ) untuk proses ingestion massal, serta komponen Cross-Encoder Re-ranker untuk akurasi maksimal. Arsitektur ini menghasilkan pencerdasan dokumen offline yang aman, cepat, dan terukur.


