SISTEM AKSES KOMPUTER

Pola Penggunaan Alat: Membangun Antarmuka Agen-Alat yang Andal


Agenmu memanggil sebuah alat dan mendapat blob JSON 40 baris — respons API mentah, objek bersarang, kode error terkubur di dalam field status. Model membacanya, memilih nilai yang terlihat masuk akal, dan melanjutkan. Nilai itu salah. Tiga langkah kemudian, agen dengan percaya diri menulis laporan berdasarkan data yang salah.

Alatnya bekerja. Antarmukanya gagal.

Penggunaan alat adalah mekanisme yang mengubah model bahasa menjadi agen. Setiap kemampuan agenmu — mencari database, menulis file, memanggil API, mengkueri layanan — datang melalui antarmuka alat. Jika antarmukanya dirancang buruk, model membuat keputusan yang lebih buruk meskipun layanan yang mendasarinya berfungsi dengan benar. Panduan ini mencakup lima pola untuk membangun antarmuka alat yang presisi, andal, dan siap produksi.

Prasyarat: Familiarity dengan Python dan API Claude. Untuk latar belakang MCP sebagai lapisan transport alat, lihat Membangun Server MCP Pertamamu.


Mengapa Desain Antarmuka Penting

Ketika agen memilih dan menggunakan alat, ia membuat dua keputusan:

  1. Alat mana yang dipanggil — digerakkan oleh name dan description alat
  2. Argumen apa yang diteruskan — digerakkan oleh input_schema alat

Deskripsi yang ambigu mengarah ke pemilihan alat yang salah. Skema yang longgar memungkinkan model meneruskan input yang malformed. Hasil yang tidak terstruktur membuat model menebak apa yang terjadi. Sebagian besar bug agen tidak berada di penalaran — mereka berada di batas alat.


Pola 1: Desain Schema-First

Tulis JSON schema sebelum menulis implementasinya. Schema yang ketat membatasi perilaku model pada tahap input — sebelum apapun dieksekusi.

import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "search_products",
"description": (
"Mencari katalog produk berdasarkan kata kunci. "
"Mengembalikan daftar produk yang cocok dengan ID, nama, dan harga. "
"Gunakan ketika pengguna ingin menemukan atau menjelajahi produk."
),
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Kata kunci untuk dicari"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food", "home", "all"],
"description": "Kategori produk untuk difilter. Gunakan 'all' jika tidak ditentukan."
},
"max_results": {
"type": "integer",
"minimum": 1,
"maximum": 20,
"description": "Jumlah hasil yang dikembalikan. Default: 5"
}
},
"required": ["query", "category"]
}
}
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "Temukan elektronik di bawah Rp 1.000.000"}]
)

Aturan schema yang mengurangi error:

  • Gunakan enum untuk field dengan kumpulan nilai valid yang tetap
  • Set minimum/maximum pada field numerik untuk mencegah input di luar rentang
  • Tandai field sebagai required hanya ketika alat benar-benar tidak bisa berjalan tanpanya
  • Tulis deskripsi dari perspektif model: “Gunakan ketika…” memberi tahu model kapan memanggil alat

Kapan digunakan: Setiap definisi alat.


Pola 2: Hasil Alat yang Terstruktur

Kembalikan hasil bertipe dan dapat dibaca mesin. Jangan pernah mengembalikan respons API mentah.

import json
from dataclasses import dataclass, asdict
from typing import Any, Optional
@dataclass
class ToolResult:
success: bool
data: Optional[Any] = None
error: Optional[str] = None
def to_content(self) -> str:
return json.dumps(asdict(self))
def search_products(
query: str,
category: str,
max_results: int = 5,
) -> ToolResult:
try:
raw_results = _query_database(query, category, limit=max_results)
products = [
{"id": r["product_id"], "name": r["title"], "price": r["price_usd"]}
for r in raw_results
]
return ToolResult(success=True, data={"products": products, "count": len(products)})
except ConnectionError as e:
return ToolResult(success=False, error=f"Database tidak tersedia: {e}")
except Exception as e:
return ToolResult(success=False, error=f"Pencarian gagal: {type(e).__name__}: {e}")
def _query_database(query, category, limit):
return []

Amplop {success, data, error} yang konsisten berarti model selalu tahu di mana mencari. Untuk penanganan kegagalan yang elegan, lihat Pola Pemulihan Error Agen.


Pola 3: Panggilan Alat Paralel

Claude dapat meminta beberapa alat dalam satu respons. Proses secara paralel daripada berurutan.

from concurrent.futures import ThreadPoolExecutor, as_completed
TOOL_REGISTRY = {
"search_products": search_products,
}
def dispatch_tool(name: str, inputs: dict) -> ToolResult:
handler = TOOL_REGISTRY.get(name)
if not handler:
return ToolResult(success=False, error=f"Alat tidak dikenal: {name}")
return handler(**inputs)
def process_tool_calls(response: anthropic.types.Message) -> list[dict]:
tool_uses = [
block for block in response.content
if block.type == "tool_use"
]
if not tool_uses:
return []
def execute(tool_use):
result = dispatch_tool(tool_use.name, tool_use.input)
return {
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result.to_content(),
}
with ThreadPoolExecutor(max_workers=len(tool_uses)) as executor:
futures = {executor.submit(execute, tu): tu for tu in tool_uses}
results = []
for future in as_completed(futures):
results.append(future.result())
return results
def run_agent(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
for block in response.content:
if hasattr(block, "text"):
return block.text
return ""
if response.stop_reason == "tool_use":
tool_results = process_tool_calls(response)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
break
return ""

Untuk mengorkestrasikan beberapa agen yang masing-masing memanggil alat, lihat Pola Multi-Agen.


Pola 4: Wrapper Panggilan Alat yang Aman

Jangan biarkan eksepsi alat mencapai loop agen tanpa ditangani.

import signal
def timeout_handler(signum, frame):
raise TimeoutError("Eksekusi alat melampaui batas waktu")
def safe_tool_call(
name: str,
inputs: dict,
timeout_seconds: int = 30,
) -> ToolResult:
"""
Menjalankan alat dengan timeout, menangkap semua eksepsi.
Selalu mengembalikan ToolResult — tidak pernah raise.
"""
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout_seconds)
try:
return dispatch_tool(name, inputs)
except TimeoutError:
return ToolResult(
success=False,
error=f"Alat '{name}' timeout setelah {timeout_seconds}s"
)
except Exception as e:
return ToolResult(
success=False,
error=f"Alat '{name}' menimbulkan {type(e).__name__}: {e}"
)
finally:
signal.alarm(0)

Ketika alat mengembalikan success: false, model dapat memutuskan apakah akan mencoba ulang, mencoba alternatif, atau melaporkan kegagalan. Untuk strategi retry yang lebih luas, lihat Pola Pemulihan Error.


Pola 5: Validasi dan Pemotongan Hasil

Validasi hasil alat sebelum mengembalikannya ke model.

MAX_TOOL_RESULT_CHARS = 8000
def validate_result(result: ToolResult, expected_keys: list[str]) -> ToolResult:
if not result.success or not isinstance(result.data, dict):
return result
missing = [k for k in expected_keys if k not in result.data]
if missing:
return ToolResult(
success=False,
error=f"Respons alat kehilangan field yang diharapkan: {missing}"
)
return result
def truncate_result(result: ToolResult) -> ToolResult:
content = result.to_content()
if len(content) <= MAX_TOOL_RESULT_CHARS:
return result
truncated_data = {
"truncated": True,
"chars_omitted": len(content) - MAX_TOOL_RESULT_CHARS,
"content": content[:MAX_TOOL_RESULT_CHARS],
}
return ToolResult(
success=result.success,
data=truncated_data,
error="Hasil dipotong — terlalu besar untuk jendela konteks",
)
def safe_tool_call_validated(
name: str,
inputs: dict,
expected_keys: list[str] | None = None,
) -> ToolResult:
result = safe_tool_call(name, inputs)
if expected_keys:
result = validate_result(result, expected_keys)
result = truncate_result(result)
return result

Untuk mengamati dan men-debug kegagalan ini di produksi, lihat Debugging dan Observabilitas.


Kesalahan Umum

Kesalahan 1: Mengembalikan Respons API Mentah

Model menerima objek bersarang dengan 30 field, sebagian besar tidak relevan. Ia memilih yang salah.

Solusi: Bentuk respons sebelum mengembalikannya. Kembalikan hanya apa yang dibutuhkan model untuk keputusan berikutnya.

Kesalahan 2: Alat dengan Efek Samping Tanpa Konfirmasi

Alat yang mengirim email, menghapus record, atau membebankan biaya tidak boleh dieksekusi diam-diam.

Solusi: Untuk aksi yang tidak dapat dibatalkan, gunakan pola dua alat: plan_email mengembalikan pratinjau, send_email benar-benar mengirim.

Kesalahan 3: Tanggung Jawab Alat yang Tumpang Tindih

Dua alat yang melakukan hal serupa memaksa model untuk menebak mana yang digunakan.

Solusi: Setiap alat harus memiliki tujuan yang berbeda dan tidak tumpang tindih.

Kesalahan 4: Tidak Ada Timeout untuk Alat Eksternal

Panggilan API pihak ketiga yang lambat memblokir seluruh loop agen tanpa batas waktu.

Solusi: Selalu set timeout (Pola 4).


Daftar Periksa Produksi

  • Setiap alat memiliki deskripsi dari perspektif model (“Gunakan ketika…”)
  • Field enum untuk semua input bernilai tetap
  • Setiap alat mengembalikan {success, data, error} — tidak pernah respons mentah
  • Panggilan alat dibungkus dalam safe_tool_call
  • Eksekusi paralel untuk respons multi-alat
  • Timeout ditetapkan pada setiap alat eksternal
  • Pemotongan hasil untuk payload berukuran variabel
  • Validasi untuk alat yang memanggil API eksternal

Langkah Selanjutnya

  1. Mulai dengan schema — tulis input_schema sebelum badan fungsi
  2. Tambahkan wrapper ToolResult ke setiap alat yang ada
  3. Integrasikan safe_tool_call untuk mengamankan loop agenmu
  4. Siapkan validasi hasil untuk alat yang memanggil API yang tidak kamu kontrol

Panduan terkait: