Pemanggilan tool
Model meminta menjalankan fungsimu, lalu kamu balas hasilnya.
Ringkasan
Kamu kirim daftar fungsi, model memilih satu, kamu jalankan, lalu balas hasilnya. Model tidak menjalankan apa pun sendiri, ia hanya meminta.
/v1/chat/completionsbutuh API keyLoop-nya tiga langkah, dan berulang sampai model berhenti minta:
- Kirim
toolsberisi fungsi yang boleh dipanggil, lalu model menjawab dengantool_callsdanfinish_reason: "tool_calls". - Jalankan fungsinya di sisimu, lalu kirim hasilnya sebagai pesan
role: "tool". - Ulangi langkah 1. Model akan minta lagi, atau berhenti dengan
finish_reason: "stop"dan jawaban akhir dicontent.
Yang perlu kamu sadari: yang menjalankan fungsinya adalah kodemu. Kami cuma menyampaikan permintaan dan hasilnya. Jadi fungsi yang kamu daftarkan tetap harus kamu validasi sendiri, seperti input dari mana pun.
Mengirim daftar tool
Tambahkan tools ke body request seperti ini:
curl https://api.nusarouter.web.id/v1/chat/completions \
-H "Authorization: Bearer sk-nusa-..." \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-flash-v4.1",
"messages": [{"role": "user", "content": "Cuaca di Jakarta?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Cuaca sebuah kota",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}'| Field | Tipe | Wajib | Artinya |
|---|---|---|---|
tools | array | Opsional | Daftar fungsi. Kosongkan atau hilangkan kalau tidak ada yang perlu dipanggil. |
tool_choice | string | object | Opsional | "auto", "required", "none", atau {"type":"function","function":{"name":"…"}} untuk memaksa satu fungsi. Diteruskan apa adanya, tapi seberapa tegas ia ditegakkan bergantung modelnya. Lihat Batasan. |
parallel_tool_calls | boolean | Opsional | Mengizinkan beberapa panggilan dalam satu balasan. |
Tiap entri tools berbentuk {type, function}, dengan function berisi name, description, dan parameters.
Bentuk balasannya
Kalau model memilih memanggil fungsi, response-nya seperti ini:
{
"id": "chatcmpl-bc02dceb-…",
"object": "chat.completion",
"created": 1790164911,
"model": "deepseek-flash-v4.1",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"reasoning_content": "User asks weather in Jakarta, use tool.",
"tool_calls": [
{
"id": "call_00_bIaFbd4YimVPH8K1YYDn2086",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Jakarta\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 3052,
"completion_tokens": 71,
"total_tokens": 3123,
"cached_tokens": 0,
"reasoning_tokens": 31
}
}Empat hal yang perlu kamu perhatikan:
contentberisinull, bukan string kosong. Itu penanda bahwa balasan ini adalah permintaan memanggil fungsi, bukan jawaban. Cektool_calls, bukancontent.finish_reasonberisi"tool_calls".argumentsadalah string berisi JSON, bukan object. Kamu perluJSON.parsesendiri. Argumen yang tidak bisa diparse berarti model salah menulis, dan itu urusanmu untuk memutuskan mau diapakan.iddipakai untuk memasangkan hasilnya nanti. Simpan apa adanya, jangan diubah.
Membalas dengan hasilnya
Kirim ulang percakapannya, dengan pesan assistant tadi ditambahkan apa adanya, lalu satu pesan tool untuk tiap panggilan.
Contoh lengkapnya, dua putaran:
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.nusarouter.web.id/v1",
api_key="sk-nusa-...",
)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Cuaca sebuah kota",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string", "description": "Nama kota"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "Cuaca di Jakarta?"}]
# Putaran 1: model meminta memanggil tool.
res = client.chat.completions.create(
model="deepseek-flash-v4.1", messages=messages, tools=tools, max_tokens=400,
)
msg = res.choices[0].message
# Balas dengan hasil tool, satu pesan "tool" per panggilan.
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps({"city": args["city"], "temp_c": 31}),
})
# Putaran 2: model menjawab dengan hasilnya.
res = client.chat.completions.create(
model="deepseek-flash-v4.1", messages=messages, tools=tools, max_tokens=400,
)
print(res.choices[0].message.content)Kode itu dijalankan apa adanya dan menghasilkan jawaban akhir di putaran kedua. Fungsi get_weather-nya sendiri tidak ada di contoh, karena di situlah kodemu masuk.
Padanan Anthropic
Permukaan Anthropic (POST /v1/messages) menjalankan loop yang sama, dengan bentuk yang berbeda.
{
"tools": [
{ "name": "get_weather", "input_schema": { "type": "object", "properties": { "city": { "type": "string" } } } }
]
}
// balasan model: blok tool_use
{ "role": "assistant", "content": [
{ "type": "tool_use", "id": "toolu_…", "name": "get_weather", "input": { "city": "Jakarta" } }
] }
// balasanmu: pesan user berisi blok tool_result
{ "role": "user", "content": [
{ "type": "tool_result", "tool_use_id": "toolu_…", "content": "31°C" }
] }Tool dideklarasikan dengan input_schema (bukan parameters), model membalas dengan blok tool_use, dan hasilnya kamu kirim sebagai pesan user berisi blok tool_result yang dipasangkan lewat tool_use_id. tool_choice menerima auto, any, tool, atau none. Bentuk lengkapnya di Format Anthropic.
Kalau pakai streaming
Dengan stream: true, panggilan tool datang bertahap. Bentuknya beda dari balasan biasa, dan bagian ini yang paling mudah salah.
data: {"choices":[{"delta":{"tool_calls":[{"function":{"arguments":"","name":"get_weather"},"id":"call_00_bJXKcMskleYxzU4rlsAV3026","index":0,"type":"function"}]},"finish_reason":null,"index":0,"logprobs":null}],"created":1790164917,"id":"de4fab0d5f34492fae467e9ef3bd6b4c","model":"deepseek-flash-v4.1","object":"chat.completion.chunk","usage":null}
data: {"choices":[{"delta":{"tool_calls":[{"function":{"arguments":"{"},"index":0}]},"finish_reason":null,"index":0,"logprobs":null}],"created":1790164917,"id":"de4fab0d5f34492fae467e9ef3bd6b4c","model":"deepseek-flash-v4.1","object":"chat.completion.chunk","usage":null}
data: {"choices":[{"delta":{"function_call":{"arguments":"","name":""},"role":"assistant"},"finish_reason":"tool_calls","index":0,"logprobs":null}],"created":1790164917,"id":"de4fab0d5f34492fae467e9ef3bd6b4c","model":"deepseek-flash-v4.1","object":"chat.completion.chunk","usage":{"prompt_tokens":3052,"completion_tokens":49,"total_tokens":3101,"cached_tokens":896,"reasoning_tokens":9}}
data: [DONE]Kunci membacanya: frame pertama membawa id dan name dengan arguments kosong. Frame-frame setelahnya hanya membawa potongan arguments, dengan index sebagai penanda panggilan mana. Jadi gabungkan potongannya per index, baru parse di akhir. Memparse tiap potongan akan gagal, karena satu potongan bukan JSON yang utuh.
Penanda selesainya tetap data: [DONE], dan frame terakhir membawa finish_reason: "tool_calls" beserta usage yang ditagihkan. Frame terakhir itu juga membawa function_call berisi string kosong, dan itu memang begitu adanya, bukan sesuatu yang perlu kamu pakai.
Batasan
Tiga hal yang perlu kamu tahu sebelum mengandalkan fitur ini:
tool_choice: "none"belum dihormati. Nilainya kami terima dan teruskan, tapi model di baliknya masih bisa memanggil fungsi. Kalau kamu benar-benar ingin model menjawab tanpa memanggil apa pun, jangan kirimtoolssama sekali.- Modelnya harus mendukung. Tidak semua model di katalog bisa memanggil fungsi. Model yang tidak bisa akan menjawab dengan teks biasa seolah tidak ada
toolsdi request, tanpa error. Uji dulu model yang mau kamu pakai. - Tidak ada penyimpanan status. Tiap request berdiri sendiri. Riwayat percakapan, termasuk hasil tool sebelumnya, kamu kirim ulang setiap kali.
Lihat katalog publik untuk model yang aktif hari ini, dan Model dan harga untuk cara membacanya.
Troubleshooting
| Gejala | Penyebab | Solusi |
|---|---|---|
Putaran kedua gagal 422 | Pesan assistant tidak dikirim utuh: tool_calls-nya hilang, atau content: null-nya diganti jadi string kosong. | tambahkan pesan assistant apa adanya, jangan diubah bentuknya |
422 | Pesan tool tanpa tool_call_id, atau arguments dikirim sebagai object. | tool_call_id wajib ada dan harus cocok dengan id panggilannya; arguments selalu string |
502 | Skema parameters tidak valid, jadi modelnya yang menolak. Skema tidak diperiksa di sisi kami. | perbaiki JSON Schema-nya, lalu uji lagi lewat playground |
| Model menjawab teks biasa, tidak memanggil apa pun | Model itu tidak mendukung pemanggilan fungsi, atau tidak menganggap perlu memanggil. | pakai model lain, atau pertegas description fungsinya |
| Argumen hasil gabungan gagal diparse | Potongan arguments diparse satu per satu, bukan digabung dulu per index. | gabungkan semua potongan dengan index yang sama, baru parse sekali |