Model bahasa besar (LLM) seperti GPT atau Llama memang luar biasa pintar. Namun, mereka punya dua kelemahan besar: tidak tahu isi dokumen pribadimu, dan bisa mengarang jawaban alias berhalusinasi. Retrieval-Augmented Generation (RAG) hadir untuk menjawab dua masalah ini sekaligus — memadukan kekuatan pencarian informasi dengan kemampuan berbahasa LLM.

Di tutorial ini kamu akan membangun chatbot RAG dari nol menggunakan Python. Chatbot ini bisa menjawab pertanyaan berdasarkan dokumen yang kamu berikan sendiri — entah itu FAQ perusahaan, panduan produk, atau catatan pribadi. Semua kode di bawah bisa langsung dijalankan, dan kamu tidak perlu GPU mahal atau membayar API (kita pakai model lokal yang gratis).

Apa Itu RAG dan Bagaimana Cara Kerjanya?

Secara sederhana, RAG adalah pola arsitektur yang menyisipkan konteks hasil pencarian ke dalam prompt sebelum dikirim ke LLM. Dengan begitu, model menjawab berdasarkan data nyata, bukan sekadar dari ingatan parameternya. Alur kerjanya terdiri dari lima langkah:

  1. Chunking — memecah dokumen menjadi potongan kecil (chunk) agar mudah dicari.
  2. Embedding — mengubah setiap chunk menjadi vektor angka yang mewakili maknanya.
  3. Indexing — menyimpan vektor-vektor tersebut ke vector database.
  4. Retrieval — saat ada pertanyaan, cari chunk yang paling mirip secara makna.
  5. Generation — kirim chunk relevan sebagai konteks ke LLM untuk menghasilkan jawaban.

Prasyarat

Sebelum mulai, pastikan kamu sudah menyiapkan hal-hal berikut:

  • Python 3.9 atau lebih baru (cek dengan python --version).
  • pip, package manager bawaan Python.
  • Ollama (opsional) untuk menjalankan LLM secara lokal dan gratis — unduh di ollama.com.
  • Atau API key OpenAI jika kamu lebih suka memakai model berbasis cloud.

Langkah 1: Instalasi Dependensi

Buat direktori proyek, lalu install library yang dibutuhkan. Kita akan memakai sentence-transformers untuk embedding, chromadb untuk vector database, serta ollama dan openai untuk sisi generation.

bash
mkdir belajar-rag && cd belajar-rag
python -m venv venv
source venv/bin/activate      # Windows: venv\Scripts\activate
pip install chromadb sentence-transformers ollama openai

Catatan singkat soal masing-masing library:

  • sentence-transformers — menghasilkan embedding (vektor makna) dari teks, berjalan lokal.
  • chromadb — vector database ringan untuk menyimpan dan mencari vektor.
  • ollama — client Python untuk memanggil LLM lokal via Ollama.
  • openai — client untuk memanggil model OpenAI (alternatif berbasis cloud).

Langkah 2: Siapkan Dokumen dan Pecah Menjadi Chunk

RAG dimulai dari dokumen. Pada contoh ini kita pakai sebuah knowledge base sederhana tentang layanan berlangganan kopi fiktif. Kamu bisa menggantinya dengan dokumen apa pun — PDF, artikel, atau catatan internal.

LLM tidak bisa menerima dokumen utuh yang sangat panjang sekaligus, dan pencarian akan lebih presisi jika dilakukan per potongan. Karena itu kita pecah dokumen menjadi chunk berukuran kecil dengan overlap (tumpang tindih) agar tidak ada informasi yang terpotong di batas antar-chunk.

python
# siapkan_dokumen.py
dokumen = """
Kopi Nusantara adalah layanan berlangganan kopi bulanan asal Bandung.
Setiap bulan, pelanggan menerima 250 gram biji kopi arabika pilihan dari petani lokal.
Harga langganan adalah Rp 150.000 per bulan, sudah termasuk ongkos kirim ke seluruh Pulau Jawa.
Untuk pengiriman ke luar Jawa, dikenakan biaya tambahan sebesar Rp 25.000.
Pelanggan dapat memilih level roasting: light, medium, atau dark.
Pembatalan langganan dapat dilakukan kapan saja melalui aplikasi tanpa penalti.
Pengiriman dilakukan setiap tanggal 5 dan tiba dalam 2-3 hari kerja.
"""


def chunk_text(teks, chunk_size=30, overlap=5):
    """Pecah teks menjadi potongan kecil (chunk) dengan overlap antar potongan."""
    kata = teks.split()
    chunks = []
    start = 0
    while start < len(kata):
        chunk = " ".join(kata[start : start + chunk_size])
        chunks.append(chunk)
        start += chunk_size - overlap
    return chunks


chunks = chunk_text(dokumen)
print(f"Jumlah chunk: {len(chunks)}")
for i, c in enumerate(chunks):
    print(f"[{i}] {c[:60]}...")

Perhatikan dua parameter pada fungsi chunk_text: chunk_size menentukan jumlah kata per potongan, dan overlap menentukan berapa kata yang tumpang tindih dengan potongan berikutnya. Pada contoh kita pakai 30 kata supaya efeknya terlihat jelas — untuk dokumen nyata, gunakan 300–500 kata per chunk.

Tips memilih ukuran chunk: chunk yang terlalu kecil kehilangan konteks, sedangkan yang terlalu besar membuat hasil pencarian kurang presisi. Mulailah dari 300–500 kata, lalu sesuaikan dengan jenis dokumenmu.

Langkah 3: Buat Embedding untuk Setiap Chunk

Embedding adalah proses mengubah teks menjadi vektor angka (daftar bilangan) yang merepresentasikan makna semantiknya. Teks yang maknanya mirip akan punya vektor yang berdekatan. Kita pakai model all-MiniLM-L6-v2 dari sentence-transformers — model ini kecil, cepat, dan berjalan penuh di mesinmu tanpa koneksi internet.

python
# embedding.py  (lanjutan dari langkah sebelumnya, variabel `chunks` sudah ada)
from sentence_transformers import SentenceTransformer

# Model multilingual kecil yang cepat dan gratis (384 dimensi)
embedder = SentenceTransformer("all-MiniLM-L6-v2")

embeddings = embedder.encode(chunks, normalize_embeddings=True)
print("Bentuk matriks embedding:", embeddings.shape)  # (jumlah_chunk, 384)

Hasilnya berupa matriks berukuran (jumlah_chunk, 384) — setiap chunk diwakili oleh vektor 384 dimensi. Argumen normalize_embeddings=True membuat semua vektor punya panjang yang sama, sehingga perbandingan kemiripan (cosine similarity) menjadi lebih akurat.

Langkah 4: Simpan Vektor ke ChromaDB

Setelah punya vektor, kita simpan ke ChromaDB, sebuah vector database ringan yang bisa dipakai langsung tanpa server tambahan. ChromaDB menyimpan dokumen beserta vektornya, lalu menyediakan operasi pencarian kemiripan dengan cepat.

python
# simpan_vektor.py
import chromadb

client = chromadb.PersistentClient(path="./rag_db")
collection = client.get_or_create_collection(name="dokumen")

collection.add(
    ids=[f"chunk-{i}" for i in range(len(chunks))],
    documents=chunks,
    embeddings=embeddings.tolist(),
)
print("Total vektor tersimpan:", collection.count())

Dengan PersistentClient, data tersimpan permanen di folder ./rag_db. Artinya, kamu tidak perlu menghitung ulang embedding setiap kali program dijalankan — cukup buka kembali collection yang sama.

Langkah 5: Retrieval — Cari Konteks yang Relevan

Inilah inti RAG: saat pengguna bertanya, kita ubah pertanyaan menjadi vektor dengan model yang sama, lalu cari chunk yang paling mirip di database.

python
# retrieval.py
pertanyaan = "Berapa harga langganan bulanan?"

q_emb = embedder.encode([pertanyaan], normalize_embeddings=True).tolist()
hasil = collection.query(query_embeddings=q_emb, n_results=2)

for doc, skor in zip(hasil["documents"][0], hasil["distances"][0]):
    print(f"[{skor:.4f}] {doc[:80]}...")

Fungsi query mengembalikan dokumen teratas beserta jaraknya — semakin kecil jaraknya, semakin mirip maknanya. Chunk teratas inilah yang akan kita jadikan konteks untuk LLM pada langkah berikutnya.

Langkah 6: Generation — Jawab dengan LLM

Langkah terakhir adalah meminta LLM menjawab menggunakan konteks hasil retrieval. Kamu punya dua pilihan: Ollama (lokal dan gratis) atau OpenAI (cloud). Keduanya sama-sama cocok untuk kebutuhan ini.

Opsi A: Ollama (Lokal & Gratis)

Ollama memungkinkan kamu menjalankan LLM seperti Llama 3.2 langsung di komputermu. Setelah menginstalnya, unduh model lalu panggil lewat library Python ollama.

bash
# Unduh model sekali saja (sekitar 2 GB)
ollama pull llama3.2
python
import ollama

konteks = "\n\n".join(hasil["documents"][0])
prompt = (
    "Jawab pertanyaan berikut HANYA berdasarkan konteks yang diberikan.\n"
    "Jika jawabannya tidak ada di konteks, katakan 'Saya tidak tahu'.\n\n"
    f"Konteks:\n{konteks}\n\n"
    f"Pertanyaan: {pertanyaan}"
)

resp = ollama.chat(model="llama3.2", messages=[{"role": "user", "content": prompt}])
print(resp["message"]["content"])

Opsi B: OpenAI API

Jika kamu punya API key OpenAI, gunakan model seperti gpt-4o-mini yang murah dan cepat. Library openai otomatis membaca API key dari environment variable OPENAI_API_KEY.

python
from openai import OpenAI

client_ai = OpenAI()  # otomatis membaca OPENAI_API_KEY dari environment

resp = client_ai.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Jawab HANYA berdasarkan konteks yang diberikan."},
        {"role": "user", "content": f"Konteks:\n{konteks}\n\nPertanyaan: {pertanyaan}"},
    ],
)
print(resp.choices[0].message.content)

Jangan pernah menulis API key langsung di kode. Simpan di environment variable atau file .env, dan pastikan file tersebut tidak ikut ter-commit ke Git.

Langkah 7: Rakit Semuanya Jadi Chatbot Interaktif

Sekarang kita gabungkan semua langkah menjadi satu program rag_chatbot.py yang bisa diajak ngobrol langsung di terminal.

python
# rag_chatbot.py
import chromadb
from sentence_transformers import SentenceTransformer

# 1) Muat model embedding (cukup dijalankan sekali)
embedder = SentenceTransformer("all-MiniLM-L6-v2")

# 2) Buka vector database
client = chromadb.PersistentClient(path="./rag_db")
collection = client.get_or_create_collection(name="dokumen")


def cari_konteks(pertanyaan, n=3):
    q_emb = embedder.encode([pertanyaan], normalize_embeddings=True).tolist()
    hasil = collection.query(query_embeddings=q_emb, n_results=n)
    return "\n\n".join(hasil["documents"][0])


def jawab(pertanyaan, mode="ollama"):
    konteks = cari_konteks(pertanyaan)
    if mode == "ollama":
        import ollama
        prompt = (
            "Kamu adalah asisten yang menjawab HANYA berdasarkan konteks di bawah.\n"
            "Jika jawaban tidak ada di konteks, katakan 'Saya tidak tahu'.\n\n"
            f"Konteks:\n{konteks}\n\nPertanyaan: {pertanyaan}"
        )
        resp = ollama.chat(model="llama3.2", messages=[{"role": "user", "content": prompt}])
        return resp["message"]["content"]
    elif mode == "openai":
        from openai import OpenAI
        client_ai = OpenAI()
        resp = client_ai.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": "Jawab HANYA berdasarkan konteks yang diberikan."},
                {"role": "user", "content": f"Konteks:\n{konteks}\n\nPertanyaan: {pertanyaan}"},
            ],
        )
        return resp.choices[0].message.content
    else:
        raise ValueError("mode harus 'ollama' atau 'openai'")


if __name__ == "__main__":
    print("Chatbot RAG siap! Ketik 'keluar' untuk berhenti.\n")
    while True:
        tanya = input("Kamu: ").strip()
        if tanya.lower() in {"keluar", "exit", "quit"}:
            break
        print(f"Bot: {jawab(tanya)}\n")

Jalankan dengan python rag_chatbot.py, lalu ajukan pertanyaan seperti "Berapa harga langganan?" atau "Apakah ongkir ke luar Jawa gratis?". Chatbot akan menjawab hanya berdasarkan isi dokumenmu — dan dengan jujur mengaku "Saya tidak tahu" jika jawabannya tidak ada di dokumen.

RAG vs fine-tuning: RAG tidak mengubah model sama sekali, hanya menambahkan konteks saat inference. Hasilnya lebih murah, mudah diperbarui (cukup ganti dokumen), dan sumber jawabannya bisa dilacak — berbeda dengan fine-tuning yang menanam pengetahuan langsung ke bobot model secara permanen.

Kesimpulan

Selamat! Kamu baru saja membangun chatbot RAG yang berfungsi penuh dari nol. Sepanjang tutorial ini kamu telah mempraktikkan seluruh alur RAG: chunking → embedding → indexing → retrieval → generation.

Untuk melangkah lebih jauh, kamu bisa mencoba mengganti dokumen contoh dengan PDF sungguhan, meningkatkan kualitas retrieval dengan hybrid search, atau membungkus chatbot ini menjadi REST API menggunakan FastAPI. Selamat bereksperimen!

Catatan penting: RAG mengurangi halusinasi, tapi tidak menghilangkannya sepenuhnya. Selalu verifikasi jawaban penting dari LLM sebelum digunakan untuk keputusan yang kritis.