← Kembali ke Blog

Bikin AI Agent Pertama Kamu dengan LangGraph: Tools, State, dan Conditional Routing

Kamu pengen bikin AI agent. Bukan chatbot yang jawab pertanyaan sekali tembak — tapi agent yang reasoning, milih tool, manggil tool, dan mutusin langkah selanjutnya berdasarkan hasil tool itu. Yang looping think → act → observe sampai tugasnya selesai.

LangGraph framework-nya buat itu. Dia modelin agent kamu sebagai directed graph: node yang ngerjain kerjaan (manggil LLM, jalanin tool), edge yang ngontrol alur (pergi ke tool A kalau LLM mintanya itu, selesai kalau jawaban udah siap). State-nya persist antar node, jadi agent inget apa yang terjadi tiga langkah sebelumnya.

Di akhir tutorial ini, kamu bakal punya agent yang bisa search web dan ngitung matematika, semua dalam kurang dari 100 baris Python.

Prerequisites

  • Python 3.10+ (cek dengan python3 --version)
  • Anthropic API key (dapetin di console.anthropic.com) atau OpenAI key
  • pip install langgraph langchain-anthropic langchain-community tavily-python

Kita pake Anthropic Claude (Sonnet) buat LLM-nya dan Tavily buat web search. Kamu bisa ganti keduanya — struktur graph tetap sama.

pip install langgraph langchain-anthropic langchain-community tavily-python

Set API key:

export ANTHROPIC_API_KEY="sk-ant-..."
export TAVILY_API_KEY="tvly-..."

Tavily punya free tier lumayan (1.000 search/bulan). Daftar di tavily.com.

Langkah 1: Definisikan State Agent

LangGraph perlu tahu data apa yang mengalir di dalam graph. Kita definisikan state object dengan tipe:

from typing import Annotated, List
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    messages: Annotated[List, add_messages]

Satu field: messages. Isinya list LangChain message objects (HumanMessage, AIMessage, ToolMessage). Reducer add_messages nge-handle append message baru, bukan overwrite — state terakumulasi antar node.

Itu aja state-nya. Kalau agent kamu butuh konteks tambahan (user preferences, session data, file handles), tinggal tambahin field di sini. LangGraph nge-serialize dan nge-pass ini ke setiap node.

Langkah 2: Definisikan Tools

Tools adalah fungsi yang bisa dipanggil LLM. Kita bikin dua: web search tool dan kalkulator.

from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.tools import tool

# Web search via Tavily
tavily_tool = TavilySearchResults(
    max_results=3,
    search_depth="basic",      # "advanced" buat pencarian lebih dalem (lebih lambat)
    include_answer=True,       # AI-generated summary dari Tavily
)

# Calculator tool
@tool
def calculator(expression: str) -> str:
    """Evaluate a mathematical expression. Use for arithmetic, percentages, and simple math."""
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return str(result)
    except Exception as e:
        return f"Error: {e}"

tools = [tavily_tool, calculator]

Docstring di calculator itu penting. LLM bacanya buat mutusin kapan manggil tool ini, bukan search tool. Tulis docstring yang spesifik: apa yang tool lakukan dan kapan harus dipakai.

Catatan soal eval(): aman di sini karena kita sandbox dengan empty builtins. Di production, pake math parser yang proper (kayak numexpr). Ini tutorial.

Langkah 3: Bikin LLM dengan Tool Binding

Kita binding tools ke LLM biar dia tahu tools apa yang tersedia:

from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(
    model="claude-sonnet-4-20250514",
    temperature=0,
)

llm_with_tools = llm.bind_tools(tools)

temperature=0 bikin routing-nya konsisten. Pas LLM mutusin tool mana yang dipanggil, kita pengen determinisme, bukan kreativitas.

Langkah 4: Bangun Node

Node itu fungsi Python biasa. Dia terima state, ngerjain sesuatu, dan return update:

from langchain_core.messages import ToolMessage

def agent_node(state: AgentState):
    """Panggil LLM dengan message history saat ini."""
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

def tool_node(state: AgentState):
    """Jalanin tool calls yang diminta LLM."""
    last_message = state["messages"][-1]
    results = []

    for tool_call in last_message.tool_calls:
        tool_name = tool_call["name"]
        tool_args = tool_call["args"]

        # Cari tool yang cocok
        selected_tool = next(t for t in tools if t.name == tool_name)
        output = selected_tool.invoke(tool_args)

        results.append(ToolMessage(
            content=str(output),
            tool_call_id=tool_call["id"],
        ))

    return {"messages": results}

agent_node ngirim seluruh message history ke LLM. tool_node nyari fungsi yang diminta LLM, jalanin, dan bungkus hasilnya sebagai ToolMessage. ToolMessage nyertakan tool_call_id biar LLM bisa cocokin hasil dengan request.

Langkah 5: Definisikan Logika Routing

Graph harus tahu ke mana setelah tiap node:

from langgraph.graph import END

def should_continue(state: AgentState):
    """Putuskan: panggil tools atau selesai."""
    last_message = state["messages"][-1]

    # Kalau LLM minta tools, route ke tool_node
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"

    # Selain itu, kita selesai
    return END

Ini conditional edge. Setelah LLM ngerespon, kita inspeksi messagenya. Kalau ada tool_calls, kita route ke tool node. Kalau response teks biasa, graph selesai dan return ke user.

Langkah 6: Compile Graph

Sekarang kita satukan semuanya:

from langgraph.graph import StateGraph

# Bikin graph dengan schema state kita
workflow = StateGraph(AgentState)

# Tambah node
workflow.add_node("agent", agent_node)
workflow.add_node("tools", tool_node)

# Set titik mulai
workflow.set_entry_point("agent")

# Tambah conditional edge dari agent
workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "tools": "tools",   # Kalau should_continue return "tools"
        END: END,           # Kalau should_continue return END
    }
)

# Setelah tools, selalu balik ke agent buat reasoning berikutnya
workflow.add_edge("tools", "agent")

# Compile
agent = workflow.compile()

Alurnya: entry → agent → (conditional) → tools → agent → (conditional) → END.

Agent bisa looping lewat tools berkali-kali. Kalau user tanya "Cari PDB Indonesia dan hitung 15% darinya," agent bakal manggil search, dapet angkanya, manggil kalkulator, lalu sintesis kedua hasil itu.

Langkah 7: Jalanin Agent

from langchain_core.messages import HumanMessage

# Jalanin dengan query yang butuh dua tool
result = agent.invoke({
    "messages": [HumanMessage(content="Cari jumlah penduduk Jakarta tahun 2024 dan hitung 3.5% nya")]
})

# Print percakapannya
for msg in result["messages"]:
    if hasattr(msg, "tool_calls") and msg.tool_calls:
        print(f"\n🔧 LLM minta tools:")
        for tc in msg.tool_calls:
            print(f"   • {tc['name']}({tc['args']})")
    elif hasattr(msg, "content"):
        role = "🤖 Agent" if msg.type == "ai" else "👤 Kamu"
        print(f"{role}: {msg.content[:200]}...")

Output yang diharapkan:

👤 Kamu: Cari jumlah penduduk Jakarta tahun 2024 dan hitung 3.5% nya

🔧 LLM minta tools:
   • tavily_search_results_json({'query': 'jumlah penduduk Jakarta 2024'})

🔧 LLM minta tools:
   • calculator({'expression': '11000000 * 0.035'})

🤖 Agent: Jumlah penduduk Jakarta tahun 2024 sekitar 11 juta. 3.5% dari jumlah tersebut adalah 385.000 orang.

Agent secara otonom mutusin: pertama cari data populasi, lalu hitung kalkulasi, lalu sintesis jawaban akhir. Dua tool call, nol routing manual.

Tambahan: Human-in-the-Loop

Untuk agent production, kamu pengen pause sebelum tool call tertentu. LangGraph support ini lewat interrupt_before:

agent = workflow.compile(
    interrupt_before=["tools"]   # Pause sebelum eksekusi tool apapun
)

# Mulai eksekusi — pause sebelum tools
config = {"configurable": {"thread_id": "1"}}
for event in agent.stream(
    {"messages": [HumanMessage(content="Cari release notes terbaru Go 1.24")]},
    config,
):
    print(event)
    # Di titik interrupt, inspeksi state dengan agent.get_state(config)

Pas graph pause, kamu bisa inspeksi tool apa yang diminta LLM dan approve atau modifikasi. Panggil agent.invoke(None, config) buat lanjutin dengan state saat ini. Ini berguna buat transaksi finansial, database writes, atau tool apapun yang punya side effect.

Tambahan: Persistence (Memory Antar Sesi)

LangGraph punya checkpointer built-in. Ganti in-memory state ke SQLite:

from langgraph.checkpoint.sqlite import SqliteSaver

with SqliteSaver.from_conn_string("agent_memory.db") as checkpointer:
    agent = workflow.compile(checkpointer=checkpointer)

    config = {"configurable": {"thread_id": "user-123"}}

    # Percakapan pertama
    agent.invoke(
        {"messages": [HumanMessage(content="Nama saya Arya")]},
        config,
    )

    # Percakapan kedua — thread_id sama, agent ingat
    result = agent.invoke(
        {"messages": [HumanMessage(content="Siapa nama saya?")]},
        config,
    )
    # Response: "Nama kamu Arya."

thread_id nge-group message ke dalam satu percakapan. Thread ID berbeda dapet state terisolasi. Restart script dan agent masih ingat.

Kapan Pakai LangGraph vs Alternatif

Pakai LangGraph kalau:

  • Agent kamu butuh multi-step reasoning dengan tool calls (think → act → observe → repeat)
  • Kamu pengen kontrol eksplisit atas alur (kamu yang nentuin node mana ke mana)
  • Kamu butuh persistence, human-in-the-loop, atau streaming
  • Kamu bangun sesuatu yang jalan di production, bukan cuma notebook demo

Pakai OpenAI Assistants API kalau:

  • Kamu pengen solusi managed dan nggak mau urus state sendiri
  • Agent kamu simpel (satu tool, satu pola panggilan)
  • Kamu oke dengan vendor lock-in

Pakai CrewAI kalau:

  • Kamu pengen role-based multi-agent collaboration out of the box
  • Kamu butuh agent dengan "kepribadian" (researcher, writer, reviewer)
  • Kamu prototyping cepet dan alur persisnya nggak kritis

Pakai function calling langsung (Anthropic/OpenAI SDK) kalau:

  • Kamu cuma punya single-turn tool call pattern
  • Kamu pengen dependensi minimal
  • Kamu nggak butuh state management antar turn

LangGraph duduk di antara raw SDK dan high-level framework. Dia kasih abstraksi graph tanpa maksain agent personality atau pola komunikasi tertentu.

Kesalahan Umum

Lupa reducer add_messages. Tanpa ini, tiap node nge-overwrite message list, bukan nambahin. Agent kehilangan semua konteks dari langkah sebelumnya.

Node nggak return dict. Node harus return {"messages": [...]}, bukan message object mentah. LangGraph merge return dict ke state.

Binding tools setelah bikin graph. Tool binding terjadi di objek LLM, bukan di graph. Kalau kamu nambah tool belakangan, kamu harus re-bind dan re-compile.

Pake invoke() padahal maksudnya stream(). invoke() block sampai graph selesai. Buat agent yang jalannya lama, pake stream() biar dapet hasil intermediate. User dapet feedback pas agent lagi kerja.

Nggak set temperature=0 buat routing. LLM mutusin tool mana yang dipanggil berdasarkan outputnya. Temperature selain nol artinya routing nggak konsisten — input yang sama bisa hasilin pilihan tool yang beda. Untuk agent graph, jaga temperature di 0.

Langkah Selanjutnya

Kalau agent kamu udah jalan, ini langkah natural berikutnya:

  • Tambah tools lain. Query database, operasi file, panggilan API. Pola graph nggak berubah — tinggal tambah node dan update should_continue.
  • Tambah subgraph. Untuk agent kompleks, nest graph di dalam graph. Subgraph "research" mungkin punya loop search → read → summarize sendiri.
  • Tambah LangSmith tracing. Panggil langsmith buat debugging: lihat setiap transisi node, setiap tool call, dan setiap perubahan state dalam timeline visual.
  • Deploy dengan LangGraph Platform. LangChain nawarin platform deployment dengan built-in persistence, streaming, dan horizontal scaling. Nggak gratis, tapi hemat waktu berminggu-minggu kerjaan infrastruktur kalau kamu bangun produk di atas agent.

Kesimpulan

Kamu sekarang punya agent yang bisa reasoning, manggil tools, dan looping sampai dapet jawaban. Pola graph yang sama bisa diskalakan dari asisten riset 2-tool ke agent production 20-tool — tambah node, tambah edge, update fungsi routing.

Bagian tersulit dari bangun agent bukan LLM-nya. Tapi orkestrasinya: tracking state, mutusin apa yang terjadi selanjutnya, recovery dari error. LangGraph yang nge-handle bagian itu. Kamu fokus ke apa yang agent kamu harus lakukan, bukan gimana cara nyambunginnya.

Referensi

Butuh Bantuan Implementasi?

Saya membantu tim mendesain dan membangun infrastruktur cloud scalable, pipeline DevOps, dan sistem production-grade.

Konsultasi Gratis