Beranda Blog Store
Artificial Intelegence

RAG dari Nol: Menghubungkan LLM dengan Dokumen Anda Menggunakan Embedding dan Vector Database

23 Agu 2026 Hartono 5 menit baca 10 Dilihat

Kalau Anda ingin model bahasa menjawab dari dokumen milik Anda sendiri, Rekan ArtonLabs, inilah pendekatan yang kami anggap paling masuk akal untuk memulai.

Model bahasa tidak tahu isi SOP kantor Anda, panduan akademik kampus, atau katalog produk bulan ini. Melatih ulang model untuk itu mahal dan cepat basi. RAG, singkatan dari Retrieval-Augmented Generation, menempuh jalan yang lebih masuk akal: cari potongan dokumen yang relevan dengan pertanyaan, sisipkan ke dalam prompt, dan biarkan model menjawab berdasarkan potongan itu. Tulisan ini membangun RAG sederhana yang sepenuhnya lokal, dengan Ollama untuk embedding dan model bahasa, serta ChromaDB sebagai penyimpan vektor.

Daftar Isi

Konsep: Embedding, Vector Database, dan Retrieval

Embedding adalah cara mengubah teks menjadi daftar angka (vektor) sedemikian rupa sehingga teks yang maknanya mirip menghasilkan vektor yang berdekatan. Vector database menyimpan vektor-vektor itu dan bisa menjawab "berikan lima potongan yang paling dekat dengan vektor ini" dengan cepat. Retrieval adalah langkah mencari potongan itu untuk pertanyaan pengguna. Setelah potongan ditemukan, ia dimasukkan ke prompt bersama pertanyaan, dan model bahasa menyusun jawaban. Tidak ada pelatihan; dokumen baru cukup diindeks ulang.

Persiapan

pip install chromadb ollama pypdf
ollama pull nomic-embed-text
ollama pull qwen3:8b

nomic-embed-text adalah model embedding yang ringan dan cukup baik untuk teks berbahasa Indonesia dalam pengalaman kami; ada pilihan lain di katalog Ollama kalau hasilnya kurang memuaskan. ChromaDB berjalan sebagai pustaka Python biasa dan menyimpan datanya ke folder, tanpa layanan terpisah.

Memotong Dokumen Menjadi Chunk

Dokumen utuh terlalu panjang untuk satu vektor dan terlalu panjang untuk prompt. Potong menjadi bagian 300 sampai 500 kata dengan sedikit tumpang tindih, supaya kalimat yang terpotong di batas tetap tertangkap di salah satu bagian.

from pypdf import PdfReader

def baca_pdf(path):
    return "\n".join(p.extract_text() or "" for p in PdfReader(path).pages)

def potong(teks, ukuran=400, tumpang=60):
    kata = teks.split()
    for i in range(0, len(kata), ukuran - tumpang):
        yield " ".join(kata[i:i + ukuran])

Untuk dokumen dengan judul bagian yang jelas, memotong per bagian biasanya memberi hasil lebih baik daripada memotong per jumlah kata, karena tiap chunk lalu membahas satu topik utuh.

Membuat Indeks Vektor

import chromadb, ollama, glob

client = chromadb.PersistentClient(path="./indeks")
col = client.get_or_create_collection("dokumen")

def embed(teks):
    return ollama.embed(model="nomic-embed-text", input=teks)["embeddings"][0]

for path in glob.glob("dokumen/*.pdf"):
    for n, chunk in enumerate(potong(baca_pdf(path))):
        col.add(ids=[f"{path}-{n}"], documents=[chunk],
                embeddings=[embed(chunk)],
                metadatas=[{"sumber": path, "bagian": n}])
print(col.count(), "chunk terindeks")

Metadata sumber penting: nanti jawaban bisa menyebutkan dari dokumen mana informasinya berasal, dan itu yang membuat pengguna percaya.

Menjawab Pertanyaan

def jawab(pertanyaan, k=5):
    hasil = col.query(query_embeddings=[embed(pertanyaan)], n_results=k)
    konteks = "\n\n".join(
        f"[{m['sumber']} bagian {m['bagian']}]\n{d}"
        for d, m in zip(hasil["documents"][0], hasil["metadatas"][0]))
    prompt = (
        "Jawab pertanyaan HANYA berdasarkan konteks berikut. "
        "Jika jawabannya tidak ada di konteks, katakan tidak tahu. "
        "Sebutkan sumber yang dipakai.\n\n"
        f"KONTEKS:\n{konteks}\n\nPERTANYAAN: {pertanyaan}")
    res = ollama.chat(model="qwen3:8b",
                      messages=[{"role": "user", "content": prompt}])
    return res["message"]["content"]

print(jawab("Berapa lama masa studi maksimal program sarjana?"))

Untuk percakapan lanjutan, sertakan pertanyaan sebelumnya saat membuat embedding pencarian, karena pertanyaan seperti "kalau untuk mahasiswa asing bagaimana?" tidak bermakna tanpa konteks pertanyaan sebelumnya. Cara termudah: gabungkan dua pertanyaan terakhir menjadi satu teks pencarian.

Instruksi "katakan tidak tahu" bukan hiasan. Tanpa itu, model akan mengarang jawaban yang terdengar meyakinkan ketika dokumen tidak memuat informasinya, dan itulah kegagalan RAG yang paling merusak kepercayaan.

Meningkatkan Kualitas

Kalau jawabannya sering meleset, periksa dulu langkah retrieval, bukan modelnya. Cetak lima chunk yang ditemukan untuk beberapa pertanyaan uji; kalau chunk yang benar tidak muncul, masalahnya ada di pemotongan atau model embedding, dan mengganti model bahasa tidak akan menolong. Beberapa perbaikan yang terbukti: chunk yang lebih kecil dengan tumpang tindih, menyertakan judul dokumen di awal tiap chunk, mengambil lebih banyak kandidat lalu menyaringnya ulang, dan menggabungkan pencarian kata kunci biasa dengan pencarian vektor untuk istilah khusus seperti kode mata kuliah atau nomor SK yang tidak "bermakna" bagi embedding.

Buat juga kumpulan 20 sampai 30 pertanyaan uji dengan jawaban yang diharapkan. Setiap kali Anda mengubah sesuatu, jalankan semuanya. Tanpa ini, perbaikan di satu sisi sering merusak sisi lain tanpa disadari.

Batasan yang Perlu Diketahui

RAG menjawab dari potongan, jadi pertanyaan yang butuh melihat seluruh dokumen ("rangkum semua perubahan tahun ini") tidak cocok untuknya. Tabel dan gambar di PDF sering hilang saat ekstraksi teks; periksa hasil extract_text sebelum mengindeks. Dan hak akses: kalau dokumen punya tingkat kerahasiaan berbeda, simpan tingkatnya di metadata dan saring saat query, supaya seorang pengguna tidak mendapat jawaban dari dokumen yang tidak boleh ia baca.

Mengindeks Ulang Saat Dokumen Berubah

Dokumen berubah, dan indeks yang basi memberi jawaban yang salah dengan percaya diri. Simpan hash isi tiap berkas bersama id chunk-nya; saat berkas berubah, hapus semua chunk milik berkas itu dengan col.delete(where={"sumber": path}) lalu indeks ulang hanya berkas tersebut. Jalankan pemeriksaan ini sebagai tugas terjadwal, misalnya tiap malam dengan cron atau Django-Q2, bukan saat pengguna bertanya. Untuk koleksi yang besar, pisahkan koleksi per jenis dokumen (SOP, panduan, pengumuman) supaya penyaringan lebih cepat dan penghapusan lebih aman.

Contoh Himpunan Pertanyaan Uji

Beberapa contoh dari himpunan uji yang kami pakai untuk asisten panduan akademik: pertanyaan faktual langsung ("berapa SKS minimal untuk yudisium"), pertanyaan yang butuh dua bagian dokumen sekaligus ("syarat cuti akademik dan dampaknya pada masa studi"), pertanyaan yang jawabannya tidak ada di dokumen ("berapa biaya parkir"), dan pertanyaan dengan istilah informal ("kalau mau berhenti kuliah sebentar gimana"). Kelompok ketiga menguji apakah model berani berkata tidak tahu; kelompok keempat menguji apakah embedding menangkap makna, bukan kata. Catat skor tiap kelompok secara terpisah, karena perbaikan untuk satu kelompok sering menurunkan kelompok lain.

dan tanpa data keluar. Untuk tim kecil dengan beberapa ratus dokumen, ini cukup untuk asisten internal yang benar-benar berguna. Lapisan API dari artikel sebelumnya adalah tempat alami untuk menaruh fungsi jawab() ini, lengkap dengan autentikasi dan riwayat.

Sampai jumpa di tulisan berikutnya. Pertanyaan tentang RAG, terutama soal kualitas retrieval, selalu kami sambut.