L
Artificial Intelligence & LLM • Literally Web ID

Implementasi RAG (Retrieval-Augmented Generation) dengan LLM Lokal

⏱️ 18 menit baca • 📅 14 Oct 2026 • 👁️ 1 views
Implementasi RAG (Retrieval-Augmented Generation) dengan LLM Lokal

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap pengembangan enterprise modern, integrasi kecerdasan buatan generatif (Generative AI) sering kali terbentur oleh dua kendala mendasar: halusinasi model dan keterbatasan jendela konteks (context window) dari Large Language Model (LLM). Ketika sebuah perusahaan ingin membangun basis pengetahuan internal (enterprise knowledge base) menggunakan LLM, mengandalkan memori internal model saja tidak cukup karena data perusahaan bersifat privat, dinamis, dan terus berkembang setiap hari.

Secara tradisional, perusahaan mencoba mengatasi hal ini dengan melakukan fine-tuning pada model dasar. Namun, pendekatan ini memiliki bottleneck performa dan operasional yang masif: biaya komputasi yang tinggi, risiko catastrophic forgetting, serta latensi pembaruan data yang lambat karena setiap kali dokumen berubah, model harus dilatih ulang (re-trained). Di sisi lain, mengandalkan arsitektur web konvensional dengan pencarian teks berbasis SQL LIKE atau full-text search (seperti MySQL MyISAM/InnoDB atau PostgreSQL tsvector) sering kali gagal memahami konteks semantik atau niat di balik kueri pengguna (semantic gap).

Sebelum penerapan RAG (Retrieval-Augmented Generation) lokal:

  • Kondisi Lama: Pencarian berbasis kata kunci parsial, sering menghasilkan jawaban false-positive atau no-results jika terminologi pengguna tidak persis sama dengan dokumen.
  • Ketergantungan Eksternal: Ketergantungan pada API komersial pihak ketiga (seperti OpenAI atau Anthropic) memicu masalah kepatuhan data (data compliance, GDPR, ISO 27001) karena data rahasia perusahaan harus dikirim ke luar infrastruktur lokal.

Sesudah penerapan RAG lokal terintegrasi:

  • Infrastruktur Mandiri: Seluruh pemrosesan, mulai dari embedding generation, penyimpanan vector database, hingga inferensi LLM, berjalan di atas infrastruktur lokal (on-premise) atau private cloud yang dikelola sendiri menggunakan Nginx, Docker, PostgreSQL dengan pgvector, dan Ollama/vLLM.
  • Akurasi Tinggi & Real-Time: Dokumen baru yang dimasukkan ke dalam sistem langsung dapat diindeks dan dicari secara semantik dalam hitungan detik tanpa modifikasi pada parameter bobot LLM.

2. Konsep Inti & Deep Dive Arsitektur

Arsitektur RAG bekerja dengan cara menggabungkan sistem pencarian informasi (Information Retrieval) dengan model generasi teks (Text Generation). Alur data (data flow) sistem RAG lokal terbagi menjadi dua fase utama: Fase Ingesti Dokumen (Offline/Batch) dan Fase Kueri Real-time (Online).

Diagram Alur Arsitektur RAG Lokal

+-------------------------------------------------------------------------+
|                         FASE INGESTI DOKUMEN                            |
+-------------------------------------------------------------------------+
  [Dokumen PDF/MD/TXT] ---> [Text Chunking Engine] ---> [Embedding Model] 
                                                             |
                                                             v
                          [PostgreSQL + pgvector] <--- [Vector Normalization]

+-------------------------------------------------------------------------+
|                         FASE KUERI REAL-TIME                            |
+-------------------------------------------------------------------------+
  [Client / Web UI] ---> [Nginx Reverse Proxy] ---> [Backend API (Go/Node)]
                                                         |
                                    +--------------------+--------------------+
                                    |                                         |
                                    v                                         v
                        [Embedding Kueri (Lokal)]                [Prompt Generator]
                                    |
                                    v
                        [Cari Vector Terdekat (KNN)] 
                                    |
                                    v
                        [Context + Prompt Template] --------------------------+
                                                                              |
                                                                              v
                                                                   [LLM Lokal (Ollama/vLLM)]
                                                                              |
                                                                              v
                                                                   [Response ke Client]
  1. Text Chunking Engine: Memecah dokumen panjang menjadi potongan-potongan kecil (chunks) dengan ukuran optimal (misal 512 token) dengan overlap 50 token untuk memastikan konteks kalimat tidak terpotong di tengah.
  2. Embedding Model: Mengubah setiap chunk teks menjadi representasi vektor numerik berdimensi tinggi (misal 768 atau 1536 dimensi) menggunakan model lokal seperti nomic-embed-text atau bge-large-en-v1.5.
  3. Vector Database (pgvector): Menyimpan vektor dan teks asli, serta menyediakan indeks pencarian tetangga terdekat (Approximate Nearest Neighbor / ANN) menggunakan algoritma HNSW (Hierarchical Navigable Small World).
  4. LLM Lokal: Menerima prompt gabungan yang berisi kueri pengguna dan konteks dokumen hasil pencarian semantik untuk menghasilkan jawaban yang akurat, faktual, dan bebas halusinasi.

3. Perbandingan Komparatif / Analisis Pendekatan

Pemilihan komponen infrastruktur sangat menentukan performa sistem RAG secara keseluruhan. Tabel berikut membandingkan pendekatan penyimpanan vektor dan model embedding populer untuk environment produksi.

Komponen / Dimensi Opsi A: PostgreSQL + pgvector Opsi B: Qdrant / Milvus (Dedicated Vector DB) Opsi C: ChromaDB / FAISS (Embedded)
Arsitektur Penyimpanan Relasional + Ekstensi Vektor Layanan Mandiri (Stand-alone Service) Memori / File Lokal (Embedded)
Pengelolaan Operasional Sangat Mudah (memanfaatkan DB eksisting) Sedang (membutuhkan klaster terpisah) Sangat Mudah (cocok untuk dev/testing)
Skalabilitas Data (> 10M Vektor) Baik (dengan tuning HNSW & RAM cukup) Sangat Tinggi (didesain khusus untuk klaster) Terbatas (kurang optimal untuk konkurensi tinggi)
Konsumsi RAM & CPU Moderat (berbagi dengan beban database) Tinggi (dioptimalkan untuk performa tinggi) Rendah hingga Sedang
Latensi Kueri (P99) ~15ms - 45ms ~5ms - 15ms ~10ms - 30ms
Rekomendasi Penggunaan Enterprise skala menengah-besar Enterprise skala masif & real-time tinggi Proyek kecil, PoC, atau aplikasi monolitik

[!IMPORTANT] Untuk arsitektur enterprise yang sudah menggunakan PostgreSQL sebagai basis data utama, penggunaan ekstensi pgvector jauh lebih disarankan guna menghindari kompleksitas operasional penambahan sistem database baru (infrastructure sprawl).

4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum melakukan deploy, pastikan infrastruktur server memenuhi spesifikasi minimum berikut agar inferensi LLM dan pencarian vektor berjalan lancar tanpa mengalami Out of Memory (OOM).

Spesifikasi Hardware Minimum (Produksi):

  • CPU: 8 Core / 16 Thread (Intel Xeon / AMD EPYC modern atau Apple Silicon M2/M3).
  • RAM: Minimum 32 GB (Disarankan 64 GB ECC).
  • Storage: 500 GB NVMe SSD (IOPS tinggi sangat krusial untuk pemuatan bobot model LLM).
  • GPU: NVIDIA GPU (Contoh: RTX 4090 / A10G / L4) dengan VRAM minimal 24 GB untuk model ukuran 7B/8B parameter.

Dependensi Perangkat Lunak:

  • Ubuntu Server 22.04 LTS / Debian 12
  • Docker & Docker Compose v2+
  • Nginx (Reverse Proxy & SSL Termination)
  • PostgreSQL 15+ dengan ekstensi pgvector
  • Ollama (untuk menjalankan LLM dan Embedding Model secara lokal)

Verifikasi ketersediaan Docker dan sistem GPU pada terminal:

sudo apt update && sudo apt upgrade -y
docker --version
docker compose version
nvidia-smi

5. Panduan Implementasi Langkah demi Langkah

Kita akan membangun stack RAG lokal menggunakan Docker Compose yang mencakup Nginx, PostgreSQL (pgvector), Ollama, dan layanan backend berbasis Node.js/Go untuk menangani logika orkestrasi.

Langkah 1: Konfigurasi Docker Compose (docker-compose.yml)

Buat file direktori proyek /opt/local-rag dan buat file docker-compose.yml dengan konfigurasi berikut:

version: '3.8'

services:
  db:
    image: pgvector/pgvector:pg16
    container_name: rag_postgres
    environment:
      POSTGRES_USER: rag_user
      POSTGRES_PASSWORD: secure_database_password_production
      POSTGRES_DB: enterprise_rag
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U rag_user -d enterprise_rag"]
      interval: 5s
      timeout: 5s
      retries: 5

  ollama:
    image: ollama/ollama:latest
    container_name: rag_ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    restart: unless-stopped

  backend:
    build: .
    container_name: rag_backend
    environment:
      - PORT=8080
      - DB_HOST=db
      - DB_PORT=5432
      - DB_USER=rag_user
      - DB_PASSWORD=secure_database_password_production
      - DB_NAME=enterprise_rag
      - OLLAMA_HOST=http://ollama:11434
    ports:
      - "8080:8080"
    depends_on:
      db:
        condition: service_healthy
      ollama:
        condition: service_started
    restart: unless-stopped

volumes:
  pgdata:
  ollama_data:

Langkah 2: Konfigurasi Nginx Reverse Proxy (/etc/nginx/sites-available/rag.enterprise.internal)

upstream rag_backend_cluster {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    listen 80;
    server_name rag.enterprise.internal;
    return 30 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name rag.enterprise.internal;

    ssl_certificate /etc/letsencrypt/live/rag.enterprise.internal/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/rag.enterprise.internal/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    client_max_body_size 50M;

    location / {
        proxy_pass http://rag_backend_cluster;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;
        proxy_send_timeout 300s;
    }
}

Langkah 3: Skema Database Inisialisasi (init.sql atau migrasi backend)

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE IF NOT EXISTS knowledge_chunks (
    id SERIAL PRIMARY KEY,
    document_title VARCHAR(255) NOT NULL,
    chunk_text TEXT NOT NULL,
    embedding VECTOR(768),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- Membuat indeks HNSW untuk pencarian vektor berkecepatan tinggi
CREATE INDEX IF NOT EXISTS knowledge_chunks_embedding_hnsw_idx 
ON knowledge_chunks 
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

Langkah 4: Implementasi Backend Engine (Node.js / TypeScript atau Go)

Berikut adalah contoh implementasi layanan backend dalam Node.js (server.js) yang menangani proses embedding, pencarian vektor, dan komunikasi dengan Ollama:

const express = require('express');
const { Pool } = require('pg');
const axios = require('axios');

const app = express();
app.use(express.json());

const pool = new Pool({
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
});

const OLLAMA_HOST = process.env.OLLAMA_HOST || 'http://localhost:11434';

// Fungsi untuk menghasilkan embedding menggunakan model lokal (misal: nomic-embed-text)
async function getEmbedding(text) {
    try {
        const response = await axios.post(`${OLLAMA_HOST}/api/embeddings`, {
            model: 'nomic-embed-text',
            prompt: text
        });
        return response.data.embedding;
    } catch (error) {
        console.error('Error generating embedding:', error.message);
        throw error;
    }
}

// Endpoint untuk Ingesti Dokumen
app.post('/api/ingest', async (req, res) => {
    const { title, text } = req.body;
    try {
        const embedding = await getEmbedding(text);
        const query = `
            INSERT INTO knowledge_chunks (document_title, chunk_text, embedding)
            VALUES ($1, $2, $3::vector)
            RETURNING id;
        `;
        const values = [title, text, `[${embedding.join(',')}]`];
        const result = await pool.query(query, values);
        res.status(201).json({ success: true, chunkId: result.rows[0].id });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

// Endpoint untuk Kueri RAG
app.post('/api/query', async (req, res) => {
    const { question } = req.body;
    try {
        // 1. Buat embedding dari pertanyaan pengguna
        const questionEmbedding = await getEmbedding(question);

        // 2. Lakukan pencarian similarity di PostgreSQL menggunakan pgvector (Cosine Distance)
        const searchQuery = `
            SELECT chunk_text, 1 - (embedding <=> $1::vector) AS similarity
            FROM knowledge_chunks
            ORDER BY embedding <=> $1::vector
            LIMIT 3;
        `;
        const searchResult = await pool.query(searchQuery, [`[${questionEmbedding.join(',')}]`]);
        
        const context = searchResult.rows.map(row => row.chunk_text).join('\n\n');

        // 3. Konstruksi Prompt untuk LLM Lokal (Llama3 atau Mistral)
        const prompt = `Anda adalah asisten AI korporat yang akurat. Gunakan konteks di bawah ini untuk menjawab pertanyaan dengan tepat. Jika jawaban tidak ditemukan dalam konteks, katakan dengan jujur bahwa Anda tidak tahu.\n\nKonteks:\n${context}\n\nPertanyaan: ${question}\n\nJawaban:`;

        // 4. Kirim ke Ollama LLM
        const llmResponse = await axios.post(`${OLLAMA_HOST}/api/generate`, {
            model: 'llama3',
            prompt: prompt,
            stream: false
        });

        res.json({
            question,
            answer: llmResponse.data.response,
            sources: searchResult.rows.map(r => ({ text: r.chunk_text, similarity: r.similarity }))
        });
    } catch (err) {
        res.status(500).json({ error: err.message });
    });
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
    console.log(`RAG Enterprise Backend running on port ${PORT}`);
});

6. Bedah Kode & Penjelasan Detail Baris Kunci

  1. Ekstensi vector & Indeks HNSW (init.sql):

    • CREATE EXTENSION IF NOT EXISTS vector; mengaktifkan modul C di dalam PostgreSQL untuk mengelola tipe data vektor.
    • USING hnsw (embedding vector_cosine_ops) membangun indeks hierarkis untuk mempercepat pencarian tetangga terdekat secara eksponensial. Tanpa indeks HNSW, database akan melakukan Sequential Scan yang sangat lambat seiring bertambahnya jumlah baris dokumen.
  2. Operator Cosine Distance (<=>):

    • embedding <=> $1::vector menghitung jarak kosinus antara vektor dokumen yang tersimpan dengan vektor kueri. Nilai 1 - distance digunakan untuk mendapatkan skor similarity (kemiripan) antara 0 hingga 1.
  3. Manajemen Konteks Prompt Engineering:

    • Penggabungan variabel context ke dalam prompt LLM memastikan model tidak berhalusinasi bebas, melainkan terikat pada batasan dokumen internal enterprise yang relevan.

[!WARNING] Pastikan ukuran chunk teks tidak melebihi batas token maksimum model embedding (misalnya 512 token untuk model nomic-embed-text). Melampaui batas ini akan menyebabkan pemotongan data (truncation) yang menghilangkan informasi penting.

7. Skenario Produksi & Studi Kasus Dunia Nyata

Pada lingkungan enterprise dengan ribuan karyawan yang melakukan kueri secara bersamaan (high concurrency), arsitektur monolitik di atas dapat mengalami bottleneck pada koneksi database dan penggunaan sumber daya GPU.

Strategi Skala Produksi:

  • Load Balancing Ollama: Deploy beberapa kontainer Ollama di belakang reverse proxy Nginx internal untuk mendistribusikan beban inferensi LLM (Load Balancing Round-Robin).
  • Connection Pooling: Gunakan pgBouncer di depan PostgreSQL untuk mengelola ribuan koneksi masuk dari backend Node.js tanpa membebani proses internal database.
  • Caching Redis: Simpan hasil embedding dari kueri yang sering diajukan di dalam Redis guna memangkas latensi pencarian hingga < 5ms.

8. Common Pitfalls, Anti-Patterns & Solusi

  • Anti-Pattern 1: Mengabaikan Overlap saat Chunking Dokumen.
    • Dampak: Kalimat atau informasi penting yang berada di perbatasan chunk terpotong, menyebabkan hilangnya konteks semantik.
    • Solusi: Selalu terapkan overlap sebesar 10-15% dari total ukuran chunk (misalnya chunk size 512, overlap 64 token).
  • Anti-Pattern 2: Tidak Melakukan Normalisasi Teks.
    • Dampak: Karakter khusus, spasi ganda, atau tag HTML yang terbawa dalam dokumen mentah merusak akurasi vektor.
    • Solusi: Bersihkan teks (sanitization) dari tag HTML, skrip, dan karakter non-printable sebelum dikirim ke embedding engine.
  • Anti-Pattern 3: Menggunakan Model Embedding Campuran.
    • Dampak: Menggunakan model embedding A untuk dokumen dan model B untuk kueri akan menghasilkan ruang vektor yang tidak kompatibel.
    • Solusi: Kunci satu model embedding yang konsisten untuk seluruh siklus hidup aplikasi.

9. Benchmark, Metrik Kinerja & Tuning Performa

Untuk menguji ketahanan sistem terhadap beban tinggi, gunakan tool pengujian beban seperti k6 atau wrk. Berikut adalah skrip pengujian k6 sederhana untuk menguji endpoint /api/query:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
    stages: [
        { duration: '30s', target: 50 },  // Ramp-up hingga 50 pengguna
        { duration: '1m', target: 50 },   // Pertahankan beban selama 1 menit
        { duration: '10s', target: 0 },   // Ramp-down
    ],
};

export default function () {
    const url = 'https://rag.enterprise.internal/api/query';
    const payload = JSON.stringify({
        question: 'Bagaimana prosedur klaimasuransi 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);
}

Parameter Tuning Lanjutan PostgreSQL:

Sesuaikan parameter berikut di postgresql.conf untuk performa pencarian vektor optimal:

shared_buffers = '16GB'
work_mem = '64MB'
mantenance_work_mem = '2GB'
ef_search = 100

10. Checklist Penerapan Produksi & Kesimpulan

Sebelum meluncurkan sistem RAG lokal ke lingkungan produksi (production release), pastikan seluruh item dalam checklist berikut telah terpenuhi:

  • Konfigurasi SSL/TLS yang valid dan aman pada Nginx Reverse Proxy.
  • Implementasi rate limiting pada Nginx untuk mencegah serangan Denial of Service (DoS) pada endpoint LLM.
  • Backup otomatis harian untuk volume data PostgreSQL (pgdata) dan direktori model Ollama.
  • Monitoring kesehatan sistem menggunakan Prometheus dan Grafana untuk memantau penggunaan VRAM, CPU, dan latensi kueri P99.
  • Audit izin akses database (Principle of Least Privilege) untuk akun koneksi backend.

Dengan menerapkan arsitektur RAG lokal yang komprehensif ini, enterprise Anda dapat membangun sistem kecerdasan buatan yang sangat akurat, aman, berdaulat atas data sendiri (data sovereignty), serta siap menghadapi lonjakan trafik skala besar.

🏷️ Tags: #Nginx #Devops #Postgresql #Docker #Ai Engineering