Bangun Local RAG App dengan LlamaIndex dan Ollama
Tutorial praktis RAG lokal dengan LlamaIndex dan Ollama: arsitektur, kode, dan batasan sebelum dipakai production.
Banyak tutorial RAG langsung mengarahkan ke OpenAI atau penyedia LLM cloud lain. Itu praktis, tapi tidak selalu cocok: dokumen internal, data klien, atau eksperimen pribadi sering kali tidak boleh keluar dari laptop. Solusinya adalah menjalankan seluruh pipeline RAG secara lokal: embedding model dan LLM-nya di mesin sendiri, prompt dan dokumen tidak dikirim ke API pihak ketiga.
Pada tulisan ini kita akan membangun aplikasi RAG lokal sederhana dengan LlamaIndex sebagai framework indexing/query dan Ollama untuk menjalankan model lokal. Targetnya bukan aplikasi production yang kompleks, tapi fondasi yang bisa langsung kamu jalankan dan kembangkan sesuai kebutuhan.
Apa yang Sebenarnya Kita Bangun?
RAG bekerja dengan menggabungkan retrieval dan generation. Alurnya sederhana: dokumen dimuat, dipotong-potong jadi chunk, diubah menjadi vektor embedding, lalu disimpan di vector index. Saat pengguna bertanya, sistem mencari chunk yang paling relevan, memasukkannya sebagai konteks, dan meminta LLM menjawab berdasarkan konteks tersebut.
Arsitektur yang akan kita pakai:
data/ → SimpleDirectoryReader → chunking → embeddings → VectorStoreIndex
↓
user question → retriever (top-k chunks) → query engine → Ollama LLM → answer
Dua model berbeda akan berjalan di Ollama: satu sebagai embedding model dan satu lagi sebagai LLM untuk generate jawaban. Ini titik yang sering salah dipahami: model chat/generation seperti llama3.2:3b tidak cocok dipakai sebagai embedding model, dan sebaliknya.
Prasyarat dan Instalasi
Pastikan Ollama sudah terinstal. Jika belum, unduh dari ollama.com dan ikuti petunjuk instalasi untuk sistem operasi kamu.
Setelah Ollama siap, pull model yang akan dipakai:
ollama pull llama3.2:3b
ollama pull nomic-embed-text
llama3.2:3b adalah model chat kecil yang cukup ringan untuk laptop, sedangkan nomic-embed-text adalah model embedding khusus yang dirancang untuk mengubah teks menjadi vektor. Banyak tutorial lama memakai endpoint /api/embeddings untuk embedding, tapi dokumentasi Ollama saat ini menandai endpoint tersebut sebagai superseded oleh /api/embed. Integrasi LlamaIndex yang lebih baru sudah mengikuti endpoint ini, jadi pastikan versi Ollama dan library kamu tidak terlalu jauh tertinggal.
Selanjutnya siapkan project Python dengan uv (lihat panduan lengkap uv untuk detailnya):
uv init local-rag
cd local-rag
uv add llama-index llama-index-llms-ollama llama-index-embeddings-ollama
mkdir data
Letakkan file .txt atau .md yang ingin kamu tanyakan di dalam folder data/. Untuk percobaan pertama, gunakan dokumen teks murni supaya tidak perlu khawatir tentang parsing PDF atau HTML yang rumit.
Kode Aplikasi RAG Lokal
Berikut skrip minimal yang bisa langsung dijalankan. Strukturnya sengaja dibuat sederhana agar mudah dipahami sebelum ditambahkan fitur seperti API, UI, atau vector store eksternal.
from pathlib import Path
from llama_index.core import Settings, SimpleDirectoryReader, StorageContext
from llama_index.core import VectorStoreIndex, load_index_from_storage
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.llms.ollama import Ollama
DATA_DIR = Path("data")
PERSIST_DIR = Path("storage")
Settings.llm = Ollama(model="llama3.2:3b", request_timeout=120.0)
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")
if PERSIST_DIR.exists():
storage_context = StorageContext.from_defaults(persist_dir=str(PERSIST_DIR))
index = load_index_from_storage(storage_context)
else:
documents = SimpleDirectoryReader(str(DATA_DIR), recursive=True).load_data()
index = VectorStoreIndex.from_documents(documents)
index.storage_context.persist(persist_dir=str(PERSIST_DIR))
query_engine = index.as_query_engine(similarity_top_k=4)
while True:
question = input("Ask docs> ").strip()
if question in {"", "exit", "quit"}:
break
response = query_engine.query(question)
print(response)
Simpan sebagai app.py, lalu jalankan:
uv run app.py
Pertama kali dijalankan, skrip akan membaca file dari data/, memotongnya menjadi chunk, mengirim tiap chunk ke nomic-embed-text untuk dibuat embedding, lalu menyimpan hasilnya di folder storage/. Proses ini bisa memakan waktu beberapa menit tergantung jumlah dan ukuran dokumen. Jika storage/ sudah ada, skrip akan memuat ulang index yang sudah jadi, jadi tidak perlu embedding berulang kali.
Memahami Bagian-Bagian Kode
Settings dan Model
Settings.llm = Ollama(model="llama3.2:3b", request_timeout=120.0)
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")
Settings di LlamaIndex adalah konfigurasi global untuk pipeline. Di sini kita menetapkan LLM dan embedding model secara terpisah. request_timeout=120.0 memberi waktu lebih lama untuk model lokal, karena laptop biasanya lebih lambat dibanding GPU server.
Ingesti dan Persistensi
if PERSIST_DIR.exists():
storage_context = StorageContext.from_defaults(persist_dir=str(PERSIST_DIR))
index = load_index_from_storage(storage_context)
else:
documents = SimpleDirectoryReader(str(DATA_DIR), recursive=True).load_data()
index = VectorStoreIndex.from_documents(documents)
index.storage_context.persist(persist_dir=str(PERSIST_DIR))
SimpleDirectoryReader adalah loader paling mudah untuk file lokal. Dia otomatis membaca teks dari berbagai format seperti .txt dan .md. Namun, perlu diingat bahwa ini adalah loader untuk tutorial dan prototyping, bukan solusi ingestion production yang harus menangani PDF rumit, tabel, metadata, atau otorisasi akses file.
VectorStoreIndex menyimpan embedding di memori sebagai in-process vector store. Untuk development ini cukup, tapi untuk production kamu biasanya perlu menggantinya dengan Qdrant, Chroma, Postgres dengan pgvector, atau Redis vector search.
Query Engine
query_engine = index.as_query_engine(similarity_top_k=4)
Baris ini membuat objek yang menggabungkan retriever dan LLM. Saat ada pertanyaan, retriever mencari 4 chunk paling mirip, lalu LLM menjawab berdasarkan keempat chunk tersebut. Jumlah similarity_top_k patut diperhatikan: terlalu sedikit jawaban kurang informatif, terlalu banyak konteks jadi berantakan dan memperlambat inference.
Keunggulan dan Batasan yang Perlu Dijelaskan dengan Jujur
Menjalankan RAG secara lokal punya manfaat nyata:
- Privasi lebih baik: prompt dan dokumen tidak keluar dari mesin sendiri, asalkan kamu benar-benar memakai model lokal dan tidak mengaktifkan fitur telemetry atau cloud opsional.
- Biaya nol untuk inference: tidak ada token metered, cocok untuk eksperimen dan iterasi cepat.
- Tidak perlu koneksi internet: setelah model dan library diunduh, aplikasi bisa berjalan offline.
Tapi ada batasan yang sering diabaikan:
- Model lokal butuh resource: meskipun
llama3.2:3bcukup ringan, model embedding juga memakan RAM/VRAM. Di laptop dengan RAM terbatas, inference bisa lambat atau bahkan gagal. - Kualitas jawaban lebih rendah: model 3B parameter masih lebih sering menghasilkan halusinasi dibanding model besar seperti GPT-4 atau Llama 3 70B. RAG membantu membatasi jawaban pada konteks, tapi tidak menghilangkan risiko sepenuhnya.
- Retrieval adalah bottleneck: jika chunking terlalu besar/kecil, atau embedding model tidak cocok dengan domain dokumen, jawaban akan jelek meski LLM-nya bagus.
- Lokal bukan otomatis aman: log aplikasi, file sementara, copy dokumen, atau UI browser bisa saja bocor. Jika kamu nantinya menambahkan vector store cloud atau model cloud opsional, privasi lokal akan hilang.
Variasi dan Langkah Selanjutnya
Setelah versi dasar ini berjalan, beberapa pengembangan logis berikutnya:
- Menampilkan sumber: ubah output agar menunjukkan chunk atau dokumen mana yang dipakai sebagai konteks. Ini penting supaya pengguna bisa memverifikasi jawaban.
- Chunking yang lebih baik: LlamaIndex punya berbagai
NodeParseruntuk mengontrol ukuran dan overlap chunk. - Ganti vector store: pindah dari in-process index ke Chroma, Qdrant, atau Postgres/pgvector kalau data besar atau perlu diakses banyak instance.
- Expose sebagai API: bungkus query engine dengan FastAPI dan berikan endpoint REST atau streaming response.
Kapan Solusi Ini Cocok dan Kapan Tidak
Cocok untuk:
- Prototyping fitur RAG sebelum memutuskan apakah akan pakai model cloud.
- Dokumen internal atau pribadi yang tidak boleh diproses di API pihak ketiga.
- Eksperimen lokal tanpa biaya token.
Kurang cocok untuk:
- Production dengan banyak pengguna simultan, karena Ollama lokal tidak didesain untuk high-throughput serving.
- Kasus yang membutuhkan kualitas jawaban sangat tinggi, di mana model lokal kecil masih kurang andal.
- Workflow yang butuh ingestion kompleks dari berbagai sumber, PDF berformat rumit, atau akses kontrol dokumen.
Kesimpulan
LlamaIndex dan Ollama memungkinkan kita membangun aplikasi RAG lokal dengan kode yang singkat dan transparan. Versi yang kita buat di sini bukan aplikasi production siap pakai, tapi fondasi yang jujur: menunjukkan bagian mana yang benar-benar berjalan di mesin sendiri, bagian mana yang masih bisa jadi masalah privasi, dan di mana batasan model lokal mulai terasa.
Langkah paling penting setelah ini adalah tidak langsung mempercayai jawaban LLM. Verifikasi sumber, evaluasi kualitas retrieval, dan kembangkan iteratif. Kalau kamu sudah coba variasi sendiri, misalnya mengganti model embedding atau menambahkan FastAPI; bagikan di komentar.
