Tutorial ini membangun agen manajemen kalender dalam lima ring konsentris. Setiap ring adalah program lengkap yang dapat dijalankan dan menambahkan tepat satu konsep ke ring sebelumnya. Pada akhirnya Anda akan menulis loop agentik secara manual dan kemudian menggantinya dengan abstraksi Tool Runner SDK.
Alat contohnya adalah create_calendar_event. Skemanya menggunakan objek bersarang, array, dan field opsional, sehingga Anda akan melihat bagaimana Claude menangani bentuk input yang realistis alih-alih satu string datar.
Program penggunaan alat terkecil yang mungkin: satu alat, satu pesan pengguna, satu panggilan alat, satu hasil. Kode ini diberi komentar secara rinci sehingga Anda dapat memetakan setiap baris ke siklus hidup penggunaan alat.
Permintaan mengirimkan array tools bersama pesan pengguna. Ketika Claude menentukan bahwa panggilan alat diperlukan, respons kembali dengan stop_reason: "tool_use" dan blok konten tool_use yang berisi nama alat, id unik, dan input terstruktur. Kode Anda menjalankan alat tersebut, lalu mengirimkan hasilnya kembali dalam blok tool_result yang tool_use_id-nya cocok dengan id dari panggilan tersebut.
# Ring 1: Satu alat, satu giliran.
import json
import anthropic
# Buat klien. Klien membaca ANTHROPIC_API_KEY dari environment.
client = anthropic.Anthropic()
# Definisikan satu alat. input_schema adalah objek JSON Schema yang mendeskripsikan
# argumen yang harus diteruskan Claude saat memanggil alat ini. Skema ini
# mencakup objek bersarang (recurrence), array (attendees), dan field
# opsional, yang lebih mendekati alat dunia nyata daripada argumen string datar.
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
# Kirim permintaan pengguna bersama definisi alat. Claude memutuskan
# apakah akan memanggil alat berdasarkan permintaan dan deskripsi alat.
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[
{
"role": "user",
"content": "Schedule a 30-minute sync with [email protected] and [email protected] on Monday, March 30, 2026 at 10am.",
}
],
)
# Ketika Claude memanggil alat, respons memiliki stop_reason "tool_use"
# dan array content berisi blok tool_use di samping teks apa pun.
print(f"stop_reason: {response.stop_reason}")
# Temukan blok tool_use. Respons mungkin berisi blok teks sebelum blok
# tool_use, jadi pindai array content alih-alih mengasumsikan posisinya.
tool_use = next(block for block in response.content if block.type == "tool_use")
print(f"Tool: {tool_use.name}")
print(f"Input: {tool_use.input}")
# Eksekusi alat. Di sistem nyata, ini akan memanggil API kalender Anda.
# Di sini hasilnya di-hardcode agar contoh ini tetap mandiri.
result = {"event_id": "evt_123", "status": "created"}
# Kirim hasilnya kembali. Blok tool_result masuk ke pesan user dan
# tool_use_id-nya harus cocok dengan id dari blok tool_use di atas. Respons
# asisten sebelumnya disertakan agar Claude memiliki riwayat lengkap.
followup = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[
{
"role": "user",
"content": "Schedule a 30-minute sync with [email protected] and [email protected] on Monday, March 30, 2026 at 10am.",
},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
},
],
)
# Dengan hasil alat di tangan, Claude menghasilkan jawaban akhir dalam
# bahasa alami dan stop_reason menjadi "end_turn".
print(f"stop_reason: {followup.stop_reason}")
final_text = next(block for block in followup.content if block.type == "text")
print(final_text.text)Apa yang diharapkan
stop_reason: tool_use
Tool: create_calendar_event
Input: {'title': 'Sync', 'start': '2026-03-30T10:00:00', 'end': '2026-03-30T10:30:00', 'attendees': ['[email protected]', '[email protected]']}
stop_reason: end_turn
I've scheduled your 30-minute sync with Alice and Bob for Monday, March 30 at 10am.stop_reason pertama adalah tool_use karena Claude sedang menunggu hasil kalender. Setelah Anda mengirimkan hasilnya, stop_reason kedua adalah end_turn dan kontennya adalah bahasa alami untuk pengguna.
Ring 1 mengasumsikan Claude akan memanggil alat tepat satu kali. Tugas nyata sering membutuhkan beberapa panggilan: Claude mungkin membuat sebuah acara, membaca konfirmasinya, lalu membuat acara lain. Solusinya adalah loop while yang terus menjalankan alat dan mengirimkan hasilnya kembali sampai stop_reason tidak lagi "tool_use".
Perubahan lainnya adalah riwayat percakapan. Alih-alih membangun ulang array messages dari awal pada setiap permintaan, simpan daftar yang terus berjalan dan tambahkan ke dalamnya. Setiap giliran melihat konteks lengkap sebelumnya.
# Ring 2: Loop agentik.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
return {"error": f"Unknown tool: {name}"}
# Simpan seluruh riwayat percakapan dalam list agar setiap giliran melihat konteks sebelumnya.
messages = [
{
"role": "user",
"content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: [email protected], [email protected], [email protected].",
}
]
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
# Ulangi hingga Claude berhenti meminta alat. Setiap iterasi menjalankan alat
# yang diminta, menambahkan hasilnya ke riwayat, dan meminta Claude melanjutkan.
while response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
result = run_tool(tool_use.name, tool_use.input)
messages.append({"role": "assistant", "content": response.content})
messages.append(
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
}
)
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)Apa yang diharapkan
I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.Loop mungkin berjalan sekali atau beberapa kali tergantung pada bagaimana Claude memecah tugas. Kode Anda tidak lagi perlu mengetahuinya sebelumnya.
Agen jarang hanya memiliki satu kemampuan. Tambahkan alat kedua, list_calendar_events, sehingga Claude dapat memeriksa jadwal yang ada sebelum membuat sesuatu yang baru.
Ketika Claude memiliki beberapa panggilan alat independen yang harus dilakukan, Claude mungkin mengembalikan beberapa blok tool_use dalam satu respons. Loop Anda perlu memproses semuanya dan mengirimkan kembali semua hasil bersama-sama dalam satu pesan pengguna. Iterasi setiap blok tool_use di response.content, bukan hanya yang pertama.
# Ring 3: Beberapa alat, panggilan paralel.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "format": "date"},
},
"required": ["date"],
},
},
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
if name == "list_calendar_events":
return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
return {"error": f"Unknown tool: {name}"}
messages = [
{
"role": "user",
"content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
}
]
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
# Satu respons dapat berisi beberapa blok tool_use. Proses semuanya
# dan kembalikan semua hasilnya bersama dalam satu pesan user.
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)Apa yang diharapkan
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.Untuk informasi lebih lanjut tentang eksekusi bersamaan dan jaminan urutan, lihat Penggunaan alat paralel.
Alat bisa gagal. API kalender mungkin menolak acara dengan terlalu banyak peserta, atau tanggal mungkin salah format. Ketika alat menimbulkan error, kirimkan pesan error kembali dengan is_error: true alih-alih membuat program crash. Claude membaca error tersebut dan dapat mencoba lagi dengan input yang diperbaiki, meminta klarifikasi dari pengguna, atau menjelaskan keterbatasannya.
# Ring 4: Penanganan error.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "format": "date"},
},
"required": ["date"],
},
},
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
if "attendees" in tool_input and len(tool_input["attendees"]) > 10:
raise ValueError("Too many attendees (max 10)")
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
if name == "list_calendar_events":
return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
raise ValueError(f"Unknown tool: {name}")
messages = [
{
"role": "user",
"content": "Schedule an all-hands with everyone: " + ", ".join(f"user{i}@example.com" for i in range(15)),
}
]
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
try:
result = run_tool(block.name, block.input)
tool_results.append(
{"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)}
)
except Exception as exc:
# Sinyalkan kegagalan agar Claude dapat mencoba lagi atau meminta klarifikasi.
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(exc),
"is_error": True,
}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)Apa yang diharapkan
I tried to schedule the all-hands but the calendar only allows 10 attendees per event. I can split this into two sessions, or you can let me know which 10 people to prioritize.Flag is_error adalah satu-satunya perbedaan dari hasil yang berhasil. Claude melihat flag tersebut dan teks error-nya, lalu merespons sesuai. Lihat Menangani panggilan alat untuk referensi penanganan error lengkap.
Ring 2 hingga 4 menulis loop yang sama secara manual: panggil API, periksa stop_reason, jalankan alat, tambahkan hasil, ulangi. Tool Runner melakukan ini untuk Anda. Definisikan setiap alat sebagai fungsi, berikan daftarnya ke tool_runner, dan ambil pesan akhir setelah loop selesai. Pembungkusan error, pemformatan hasil, dan manajemen percakapan ditangani secara internal.
Setiap SDK menyediakan helper yang mengubah fungsi biasa menjadi alat yang dapat dijalankan dan menurunkan skema input dari signature-nya; tab di bawah menunjukkan bentuk idiomatik untuk setiap bahasa.
# Ring 5: Abstraksi Tool Runner SDK.
import json
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def create_calendar_event(
title: str,
start: str,
end: str,
attendees: list[str] | None = None,
recurrence: dict | None = None,
) -> str:
"""Create a calendar event with attendees and optional recurrence.
Args:
title: Event title.
start: Start time in ISO 8601 format.
end: End time in ISO 8601 format.
attendees: Email addresses to invite.
recurrence: Dict with 'frequency' (daily, weekly, monthly) and 'count'.
"""
if attendees and len(attendees) > 10:
raise ValueError("Too many attendees (max 10)")
return json.dumps({"event_id": "evt_123", "status": "created", "title": title})
@beta_tool
def list_calendar_events(date: str) -> str:
"""List all calendar events on a given date.
Args:
date: Date in YYYY-MM-DD format.
"""
return json.dumps({"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]})
final_message = client.beta.messages.tool_runner(
model="claude-opus-5",
max_tokens=1024,
tools=[create_calendar_event, list_calendar_events],
messages=[
{
"role": "user",
"content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
}
],
).until_done()
for block in final_message.content:
if block.type == "text":
print(block.text)Apa yang diharapkan
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.Output-nya identik dengan Ring 3. Perbedaannya ada pada kode: kira-kira setengah jumlah baris, tanpa loop manual, dan skema berada tepat di samping implementasinya.
Anda memulai dengan satu panggilan alat yang di-hardcode dan berakhir dengan agen berbentuk produksi yang menangani beberapa alat, panggilan paralel, dan error, lalu meringkas semuanya ke dalam Tool Runner. Sepanjang jalan Anda melihat setiap bagian dari protokol penggunaan alat: blok tool_use, blok tool_result, pencocokan tool_use_id, pemeriksaan stop_reason, dan pensinyalan is_error.
Spesifikasi skema dan praktik terbaik.
Referensi lengkap abstraksi SDK.
Perbaiki error penggunaan alat yang umum.
Was this page helpful?