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:
- Alat mana yang dipanggil — digerakkan oleh
namedandescriptionalat - Argumen apa yang diteruskan — digerakkan oleh
input_schemaalat
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
enumuntuk field dengan kumpulan nilai valid yang tetap - Set
minimum/maximumpada field numerik untuk mencegah input di luar rentang - Tandai field sebagai
requiredhanya 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 jsonfrom dataclasses import dataclass, asdictfrom typing import Any, Optional
@dataclassclass 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 resultUntuk 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
- Mulai dengan schema — tulis
input_schemasebelum badan fungsi - Tambahkan wrapper
ToolResultke setiap alat yang ada - Integrasikan
safe_tool_calluntuk mengamankan loop agenmu - Siapkan validasi hasil untuk alat yang memanggil API yang tidak kamu kontrol
Panduan terkait:
- Membangun Server MCP Pertamamu
- Pola Multi-Agen
- Sistem Memori Agen
- Pola Pemulihan Error Agen
- Debugging dan Observabilitas