Python & AI Tutorials Logo
LangChain & LangGraph

15. Membangun Graph Pertama Anda dengan LangGraph

Di Bagian IV, kita mendefinisikan alat, menghubungkannya ke LLM, dan menyelesaikan sebuah loop agen yang mengulang siklus putuskan-dan-eksekusi. Menggerakkan loop, menjalankan alat ketika LLM memintanya, mengetahui kapan harus berhenti — kita menulis kode setiap bagian dari alur tersebut secara manual.

Di bab ini, kita akan membangun agen yang sama dengan cara yang sepenuhnya berbeda. Alih-alih menulis alurnya secara langsung, kita akan mendaftarkan langkah (node) dan aturan koneksi (edge) ke framework LangGraph dan membiarkannya menangani eksekusi. Perilakunya identik dengan Bab 14, tetapi cara kita membangunnya berubah.

Bab ini membahas empat konsep inti — StateGraph, node, edge, dan State — lalu me-refactor loop agen dari Bab 14 menjadi graph LangGraph. Di bab-bab berikutnya, Bab 16 membahas conditional routing dan komponen prebuilt, dan Bab 17 membahas persistensi state yang memungkinkan agen melanjutkan dari titik saat ia terhenti.

15.1) Mengapa Graph?

15.1.1) Keterbatasan Loop Agen yang Sudah Ada

Mari kita tinjau kembali loop agen dari Bab 14. Dengan menghilangkan penanganan error dan detail lainnya, struktur intinya tampak seperti ini:

python
# Loop agen Bab 14 — struktur inti (disederhanakan)
messages = [
    SystemMessage(content="You are a helpful assistant."),
    HumanMessage(content=user_input),
]
 
for step in range(max_steps):
    # Minta LLM untuk memutuskan aksi berikutnya
    ai_message = llm_with_tools.invoke(messages)
    messages.append(ai_message)
 
    # Jika tidak ada tool call yang diminta, kembalikan jawaban akhir
    if not ai_message.tool_calls:
        return ai_message.content
 
    # Jalankan alat yang diminta
    for tool_call in ai_message.tool_calls:
        selected_tool = tool_map[tool_call["name"]]
        tool_message = selected_tool.invoke(tool_call)
        messages.append(tool_message)

Kode ini hanya mencakup dasar-dasarnya dan tidak lebih. Namun, di lingkungan produksi nyata, jauh lebih banyak yang dibutuhkan. Berikut beberapa contohnya.

  • Pemulihan dari crash — Jika sebuah agen crash pada langkah ke-7 dari tugas riset 10 langkah, ia seharusnya bisa melanjutkan dari langkah ke-7 alih-alih memulai dari awal.
  • Permintaan persetujuan — Sebelum agen melakukan operasi kritis, ia seharusnya bisa berhenti sejenak dan bertanya kepada manusia "Apakah boleh dilanjutkan?"
  • Pemantauan real-time — Pengguna seharusnya bisa melihat apa yang sedang dilakukan agen dan alat mana yang sedang dipanggilnya.
  • Visualisasi dan debugging — Diagram yang menunjukkan cara kerja agen seharusnya tersedia sehingga ketika masalah muncul, Anda bisa menelusuri langkah mana yang salah.

Mengimplementasikan fitur-fitur ini sendiri bukan hal yang mustahil, tetapi juga tidak mudah. Pemulihan dari crash saja membutuhkan penulisan kode untuk men-serialize state di setiap langkah, menyimpannya ke disk, memulihkannya, dan melanjutkan tepat di posisi yang sama. Anda bisa berakhir dengan lebih banyak kode infrastruktur daripada logika bisnis.

LangGraph dibangun untuk menyediakan fitur-fitur ini di tingkat framework. Pemulihan dari crash, permintaan persetujuan, pemantauan, visualisasi — framework-nya menangani semua ini. Tetapi ada satu syarat: Anda harus membangun agen Anda dalam struktur yang bisa dipahami oleh framework.

Loop agen Bab 14 menangani semua logika secara langsung, jadi tidak ada tempat bagi framework untuk terhubung. Untuk memanfaatkan apa yang ditawarkan LangGraph, kita perlu membangun kembali agen dalam struktur yang dipahami LangGraph — sebuah graph. Itulah yang menjadi inti bab ini.

15.1.2) Apa Itu LangGraph?

LangGraph adalah framework orkestrasi yang mendefinisikan dan menjalankan alur kerja agen sebagai graph. Graph di sini berarti sebuah struktur di mana setiap node (langkah) yang dilakukan agen dihubungkan oleh edge (aturan koneksi).

Di LangGraph, Anda memecah alur kerja menjadi node-node independen dan menghubungkannya dengan edge. LangGraph kemudian menelusuri graph, menjalankan setiap node di sepanjang jalan. Berikut adalah tampilan loop agen Bab 14 yang diekspresikan sebagai graph:

Ya

Tidak

START

Panggilan LLM

Tool call diminta?

Eksekusi Alat

END

Kotak persegi panjang adalah node, dan panah adalah edge. Belah ketupat merepresentasikan sebuah conditional edge yang bercabang ke jalur berbeda berdasarkan suatu kondisi.

Di Bab 14, seluruh alur kerja berada di dalam loop for, pemeriksaan if, dan kode tulisan tangan lainnya. Dengan LangGraph, Anda mendefinisikan apa yang dilakukan setiap node dan menghubungkan node-node tersebut dengan edge. Singkatnya, Anda beralih dari menuliskan alur kerja menjadi mendeklarasikannya sebagai struktur.

LangGraph tidak menggantikan apa pun yang Anda pelajari di Bab 12–14. Definisi alat, bind_tools(), tool_calls, ToolMessage — semua ini masih digunakan di dalam node, persis seperti sebelumnya.

Bagian berikutnya membahas komponen inti LangGraph — StateGraph, State, node, dan edge — satu per satu.

15.2) Komponen LangGraph: StateGraph, State, Node, Edge

Bagian ini membahas empat komponen inti LangGraph satu per satu. Kita akan mulai dengan StateGraph — kelas yang membungkus State, node, dan edge menjadi sebuah graph — lalu membahas masing-masing bagian (State, node, edge) yang ada di dalamnya.

15.2.1) StateGraph

StateGraph adalah kelas yang digunakan untuk membangun graph di LangGraph. Anda menentukan State yang akan dikelola graph, menambahkan node, menghubungkannya dengan edge, lalu compile untuk menghasilkan graph yang dapat dieksekusi.

Mari lihat cara kerjanya.

Membuat Instance StateGraph

Panggil konstruktor StateGraph untuk membuat sebuah instance. Anda perlu meneruskan skema State (kelasnya sendiri) sebagai parameter. Di sini kita menggunakan MessagesState, sebuah State bawaan yang disediakan LangGraph untuk mengelola daftar pesan. Kita akan membahas detailnya di 15.2.2.

python
from langgraph.graph import StateGraph, MessagesState
 
builder = StateGraph(MessagesState)

Menambahkan Node

Gunakan add_node() untuk mendaftarkan sebuah node. Node adalah fungsi Python yang menerima State saat ini dan mengembalikan bagian yang ingin diubahnya. Kita akan membahas fungsi node secara detail di 15.2.3.

python
def say_hello(state: MessagesState):
    return {"messages": [{"role": "ai", "content": "hello world"}]}
 
builder.add_node(say_hello)    # nama node menjadi "say_hello"

Menghubungkan Edge

Gunakan add_edge(source, target) untuk menghubungkan node. source adalah tempat edge dimulai; target adalah tujuannya. START dan END adalah penanda khusus untuk titik masuk dan keluar graph. Kita akan membahas edge di 15.2.4.

python
from langgraph.graph import START, END
 
builder.add_edge(START, "say_hello")   # graph dimulai → jalankan say_hello
builder.add_edge("say_hello", END)     # say_hello selesai → akhiri graph

Compile dan Menjalankan

Memanggil compile() memvalidasi struktur graph dan menghasilkan objek yang dapat dieksekusi. Anda menjalankan graph yang telah di-compile dengan invoke(), meneruskan nilai State awal.

python
graph = builder.compile()
 
initial_state = {"messages": [{"role": "user", "content": "hi!"}]}
result = graph.invoke(initial_state)

Sekarang mari kita satukan semuanya dan bangun sebuah graph sederhana:

python
from langgraph.graph import StateGraph, MessagesState, START, END
 
def say_hello(state: MessagesState):
    return {"messages": [{"role": "ai", "content": "hello world"}]}
 
builder = StateGraph(MessagesState)
 
builder.add_node(say_hello)
builder.add_edge(START, "say_hello")
builder.add_edge("say_hello", END)
 
graph = builder.compile()
 
initial_state = {"messages": [{"role": "user", "content": "hi!"}]}
result = graph.invoke(initial_state)
print(result["messages"][-1].content)

Output:

hello world

Ketika Anda memanggil invoke(), graph berjalan dengan urutan STARTsay_helloEND. say_hello mengembalikan sebuah dictionary dengan messages sebagai kunci, dan nilai tersebut ditambahkan ke daftar messages di MessagesState. Kita akan menggali cara kerjanya di 15.2.2. Intinya, mengambil konten pesan terakhir memberi kita "hello world".

15.2.2) State: Data yang Mengalir Melalui Graph

State adalah data yang dibagikan oleh setiap node dalam graph. Ketika sebuah node berjalan, ia menerima State saat ini, melakukan tugasnya, dan mengembalikan hanya bagian yang ingin diubahnya. LangGraph menyatukan kembali perubahan tersebut ke dalam State dan menyerahkan versi yang telah diperbarui ke node berikutnya.

Mendefinisikan State

Anda mendefinisikan State dengan membuat subclass dari TypedDict. Pilih field dan tipe yang sesuai dengan apa yang perlu dilacak agen Anda. Berikut contoh sederhananya:

python
from typing_extensions import TypedDict
 
class AgentState(TypedDict):
    messages: list       # daftar pesan
    llm_calls: int       # jumlah panggilan LLM

Dari sini, Anda meneruskan AgentState saat membuat StateGraph dan menggunakannya sebagai type hint untuk fungsi node Anda.

Reducer

Ketika sebuah node mengembalikan sebuah nilai, field State yang bersesuaian akan diperbarui. Perilaku default-nya adalah overwrite — jika sebuah node mengembalikan {"llm_calls": 3}, llm_calls cukup menjadi 3, apa pun nilainya sebelumnya.

Tetapi beberapa field perlu ditambahkan (append), bukan ditimpa (overwrite). Apa yang terjadi jika messages ditimpa? Setiap kali sebuah node mengembalikan pesan baru, seluruh riwayat percakapan lenyap. Untuk messages, append adalah perilaku yang tepat.

LangGraph memungkinkan Anda mengatur strategi pembaruan yang berbeda per field melalui fungsi reducer. Anda menentukan reducer sebagai argumen kedua dalam Annotated:

python
from typing_extensions import TypedDict, Annotated
from langgraph.graph.message import add_messages
 
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]   # reducer: append
    llm_calls: int                            # tanpa reducer: overwrite

add_messages adalah reducer yang disediakan LangGraph. Alih-alih mengganti daftar, ia menambahkan pesan baru ke apa yang sudah ada. Karena reducer ini diatur pada field messages, nilai apa pun yang dikembalikan sebuah node untuk messages akan ditambahkan. llm_calls tidak memiliki reducer, jadi nilai yang dikembalikan cukup menimpa apa pun yang ada sebelumnya.

Inilah alasan pesan yang dikembalikan say_hello di 15.2.1 ditambahkan ke messages alih-alih menggantikannya — reducer yang menanganinya.

MessagesState

LangGraph hadir dengan State bawaan bernama MessagesState. Berikut tampilannya di balik layar:

python
class MessagesState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

Struktur yang sama yang baru saja kita bahas — sebuah field messages dengan reducer add_messages yang sudah terpasang.

Jika Anda membutuhkan field tambahan, cukup buat subclass-nya:

python
from langgraph.graph import MessagesState
 
class AgentState(MessagesState):
    llm_calls: int  # perilaku default overwrite

15.2.3) Node: Fungsi yang Memperbarui State

Sebuah node adalah fungsi Python yang melakukan satu tugas spesifik di dalam graph.

python
def say_hello(state: MessagesState):
    return {"messages": [{"role": "ai", "content": "hello world"}]}

Dua hal yang perlu diketahui saat menulis fungsi node:

Aturan 1: Node menerima State saat ini sebagai argumennya. LangGraph meneruskan objek State saat ini ketika menjalankan node tersebut.

Aturan 2: Node hanya mengembalikan bagian yang ingin diubah, bukan seluruh State. Node tidak memodifikasi State secara langsung. Cukup kembalikan field yang ingin Anda perbarui, dan LangGraph menggabungkannya ke dalam State yang ada sesuai aturan reducer masing-masing field.

Gunakan add_node() untuk menambahkan node ke StateGraph:

python
builder.add_node(say_hello)          # nama fungsi "say_hello" menjadi nama node
builder.add_node("my_node", my_func) # Anda juga bisa menentukan namanya secara eksplisit

15.2.4) Edge: Aturan yang Menghubungkan Node

Sebuah edge menentukan "setelah node ini selesai, apa yang berjalan berikutnya?" Ada dua jenis.

Edge Normal

Edge normal menghubungkan "go-to" yang tetap antara dua node. Gunakan add_edge(source, target)source adalah node awal, target adalah tujuan.

python
builder.add_edge(START, "say_hello")       # ketika graph dimulai, jalankan say_hello
builder.add_edge("say_hello", "llm_call")  # setelah say_hello, jalankan llm_call
builder.add_edge("llm_call", END)          # setelah llm_call, akhiri graph

Conditional Edge

Sebuah conditional edge memilih node berikutnya saat runtime berdasarkan State saat ini. Gunakan add_conditional_edges(source, routing_function)source adalah node awal, dan routing_function adalah fungsi yang menerima State saat ini dan mengembalikan nama node berikutnya:

python
from langgraph.graph import END
 
def should_continue(state: AgentState):
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tool_node"   # tool call diminta → pergi ke tool_node
    return END               # tidak ada tool call → akhiri
 
builder.add_conditional_edges("llm_call", should_continue)

add_conditional_edges("llm_call", should_continue) memberi tahu LangGraph: "ketika llm_call selesai, panggil should_continue untuk memutuskan apa yang berjalan berikutnya." should_continue mengarahkan ke "tool_node" jika pesan terakhir memiliki tool_calls, atau ke END jika tidak. Dalam praktiknya, itu berarti graph melanjutkan ke node eksekusi alat ketika LLM meminta tool call, dan berhenti ketika tidak.

Sekarang setelah kita membahas keempat komponen, bagian berikutnya menggunakannya untuk me-refactor loop agen Bab 14 menjadi graph LangGraph.

15.3) Me-refactor Loop Agen Menjadi Graph

Mari kita bangun kembali loop agen Bab 14 menggunakan LangGraph. Perilakunya identik dengan Bab 14 — LLM memutuskan, alat dieksekusi berdasarkan permintaan LLM, dan siklusnya berulang hingga selesai. Satu-satunya yang berubah adalah bagaimana kita menyusun alur ini.

Berikut tampilan graph yang sudah jadi nanti:

Ya

Tidak

START

llm_call

Tool call diminta?

tool_node

END

Graph berputar antara llm_call dan tool_node hingga LLM berhenti meminta tool call, pada saat itu ia keluar ke END. Mari kita bangun langkah demi langkah.

15.3.1) Mendefinisikan State

Kita membuat subclass dari MessagesState dari 15.2.2 untuk mendefinisikan State agen. Field messages diwarisi dari MessagesState, dan kita menambahkan field llm_calls untuk melacak jumlah panggilan LLM.

python
from langgraph.graph import MessagesState
 
class AgentState(MessagesState):
    llm_calls: int    # jumlah panggilan LLM (overwrite)

Daftar messages akan mengumpulkan input pengguna (HumanMessage), respons LLM (AIMessage), dan hasil eksekusi alat (ToolMessage) secara berurutan.

15.3.2) Membangun Node

Pertama, mari kita siapkan alat dan model dari Bab 14:

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import ToolMessage
from langchain.tools import tool
 
@tool
def get_weather(city: str) -> str:
    """Mendapatkan cuaca saat ini untuk sebuah kota."""
    fake_data = {"Tokyo": "18°C, cloudy", "Cairo": "31°C, sunny"}
    return fake_data.get(city, f"No weather data for {city}.")
 
@tool
def calculate(expression: str) -> str:
    """Menghitung ekspresi aritmetika sederhana. Contoh: '3 * 21'."""
    return str(eval(expression))  # Peringatan: eval() adalah risiko keamanan. Jangan gunakan di produksi.
 
tools = [get_weather, calculate]
tool_map = {t.name: t for t in tools}
 
llm = ChatOpenAI(model="gpt-5-mini")
model_with_tools = llm.bind_tools(tools)

Sekarang mari kita tulis dua fungsi node.

Node llm_call — Memanggil LLM dan mengembalikan responsnya:

python
def llm_call(state: AgentState):
    """Memanggil LLM dan mengembalikan responsnya."""
    response = model_with_tools.invoke(state["messages"])
    return {
        "messages": [response],
        "llm_calls": state.get("llm_calls", 0) + 1,
    }

model_with_tools.invoke() memanggil LLM, dan responsnya dibungkus di bawah kunci messages dalam dictionary yang dikembalikan. Reducer menambahkannya ke messages yang ada di AgentState. llm_calls mengembalikan jumlah saat ini ditambah 1, menimpa nilai sebelumnya.

Node tool_node — Menjalankan alat yang diminta LLM dan mengembalikan hasilnya:

python
def tool_node(state: AgentState):
    """Menjalankan alat yang diminta oleh LLM."""
    last_message = state["messages"][-1]
    results = []
    for tool_call in last_message.tool_calls:
        selected_tool = tool_map[tool_call["name"]]
        tool_message = selected_tool.invoke(tool_call)
        results.append(tool_message)
    return {"messages": results}

Karena tool_node selalu berjalan tepat setelah llm_call, pesan terakhir di messages dijamin adalah AIMessage yang baru saja dihasilkan LLM. Field tool_calls pada pesan tersebut berisi tool call yang diminta LLM. Node menjalankan setiap alat, mengumpulkan hasilnya di results, dan mengembalikannya di bawah kunci messages — reducer yang mengurus penambahannya ke daftar yang ada.

15.3.3) Conditional Edge

Setelah llm_call selesai, kita membutuhkan conditional edge untuk memutuskan apakah akan menjalankan tool_node atau mengakhiri graph. Ini mengikuti pola yang sama dari 15.2.4:

python
from typing import Literal
from langgraph.graph import END
 
def should_continue(state: AgentState) -> Literal["tool_node", "__end__"]:
    """Memutuskan apakah akan menjalankan alat atau mengakhiri graph."""
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tool_node"
    return END

Jika last_message.tool_calls ada, LLM sedang meminta tool call, jadi kita mengarahkan ke tool_node. Jika tidak, kita mengarahkan ke END dan graph berhenti.

Type hint return Literal["tool_node", "__end__"] mendeklarasikan tujuan yang mungkin dikembalikan fungsi ini. LangGraph membutuhkan hint ini untuk menggambar jalur conditional edge dengan benar dalam visualisasi graph. Ini tidak berpengaruh pada perilaku runtime.

"__end__" adalah nilai string dasar dari END. Karena Literal hanya menerima literal string, kita menulis "__end__" alih-alih END.

15.3.4) Merakit dan Menjalankan Graph

Saatnya menghubungkan semuanya. Mari kita rakit State, node, dan conditional edge menjadi sebuah StateGraph dan compile:

python
from langgraph.graph import StateGraph, START, END
 
builder = StateGraph(AgentState)
 
builder.add_node("llm_call", llm_call)
builder.add_node("tool_node", tool_node)
 
builder.add_edge(START, "llm_call")                         # start → llm_call
builder.add_conditional_edges("llm_call", should_continue)  # llm_call → tool_node atau END
builder.add_edge("tool_node", "llm_call")                   # tool_node → llm_call (loop)
 
agent = builder.compile()

Edge dari tool_node kembali ke llm_call membuat sebuah loop. Eksekusi terus berputar hingga LLM merespons dengan jawaban akhir alih-alih meminta tool call lain, pada saat itu loop keluar.

Mari kita jalankan:

python
from langchain_core.messages import HumanMessage
 
result = agent.invoke({
    "messages": [HumanMessage(content="Get the temperature in Cairo, then multiply the number by 3.")],
    "llm_calls": 0,
})
 
print(result["messages"][-1].content)
print(f"\nTotal LLM calls: {result['llm_calls']}")

Output:

Current temperature in Cairo: 31°C. Multiplied by 3 = 93.
 
Total LLM calls: 3

Agen memanggil get_weather("Cairo"), melihat hasilnya, memanggil calculate("31 * 3"), dan menghasilkan jawaban akhir — hasil yang sama yang kita dapatkan di Bab 14.

Memeriksa riwayat pesan lengkap menunjukkan setiap langkah terekam di messages, secara berurutan:

python
for message in result["messages"]:
    message.pretty_print()

Output:

================================ Human Message =================================
Get the temperature in Cairo, then multiply the number by 3.
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_DiL9WF)
  Args:
    city: Cairo
================================= Tool Message =================================
Name: get_weather
31°C, sunny
================================== Ai Message ==================================
Tool Calls:
  calculate (call_wa6RqWST)
  Args:
    expression: 31 * 3
================================= Tool Message =================================
Name: calculate
93
================================== Ai Message ==================================
Current temperature in Cairo: 31°C. Multiplied by 3 = 93.

15.3.5) Visualisasi Graph

Di dalam notebook Jupyter, agent.get_graph().draw_mermaid_png() merender struktur graph sebagai gambar langsung di output cell.

python
from IPython.display import Image, display
 
display(Image(agent.get_graph().draw_mermaid_png()))

Di lingkungan terminal, simpan sebagai file PNG saja.

python
agent.get_graph().draw_mermaid_png(output_file_path="agent_graph.png")

Gambar yang dihasilkan:

__start__

llm_call

tool_node

__end__

Garis solid adalah edge normal dan garis putus-putus adalah conditional edge. Diagram ini dibuat secara otomatis dari kode.

15.3.6) Recursion Limit

Sama seperti kita menggunakan max_steps untuk menjaga dari loop tak terbatas di Bab 14, LangGraph memiliki jaring pengaman bawaan. Setiap kali sebuah node berjalan selama eksekusi graph, sebuah counter internal bertambah satu. Ketika counter tersebut melampaui batas yang dikonfigurasi, LangGraph memunculkan GraphRecursionError.

Untuk melihat cara kerja penghitungannya, lihat run sebelumnya. Memanggil get_weather dan calculate mengunjungi node dengan urutan ini:

llm_call(1) → tool_node(2) → llm_call(3) → tool_node(4) → llm_call(5) → END

Itu total 5 kunjungan node. Jika Anda mengatur recursion_limit ke 3, batas mulai berlaku pada kunjungan ke-3 dan run terpotong lebih awal:

python
from langgraph.errors import GraphRecursionError
 
try:
    result = agent.invoke(
        {"messages": [HumanMessage(content="Get the temperature in Cairo, then multiply the number by 3.")],
         "llm_calls": 0},
        config={"recursion_limit": 3},
    )
except GraphRecursionError:
    print("Agent hit the recursion limit — stopping execution.")

Output:

Agent hit the recursion limit — stopping execution.

Atur batasnya dengan meneruskan config={"recursion_limit": number} ke invoke(). Nilai yang tepat bergantung pada kasus penggunaan Anda dan kompleksitas graph Anda. Mulailah dengan angka yang murah hati dan sesuaikan melalui pengujian.