Tutorial ini membangun agen pengelola kalender dalam lima lapisan konsentris. Setiap lapisan adalah program lengkap yang dapat dijalankan dan menambahkan tepat satu konsep ke lapisan sebelumnya. Pada akhirnya, Anda akan menulis loop agentik secara manual dan kemudian menggantinya dengan abstraksi SDK Tool Runner.
Alat contoh yang digunakan adalah create_calendar_event. Skemanya menggunakan objek bersarang, array, dan field opsional, sehingga Anda akan melihat bagaimana Claude menangani bentuk input yang realistis, bukan sekadar satu string datar.
Setiap lapisan dapat dijalankan secara mandiri. Salin lapisan mana pun ke dalam file baru dan kode tersebut akan berjalan tanpa memerlukan kode dari lapisan sebelumnya.
Program penggunaan alat sekecil mungkin: satu alat, satu pesan pengguna, satu pemanggilan alat, satu hasil. Kode ini diberi banyak komentar sehingga Anda dapat memetakan setiap baris ke siklus hidup penggunaan alat.
Permintaan mengirimkan array tools bersama dengan pesan pengguna. Ketika Claude memutuskan untuk memanggil alat, respons kembali dengan stop_reason: "tool_use" dan blok konten tool_use yang berisi nama alat, id unik, dan input terstruktur. Kode Anda mengeksekusi alat tersebut, lalu mengirimkan hasilnya kembali dalam blok tool_result yang tool_use_id-nya cocok dengan id dari pemanggilan 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-4-8",
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] next Monday 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-4-8",
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] next Monday 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)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 next Monday at 10am.stop_reason pertama adalah tool_use karena Claude sedang menunggu hasil dari kalender. Setelah Anda mengirimkan hasilnya, stop_reason kedua adalah end_turn dan kontennya berupa bahasa alami untuk pengguna.
Lapisan 1 mengasumsikan Claude akan memanggil alat tepat satu kali. Tugas nyata sering kali membutuhkan beberapa pemanggilan: Claude mungkin membuat sebuah acara, membaca konfirmasinya, lalu membuat acara lain. Solusinya adalah loop while yang terus menjalankan alat dan mengirimkan hasilnya kembali hingga stop_reason tidak lagi bernilai "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-4-8",
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-4-8",
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)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 tersebut. Kode Anda tidak lagi perlu mengetahuinya di awal.
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 pemanggilan alat independen yang perlu dilakukan, Claude mungkin mengembalikan beberapa blok tool_use dalam satu respons. Loop Anda perlu memproses semuanya dan mengirimkan semua hasil bersama-sama dalam satu pesan pengguna. Iterasi setiap blok tool_use dalam 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-4-8",
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-4-8",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)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 konkuren dan jaminan urutan, lihat Penggunaan alat paralel.
Alat bisa gagal. API kalender mungkin menolak acara dengan terlalu banyak peserta, atau format tanggal mungkin salah. Ketika alat memunculkan error, kirimkan pesan error kembali dengan is_error: true alih-alih membiarkan 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-4-8",
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-4-8",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)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 dengan itu. Lihat Menangani pemanggilan alat untuk referensi lengkap penanganan error.
Lapisan 2 hingga 4 menulis loop yang sama secara manual: memanggil API, memeriksa stop_reason, menjalankan alat, menambahkan 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 pengelolaan percakapan ditangani secara internal.
SDK Python menggunakan dekorator @beta_tool untuk menyimpulkan skema dari type hint dan docstring. SDK TypeScript menggunakan betaZodTool dengan skema Zod. SDK lainnya mengikuti pola yang sama dengan helper masing-masing: BetaRunnableTool di C# dan PHP, kelas alat bertipe di Java dan Ruby, serta toolrunner.NewBetaToolFromJSONSchema di Go.
Tool Runner tersedia di ketujuh SDK: Python, TypeScript, C#, Go, Java, PHP, dan Ruby. Lihat Tool Runner untuk referensi lengkapnya. Tab cURL dan CLI menampilkan catatan alih-alih kode; tetap gunakan loop Lapisan 4 untuk skrip berbasis curl atau CLI.
# 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-4-8",
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)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 Lapisan 3. Perbedaannya ada pada kode: kira-kira setengah jumlah baris, tanpa loop manual, dan skema berada tepat di samping implementasinya.
Anda memulai dengan satu pemanggilan alat yang di-hardcode dan berakhir dengan agen berbentuk produksi yang menangani beberapa alat, pemanggilan paralel, dan error, lalu meringkas semua itu ke dalam Tool Runner. Sepanjang proses, Anda telah melihat setiap bagian dari protokol penggunaan alat: blok tool_use, blok tool_result, pencocokan tool_use_id, pemeriksaan stop_reason, dan penandaan is_error.
Spesifikasi skema dan praktik terbaik.
Referensi lengkap abstraksi SDK.
Perbaiki error umum penggunaan alat.
Was this page helpful?