← Semua pembelajaran / Python untuk AI Engineer
Fase 4 · Bicara dengan LLM

Tool Use Overview

Tool use adalah fondasi semua agent. LLM tidak mengeksekusi apa pun — ia hanya meminta kamu menjalankan fungsi.

Intisari

  • LLM tidak menjalankan kode. Ia mengembalikan blok tool_use berisi nama fungsi dan argumennya; kamu yang mengeksekusi.
  • Loop-nya: kirim tools → model minta → kamu jalankan → kirim tool_result → ulangi sampai end_turn.
  • Definisi tool = nama + deskripsi + JSON Schema. Persis output model_json_schema() dari Fase 2.
  • tool_result harus punya tool_use_id yang cocok, dan semua hasil dikirim dalam satu pesan user.
  • SDK punya tool_runner yang mengurus loop-nya. Pahami loop manualnya dulu, baru pakai abstraksinya.

Model mental

      KAMU                          MODEL
        │                              │
        │ ── messages + tools ──────>  │
        │                              │  "aku butuh cari_tiket(status='open')"
        │ <── stop_reason: tool_use ──  │
        │                              │
   [jalankan fungsinya]                │
        │                              │
        │ ── tool_result ───────────>  │
        │                              │  "ada 12 tiket open, yaitu..."
        │ <── stop_reason: end_turn ──  │

Ini seluruh rahasia "agent AI". Model tidak punya akses ke database, internet, atau filesystem-mu. Ia hanya bisa memintamu memanggil fungsi dan membaca hasilnya. Semua keputusan tentang apa yang boleh dan tidak boleh dijalankan tetap ada di kodemu.

Mendefinisikan tool

tools = [
    {
        "name": "cari_tiket",
        "description": (
            "Cari tiket support berdasarkan status. "
            "Gunakan saat pengguna bertanya tentang tiket yang sedang berjalan, "
            "belum selesai, atau sudah ditutup."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "status": {
                    "type": "string",
                    "enum": ["open", "closed", "pending"],
                    "description": "Status tiket yang dicari",
                },
                "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "description": "Jumlah maksimal hasil. Default 10.",
                },
            },
            "required": ["status"],
        },
    }
]

input_schema itu JSON Schema. Yaitu format yang persis dihasilkan Model.model_json_schema() dari Pydantic. Inilah alasan Fase 2 tidak bisa dilewati — tanpa memahami JSON Schema, bagian ini hanya jadi salinan kode.

Deskripsi adalah prompt engineering

Deskripsi tool menentukan seberapa tepat model memanggilnya. Yang perlu ada di dalamnya:

Kesalahan yang paling umum bukan deskripsi yang terlalu panjang, tapi terlalu pendek. Tiga sampai empat kalimat adalah minimum yang wajar untuk tool nontrivial.

Loop manual — pahami ini dulu

import anthropic

client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Ada berapa tiket yang masih open?"}]

while True:
    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=16000,
        tools=tools,
        messages=messages,
    )

    if response.stop_reason == "end_turn":
        break

    # 1. Simpan balasan model APA ADANYA (termasuk blok tool_use)
    messages.append({"role": "assistant", "content": response.content})

    # 2. Jalankan tiap tool yang diminta
    hasil = []
    for block in response.content:
        if block.type != "tool_use":
            continue
        try:
            keluaran = jalankan_tool(block.name, block.input)
            hasil.append({
                "type": "tool_result",
                "tool_use_id": block.id,          # ← WAJIB cocok
                "content": str(keluaran),
            })
        except Exception as e:
            hasil.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": f"Error: {e}",
                "is_error": True,
            })

    # 3. SEMUA hasil dikirim dalam SATU pesan user
    messages.append({"role": "user", "content": hasil})

jawaban = next(b.text for b in response.content if b.type == "text")

Tiga aturan yang mudah dilanggar:
1. Simpan response.content utuh, jangan hanya teksnya — blok tool_use harus ikut.
2. Tiap tool_result wajib punya tool_use_id yang cocok, atau API menolak.
3. Kalau model minta tiga tool sekaligus, ketiga hasilnya dikirim dalam satu pesan user. Memecahnya jadi beberapa pesan akan membuat model berhenti memanggil tool secara paralel.

Menangani error tool

{
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": "Error: kota 'xyz' tidak ditemukan. Berikan nama kota yang valid.",
    "is_error": True,
}

Kembalikan pesan error yang informatif, bukan sekadar melempar exception. Model akan membacanya dan bisa mencoba pendekatan lain — misalnya bertanya balik ke pengguna atau memperbaiki argumennya.

Tool runner — abstraksinya

from anthropic import beta_tool
import anthropic

client = anthropic.Anthropic()

@beta_tool
def cari_tiket(status: str, limit: int = 10) -> str:
    """Cari tiket support berdasarkan status.

    Args:
        status: Status tiket, misal "open" atau "closed".
        limit: Jumlah maksimal hasil yang dikembalikan.
    """
    return db.query(status=status, limit=limit)


runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=16000,
    tools=[cari_tiket],
    messages=[{"role": "user", "content": "Ada berapa tiket yang masih open?"}],
)

for message in runner:
    print(message)

Skema tool dibuat otomatis dari type hint dan docstring. status: str jadi {"type": "string"}, dan blok Args: di docstring jadi description tiap properti. Sekarang jelas kenapa Fase 1 (docstring) dan Fase 2 (type hint) bukan formalitas.

Tool runner tetap memberi kendali

Anggapan umum bahwa "tool runner itu kotak hitam" keliru. Tiap iterasi memberimu pesan sebelum tool dijalankan:

Loop manual baru benar-benar perlu kalau kamu butuh bentuk kendali yang tidak disediakan hook-nya.

tool_choice

tool_choice={"type": "auto"}                      # model memutuskan (default)
tool_choice={"type": "any"}                       # wajib pakai salah satu tool
tool_choice={"type": "tool", "name": "cari"}      # wajib pakai tool tertentu
tool_choice={"type": "none"}                      # dilarang pakai tool

Pemanggilan paralel

Model bisa meminta beberapa tool sekaligus dalam satu balasan. Jalankan bersamaan (lihat Fase 3), lalu kirim semua hasilnya dalam satu pesan:

import asyncio

panggilan = [b for b in response.content if b.type == "tool_use"]
keluaran = await asyncio.gather(*(jalankan_async(b.name, b.input) for b in panggilan))

hasil = [
    {"type": "tool_result", "tool_use_id": b.id, "content": str(o)}
    for b, o in zip(panggilan, keluaran)
]
messages.append({"role": "user", "content": hasil})

Merancang permukaan tool

PrinsipAlasan
Sedikit tool dengan batas jelasTerlalu banyak tool membingungkan model
Deskripsi rinciFaktor terbesar yang menentukan akurasi pemanggilan
Argumen bertipe ketat (enum)Menutup ruang nilai yang salah
Hasil ringkas, bukan dump mentahHasil tool ikut masuk konteks dan dibayar
Aksi berbahaya jadi tool tersendiriBisa digerbangi persetujuan manusia
Validasi argumen dengan PydanticSkema itu panduan, bukan jaminan

Selalu validasi argumen tool sebelum memakainya. Model bisa mengirim argumen yang lolos skema tapi tidak masuk akal secara bisnis. Model.model_validate(block.input) adalah baris pertama di setiap implementasi tool yang baik.

Tool sisi server

Beberapa tool dijalankan di infrastruktur Anthropic — kamu cukup mendeklarasikannya:

tools=[
    {"type": "web_search_20260209", "name": "web_search"},
    {"type": "web_fetch_20260209", "name": "web_fetch"},
    {"type": "code_execution_20260120", "name": "code_execution"},
]

Hasilnya datang sebagai content block di respons yang sama — tidak ada loop tool_result untuk yang ini.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.