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:
- Chunking — memecah dokumen menjadi potongan kecil (chunk) agar mudah dicari.
- Embedding — mengubah setiap chunk menjadi vektor angka yang mewakili maknanya.
- Indexing — menyimpan vektor-vektor tersebut ke vector database.
- Retrieval — saat ada pertanyaan, cari chunk yang paling mirip secara makna.
- 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.
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.
# 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.
# 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.
# 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.
# 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.
# Unduh model sekali saja (sekitar 2 GB)
ollama pull llama3.2
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.
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.
# 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.
BengkelKode