Function calling (di Anthropic namanya tool use) artinya kamu memberi model daftar fungsi yang boleh dipakai, dan model bisa membalas "tolong panggil cekStok dengan judul Laskar Pelangi" alih-alih menjawab langsung. Server-mu yang menjalankan fungsinya, mengirim hasilnya balik, lalu model menjawab dengan data asli. Bayangin asisten toko yang melayani lewat telepon tapi gak boleh masuk gudang. Dia bilang ke kamu, "tolong cek rak Laskar Pelangi", kamu yang ke gudang dan melapor "ada 12", lalu dia menjawab pelanggan. Asistennya gak pernah menyentuh gudang.
🧪 Tebakan cepat: model membalas dengan permintaan memanggil
cekStokuntuk "Laskar Pelangi". Siapa yang benar-benar menjalankanGET /books?q=Laskar%20Pelangi: model di server OpenAI, atau server Toko Buku? Tebak dulu, jawabannya ada di bagian "Loop-nya, langkah demi langkah".
Tool: nama, deskripsi, dan JSON Schema
Satu tool (alat) didefinisikan dengan nama, deskripsi kapan dipakai, dan JSON Schema untuk argumennya. JSON Schema adalah format standar untuk menjelaskan bentuk sebuah JSON: field apa saja, tipenya apa, mana yang wajib. Bentuk di Responses API OpenAI (API reference dan panduan "Function calling", dicek Oktober 2026):
const tools = [
{
type: "function",
name: "cekStok",
description: "Cek stok dan harga buku di Toko Buku. Pakai setiap kali pelanggan menanyakan ketersediaan atau harga.",
parameters: {
type: "object",
properties: {
judul: { type: "string", description: "Judul buku, misalnya Laskar Pelangi" },
},
required: ["judul"],
additionalProperties: false,
},
strict: true,
},
];
type: "function", laluname,description,parameters, danstrictlangsung di objek itu. (Chat Completions yang lama membungkusnya lagi di dalamfunction: {...}.)descriptiondibaca model untuk memutuskan kapan tool dipakai. Tulis sejelas briefing untuk karyawan baru.strict: truemembuat argumen dari model selalu cocok dengan schema. Syaratnya:additionalProperties: falsedi tiap object, dan semua field ada direquired(field opsional ditulistype: ["string", "null"]). Kalaustrictgak ditulis, Responses API mencoba menjadikannya strict dan mundur ke mode biasa kalau schema-nya gak memenuhi syarat; Chat Completions tetap gak strict.- Definisi tool ikut dihitung sebagai token input, jadi jangan kirim 50 tool kalau cuma butuh 2.
Loop-nya, langkah demi langkah
Jalannya di Lab untuk "Stok Laskar Pelangi masih ada?". Balasan model di Lab adalah stand-in yang ditulis tangan; bentuk JSON-nya mengikuti API reference.
| # | Dari, ke | Isinya |
|---|---|---|
| 1 | Server, ke OpenAI | input: pertanyaan pelanggan, tools: cekStok |
| 2 | OpenAI, ke server | item {"type":"function_call","call_id":"call_1","name":"cekStok","arguments":"{\"judul\":\"Laskar Pelangi\"}"} |
| 3 | Server, ke API Toko Buku | GET /books?q=Laskar%20Pelangi, dijawab 200 [{"id":1,"title":"Laskar Pelangi","price":89000,"stock":12}] |
| 4 | Server, ke OpenAI | input yang sama, plus {"type":"function_call_output","call_id":"call_1","output":"[{\"judul\":\"Laskar Pelangi\",\"harga\":89000,\"stok\":12}]"} |
| 5 | OpenAI, ke server | pesan: "Masih ada, stok Laskar Pelangi 12, harganya Rp89.000." |
Langkah 3 dijalankan server Toko Buku. Itu jawaban tebakannya. Model cuma menulis JSON berisi nama fungsi dan argumennya; dia gak bisa menyentuh database atau API-mu. Jadi function calling adalah kemampuan model memutuskan kapan dan dengan argumen apa sebuah fungsi perlu dipanggil, sedangkan yang mengeksekusi tetap kode aplikasimu.
Kodenya di server, mengikuti contoh di panduan "Function calling":
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
async function tanyaAsisten(pertanyaan) {
const input = [{ role: "user", content: pertanyaan }];
const minta = () => client.responses.create({ model: "gpt-6-luna", reasoning: { effort: "none" }, instructions: ATURAN, tools, input });
let res = await minta();
for (let putaran = 1; putaran <= 3; putaran++) {
const panggilan = res.output.filter((item) => item.type === "function_call");
if (panggilan.length === 0) return res.output_text; // model sudah menjawab
input.push(...toResponseInputItems(res.output)); // simpan permintaan model di riwayat
for (const p of panggilan) {
const hasil = await jalankanTool(p.name, p.arguments);
input.push({ type: "function_call_output", call_id: p.call_id, output: JSON.stringify(hasil) });
}
res = await minta();
}
if (!res.output.some((item) => item.type === "function_call")) return res.output_text; // balasan request ke-4 tetap dibaca
return "Maaf, aku belum bisa menjawab itu.";
}
res.outputbisa berisi nol, satu, atau beberapafunction_call; panduannya bilang anggap selalu bisa lebih dari satu.- Permintaan model ikut disimpan di
input, lalu tiap hasil dikirim sebagaifunction_call_outputdengancall_idyang sama, supaya model tahu hasil mana untuk panggilan mana. putaran <= 3membatasi loop: paling banyak 3 putaran tool, jadi paling banyak 4 request, dan balasan request ke-4 tetap dibaca sebelum menyerah. Tanpa batas, model yang terus meminta tool bisa membuat request gak ada habisnya, dan tiap request ditagih.
Validasi argumen di server
strict: true menjamin bentuk, bukan isi. judul bisa string kosong, 5.000 karakter, atau berisi teks aneh hasil prompt injection. Periksa dulu sebelum dipakai:
async function jalankanTool(nama, argumenJson) {
if (nama !== "cekStok") return { error: `tool ${nama} gak dikenal` };
let argumen;
try {
argumen = JSON.parse(argumenJson); // arguments datang sebagai teks JSON
} catch {
return { error: "argumen bukan JSON yang valid" };
}
const judul = typeof argumen.judul === "string" ? argumen.judul.trim() : "";
if (judul === "" || judul.length > 100) return { error: "judul wajib diisi, paling banyak 100 karakter" };
const res = await fetch(`https://api.tokobuku.test/books?q=${encodeURIComponent(judul)}`);
const books = await res.json();
return books.map((b) => ({ judul: b.title, harga: b.price, stok: b.stock }));
}
- Error dikembalikan sebagai hasil tool, bukan dilempar. Model membacanya, lalu bisa memperbaiki argumennya atau menjelaskan ke pelanggan.
encodeURIComponentmembuat judul aman di URL, dan API Toko Buku tetap memakai placeholder SQL seperti di track API.- Di Lab, matikan
strictlalu tanya harga Bumi Manusia: stand-in model mengirim{"title":"Bumi Manusia"}(nama field salah), validasi menolak, model membaca error-nya dan memanggil lagi dengan{"judul":"Bumi Manusia"}. Dua putaran.
Tool yang menulis: minta konfirmasi dulu
cekStok cuma membaca, jadi boleh jalan otomatis. Tool yang mengubah sesuatu, misalnya pesanBuku yang memanggil POST /orders, lain cerita. Model bisa salah paham ("pesan" padahal cuma tanya), atau terbujuk teks jahat. Aturannya:
- Saat model meminta
pesanBuku({"book_id":1,"jumlah":2}), server gak memanggilPOST /orders. Server mengembalikan{"status":"menunggu_konfirmasi","total":178000}ke model, dan menampilkan tombol Konfirmasi ke pelanggan. POST /ordersbaru dikirim saat pelanggan menekan tombol itu, lewat endpoint biasa dengan sesi dan pengecekan hak aksesnya sendiri.- Tool yang cuma untuk admin (ubah harga, hapus buku) gak pernah diberikan ke model.
Bentuk yang sama di Anthropic
Idenya sama, nama field-nya beda (dokumentasi "Tool use with Claude", platform.claude.com, dicek Oktober 2026):
const res = await anthropic.messages.create({
model: "claude-haiku-5-5",
max_tokens: 1024,
tools: [{ name: "cekStok", description: "Cek stok dan harga buku.", input_schema: { type: "object", properties: { judul: { type: "string" } }, required: ["judul"] } }],
messages,
});
// res.stop_reason === "tool_use"
const tu = res.content.find((b) => b.type === "tool_use"); // { type: "tool_use", id: "toolu_...", name: "cekStok", input: { judul: "Laskar Pelangi" } }
messages.push(
{ role: "assistant", content: res.content },
{ role: "user", content: [{ type: "tool_result", tool_use_id: tu.id, content: JSON.stringify(await cekStok(tu.input.judul)) }] },
);
| OpenAI Responses | Anthropic Messages | |
|---|---|---|
| Schema argumen | parameters |
input_schema |
| Permintaan model | item function_call, argumen berupa teks JSON di arguments |
blok tool_use, argumen sudah berupa object di input |
| Hasil dari server | item function_call_output dengan call_id |
blok tool_result dengan tool_use_id, di pesan user |
| Tanda error | isi output sendiri |
is_error: true |
Di Anthropic, pesan user yang membawa tool_result harus langsung setelah pesan assistant yang berisi tool_use, dan blok tool_result harus di depan teks lain.
⚠️ Jebakan yang sering ketemu
- Mengira model yang mengeksekusi. Model cuma meminta. Kalau server-mu gak menjalankan apa-apa, gak ada yang terjadi, dan kalau server-mu menjalankan apa saja yang diminta, itu lebih bahaya lagi.
JSON.parsetanpa validasi.strictmenjamin bentuk, bukan isi. Cek tipe, panjang, dan nilai yang diizinkan di server.- Tool tulis yang jalan otomatis. Pesanan, pembayaran, dan penghapusan butuh klik pengguna, bukan kalimat model.
- Loop tanpa batas. Batasi putaran, dan kembalikan error sebagai hasil tool, jangan diam.
call_idatautool_use_idgak cocok. Hasil harus menunjuk ke permintaan yang benar, terutama kalau model meminta beberapa tool sekaligus.
TL;DR: Function calling membuat model bisa memutuskan kapan memanggil fungsi dan dengan argumen apa; yang mengeksekusi tetap kode aplikasimu. Tool didefinisikan dengan type: "function", name, description, parameters (JSON Schema) dan strict: true (dengan additionalProperties: false dan semua field di required). Loop: model membalas function_call, server memvalidasi argumen dan memanggil GET /books?q=..., hasilnya dikirim sebagai function_call_output dengan call_id yang sama, lalu model menjawab; batasi putarannya. Di Anthropic: input_schema, blok tool_use, lalu tool_result dengan tool_use_id di pesan user. Tool yang menulis (POST /orders) jangan jalan tanpa konfirmasi pengguna.