← semua materi
15:00 Teknis Β· 04 Nov 2026 Β· 15:06

πŸ“š Tool/Function Calling di Agent: Desain Schema & Error Recovery

Tanggal sesi: 2026-11-04. Verifikasi via dokumentasi resmi OpenAI, Anthropic, dan spesifikasi MCP (sumber di bawah). Angka yang tidak bisa diverifikasi saya tandai eksplisit.


🎯 Tujuan Belajar

Setelah sesi ini kamu bisa: 1. Menulis tool schema yang lolos strict mode OpenAI + tidak bikin model salah pilih parameter 2. Membedakan 4 kelas error tool dan memilih strategi recovery yang benar per kelas (retry di kode vs kembali ke model vs stop) 3. Memahami arah industri 2025–2026: tool search / defer loading (skala token), stateless tool protocol (MCP 2026-07-28), dan cara benchmark tool calling (BFCL V4)


πŸ—οΈ Arsitektur & Alur Teknis

Tool calling = loop lima langkah:

[app] --tools + messages--> [LLM]
[LLM] --tool_calls[]-------> [app]
[app] --validate args-------> eksekusi (dengan validation + error taxonomy)
[app] --tool results--------> [LLM]   (semua hasil dipulangkan, error pun)
[LLM] --final answer / more tool_calls--> ulang sampai selesai / budget habis

Tiga lapis pertahanan yang harus ada di layer eksekusi (dari luar ke dalam):

  1. Schema layer β€” schema ketat supaya model sulit generate argumen salah (strict mode, pattern, enum, minimum)
  2. Validation layer β€” pydantic/Zod tetap validasi hasil model sebelum eksekusi. Strict mode mengurangi kesalahan, bukan menjamin (laporan komunitas Jan–Mei 2025: strict mode kadang tidak dipaksa pada UI dashboard; anggap "best effort + constraint decoding", bukan jaminan absolut)
  3. Error taxonomy layer β€” klasifikasi kegagalan menentukan siapa yang memperbaiki: kode (retry sama-input), model (input berbeda), atau manusia (stop)

πŸ’» Kode Nyata

Stack: Python 3.11, pydantic 2.13.4, stdlib. File /tmp/toolcall/toolkit.py sudah saya jalankan, semua assertion lulus. Tiga komponen:

1. Compiler pydantic β†’ OpenAI strict-mode schema

OpenAI Structured Outputs/strict mode mensyaratkan (dok resmi): semua field required, setiap object additionalProperties: false, subset JSON Schema, $defs/$ref tidak boleh tersisa. Pydantic default menghasilkan optional fields dan $defs β€” jadi perlu compiler:

def to_openai_strict(model: type[BaseModel], name: str, desc: str) -> dict:
    schema = model.model_json_schema()
    defs = schema.pop("$defs", {})

    def resolve(node):
        if isinstance(node, list):
            return [resolve(n) for n in node]
        if not isinstance(node, dict):
            return node
        if "$ref" in node:                       # inline $ref; strict menolak $defs
            ref = node["$ref"].rsplit("/", 1)[-1]
            return resolve(defs[ref])
        out = {k: resolve(v) for k, v in node.items() if k not in ("title", "default")}
        if out.get("type") == "object" or "properties" in out:
            out["required"] = list(out.get("properties", {}))  # SEMUA field required
            out["additionalProperties"] = False
        return out

    return {"type": "function", "function": {
        "name": name, "description": desc,
        "parameters": resolve(schema), "strict": True}}

Output aktual dari kode demo (model GetOrder):

{
  "properties": {
    "order_id":     {"description": "Order id like ORD-123456",
                     "pattern": "^ORD-\\d{6}$", "type": "string"},
    "include_items":{"description": "Include line items", "type": "boolean"}
  },
  "required": ["order_id", "include_items"],
  "type": "object",
  "additionalProperties": false
}

πŸ’‘ Catatan: required berisi semua properti bukan berarti semua wajib diisi secara semantik β€” teknik standar: jadikan field opsional sebagai type: ["string", "null"] (nullable), model diberi tahu via description kapan boleh null.

2. Error taxonomy β€” 4 kelas, 4 strategi

class ErrKind(str, Enum):
    INVALID_ARGS = "invalid_args"   # salah model -> balikin ke model, bisa dia perbaiki
    NOT_FOUND    = "not_found"      # salah model, retry input sama percuma
    TRANSIENT    = "transient"      # salah infra -> retry DI KODE, model tak perlu tahu
    PERMANENT    = "permanent"      # unrecoverable -> stop, jangan biarkan model retry

def render(e: ToolError) -> str:
    """Teks error DIBACA MODEL. 1-2 kalimat, actionable, tanpa stack trace."""
    hint = {
        ErrKind.INVALID_ARGS: f"Fix the arguments and call again. Problem: {e.msg}",
        ErrKind.NOT_FOUND:    f"Do not retry with the same input. {e.msg} Choose a different id or ask the user.",
        ErrKind.TRANSIENT:    "Temporary. Retry once; already retried in code.",
        ErrKind.PERMANENT:    "Do not retry this tool. Report the failure to the user.",
    }[e.kind]
    return f"[{e.kind.value}] {hint}"

3. Executor dengan validation gate + retry budget

def call_tool(call_id, tool, raw_args: str, model, budget: RetryBudget,
              code_retries: int = 2) -> ToolResult:
    try:
        args = model.model_validate_json(raw_args)      # gate 1: shape validation
    except ValidationError as ve:
        budget.record(False)
        return ToolResult(call_id, render(ToolError(ErrKind.INVALID_ARGS,
                          ve.errors()[0]["msg"])), is_error=True)

    for attempt in range(code_retries + 1):             # gate 2: retry same-input DI KODE
        try:
            out = tool(**args.model_dump())
            budget.record(True)
            return ToolResult(call_id, json.dumps(out, default=str)[:4000])
        except ToolError as te:
            if te.kind is ErrKind.TRANSIENT and attempt < code_retries:
                time.sleep(0.2 * 2 ** attempt); continue   # exponential backoff
            budget.record(False)
            return ToolResult(call_id, render(te), is_error=True)
        except Exception as exc:                          # gate 3: jangan bocorkan stack trace
            budget.record(False)
            return ToolResult(call_id, render(ToolError(ErrKind.PERMANENT,
                              type(exc).__name__)), is_error=True)

RetryBudget menahan loop gila: max_model_calls=8, dan circuit breaker β€” 3 error konsekutif β†’ sisipkan pesan "This tool has failed repeatedly. Do not retry it." lalu hentikan loop dan eskalasi ke user. Hasil eksekusi nyata:

[invalid_args] Fix the arguments and call again. Problem: String should match pattern '^ORD-\d{6}$'
[not_found]    Do not retry with the same input. No order ORD-000000. ...
{"order_id": "ORD-999999", "items": null}   is_error=False   <- retry kode menyerap 503
{"order_id": "ORD-123456", "items": null}

Aturan kunci retry (DIINFERENSI dari konsensus praktik produksi, bukan angka hard): kalau retry dengan input sama bisa berhasil β†’ tangani di kode (backoff). Kalau butuh input berbeda β†’ pulangkan ke model dengan pesan yang menjelaskan apa yang salah. Dan: kalau satu turn punya multiple tool calls, pulangkan semua hasil (sukses + error) masing-masing dengan tool_use_id β€” hasil yang hilang merusak turn berikutnya. Di Anthropic API pakai is_error: true di tool_result; MCP pakai isError: true pada tool execution result; OpenAI tidak punya flag β€” pesan role: "tool" yang berisi error text saja.


πŸ“Š Angka & Benchmark

Data point Angka Sumber
BFCL V4 overall acc, peringkat 1: Claude-Opus-4-5-20251101 (FC) 77.47 (leaderboard update 2026-04-12) gorilla.cs.berkeley.edu
BFCL V4: GLM-4.6 (FC thinking), model open-terbaik, rank 4 72.38, biaya $4.64 sama
Tool Search Tool: penghematan token tool definitions ~85% (klaim Anthropic, Nov 2025); cross-platform benchmark Spring AI: 34–64% anthropic.com/news/advanced-tool-use
Contoh kasus Anthropic: 58 tools β‰ˆ 55K token sebelum optimasi; dengan tool search β‰ˆ 8.7K ~95% context tersisa sama
Akurasi MCP eval Opus 4: tanpa→dengan tool search 49% → 74%; Opus 4.5: 79.5% → 88.1% sama (internal testing, bukan benchmark netral)
MCP adoption ~500 juta download/bulan lintas Tier-1 SDK; TS & Python >1 miliar total blog.modelcontextprotocol.io, 2026-07-28
Contoh tool definition heboh: satu server Jira β‰ˆ 17K token; kasus ekstrem Anthropic 134K token sebelum optimasi β€” sama

⚠️ Angka 85%/49β†’74% berasal dari vendor sendiri (Anthropic internal testing). Benchmark independen paling dekat untuk akurasi tool calling: BFCL (Berkeley, paper di ICML 2025). Perlakukan angka vendor sebagai DIKETAHUI-atas-klaim, bukan fakta netral.


βš–οΈ Trade-off & Alternatif

Keputusan Pro Kontra
Strict mode ON Argumen hampir selalu valid; hemat round-trip error Subset JSON Schema saja; semua field jadi required (perlu trik nullable); schema diproses+di-cache di request pertama (latency jika schema berubah-ubah); tidak eligible untuk zero data retention
Strict mode OFF Full JSON Schema, field opsional natural Validation gagal lebih sering β†’ wajib pydantic/Zod gate + error recovery yang bagus
Semua tool preload Zero friction, model lihat semua 10K–134K token overhead; akurasi turun di library besar (tool mirip nama)
Tool search / defer loading (Anthropic defer_loading: true, OpenAI tool_search) Context hemat 34–85%; akurasi naik di library besar; tidak merusak prompt caching +1 search step latency; kurang berguna <10 tools atau semua tool sering dipakai
Programmatic tool calling / code mode (Anthropic code_execution, Cloudflare Code Mode) Orkestrasi (loop, filter, gabung 5 tools) dalam 1 inference pass; data besar tidak masuk context Perlu sandbox eksekusi; model menulis kode orkestrasi bisa salah; debugging lebih susah
MCP sebagai lapisan tool transport Standar lintas-vendor, registry, OAuth Lapisan ekstra; spec bergerak cepat (4 revisi dalam 20 bulan)
Retry di kode vs biarkan model retry Kode: murah, cepat, deterministic Kode salah retensi kalau error sebenarnya butuh input baru (waste retries); model retry mahal & bisa loop

🏭 Use Case Produksi & Failure Mode Umum

Use case nyata: Claude for Excel pakai Programmatic Tool Calling untuk baca/modifikasi spreadsheet ribuan baris tanpa membanjiri context (Anthropic, Nov 2025). Supabase MCP pakai MRTR (Multi Round-Trip Requests) untuk konfirmasi elicitation ("delete data? / biaya proyek?") di server stateless.

Failure mode umum + mitigasi:

  1. Loop tak berhenti β€” model retry tool yang gagal terus. Mitigasi: retry budget + circuit breaker di kode (lihat executor).
  2. Tool mirip nama β€” notification-send-user vs notification-send-channel salah pilih. Mitigasi: prefix namespacing (github.createPullRequest), description eksplisit, defer loading biar hanya tool relevan di context.
  3. Stack trace / secret bocor ke model atau log β€” error mentah dari upstream diinterpolasi begitu saja. Mitigasi: error message dari template sendiri, whitelist tipe exception, tanpa body response mentah.
  4. Hasil tool yang valid-tapi-kosong diberi flag error β€” not found vs empty list beda makna; is_error hanya untuk kegagalan aktual. Kalau semua balikan di-flag error, model kehilangan sinyal.
  5. Side-effect tool dieksekusi dobel β€” retry setelah timeout pada tool non-idempotent (payment, kirim email). Mitigasi: idempotency key sebagai parameter schema, atau tandai tool itu allowed_callers/sekali-jalan. DIINFERENSI: ini alasan Anthropic menyarankan PTC hanya untuk tool idempotent/paralel.
  6. Schema drift β€” schema berubah tiap request merusak cache schema strict mode dan prompt cache. Mitigasi: versioning tool name, schema statis.

Perubahan ekosistem yang perlu kamu tahu (DIKETAHUI, terverifikasi): - MCP 2026-07-28 (final 28 Juli 2026, menggantikan 2025-11-25): protokol jadi stateless β€” initialize handshake dan Mcp-Session-Id DIHAPUS. Info versi/klien/kapabilitas kini di _meta tiap request; header Mcp-Method + Mcp-Name wajib untuk routing di load balancer; hasil list (tools/list dll.) dapat ttlMs + cacheScope untuk caching; serverβ†’client mid-call input via InputRequiredResult + requestState (MRTR). Breaking changes; HTTP+SSE transport dan Roots/Sampling/Logging deprecated (deprecation window β‰₯12 bulan). Konsekuensi praktis: server MCP tak lagi butuh sticky session/shared session store β€” jalan di balik round-robin LB biasa. - Tool schema di MCP: input tetap type: object root, tapi kini full JSON Schema 2020-12 (oneOf/$ref boleh). Error input validation sebaiknya dikembalikan sebagai Tool Execution Error (bukan protocol error) supaya model bisa self-correct β€” keputusan desain yang sama persis dengan taxonomy di atas. - OpenAI: tool_search hanya untuk model gpt-5.4+; GPT-6 Astra/6.1 Sol mewajibkan Responses API untuk tool calling.


πŸ› οΈ Latihan Praktis Kecil

File lengkap sudah di /tmp/toolcall/toolkit.py (sudah lulus semua assertion di laptop saya). Coba di laptop:

python3 -c "import pydantic" || pip install pydantic
# salin toolkit.py, lalu modifikasi:
# 1. Tambah field enum: status: Literal['pending','shipped'] β€” cek output schema-nya
# 2. Buat tool yang raise PERMANENT β€” verifikasi pesan "Do not retry" sampai ke model
# 3. Naikkan RetryBudget.max_consecutive_errors ke 5, trip 4 error β€” verifikasi tidak exhausted
# 4. (Bonus) jalankan loop LLM sungguhan: kirim schema ke API, sengaja kirim argumen salah
#    dari sisi model, pulangkan error, hitung berapa kali model berhasil self-correct

βœ… Cek Pemahaman

  1. Tool database-mu gagal dengan connection timeout 2x berturut-turut. Ini kelas error apa, dan siapa yang harus me-retry β€” kode atau model? Kenapa beda perlakuannya dengan error "no order ORD-000000"?
  2. Kenapa required di schema strict mode OpenAI harus berisi SEMUA properti, dan bagaimana cara tetap memberi model kemampuan mengosongkan field opsional?
  3. Server MCP-mu saat ini bergantung pada Mcp-Session-Id untuk state antar tool call. Menurut spesifikasi 2026-07-28, apa pengganti yang direkomendasikan dan apa keuntungan operasionalnya di balik load balancer?

πŸ”‘ Poin Kunci

πŸ”— Sumber

  1. OpenAI β€” Function calling & Structured Outputs docs (akses 2026-11-04): developers.openai.com/api/docs/guides/function-calling, /guides/structured-outputs
  2. Anthropic β€” "Introducing advanced tool use" (Nov 2025): anthropic.com/news/advanced-tool-use
  3. Anthropic β€” Handle tool calls (is_error): platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls
  4. MCP Specification 2025-11-25 changelog: modelcontextprotocol.io/specification/2025-11-25/changelog
  5. MCP Blog β€” 2026-07-28 spec final (28 Jul 2026) + RC announcement: blog.modelcontextprotocol.io/posts/2026-07-28/, /posts/2026-07-28-release-candidate/
  6. Stacktree β€” MCP 2026-07-28 breaking changes breakdown (updated 30 Sep 2026): stacktr.ee/blog/mcp-2026-spec-changes
  7. Berkeley Function Calling Leaderboard V4 (update 2026-04-12): gorilla.cs.berkeley.edu/leaderboard.html

➑️ Eksplorasi Lanjutan