LangChain & LlamaIndex

5 jam12 min baca
Tujuan

Paham 2 framework populer untuk LLM apps. Pilih satu untuk capstone.

02 — LangChain & LlamaIndex

Estimasi: 5 jam Tujuan: Paham 2 framework populer untuk LLM apps. Pilih satu untuk capstone.


Kenapa Materi Ini Penting?

Bisa bikin RAG manual itu skill bagus, tapi tidak akan dipakai di project nyata. Setiap startup AI Indonesia (Kata.ai, Bahasa.ai, Glair, dll) sampai perusahaan internasional bekerja di atas framework seperti LangChain atau LlamaIndex. Job posting "Junior LLM Developer" hampir selalu mensyaratkan minimal salah satu. Menguasai satu framework dengan dalam = kamu bisa kerjakan tugas production dari hari pertama: parsing dokumen kompleks, multi-turn chat, retrieval canggih, agent dengan tools. Materi ini menjembatani prototype 50-baris ke aplikasi yang siap deploy ke ratusan user.


Pembukaan

Bisa bikin RAG manual (Fase 7.1). Tapi production butuh banyak hal: streaming, caching, async, evaluation, observability, dll.

Framework menyediakan komponen siap pakai. Saving you weeks.

Analogi: LangChain = Lego untuk LLM Apps

Bayangkan kamu mau bangun rumah-rumahan. Pilihan:

  • Bikin sendiri dari kayu (manual coding) — fleksibel, tapi 1 minggu cuma jadi 1 kursi
  • Pakai Lego (framework) — sudah ada balok-balok standar (prompt, retriever, memory, agent), tinggal kombinasi sesuai kebutuhan

LangChain dan LlamaIndex adalah dua set Lego terbesar untuk LLM apps. LangChain set-nya luas (segala genre), LlamaIndex set-nya fokus ke RAG.

Dua dominan:

  • LangChain — paling populer, ekosistem besar, bagus untuk agent
  • LlamaIndex — fokus ke RAG, API lebih clean, bagus untuk document Q&A

Diagram: Komponen LangChain vs LlamaIndex

flowchart TB
    subgraph LC["🦜 LangChain (luas)"]
        LC1["📝 Prompts"]
        LC2["🔗 Chains"]
        LC3["🧠 Memory"]
        LC4["🤖 Agents + Tools"]
        LC5["📚 Retrievers"]
        LC6["📦 Document Loaders"]
        LC7["🌊 Streaming/Callbacks"]
    end
    subgraph LI["🦙 LlamaIndex (RAG-focused)"]
        LI1["📦 Data Connectors"]
        LI2["✂️ Node Parsers"]
        LI3["🗂️ Indexes<br/>(Vector/Summary/Tree)"]
        LI4["🔎 Query Engines"]
        LI5["💬 Chat Engines"]
        LI6["🎯 Re-rankers"]
    end

pip install llama-index llama-index-llms-gemini llama-index-embeddings-huggingface

Diagram: Indexing Strategies di LlamaIndex

LlamaIndex tidak hanya punya VectorStoreIndex. Setiap jenis index optimal untuk use case berbeda:

Cara Membaca Diagram:

  • Ungu kiri = dokumen sumber dipecah jadi nodes.
  • Cyan tengah = 4 jenis index yang bisa kamu pilih.
  • Emerald kanan = jenis pertanyaan yang cocok untuk masing-masing index.
  • Garis putus-putus = "cocok untuk" (use case mapping).

Walkthrough Step-by-Step:

  1. Documents → di-parse jadi Nodes (chunks).
  2. VectorStoreIndex dibangun untuk Q&A spesifik berbasis kemiripan makna.
  3. SummaryIndex menyimpan semua nodes untuk operasi rangkum lintas dokumen.
  4. TreeIndex membangun hierarki top-down untuk navigasi konsep.
  5. KnowledgeGraphIndex ekstrak entitas + relasi untuk query relasional.
  6. Pilih index yang cocok dengan tipe pertanyaan dominan user.

Analogi Sehari-hari: Seperti memilih jenis filing system di kantor. Mau cari nama klien (vector index)? Mau tahu rekap project bulan lalu (summary index)? Mau navigasi struktur perusahaan (tree index)? Mau cek siapa kerja sama dengan siapa (knowledge graph)? Beda kebutuhan, beda alat.

Diagram statis Mermaid sebagai fallback:

flowchart TB
    D["📄 Documents"] --> N["✂️ Nodes (chunks)"]
    N --> V["🗂️ VectorStoreIndex<br/>(retrieval semantik)"]
    N --> S["📋 SummaryIndex<br/>(summarize all docs)"]
    N --> T["🌳 TreeIndex<br/>(hierarchical Q&A)"]
    N --> K["🕸️ KnowledgeGraphIndex<br/>(entitas + relasi)"]
    V -.cocok untuk.-> Q1["Q: 'Apa kata Albert Einstein soal X?'"]
    S -.cocok untuk.-> Q2["Q: 'Ringkas 100 dokumen ini'"]
    T -.cocok untuk.-> Q3["Q: query yang butuh navigasi top-down"]
    K -.cocok untuk.-> Q4["Q: 'Siapa yang berkolaborasi dengan X?'"]

Basic RAG dalam 10 Baris

import os
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.gemini import Gemini
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# Setup
Settings.llm = Gemini(api_key=os.environ["GEMINI_API_KEY"], model="models/gemini-1.5-flash")
Settings.embed_model = HuggingFaceEmbedding(model_name="paraphrase-multilingual-mpnet-base-v2")

# Index documents
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents)

# Query
query_engine = index.as_query_engine()
response = query_engine.query("Apa itu transformer?")
print(response)

# Akses source
for node in response.source_nodes:
    print(node.text)
    print(node.score)

10 baris, fully functional RAG.

Persist Index

# Save (1 kali)
index.storage_context.persist(persist_dir="./storage")

# Load (next time)
from llama_index.core import StorageContext, load_index_from_storage

storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)

Custom Chunking

from llama_index.core.node_parser import SentenceSplitter

splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = splitter.get_nodes_from_documents(documents)

index = VectorStoreIndex(nodes)

Chat Engine (Multi-turn)

chat_engine = index.as_chat_engine(chat_mode="context", verbose=True)

response = chat_engine.chat("Apa itu transformer?")
print(response)

response = chat_engine.chat("Bagaimana cara kerjanya?")
# Multi-turn — ingat konteks sebelumnya

Query Engine dengan Custom Prompt

from llama_index.core import PromptTemplate

template = """Konteks:
{context_str}

Pertanyaan: {query_str}

Jawab dalam Bahasa Indonesia, ringkas, dan kalau tidak tahu bilang "Saya tidak menemukan info itu."
"""

query_engine = index.as_query_engine(
    text_qa_template=PromptTemplate(template),
    similarity_top_k=3,
)

Bagian 2 — LangChain Basics

pip install langchain langchain-community langchain-google-genai langchain-chroma

Diagram: Komponen Inti LangChain

Cara Membaca Diagram:

  • Ungu kiri = 3 sumber input ke chain: Prompt template, Retriever, Memory.
  • Amber tengah = Chain yang compose semua komponen jadi pipeline.
  • Pink = LLM yang generate jawaban.
  • Cyan = Output Parser untuk transform jawaban (string → JSON, dll).
  • Emerald kanan = jawaban final ke user.

Walkthrough Step-by-Step:

  1. Prompt template menyediakan struktur dengan slot kosong ({context}, {question}).
  2. Retriever isi {context} dari Vector DB (top-K chunk).
  3. Memory isi history percakapan untuk multi-turn.
  4. Chain susun semua → satu prompt utuh.
  5. Kirim ke LLM → output mentah.
  6. Output Parser format ke struktur yang diinginkan (e.g., JSON dengan field "answer", "sources").

Analogi Sehari-hari: Seperti pabrik perakitan otomotif. Komponen masuk dari supplier (Prompt, Retriever, Memory), assembly line merakit (Chain), mesin diuji (LLM), QC dan packaging (Parser), kendaraan siap diserahkan (Final Answer).

Diagram statis Mermaid sebagai fallback:

flowchart LR
    P["📝 Prompt<br/>Template"] --> C["🔗 Chain"]
    R["📚 Retriever<br/>(Vector DB)"] --> C
    M["🧠 Memory<br/>(catatan chat)"] --> C
    C --> L["🤖 LLM"]
    L --> O["📤 Output<br/>Parser"]
    O --> A["✅ Final Answer"]

Analogi singkat:

  • Prompt = template surat dengan slot kosong
  • Chain = jalur pipa yang menghubungkan komponen
  • Memory = buku catatan percakapan supaya chatbot ingat konteks sebelumnya
  • Retriever = perpustakaan + librarian
  • Output Parser = filter yang bersihkan jawaban LLM ke format struktur (JSON, list, dll)

Basic RAG

from langchain_google_genai import GoogleGenerativeAI, GoogleGenerativeAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA

# Load
loader = TextLoader("data.txt")
docs = loader.load()

# Split
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)

# Embed + Store
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
vectorstore = Chroma.from_documents(chunks, embeddings)

# Build chain
llm = GoogleGenerativeAI(model="gemini-1.5-flash")
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
    return_source_documents=True
)

# Query
response = qa_chain({"query": "Apa itu RAG?"})
print(response["result"])
print(response["source_documents"])

Custom Prompt di LangChain

from langchain.prompts import PromptTemplate

template = """Konteks:
{context}

Pertanyaan: {question}

Jawab dengan singkat:"""

PROMPT = PromptTemplate(
    template=template,
    input_variables=["context", "question"]
)

qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=vectorstore.as_retriever(),
    chain_type_kwargs={"prompt": PROMPT}
)

Conversational Memory

from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

chain = ConversationalRetrievalChain.from_llm(
    llm=llm,
    retriever=vectorstore.as_retriever(),
    memory=memory
)

result = chain({"question": "Apa itu transformer?"})
print(result["answer"])

result = chain({"question": "Bagaimana cara kerjanya?"})
# Ingat context

Bagian 3 — LangChain Agent (Advanced)

Untuk task yang butuh tool use:

Analogi: Agent = AI yang Bisa Pakai Tools

LLM biasa = orang pintar yang ngobrol dari ingatan saja. Agent = orang pintar yang punya akses ke kalkulator, search engine, dan database. Dia bisa memutuskan sendiri kapan harus pakai tool mana.

Cara Membaca Diagram:

  • Cyan kiri = pertanyaan user.
  • Pink = Agent (LLM jadi otaknya).
  • Amber tengah = decision point — agent reasoning tool mana yang dibutuhkan.
  • Ungu = tools yang tersedia (search, calculator, RAG, code executor).
  • Garis putus-putus balik ke agent = hasil tool feedback ke agent untuk reasoning lanjut.
  • Emerald kanan = jawaban final.

Walkthrough Step-by-Step:

  1. User tanya "Berapa harga BTC × 100?".
  2. Agent reasoning: "Aku butuh harga BTC current → search tool".
  3. Search tool kembali dengan harga BTC.
  4. Agent reasoning lagi: "Aku butuh hitung × 100 → calculator".
  5. Calculator kembali dengan hasil.
  6. Agent compose jawaban final dan kirim ke user.

Analogi Sehari-hari: Seperti researcher senior. Tahu cara reasoning, tapi tidak hafal semua. Punya akses ke library (search), kalkulator, database internal (RAG), dan komputer (code exec). Tahu kapan harus pakai apa.

Diagram statis Mermaid sebagai fallback:

flowchart TD
    Q["❓ User Question<br/>'Berapa harga BTC × 100?'"] --> AG["🤖 Agent (LLM otak)"]
    AG -->|Reason| T1{"Butuh tool apa?"}
    T1 -->|info terkini| S["🔍 Search Tool"]
    T1 -->|hitung| C["🧮 Calculator"]
    T1 -->|cari di docs| R["📚 RAG Retriever"]
    S --> AG
    C --> AG
    R --> AG
    AG --> A["✅ Final Answer"]
from langchain.agents import Tool, initialize_agent, AgentType
from langchain.tools import DuckDuckGoSearchRun

search = DuckDuckGoSearchRun()

def calculator(expression: str) -> str:
    try:
        return str(eval(expression))
    except:
        return "Invalid expression"

tools = [
    Tool(name="search", func=search.run, description="Search web for current info"),
    Tool(name="calculator", func=calculator, description="Evaluate math expression"),
]

agent = initialize_agent(
    tools, llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

agent.run("Berapa harga Bitcoin sekarang dikalikan 100?")
# Agent akan: search BTC price → calculator → answer

Bagian 4 — Document Loaders

LlamaIndex/LangChain support banyak format:

# LlamaIndex
from llama_index.core import SimpleDirectoryReader

# Auto-detect: .txt, .pdf, .docx, .md, .json, dll
docs = SimpleDirectoryReader("./data").load_data()

# Specific
from llama_index.readers.web import SimpleWebPageReader
docs = SimpleWebPageReader().load_data(["https://example.com"])

# Notion
from llama_index.readers.notion import NotionPageReader
# LangChain
from langchain_community.document_loaders import (
    TextLoader, PyPDFLoader, Docx2txtLoader,
    WebBaseLoader, NotionDirectoryLoader
)

loader = PyPDFLoader("paper.pdf")
docs = loader.load()

Bagian 5 — LangChain Expression Language (LCEL)

Modern API LangChain. Compose chains dengan operator |:

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

retriever = vectorstore.as_retriever()

prompt = PromptTemplate.from_template("""
Context: {context}
Question: {question}
Answer:""")

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

response = chain.invoke("Apa itu RAG?")

Lebih clean dari API klasik.


Bagian 6 — Comparison

Aspect LlamaIndex LangChain
Best for RAG, Q&A Agent, complex workflow
API simplicity ✅ Cleaner Lebih banyak abstraction
Documentation Good Good (overwhelming kadang)
Ecosystem Sedang Massive
Vector DB integration
Agent support Sedang ✅ Mature

Tabel Detail: LangChain vs LlamaIndex

Kriteria LangChain LlamaIndex
Filosofi "Lego untuk LLM apps" — luas "Framework data untuk LLM" — fokus retrieval
Curve belajar Lebih curam (banyak konsep) Lebih landai untuk RAG
Boilerplate Lebih banyak Lebih ringkas (one-liner sering cukup)
Document loaders 100+ 100+ (LlamaHub)
Indexing strategi Vector + memory Vector + Summary + Tree + KG + Composable
Multi-step agent ✅ Mature (LangGraph, ReAct) Sedang (function calling)
Prompt engineering tools ✅ Lengkap Sedang
Streaming ✅ Built-in ✅ Built-in
Caching ✅ Multiple backend ✅ Sedang
Evaluation ✅ via LangSmith ✅ via LlamaIndex eval
Observability ✅ LangSmith (paid) ✅ Arize / Phoenix
Komunitas Sangat besar Besar dan growing
Dipakai di production Lebih banyak Banyak untuk RAG-only app
Kapan pilih? Workflow kompleks, agent, multi-tool RAG-first, document Q&A

Rekomendasi

Untuk Capstone: LlamaIndex untuk RAG-focused project.

Untuk Agent: LangChain.

Tidak perlu master keduanya. Pilih satu, dalam.


Bagian 7 — Beyond Basics

Caching

# LlamaIndex
from llama_index.core import Settings
from llama_index.core.callbacks import CallbackManager
# ... configure cache

Streaming

# LlamaIndex
query_engine = index.as_query_engine(streaming=True)
response = query_engine.query("...")
for token in response.response_gen:
    print(token, end="", flush=True)

Async

# LlamaIndex
response = await query_engine.aquery("...")
# LlamaIndex
from llama_index.retrievers.bm25 import BM25Retriever
from llama_index.core.retrievers import QueryFusionRetriever

vector_retriever = index.as_retriever(similarity_top_k=5)
bm25_retriever = BM25Retriever.from_defaults(nodes=nodes, similarity_top_k=5)

retriever = QueryFusionRetriever([vector_retriever, bm25_retriever])

Re-ranking

from llama_index.core.postprocessor import SentenceTransformerRerank

reranker = SentenceTransformerRerank(
    model="cross-encoder/ms-marco-MiniLM-L-12-v2",
    top_n=3,
)

query_engine = index.as_query_engine(node_postprocessors=[reranker])


Common Mistakes & FAQ

Mistake 1: Pakai Embedding English untuk Dokumen Indonesia

# ❌ Hasil retrieval kacau untuk teks Indonesia
Settings.embed_model = HuggingFaceEmbedding(model_name="all-MiniLM-L6-v2")

# ✅ Pakai multilingual atau Indo-specific
Settings.embed_model = HuggingFaceEmbedding(
    model_name="paraphrase-multilingual-mpnet-base-v2"
)

Mistake 2: Hardcoding API Keys

# ❌ JANGAN PERNAH
llm = Gemini(api_key="AIzaSy...")  # bocor ke git history!

# ✅ Pakai environment variable
import os
from dotenv import load_dotenv
load_dotenv()
llm = Gemini(api_key=os.environ["GEMINI_API_KEY"])

Mistake 3: Lupa Persist Index

Tiap kali run app, indexing diulang dari nol = lama dan boros API call embedding.

# ✅ Save sekali
index.storage_context.persist(persist_dir="./storage")

# ✅ Load di run berikutnya
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)

Mistake 4: Tidak Handle Empty Retrieval

Kalau query tidak match dokumen apapun, LLM masih bisa halusinasi.

response = query_engine.query(question)
if not response.source_nodes or max(n.score for n in response.source_nodes) < 0.3:
    return "Saya tidak menemukan info itu di dokumenmu."
return str(response)

Mistake 5: Chunk Size Default untuk Semua Dokumen

Code (potong di function), tabel, dan prosa butuh strategi chunking berbeda. Untuk capstone awal, mulai dengan SentenceSplitter(chunk_size=512, chunk_overlap=50), lalu eksperimen.

Mistake 6: Tidak Test Kualitas Retrieval Sebelum Tuning Prompt

Kalau jawaban kacau, bukan selalu salah prompt. Cek dulu: apakah chunk yang ter-retrieve memang berisi info yang benar? Pakai print(node.text, node.score) untuk debug.

FAQ

Q: LangChain atau LlamaIndex untuk capstone? A: LlamaIndex untuk RAG-first project. Lebih ringkas, dokumentasi RAG lebih dalam.

Q: Boleh pakai keduanya dalam 1 project? A: Bisa, tapi tidak disarankan. Naikkan kompleksitas tanpa benefit jelas.

Q: Versi LangChain berubah cepat, code-ku break. Solusinya? A: Pin versi di requirements.txt (langchain==0.2.x). Jangan auto-update di production.

Q: Cara debug Agent yang loop atau lambat? A: verbose=True saat init agent. Cek setiap step Reasoning + Action + Observation. Kalau loop, cek deskripsi tool yang ambigu.

Q: Bisa offline/no-internet? A: Bisa kalau pakai LLM lokal (Ollama, llama.cpp) + embedding lokal (sentence-transformers) + Chroma.


Cek Pemahaman

  • Bisa setup LlamaIndex untuk RAG sederhana?
  • Bisa save/load index?
  • Bisa custom prompt template?
  • Bisa multi-turn chat engine?
  • Tahu kapan pakai LangChain vs LlamaIndex?

Challenge 7.2

Challenge 1 — RAG dengan LlamaIndex

Pakai 5 file PDF (boleh paper, buku, notes). Index dengan LlamaIndex. Test 10 pertanyaan.

Challenge 2 — Compare Frameworks

Bikin RAG yang sama dengan LlamaIndex dan LangChain. Bandingkan:

  • Kemudahan setup
  • Lines of code
  • Kualitas response

Challenge 3 — Web RAG

Index 5 artikel blog dari web (pakai WebPageReader). Q&A.

Challenge 4 — Multi-source RAG

Combine: PDF + web articles + markdown notes. Single query engine.

Challenge 5 — Re-ranking

Implement re-ranking. Compare result sebelum dan sesudah re-rank.


Selanjutnya: 03-build-rag-chatbot.md