Panggilan ke LLM hanya mengembalikan teks. Ia tidak menyimpan ingatan, tidak bisa memanggil fungsi, dan tidak bisa menentukan langkah berikutnya sendiri. Kalau kamu pernah merakit chatbot dengan LangChain, kamu pasti tahu bahwa chain itu linier: prompt masuk, jawaban keluar, selesai. Masalahnya muncul begitu kamu butuh loop — agent yang memanggil tool, membaca hasilnya, lalu memutuskan apakah perlu tool lain atau langsung menjawab. LangGraph, dari tim yang sama dengan LangChain, memodelkan alur agent sebagai graf: ada state, ada node, ada percabangan.

Kenapa Graf, Bukan Rantai Biasa?

Di LangChain, urutan langkah sudah ditetapkan dari awal. LangGraph membalik logikanya: kamu mendefinisikan node (satu langkah kerja) dan edge (penghubung antar node), lalu graf yang memutuskan node mana dieksekusi berdasarkan state yang berjalan. Begitulah cara kerja agent modern yang sebenarnya. LLM memanggil tool, hasil tool balik lagi ke LLM, siklusnya berulang sampai model merasa selesai. State machine macam ini dipakai di balik banyak framework agent yang ramai dibicarakan.

Setup Awal

Siapkan environment Python-mu, lalu pasang tiga paket: satu untuk framework-nya, dua untuk koneksi ke model.

pip install langgraph langchain-openai langchain-ollama

Mau hemat biaya API? Kamu bisa menukar ChatOpenAI dengan ChatOllama dan menjalankan model lokal seperti llama3.2. Bagian terakhir artikel ini menunjukkan caranya.

Graf Paling Sederhana: Chatbot Tanpa Ingatan

Mulai dari yang paling kecil. State di sini cuma satu: daftar pesan. Annotated[list, add_messages] memberitahu LangGraph untuk menambahkan pesan baru ke daftar alih-alih menimpa. Node chatbot memanggil LLM dan mengembalikan balasannya ke state.

python
from typing import Annotated
from typing_extensions import TypedDict

from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI


class State(TypedDict):
    messages: Annotated[list, add_messages]


llm = ChatOpenAI(model="gpt-4o-mini")


def chatbot(state: State):
    return {"messages": [llm.invoke(state["messages"])]}


builder = StateGraph(State)
builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)

graph = builder.compile()

result = graph.invoke({"messages": [("user", "Halo, siapa kamu?")]})
for m in result["messages"]:
    print(f"{m.type.upper()}: {m.content}")

Panggil graph.invoke dan kirim pesan pertama. LangGraph menjalankan node chatbot sekali, lalu berhenti karena edge dari chatbot langsung menuju END. Ini bekerja, tapi coba tanya nama di percakapan kedua dan model tidak akan ingat apa pun dari percakapan pertama.

Menambah Memory dengan Checkpointer

State graf hidup hanya selama satu pemanggilan invoke. Supaya percakapan berlanjut, pasang checkpointer. Ia menyimpan state di setiap langkah, dan thread_id memisahkan satu percakapan dari percakapan lain.

python
from langgraph.checkpoint.memory import MemorySaver

graph = builder.compile(checkpointer=MemorySaver())

config = {"configurable": {"thread_id": "1"}}

graph.invoke({"messages": [("user", "Nama saya Budi.")]}, config)
reply = graph.invoke({"messages": [("user", "Siapa nama saya?")]}, config)
print(reply["messages"][-1].content)
# Output: Nama kamu Budi.

Sekarang kirim nama di pesan pertama, lalu tanyakan lagi di pesan kedua dengan thread_id yang sama — model menjawab dengan benar. Ganti thread_id dan percakapan mulai dari nol. MemorySaver cukup untuk development; untuk produksi gunakan checkpointer berbasis PostgreSQL atau Redis.

Agent yang Bisa Memanggil Tool

Ini bagian yang membuat LangGraph bernilai. Dekorator @tool mengubah fungsi biasa jadi tool yang bisa dipanggil model. bind_tools memberitahu model tool apa saja yang tersedia. Saat model memutuskan butuh data cuaca, ia mengembalikan tool call, dan tools_condition mengarahkan alur ke node tools. Setelah tool selesai, edge kembali ke agent, model membaca hasilnya, lalu merangkai jawaban final.

python
from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode, tools_condition


@tool
def get_weather(city: str) -> str:
    """Ambil suhu terkini sebuah kota."""
    data = {
        "jakarta": "30C, cerah berawan",
        "bandung": "23C, hujan ringan",
        "surabaya": "33C, panas terik",
    }
    return data.get(city.lower(), "Kota tidak ditemukan")


tools = [get_weather]
llm_with_tools = llm.bind_tools(tools)


def agent(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}


builder = StateGraph(State)
builder.add_node("agent", agent)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent")

graph = builder.compile(checkpointer=MemorySaver())

result = graph.invoke(
    {"messages": [("user", "Bagaimana cuaca di Bandung?")]},
    config,
)
print(result["messages"][-1].content)

Tool calling butuh model yang mendukung function calling. Di OpenAI, gpt-4o-mini ke atas aman. Kalau pakai model lokal, pilih llama3.1/llama3.2 atau yang lebih baru — model lama cenderung mengabaikan tool.

Jalankan dengan Model Lokal (Ollama)

Tidak mau bergantung pada API berbayar? Ganti modelnya saja. Kalau kamu belum punya Ollama, ada panduan lengkapnya di artikel ini.

python
from langchain_ollama import ChatOllama

llm = ChatOllama(model="llama3.2")
# Sisa kodenya identik: bind_tools, StateGraph, lalu compile.

Graf di atas sudah bisa kamu kembangkan ke arah yang lebih serius. Tambahkan node retrieve yang membaca dokumenmu, lalu biarkan agent memutuskan kapan harus mencari data dan kapan cukup menjawab — pola RAG agent ini dibahas di tutorial RAG. Coba juga ganti MemorySaver dengan checkpointer SQLite lewat langgraph-checkpoint-sqlite dan lihat state tersimpan antar restart.