16. Komponen Prebuilt dan Routing Multi-Cabang
Di Bab 15 kita merakit graph agen secara manual — sebuah node model, sebuah node tool, dan sebuah conditional edge yang menentukan apakah harus terus melakukan loop atau berhenti. Ini pada dasarnya adalah struktur standar untuk agen tool-calling, sehingga LangChain dan LangGraph menyediakannya sebagai komponen prebuilt yang bisa kamu gunakan tanpa perlu menulis scaffolding yang sama dari awal setiap kali.
Di paruh pertama bab ini, kita akan membangun ulang agen dari Bab 15 menggunakan komponen prebuilt. Kita akan mengganti node eksekusi tool dan fungsi routing dengan ToolNode dan tools_condition, dan akhirnya mengganti seluruh perakitan graph dengan satu panggilan create_agent. Kamu akan melihat bahwa perilakunya tetap identik dengan Bab 15, sementara kodenya menyusut cukup banyak.
Di paruh kedua, kita akan menggabungkan komponen prebuilt dengan pendekatan perakitan graph manual dari Bab 15 untuk membangun agen yang lebih kompleks. Agen yang akan kita bangun me-routing setiap permintaan ke handler yang berbeda — konsultasi kompleks masuk ke model berperforma tinggi, sementara pertanyaan sederhana dijawab oleh model yang lebih murah dan lebih kecil. Ini adalah struktur multi-cabang di mana jalur pemrosesan bercabang berdasarkan jenis permintaan.
16.1) Komponen Prebuilt dan create_agent
Di bagian ini kita akan mengganti fungsi tool_node dan fungsi should_continue dari graph Bab 15 dengan komponen prebuilt ToolNode dan tools_condition. Setelah itu, kita akan melewati perakitan manual sepenuhnya dan membuat seluruh graph dengan satu panggilan create_agent. Yang perlu diperhatikan di setiap langkah adalah bahwa kodenya menjadi lebih pendek sementara perilaku agen tetap identik dengan Bab 15.
16.1.1) ToolNode dan tools_condition
ToolNode adalah node prebuilt yang menangani eksekusi tool untukmu. Ketika pesan terakhir dalam State (AIMessage yang dikembalikan oleh LLM) berisi tool_calls, ia menjalankan tool yang diminta dan menambahkan hasilnya ke messages sebagai objek ToolMessage. Ia melakukan pekerjaan yang sama seperti fungsi tool_node yang kita tulis di Bab 15. Selain itu, ketika LLM meminta beberapa tool sekaligus, ia menjalankannya secara paralel.
Ia juga mendukung penanganan exception selama eksekusi tool. Jika kamu mengatur ToolNode(tools, handle_tool_errors=True), graph tidak akan crash meskipun ada tool yang memunculkan exception. Exception tersebut dikonversi menjadi ToolMessage yang membawa detail error, yang diteruskan ke LLM sehingga ia bisa melihat kegagalan tersebut dan mencoba lagi dengan argumen yang telah dikoreksi.
Kamu membuat ToolNode dengan mengoperkan sebuah list tool. Kita akan menggunakan dua tool yang sama dari Bab 15.
from langgraph.prebuilt import ToolNode
from langchain.tools import tool
@tool
def get_weather(city: str) -> str:
"""Mendapatkan cuaca terkini untuk sebuah kota."""
fake_data = {"Tokyo": "18°C, berawan", "Cairo": "31°C, cerah"}
return fake_data.get(city, f"Tidak ada data cuaca untuk {city}.")
@tool
def calculate(expression: str) -> str:
"""Mengevaluasi 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 + fungsi tool_node dari Bab 15 diganti dengan satu baris ini
tool_node = ToolNode(tools)ToolNode membangun pemetaan internal nama-ke-tool dari list tool, persis seperti tool_map di Bab 15. Saat runtime ia mencari setiap tool berdasarkan nama yang diminta LLM dan memanggilnya. Dengan kata lain, dictionary tool_map, loop for yang mengiterasi tool_calls, dan kode yang membangun serta mengumpulkan objek ToolMessage — semuanya sekarang berada di dalam ToolNode.
tools_condition adalah fungsi routing prebuilt yang menggantikan fungsi should_continue dari Bab 15. Mari kita lihat lagi should_continue dari Bab 15.
def should_continue(state: AgentState) -> Literal["tool_node", "__end__"]:
"""Menentukan apakah harus mengeksekusi tool atau mengakhiri graph."""
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tool_node"
return ENDIa mengembalikan "tool_node" (nama terdaftar dari node tool kita) ketika pesan terakhir memiliki tool_calls, dan END jika tidak. tools_condition bekerja dengan cara yang persis sama, dengan satu perbedaan pada nama yang dikembalikannya. Sementara should_continue ditulis untuk mengembalikan "tool_node" — nama yang kita daftarkan di graph kita — tools_condition di-hardcode untuk mengembalikan "tools".
Seperti yang kita pelajari di Bagian 15.2.4, nilai yang dikembalikan oleh fungsi routing adalah nama node berikutnya yang akan dieksekusi. Jika tidak ada node dengan nama tersebut dalam graph, routing gagal. Jadi saat menggunakan tools_condition, node tool harus didaftarkan dengan nama "tools".
from langgraph.prebuilt import ToolNode, tools_condition
builder.add_node("tools", ToolNode(tools)) # Daftarkan dengan nama "tools"
builder.add_conditional_edges("llm_call", tools_condition) # Me-routing ke "tools" atau ENDDengan cara ini, ketika tools_condition mengembalikan "tools", ia terhubung persis ke ToolNode yang baru saja kita daftarkan.
Sekarang mari kita rakit ulang graph lengkap dari Bab 15, termasuk semua bagian yang tersisa. Tool, State, dan node llm_call tidak berubah dari Bab 15.
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool
@tool
def get_weather(city: str) -> str:
"""Mendapatkan cuaca terkini untuk sebuah kota."""
fake_data = {"Tokyo": "18°C, berawan", "Cairo": "31°C, cerah"}
return fake_data.get(city, f"Tidak ada data cuaca untuk {city}.")
@tool
def calculate(expression: str) -> str:
"""Mengevaluasi ekspresi aritmetika sederhana. Contoh: '3 * 21'."""
return str(eval(expression)) # Peringatan: eval() adalah risiko keamanan. Jangan gunakan di produksi.
tools = [get_weather, calculate]
class AgentState(MessagesState):
llm_calls: int
llm = ChatOpenAI(model="gpt-5-mini")
model_with_tools = llm.bind_tools(tools)
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,
}
builder = StateGraph(AgentState)
builder.add_node("llm_call", llm_call)
builder.add_node("tools", ToolNode(tools)) # ToolNode menggantikan fungsi tool_node dari Bab 15
builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", tools_condition) # tools_condition menggantikan should_continue dari Bab 15
builder.add_edge("tools", "llm_call")
agent = builder.compile()Bandingkan ini dengan kode Bab 15. Dictionary tool_map, fungsi tool_node, dan fungsi should_continue semuanya hilang. Pekerjaan yang mereka lakukan sekarang ditangani oleh ToolNode(tools) dan tools_condition. Node tool didaftarkan sebagai "tools" agar cocok dengan nama yang di-routing oleh tools_condition. Mari kita jalankan dengan pertanyaan yang sama dari Bab 15.
result = agent.invoke({
"messages": [HumanMessage(content="Dapatkan suhu di Cairo, lalu kalikan angkanya dengan 3.")],
"llm_calls": 0,
})
print(result["messages"][-1].content)
print(f"\nTotal panggilan LLM: {result['llm_calls']}")Output:
Suhu terkini di Cairo: 31°C. Dikalikan 3 = 93.
Total panggilan LLM: 3Hasilnya identik dengan Bab 15. Agen mencari cuaca, melakukan perhitungan, dan menghasilkan jawaban akhir — perilaku yang sama tetap terjaga, sementara kode yang perlu kita tulis dan pelihara telah menyusut.
Bagaimana jika kamu ingin mendaftarkan node tool dengan nama selain "tools"? Dalam kasus itu, operkan sebuah dictionary pemetaan sebagai argumen ketiga ke add_conditional_edges, yang menentukan node mana yang harus dihubungkan untuk setiap nilai kembalian dari tools_condition. Karena tools_condition mengembalikan "tools" atau END, kamu menggunakannya sebagai key dan memetakannya ke node target. Sebagai contoh, jika kamu mendaftarkan node tool sebagai "run_tools":
builder.add_node("run_tools", ToolNode(tools))
builder.add_conditional_edges(
"llm_call",
tools_condition,
{"tools": "run_tools", END: END} # kembalian "tools" → node run_tools, kembalian END → terminasi
)ToolNode dan tools_condition menggantikan bagian-bagian individual dari graph — yang paling merepotkan — tetapi menambahkan node dan menghubungkannya masih menjadi tugas kita. Bisakah kita menyerahkan perakitan itu juga? Itulah tepatnya yang dilakukan create_agent.
16.1.2) create_agent
create_agent adalah fungsi factory LangChain yang menangani seluruh perakitan graph untuk agen tool-calling. Operkan sebuah model dan sebuah list tool, dan ia membangun graph dengan struktur yang sama seperti yang kita rakit di 16.1.1, sudah dikompilasi dan siap dijalankan. Mari kita coba.
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_core.messages import HumanMessage
@tool
def get_weather(city: str) -> str:
"""Mendapatkan cuaca terkini untuk sebuah kota."""
fake_data = {"Tokyo": "18°C, berawan", "Cairo": "31°C, cerah"}
return fake_data.get(city, f"Tidak ada data cuaca untuk {city}.")
@tool
def calculate(expression: str) -> str:
"""Mengevaluasi ekspresi aritmetika sederhana. Contoh: '3 * 21'."""
return str(eval(expression)) # Peringatan: eval() adalah risiko keamanan. Jangan gunakan di produksi.
agent = create_agent(
model="openai:gpt-5-mini",
tools=[get_weather, calculate],
system_prompt="Kamu adalah asisten yang membantu.",
)
result = agent.invoke({
"messages": [HumanMessage(content="Dapatkan suhu di Cairo, lalu kalikan angkanya dengan 3.")],
})
print(result["messages"][-1].content)Output:
Suhu terkini di Cairo: 31°C. Dikalikan 3 = 93.Tidak ada definisi State, tidak ada fungsi node, tidak ada add_node atau add_edge. Satu panggilan create_agent melakukan semua itu, dan hasilnya identik dengan 16.1.1.
Apa yang terjadi secara internal persis seperti yang sudah kita ketahui. create_agent membuat node panggilan LLM dari model yang kamu operkan, membangun ToolNode dari list tool, dan menghubungkannya dengan edge tools_condition dan edge loop-back. Hasilnya adalah graph berstruktur loop yang identik dengan yang kita rakit di 16.1.1.
Mari kita lihat parameternya. create_agent sebenarnya bukan hal baru bagi kita — kita sudah menggunakannya sebentar di Bab 11 saat membangun RAG percakapan, tetapi kita tidak membahas parameternya secara detail. Mari kita bahas satu per satu.
model: LLM yang akan digunakan agen. Pendekatan paling sederhana adalah mengoperkan string provider seperti"openai:gpt-5-mini". Jika kamu perlu mengkonfigurasi parameter model secara langsung, operkan sebuah instance model yang sudah diinisialisasi sepertiChatOpenAI(model="gpt-5-mini"). Secara internal, node panggilan LLM menggunakan model ini.tools: List tool yang bisa digunakan agen. SebuahToolNodedibangun dari tool-tool ini secara internal.system_prompt: Instruksi perilaku untuk agen. Ini ditambahkan di awal sebagai system message ke list pesan pada setiap panggilan LLM.checkpointer: Menyimpan state percakapan sehingga agen bisa mengingat giliran-giliran sebelumnya. Ini adalah parameter yang sama yang kita gunakan denganInMemorySaver()danthread_iddi Bab 11 untuk mengimplementasikan percakapan multi-giliran. Kita akan membahas cara kerjanya secara detail di Bab 17.response_format: Gunakan ini ketika kamu ingin jawaban akhir agen sebagai output terstruktur. Operkan sebuah model Pydantic (konsep yang sama dari Bab 7), dan objek yang tervalidasi akan tersedia diresult["structured_response"].middleware: Mendaftarkan fungsi-fungsi untuk dijalankan pada titik-titik tertentu dalam loop eksekusi agen. Ini adalah parameter yang kita gunakan untuk mendaftarkantrim_old_messagesdi Bab 11. Kita akan menjelaskannya secara detail di bawah.
Mari kita lihat cara kerja response_format dalam praktiknya.
from pydantic import BaseModel
from langchain.agents import create_agent
class WeatherReport(BaseModel):
city: str
temperature: str
condition: str
agent = create_agent(
model="openai:gpt-5-mini",
tools=[get_weather, calculate],
response_format=WeatherReport,
)
result = agent.invoke({
"messages": [HumanMessage(content="Bagaimana cuaca di Tokyo?")],
})
print(result["structured_response"])Output:
city='Tokyo' temperature='18°C' condition='cloudy'Agen memanggil tool get_weather, lalu mengorganisir informasi tersebut ke dalam objek WeatherReport yang cocok dengan schema.
middleware
Loop agen memiliki tahapan-tahapan yang berbeda. Ia memanggil LLM, mengeksekusi tool, memanggil LLM lagi — tahapan-tahapan ini berulang. Middleware memungkinkanmu menyisipkan fungsimu sendiri sebelum atau sesudah tahapan-tahapan ini. Kamu menentukan waktunya dengan sebuah decorator: @before_model berarti tepat sebelum panggilan LLM, dan @after_model berarti tepat setelah LLM merespons. Daftarkan fungsi tersebut di parameter middleware, dan ia akan berjalan pada titik yang ditentukan setiap kali.
Kita sudah menggunakan middleware di Bab 11. Kita mendekorasi fungsi trim_old_messages dengan @before_model dan mendaftarkannya — karena ia berjalan sebelum setiap panggilan LLM, ia bisa memangkas list pesan setiap kali.
Mari kita bangun sebuah middleware yang mencetak jumlah pesan tepat sebelum setiap panggilan LLM, sehingga kita bisa melihat persis kapan ia berjalan.
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import before_model
@before_model
def log_llm_call(state: AgentState, runtime) -> None:
"""Mencetak jumlah pesan tepat sebelum setiap panggilan LLM."""
print(f"[before_model] Akan memanggil LLM, pesan saat ini: {len(state['messages'])}")
agent = create_agent(
model="openai:gpt-5-mini",
tools=[get_weather, calculate],
middleware=[log_llm_call],
)
result = agent.invoke({
"messages": [HumanMessage(content="Dapatkan suhu di Cairo, lalu kalikan angkanya dengan 3.")],
})
print(result["messages"][-1].content)Output:
[before_model] Akan memanggil LLM, pesan saat ini: 1
[before_model] Akan memanggil LLM, pesan saat ini: 3
[before_model] Akan memanggil LLM, pesan saat ini: 5
Suhu terkini di Cairo: 31°C. Dikalikan 3 = 93.Middleware log_llm_call berjalan tiga kali. LLM dipanggil tiga kali saat memproses permintaan pengguna, dan middleware berjalan tepat sebelum setiap panggilan. Jumlah pesan memberi tahu kita State pada setiap titik: sebelum panggilan pertama hanya ada pertanyaan pengguna (HumanMessage) — 1 pesan. Setelah setiap iterasi loop, AIMessage yang meminta panggilan tool dan ToolMessage dengan hasilnya ditambahkan, bertambah menjadi 3, lalu 5.
Satu hal yang perlu diketahui: sebelum LangChain 1.0, peran ini diisi oleh sebuah fungsi bernama
create_react_agentdi sisi LangGraph, dan sekarang sudah deprecated. Jika kamu melihatfrom langgraph.prebuilt import create_react_agentdi tutorial atau blog post yang lebih lama, pahamilah bahwa itu adalah versi sebelumnya daricreate_agentyang sedang kamu pelajari sekarang.
Kita telah membangun ulang agen dari Bab 15 secara ringkas menggunakan ToolNode, tools_condition, dan create_agent. Di bagian berikutnya, kita akan menggabungkan komponen prebuilt ini dengan perakitan graph manual untuk membangun agen yang lebih kompleks.
16.2) Membangun Agen Multi-Cabang
Agen multi-cabang yang akan kita bangun di bagian ini pertama-tama menentukan jenis permintaan apa yang sedang dihadapinya, lalu menangani setiap jenis dengan model yang berbeda atau serangkaian tool yang berbeda. Kita akan merakit graph secara keseluruhan secara manual menggunakan pendekatan dari Bab 15, dan menggunakan create_agent untuk bagian-bagian yang membutuhkan loop tool-calling.
16.2.1) Kebutuhan dan Desain
Kita akan membangun agen customer-support yang disebutkan di pengantar bab ini. Berikut kebutuhannya:
- Pertanyaan sederhana ("Berapa jam operasional Anda?") → Sebuah model kecil berbiaya rendah menjawab secara langsung.
- Konsultasi kompleks ("Pesanan saya tiba dalam keadaan rusak — sebaiknya saya menukar atau mengembalikan dana?") → Sebuah model berperforma tinggi menjawab.
- Pencarian pesanan ("Bagaimana status pengiriman pesanan #12345?") → Sebuah agen dengan tool pencarian pesanan menanganinya.
Setiap jenis pertanyaan membutuhkan setup model dan tool yang berbeda, jadi kita membutuhkan graph yang mengklasifikasi setiap permintaan terlebih dahulu, lalu me-routing-nya ke handler yang tepat. Berikut strukturnya:
Ketika sebuah permintaan masuk, node classify menentukan jenis pertanyaan apa itu dan mencatat hasilnya di State. Sebuah conditional edge kemudian membaca jenis yang tercatat dari State dan me-routing ke node yang sesuai. simple_handler menangani pertanyaan sederhana, dan complex_handler menangani konsultasi kompleks. order_agent menggunakan tool pencarian pesanan untuk memeriksa status pengiriman dan merespons. Karena order_agent adalah node tool-calling standar, kita akan membangunnya dengan create_agent. Sekarang mari kita bangun setiap bagian secara berurutan.
16.2.2) Node Classify dan Fungsi Routing
Pertama, mari kita definisikan State. Kita akan menambahkan field intent untuk menyimpan hasil klasifikasi. Ketiga jenis akan direpresentasikan oleh nilai "simple", "complex", dan "order".
from langgraph.graph import MessagesState
class State(MessagesState):
intent: str # Hasil klasifikasi: "simple", "complex", "order"Berikutnya adalah node classify. Ia menggunakan LLM untuk menentukan permintaan pengguna termasuk ke salah satu dari tiga jenis, dan mencatat hasilnya di intent. Kita menerima hasil klasifikasi menggunakan output terstruktur, yang kita pelajari di Bab 7. Ketika kita mendeklarasikan field intent pada schema dengan tipe Literal, respons LLM dibatasi ke salah satu nilai yang dideklarasikan.
from typing import Literal
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
class IntentRoute(BaseModel):
"""Hasil klasifikasi untuk sebuah pertanyaan pelanggan."""
intent: Literal["simple", "complex", "order"] = Field(
description=(
"simple: pertanyaan umum seperti jam operasional atau sapaan. "
"complex: konsultasi yang butuh penalaran cermat, seperti sengketa atau pengembalian dana. "
"order: permintaan untuk mencari pesanan tertentu."
)
)
classifier_llm = ChatOpenAI(model="gpt-5.4-nano").with_structured_output(IntentRoute)
def classify(state: State):
"""Mengklasifikasi jenis pertanyaan pelanggan."""
question = state["messages"][-1].content
result = classifier_llm.invoke(
f"Klasifikasikan permintaan pelanggan.\n\nPermintaan: {question}"
)
return {"intent": result.intent}Kita menggunakan model terkecil (gpt-5.4-nano) untuk klasifikasi. Memutuskan "termasuk jenis apa pertanyaan ini?" adalah tugas sederhana yang tidak membutuhkan model berperforma tinggi. Dan karena node classify adalah gerbang yang dilalui setiap permintaan, model yang murah dan cepat lebih disukai.
Berikutnya adalah fungsi routing. Ia hanya mengembalikan hasil klasifikasi yang tersimpan di State.
def route_by_intent(state: State) -> Literal["simple", "complex", "order"]:
"""Menentukan node berikutnya berdasarkan hasil klasifikasi."""
return state["intent"]Node classify sudah memutuskan node mana yang harus berjalan berikutnya dan mencatatnya di intent, jadi fungsi routing tinggal mengembalikan nilai tersebut apa adanya.
16.2.3) Node Handler per Jenis
Sekarang mari kita bangun handler untuk masing-masing dari tiga jenis pertanyaan.
Handler pertanyaan sederhana memanggil sebuah model kecil satu kali. Dalam agen customer-support sungguhan kamu akan menerapkan RAG untuk mencari dokumen internal guna menemukan jawaban, tetapi kita membuat handler-nya tetap sederhana agar tetap fokus pada topik bab ini.
simple_llm = ChatOpenAI(model="gpt-5.4-mini")
def simple_handler(state: State):
"""Menjawab pertanyaan sederhana dengan model kecil."""
response = simple_llm.invoke(state["messages"])
return {"messages": [response]}Handler konsultasi kompleks menggunakan model berperforma tinggi. Dengan alasan yang sama seperti handler pertanyaan sederhana, kita membuatnya tetap sederhana — hanya menghasilkan sebuah respons.
complex_llm = ChatOpenAI(model="gpt-5.4")
def complex_handler(state: State):
"""Menjawab konsultasi kompleks dengan model berperforma tinggi."""
response = complex_llm.invoke(state["messages"])
return {"messages": [response]}Handler pencarian pesanan perlu menggunakan tool pencarian pesanan, yang berarti ia membutuhkan loop tool-calling. Karena strukturnya identik dengan agen tool-calling standar, kita akan membangunnya dengan create_agent.
from langchain.tools import tool
from langchain.agents import create_agent
@tool
def get_order_status(order_id: str) -> str:
"""Mencari status pengiriman sebuah pesanan berdasarkan nomor pesanan."""
fake_data = {"12345": "Dalam perjalanan, diperkirakan besok", "67890": "Terkirim"}
return fake_data.get(order_id, f"Pesanan {order_id} tidak ditemukan.")
order_agent = create_agent(
model="openai:gpt-5.4-mini",
tools=[get_order_status],
)16.2.4) Merakit dan Menjalankan Graph
Mari kita hubungkan semua node yang telah kita bangun ke dalam sebuah graph. Kita akan menempatkan classify di titik awal, menghubungkannya ke tiga handler melalui sebuah conditional edge, dan mengkonfigurasi setiap handler untuk berhenti setelah selesai.
from langgraph.graph import StateGraph, START, END
builder = StateGraph(State)
builder.add_node("classify", classify)
builder.add_node("simple", simple_handler)
builder.add_node("complex", complex_handler)
builder.add_node("order", order_agent) # Daftarkan graph create_agent sebagai sebuah node
builder.add_edge(START, "classify")
builder.add_conditional_edges("classify", route_by_intent)
builder.add_edge("simple", END)
builder.add_edge("complex", END)
builder.add_edge("order", END)
agent = builder.compile()Mari kita jalankan satu pertanyaan dari masing-masing jenis dan lihat handler mana yang memprosesnya.
from langchain_core.messages import HumanMessage
for question in [
"Berapa jam operasional Anda?",
"Pesanan saya tiba dalam keadaan rusak. Sebaiknya saya menukar atau mengembalikan dana?",
"Bagaimana status pengiriman pesanan 12345?",
]:
result = agent.invoke({"messages": [HumanMessage(content=question)]})
print(f"T: {question}")
print(f"[{result['intent']}] J: {result['messages'][-1].content}\n")Output:
T: Berapa jam operasional Anda?
[simple] J: Saya tidak punya jam operasional tetap—saya tersedia 24/7.
...
T: Pesanan saya tiba dalam keadaan rusak. Sebaiknya saya menukar atau mengembalikan dana?
[complex] J: Jika pesanan Anda tiba dalam keadaan rusak, Anda umumnya berhak atas **penggantian/penukaran atau pengembalian dana penuh**.
...
T: Bagaimana status pengiriman pesanan 12345?
[order] J: Pesanan 12345 **sedang dalam perjalanan** dan **diperkirakan tiba besok**.Setiap pertanyaan diproses melalui jalur yang berbeda. simple_handler menjawab pertanyaan sederhana dengan model kecil, complex_handler menjawab konsultasi kompleks dengan model berperforma tinggi, dan order_agent memanggil tool get_order_status untuk menjawab pencarian pesanan.