Kamu nggak milih framework agent karena dia yang terbaik. Kamu milih karena bentuknya cocok sama masalahmu, dan daftar fitur nggak akan ngasih tahu kamu bentuk yang mana. Jadi ini perbandingan yang beda: satu tugas, tiga framework, dan kode yang masing-masing minta kamu tulis.
Tugasnya agen support. Customer bilang pesanannya nggak pernah datang. Agen cek order, mutusin apakah bisa direfund, proses refund, atau serahin percakapan ke manusia. Tiga aturan, dua tool, satu jalur eskalasi. Cukup kecil buat diikuti baris per baris, cukup nyata buat bikin framework-nya beda pendapat soal cara kamu harus bangun.
Framework-nya: Claude Agent SDK (library dari Anthropic yang bungkus agent loop-nya Claude Code), OpenAI Agents SDK (penerus produksi dari Swarm), dan LangGraph (orchestration runtime level rendah dari LangChain). Ketiganya jalanin loop yang sama di belakang layar: model mikir, model manggil tool, hasil tool balik, ulangi sampai selesai. Bedanya cuma di apa yang masing-masing kasih di sekitar loop itu, dan apa yang mereka tinggalin buat kamu bangun sendiri.
Prasyarat
- Python 3.10+
pip install claude-agent-sdk openai-agents langgraph langchain langchain-anthropicANTHROPIC_API_KEYbuat contoh Claude SDK dan LangGraph,OPENAI_API_KEYbuat contoh OpenAI SDK- Sekitar 15 menit
Versi dicek ke PyPI tanggal 6 Agustus 2026: claude-agent-sdk 0.2.131, openai-agents 0.19.4, langgraph 1.2.10.
Tugasnya
Satu store order di memori, dua tool, satu aturan.
ORDERS = {
"ORD-1001": {"status": "shipped", "amount": 249000, "refundable": True},
"ORD-1002": {"status": "delivered", "amount": 89000, "refundable": False},
}
lookup_order(order_id)balikin data order atau "order not found"issue_refund(order_id, reason)nyatet refund- Kalau order nggak bisa direfund, agen wajib eskalasi ke manusia, bukan cuma bilang nggak
Spesifikasi yang sama dipake di semua contoh kode di bawah. Yang berubah cuma framework-nya.
Agen yang sama pakai Claude Agent SDK
Agent SDK itu Claude Code dalam bentuk library. Kamu dapet agent loop yang sama, tool bawaan yang sama (baca, tulis, edit file, jalanin command, cari di web), dan sistem permission yang sama, tapi bisa dipanggil dari Python. Ini satu-satunya dari tiga yang nyelipin tool file dan shell secara bawaan, makanya jadi pilihan default buat agen yang kerjaannya ngurusin kode.
Tool custom cuma fungsi async biasa yang dibungkus @tool. SDK-nya ngubah jadi MCP server in-process, jadi nggak ada subprocess atau urusan JSON-RPC yang harus di-debug.
import anyio
from claude_agent_sdk import (
query,
tool,
create_sdk_mcp_server,
ClaudeAgentOptions,
AssistantMessage,
TextBlock,
)
ORDERS = {
"ORD-1001": {"status": "shipped", "amount": 249000, "refundable": True},
"ORD-1002": {"status": "delivered", "amount": 89000, "refundable": False},
}
@tool("lookup_order", "Look up an order by its ID", {"order_id": str})
async def lookup_order(args):
order = ORDERS.get(args["order_id"])
text = str(order) if order else "order not found"
return {"content": [{"type": "text", "text": text}]}
@tool("issue_refund", "Issue a refund for an order", {"order_id": str, "reason": str})
async def issue_refund(args):
text = f"refund issued for {args['order_id']} (reason: {args['reason']})"
return {"content": [{"type": "text", "text": text}]}
server = create_sdk_mcp_server(
name="support",
version="1.0.0",
tools=[lookup_order, issue_refund],
)
options = ClaudeAgentOptions(
system_prompt=(
"You are an order support agent. Use lookup_order to check an order. "
"If the order is refundable, call issue_refund. If it is not, tell the "
"customer a human agent will take over."
),
mcp_servers={"support": server},
allowed_tools=["mcp__support__lookup_order", "mcp__support__issue_refund"],
max_turns=5,
)
async def main():
async for message in query(
prompt="Order ORD-1001 never arrived. I want a refund.",
options=options,
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main())
Tiga detail yang perlu kamu tau:
- Nama tool di
allowed_toolsngikutin polamcp__<server>__<tool>. Prefix-nya salah, model nggak akan pernah bisa manggil tool itu. allowed_toolsitu allowlist buat auto-approval.max_turnsngebatesin loop, jadi model yang bingung nggak bisa ngabisin token manggil tool yang sama terus-terusan.query()buat pertukaran sekali jalan. Buat percakapan multi-turn, kamu pakeClaudeSDKClient, yang njaga session tetap kebuka.
Trade-off-nya nyata: runtime-nya Claude Code CLI yang dibundel, dan modelnya Claude. Kamu terima itu, dan sebagai gantinya dapet agen yang udah bisa baca, edit, dan jalanin kode. Kalau kamu butuh model lain, ini bukan framework buat kamu.
Agen yang sama pakai OpenAI Agents SDK
OpenAI Agents SDK itu penerus produksi dari Swarm, dan keliatan banget. Primitifnya kecil: Agent, Runner, handoff, guardrail. Schema tool digenerate dari signature dan docstring fungsi kamu, jadi nggak ada schema yang ditulis manual.
import asyncio
from agents import Agent, Runner
from agents.decorators import tool
ORDERS = {
"ORD-1001": {"status": "shipped", "amount": 249000, "refundable": True},
"ORD-1002": {"status": "delivered", "amount": 89000, "refundable": False},
}
@tool
def lookup_order(order_id: str) -> str:
"""Look up an order by its ID. Returns the order details, or 'order not found'."""
return str(ORDERS.get(order_id, "order not found"))
@tool
def issue_refund(order_id: str, reason: str) -> str:
"""Issue a refund for an order."""
return f"refund issued for {order_id} (reason: {reason})"
escalation_agent = Agent(
name="Human Escalation",
handoff_description="Use when the customer's order cannot be refunded automatically.",
instructions=(
"Tell the customer a human agent will take over and end the conversation "
"politely. Do not promise a refund."
),
)
support_agent = Agent(
name="Support Agent",
instructions=(
"You are an order support agent. Use lookup_order to check an order. "
"If it is refundable, call issue_refund. If it is not, hand off to the "
"Human Escalation agent."
),
tools=[lookup_order, issue_refund],
handoffs=[escalation_agent],
model="gpt-5.6", # pin a model; omit to use the SDK default
)
async def main():
result = await Runner.run(
support_agent,
"Order ORD-1001 never arrived. I want a refund.",
)
print(result.final_output)
print(f"answered by: {result.last_agent.name}")
asyncio.run(main())
Jalur eskalasinya berupa handoff: agen kedua yang ngambil alih percakapan. result.last_agent.name ngasih tahu kamu agen mana yang beneran nyelesaiin, dan itu persis yang kamu butuhin di sistem support.
Yang nggak kamu dapet: tool file, akses shell, atau environment yang dibundel. SDK ini loop dengan tracing, session, dan guardrail di sekelilingnya, bukan runtime agen coding. Dia model-agnostic lewat provider kayak LiteLLM, dan tracing nyala secara default, bisa dilihat di dashboard OpenAI. Buat agen teks dengan tool dan delegasi, ini yang paling cepet rilis dari ketiganya.
Agen yang sama pakai LangGraph
LangGraph yang paling beda sendiri: dia orchestration runtime level rendah, bukan agent kit. Kamu yang nentuin state, node, dan edge, dan kamu yang mutusin kapan model ngomong dan kapan kode kamu sendiri yang jalan. Dokumentasinya terang-terangan bilang framework ini buat bikin agen yang kelakuannya persis kayak yang aplikasi kamu butuhin. Kontrol itu poinnya, dan itu juga harganya.
from typing import Literal
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, MessagesState, StateGraph
ORDERS = {
"ORD-1001": {"status": "shipped", "amount": 249000, "refundable": True},
"ORD-1002": {"status": "delivered", "amount": 89000, "refundable": False},
}
model = init_chat_model("claude-sonnet-4-6", temperature=0)
@tool
def lookup_order(order_id: str) -> str:
"""Look up an order by its ID. Returns the order details, or 'order not found'."""
return str(ORDERS.get(order_id, "order not found"))
@tool
def issue_refund(order_id: str, reason: str) -> str:
"""Issue a refund for an order."""
return f"refund issued for {order_id} (reason: {reason})"
tools = [lookup_order, issue_refund]
tools_by_name = {t.name: t for t in tools}
model_with_tools = model.bind_tools(tools)
SYSTEM = (
"You are an order support agent. Use lookup_order to check an order. "
"If it is refundable, call issue_refund. If it is not, tell the customer "
"a human agent will take over."
)
def call_model(state: MessagesState):
return {"messages": [model_with_tools.invoke([SystemMessage(content=SYSTEM)] + state["messages"])]}
def run_tools(state: MessagesState):
last = state["messages"][-1]
results = []
for call in last.tool_calls:
fn = tools_by_name[call["name"]]
results.append(ToolMessage(content=str(fn.invoke(call["args"])), tool_call_id=call["id"]))
return {"messages": results}
def route(state: MessagesState) -> Literal["tools", END]:
return "tools" if state["messages"][-1].tool_calls else END
builder = StateGraph(MessagesState)
builder.add_node("model", call_model)
builder.add_node("tools", run_tools)
builder.add_edge(START, "model")
builder.add_conditional_edges("model", route, ["tools", END])
builder.add_edge("tools", "model")
agent = builder.compile(checkpointer=InMemorySaver())
result = agent.invoke(
{"messages": [HumanMessage(content="Order ORD-1001 never arrived. I want a refund.")]},
config={"configurable": {"thread_id": "ticket-42"}},
)
print(result["messages"][-1].content)
Loop-nya sekarang kelihatan: model manggil LLM, route ngecek apakah responsnya berisi tool call, tools ngejalaninnya, dan edge balik ke model nutup siklusnya. Kamu nulis loop yang dua framework lain sembunyiin dari kamu.
Itu beli tiga hal yang dua lainnya nggak punya sebagai fitur kelas satu:
- Durable execution. Compile dengan checkpointer, state graph-nya selamat dari crash, dan run-nya lanjut dari checkpoint terakhir.
InMemorySavercukup buat development, dan dokumentasinya ngarahin ke saver berbasis Postgres buat produksi. - Human-in-the-loop.
interrupt()ngepause graph di tengah run, nunggu input dari luar, terus lanjut dari state yang tersimpan. Langkah approval cuma beberapa baris, bukan arsitektur ulang. - Cabang yang deterministik. Eskalasi nggak harus keputusan model. Kamu bisa nambah node yang ngecek
refundabledi kode dan ngarahin ke antrean manusia, tanpa LLM sama sekali.
Harganya kurva belajar dan boilerplate. Buat satu agen dengan dua tool, LangGraph itu kode terbanyak per fitur. Kode itu kebayar pas workflow-nya long-running, stateful, atau diatur regulasi, dan ke arah situ juga dokumentasinya narik.
Masing-masing framework ngoptimalin apa
| Claude Agent SDK | OpenAI Agents SDK | LangGraph | |
|---|---|---|---|
| Agent loop | Punya Claude Code, jadi | Bawaan, minimal | Kamu rakit dari node |
| Tool file dan shell | Bawaan | Nggak ada | Nggak ada |
| Multi-agent | Subagent | Handoff | Subgraph dan pola supervisor |
| Persistence | Session dikelola SDK | Session | Checkpointer (memory, Postgres, SQLite) |
| Human in the loop | Prompt permission, hook | Mekanisme bawaan | interrupt() plus checkpointer |
| Pilihan model | Claude doang | Banyak, lewat LiteLLM dll | Banyak, lewat integrasi LangChain |
| Tracing | Dukungan OpenTelemetry | Bawaan, dashboard OpenAI | Lewat LangSmith |
| Runtime | Claude Code CLI bundel | Python ringan | Python ringan |
| Kurva belajar | Rendah | Rendah | Sedang sampai tinggi |
| Versi, Agu 2026 | 0.2.131 | 0.19.4 | 1.2.10 |
Baca tabelnya sebagai tiga jawaban beda buat satu pertanyaan yang sama: siapa yang pegang loop, dan apa aja yang ikut.
Claude Agent SDK pegang loop dan environment. Kamu dapet tool dan permission-nya Claude Code gratis, dan sebagai gantinya kamu lepas pilihan model dan runtime yang ramping.
OpenAI Agents SDK pegang loop dan ergonomi. Kamu dapet tracing, session, dan delegasi hampir tanpa boilerplate, dan tool serta environment-nya kamu bawa sendiri.
LangGraph ngasih loop-nya ke kamu. Kamu yang mutusin semuanya, dan itu persis yang kamu mau pas workflow-nya jadi produknya, dan persis yang nggak kamu mau pas kamu cuma butuh chatbot yang bisa pake tool sebelum Jumat.
Pakai yang mana
- Bikin agen coding, bot buat kerjaan repo, atau apa pun yang harus baca, edit, dan jalanin kode: Claude Agent SDK. Tool file dan shell itu pembedanya, dan sistem permission-nya ngurus bagian yang serem.
- Bikin agen teks dengan tool, routing, dan delegasi, apalagi di stack OpenAI: OpenAI Agents SDK. Handoff dan guardrail nutupin kebanyakan bentuk support dan triage produksi, dan tracing bawaannya ngurangin satu hal lagi yang harus di-setup.
- Bikin workflow long-running dan stateful dengan langkah approval manusia, retry, atau kebutuhan audit: LangGraph. Checkpointer dan interrupt() itu fitur, bukan tempelan, dan node deterministiknya njaga jalur kritis keluar dari tangan model.
- Bikin script sekali jalan yang manggil LLM satu kali terus return: nggak usah tiga-tiganya. Pake Responses API atau Messages API langsung, dan jaga jumlah dependency di nol.
Kesimpulan
Pendapat jujur saya: OpenAI Agents SDK tempat mulai kalau agen teks polos dengan tool dan handoff, karena kodenya paling sedikit buat sistem yang paling banyak berfungsi. Claude Agent SDK pilihannya pas agennya harus nyentuh file atau jalanin command, karena tool-nya udah ada plus model permission yang nyertainya. LangGraph yang saya ambil pas workflow-nya mulai stateful dan manusia perlu nyetujuin langkah, karena durable execution dan interrupt() nyelesaiin masalah yang dua lainnya tinggalin buat kamu tambal sendiri.
Kabar baiknya, loop-nya ide yang sama di mana-mana. Bangun agen yang sama sekali lagi di framework kedua, dan konsepnya pindah. Itu latihan di atas: spesifikasi sama, tiga implementasi, dan sekarang kamu tau masing-masing minta bayaran apa buat tumpangannya.
Langkah selanjutnya
- Tambah aturan keempat di tugas, misalnya "refund di atas 500000 butuh approval", dan implement di tiap framework. Perubahan satu baris itu bikin framework-nya keliatan beda dengan cepat.
- Arahin tool-nya ke API order beneran dan tambah retry buat timeout.
- Tulis sepuluh kasus eval: tool yang bener kepanggil, eskalasi pas nggak refundable, nggak ada janji refund. Jalanin terhadap agen yang sama di dua framework.
- Buat LangGraph, coba Functional API dengan @entrypoint dan @task kalau sintaks graph-nya kerasa berat.