Cara Pakai OpenAI Decisions API: Bangun Router Chat CS dengan Contoh Kode

Oleh , Tim Engineering APIPedia · Terbit · 10 menit baca

Tutorial OpenAI Decisions API (GA 22 Okt 2026): klasifikasi pesan CS dengan predicate, choice, score, lalu teruskan ke API Cek Ongkir APIPedia.

Sampul APIPedia bertuliskan Bangun router chat CS dengan Decisions API, dengan kartu fakta: POST /v1/decisions, harga input 0,10 dolar per 1 juta token, output gratis, 400 soal per request, tipe predicate, choice, score, GA sejak 22 Oktober 2026

OpenAI Decisions API adalah endpoint khusus (POST /v1/decisions) yang menjawab pertanyaan klasifikasi dalam bentuk terstruktur: probabilitas, pilihan, atau skor, bukan teks bebas. Sejak resmi GA pada 22 Oktober 2026, banyak tim memakainya untuk routing pesan karena murah ($0,10 per 1 juta token input, output gratis) dan jauh lebih cepat dari model chat.

Di tutorial ini kita membangun router chat customer service untuk toko online: satu panggilan Decisions API menentukan maksud pesan, apakah perlu agen manusia, dan seberapa kesal pelanggan. Untuk pertanyaan ongkir, router langsung memanggil API Cek Ongkir APIPedia. Semua kode bisa dijalankan dengan Node.js 18+.

Apa bedanya Decisions API dengan Responses API biasa?

Responses API (atau chat completions) menghasilkan teks. Kalau Anda memakainya untuk klasifikasi, Anda harus menulis prompt "jawab hanya dengan salah satu label ini", lalu mem-parsing teksnya dan berharap model patuh. Decisions API memotong semua itu: Anda mengirim bukti (input) dan daftar pertanyaan (questions), lalu menerima array answers yang sudah bertipe.

Menurut dokumentasi resmi OpenAI, endpoint ini hanya mendukung model gpt-6-luna dan mengembalikan jawaban sekitar 10 kali lebih cepat dibanding memanggil model yang sama lewat Responses API. Angka absolut latensi tidak dicantumkan di dokumentasi; ringkasan OODA Loop menyebut kisaran 100–180 ms per respons, tetapi itu belum diverifikasi independen.

AspekDecisions APIResponses API untuk klasifikasi
Bentuk outputJSON bertipe: probability, choice, scoreTeks bebas, perlu parsing
ModelHanya gpt-6-lunaBanyak pilihan
Biaya outputTidak ditagihDitagih per token
Harga input (tanpa cache)$0,10 / 1 juta tokenTergantung model
Pertanyaan per request1–400 (beta: 200)Satu prompt, satu jawaban
Bisa menulis balasan?TidakYa

Poin terakhir penting: Decisions API tidak menulis kalimat balasan dan tidak mengekstrak entitas seperti nama kota. Ia hanya memutuskan. Jadi pola yang masuk akal adalah: Decisions API untuk routing, lalu kode biasa, API data, atau model chat untuk langkah berikutnya.

Tiga tipe pertanyaan: predicate, choice, score

  • predicate: peluang sebuah kondisi benar, dari 0 sampai 1. Contoh: "Apakah pelanggan minta bicara dengan manusia?"
  • choice: memilih satu nilai dari daftar Anda. Jawabannya berisi choice, probabilities tiap opsi, dan confidence. Sertakan opsi cadangan seperti lainnya agar input aneh tidak dipaksa masuk kategori yang salah.
  • score: menilai input pada 2–10 level berurutan. Nilai score adalah rata-rata tertimbang indeks level (mulai 0), jadi bisa berupa pecahan seperti 1,6.

Ada satu tipe jawaban tambahan: refusal, ketika model menolak menjawab satu pertanyaan. Kode Anda wajib menanganinya terpisah.

Diagram tiga tipe pertanyaan Decisions API: predicate menghasilkan probability 0 sampai 1, choice menghasilkan satu pilihan dengan confidence, score menghasilkan skor pecahan dari 2 sampai 10 level; di bawahnya kronologi public beta 6 Oktober 2026 dengan maksimal 200 soal dan GA 22 Oktober 2026 dengan maksimal 400 soal, harga 0,10 dolar per 1 juta token input

Langkah 1: Uji coba pertama dengan curl

Siapkan API key OpenAI di variabel lingkungan, lalu kirim satu pertanyaan predicate. Struktur body mengikuti dokumentasi resmi: model, input, dan questions.

export OPENAI_API_KEY="sk-..."

curl -s https://api.openai.com/v1/decisions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-6-luna",
    "input": "Kak, paket saya dari minggu lalu belum sampai. Bisa sambungkan ke admin?",
    "questions": [
      {
        "type": "predicate",
        "name": "butuh_manusia",
        "instructions": "Apakah pelanggan secara eksplisit meminta dilayani oleh manusia/admin?"
      }
    ]
  }'

Responsnya berisi array answers (satu per pertanyaan) dan objek usage. Bentuknya kira-kira seperti ini; angka di bawah hanya ilustrasi:

{
  "answers": [
    { "type": "predicate", "name": "butuh_manusia", "probability": 0.93 }
  ],
  "usage": {
    "input_tokens": 61,
    "input_tokens_details": { "cached_tokens": 0 }
  }
}

Langkah 2: Rancang pertanyaan untuk router CS

Satu request bisa memuat sampai 400 pertanyaan, dan semuanya berbagi input yang sama. Untuk router kita cukup tiga:

  1. intent (choice): cek_ongkir, jadwal_kirim, lacak_paket, komplain, lainnya.
  2. butuh_manusia (predicate): apakah perlu dialihkan ke agen.
  3. emosi (score, 4 level): tenang, sedikit kesal, kesal, sangat marah.

Data pesanan pelanggan bisa ikut dikirim sebagai part input_json di pesan user. Fitur ini baru hadir saat GA dan hanya berlaku di pesan berperan user; isi field data diperlakukan sebagai data biasa, bukan instruksi.

Diagram alur router chat CS: pesan pelanggan plus data pesanan masuk ke POST /v1/decisions dengan model gpt-6-luna dan tiga pertanyaan choice intent, predicate butuh_manusia, score emosi; kode router menerapkan ambang batas lalu meneruskan ke APIPedia cek ongkir, APIPedia cek libur, tiket komplain, atau antrian agen manusia

Langkah 3: Fungsi klasifikasi di Node.js

Kode di bawah memakai fetch bawaan Node.js 18+, tanpa dependensi. Simpan sebagai decide.mjs.

// decide.mjs
const OPENAI_URL = "https://api.openai.com/v1/decisions";

const QUESTIONS = [
  {
    type: "choice",
    name: "intent",
    instructions: "Apa maksud utama pesan pelanggan toko online ini?",
    choices: [
      { value: "cek_ongkir", description: "Menanyakan ongkos kirim ke suatu kota" },
      { value: "jadwal_kirim", description: "Menanyakan kapan paket dikirim atau apakah toko libur" },
      { value: "lacak_paket", description: "Menanyakan posisi paket yang sudah dikirim" },
      { value: "komplain", description: "Mengeluhkan produk rusak, salah kirim, atau pelayanan" },
      { value: "lainnya", description: "Tidak cocok dengan kategori mana pun" }
    ]
  },
  {
    type: "predicate",
    name: "butuh_manusia",
    instructions: "Apakah percakapan ini sebaiknya dialihkan ke agen manusia (diminta eksplisit, ada ancaman, atau masalah pembayaran)?"
  },
  {
    type: "score",
    name: "emosi",
    instructions: "Seberapa kesal pelanggan berdasarkan nada pesannya?",
    levels: [
      { label: "tenang", description: "Bertanya biasa, tanpa keluhan" },
      { label: "sedikit_kesal", description: "Ada nada tidak sabar" },
      { label: "kesal", description: "Mengeluh jelas atau mengulang pertanyaan" },
      { label: "sangat_marah", description: "Marah, mengancam, atau memakai huruf kapital berlebihan" }
    ]
  }
];

export async function decide(pesan, dataPesanan = null) {
  const content = [{ type: "input_text", text: pesan }];
  if (dataPesanan) content.push({ type: "input_json", data: dataPesanan });

  const res = await fetch(OPENAI_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "gpt-6-luna",
      input: [{ role: "user", content }],
      questions: QUESTIONS
    })
  });
  if (!res.ok) throw new Error(`Decisions API ${res.status}: ${await res.text()}`);

  const { answers } = await res.json();
  // Ubah array answers menjadi objek { intent: {...}, butuh_manusia: {...}, ... }
  return Object.fromEntries(answers.map((a) => [a.name, a]));
}

Kalau Anda lebih suka SDK resmi, dokumentasi OpenAI menyebut pola yang sama lewat client.decisions.create({ model, input, questions }) di SDK JavaScript maupun Python, dengan nama field yang identik.

Langkah 4: Router dengan ambang batas dan API Cek Ongkir APIPedia

Inilah bagian yang sering dilupakan: probabilitas tidak berguna tanpa ambang batas. Aturan kita:

  • Jika ada jawaban refusal atau confidence intent di bawah 0,6, minta pelanggan memperjelas.
  • Jika butuh_manusia ≥ 0,7 atau emosi ≥ 2,0 (level "kesal" ke atas), langsung eskalasi.
  • Selain itu, jalankan aksi sesuai intent.

Untuk intent cek_ongkir, kota tujuan diambil dari kolom formulir chat (Decisions API tidak mengekstrak teks). Lalu kita panggil dua endpoint APIPedia: /v1/ongkir/search untuk mencari id wilayah dan /v1/ongkir/cek untuk estimasi tarif. Untuk jadwal_kirim, kita cek apakah besok hari libur nasional lewat /v1/libur/cek/{tanggal}.

// router.mjs
import { decide } from "./decide.mjs";

const APIPEDIA = "https://api.apipedia.id/v1";
const headers = { Authorization: `Bearer ${process.env.APIPEDIA_KEY}` };
const ORIGIN_ID = "3273"; // KOTA BANDUNG, id dari /v1/ongkir/search?q=bandung

async function apipedia(path) {
  const res = await fetch(`${APIPEDIA}${path}`, { headers });
  const body = await res.json();
  if (!body.is_success) throw new Error(body.message);
  return body.data;
}

async function cekOngkir(kotaTujuan, beratGram) {
  const cari = await apipedia(`/ongkir/search?q=${encodeURIComponent(kotaTujuan)}`);
  const tujuan = cari.hasil.find((w) => w.tipe === "kabupaten") ?? cari.hasil[0];
  if (!tujuan) return `Kota "${kotaTujuan}" belum ketemu, boleh tulis nama kabupaten/kotanya?`;

  const data = await apipedia(
    `/ongkir/cek?origin=${ORIGIN_ID}&destination=${tujuan.id}&weight=${beratGram}`
  );
  const termurah = [...data.tarif].sort((a, b) => a.total - b.total).slice(0, 3);
  return `Estimasi ongkir ke ${tujuan.nama} (${data.berat_tagih_kg} kg): ` +
    termurah.map((t) => `${t.nama} Rp${t.total.toLocaleString("id-ID")}`).join(", ");
}

async function cekJadwal() {
  const besok = new Date(Date.now() + 86400000)
    .toLocaleDateString("en-CA", { timeZone: "Asia/Jakarta" }); // YYYY-MM-DD
  const d = await apipedia(`/libur/cek/${besok}`);
  return d.is_libur
    ? `Besok ${d.keterangan}, pengiriman dilanjutkan di hari kerja berikutnya.`
    : "Pesanan sebelum jam 15.00 dikirim besok.";
}

export async function route({ pesan, kotaTujuan, beratGram = 1000, pesanan }) {
  const r = await decide(pesan, pesanan);

  if (Object.values(r).some((a) => a.type === "refusal")) return { aksi: "eskalasi" };
  if (r.butuh_manusia.probability >= 0.7 || r.emosi.score >= 2.0) {
    return { aksi: "eskalasi", prioritas: r.emosi.score >= 2.0 ? "tinggi" : "normal" };
  }
  if (r.intent.confidence < 0.6) return { aksi: "klarifikasi" };

  switch (r.intent.choice) {
    case "cek_ongkir":
      return { aksi: "balas", teks: await cekOngkir(kotaTujuan, beratGram) };
    case "jadwal_kirim":
      return { aksi: "balas", teks: await cekJadwal() };
    case "lacak_paket":
    case "komplain":
      return { aksi: "tiket", kategori: r.intent.choice };
    default:
      return { aksi: "klarifikasi" };
  }
}

// Uji cepat: node router.mjs
if (import.meta.url === `file://${process.argv[1]}`) {
  console.log(await route({
    pesan: "Halo kak, kirim 1,5 kg ke Surabaya kira-kira berapa ya?",
    kotaTujuan: "surabaya",
    beratGram: 1500
  }));
}

Jalankan dengan:

export OPENAI_API_KEY="sk-..."
export APIPEDIA_KEY="apk_..."   # buat di https://console.apipedia.id
node router.mjs

Saat kami menguji endpoint APIPedia untuk rute Bandung (3273) ke Kota Surabaya (3578) seberat 1.500 gram, berat tagihnya dibulatkan menjadi 2 kg dan tarif termurah yang keluar adalah POS Kilat Khusus Rp29.854. Ingat, ini estimasi dari data statis, bukan tarif resmi kurir. Panduan lengkap endpoint ongkir ada di artikel API Cek Ongkir Indonesia, dan detail kalender libur di API Hari Libur Nasional Indonesia.

Langkah 5: Hemat biaya dengan prompt caching

Kalau setiap request membawa konteks panjang yang sama (misalnya kebijakan toko atau katalog singkat), letakkan konteks itu di awal input. Dokumentasi OpenAI menjelaskan ada breakpoint cache implisit di akhir input, dengan syarat prefix yang bisa dipakai ulang minimal 1.024 token; isi questions tidak dihitung. Anda bisa memisahkan cache per tenant dengan prompt_cache_key.

Soal harga cache, sumber sedikit berbeda. Dokumentasi resmi hanya menyebut cache hit "didiskon" dan penulisan cache ditagih sesuai tarif caching gpt-6-luna. Kju dan OODA Loop menulis angka $0,01 per 1 juta token untuk input yang ter-cache. Cek halaman harga resmi sebelum menghitung anggaran.

Langkah 6: Pantau pemakaian

Sejak GA ada endpoint usage khusus. Endpoint ini butuh admin API key dengan izin api.usage.read, bukan key proyek biasa. Perhatikan bahwa num_model_requests menghitung request, bukan jumlah pertanyaan.

curl -s "https://api.openai.com/v1/organization/usage/decisions?start_time=$(date -d '7 days ago' +%s)" \
  -H "Authorization: Bearer $OPENAI_ADMIN_KEY"

Hitungan kasar: satu pesan CS sekitar 150 token input. Dengan harga $0,10 per 1 juta token, 100.000 pesan (15 juta token) kira-kira $1,50 sebelum cache. Angka ini belum termasuk premi pemrosesan regional atau pengali konteks panjang yang disebut dokumentasi.

Batasan yang perlu Anda tahu

  • Satu model saja: hanya gpt-6-luna.
  • Input yang tidak didukung: file, audio, dan file_id. Gambar boleh lewat URL publik HTTP/HTTPS atau data URL base64.
  • Region: pemrosesan tersedia di Amerika Serikat dan Eropa (EEA dan Swiss). Belum ada opsi region Asia, jadi pertimbangkan aturan data pribadi (UU PDP) sebelum mengirim data pelanggan. Kirim seperlunya saja, misalnya tanpa NIK atau nomor HP.
  • Tanpa teks balasan: kalau butuh jawaban natural, pakai model chat setelah routing.
  • Klaim kecepatan: "10x lebih cepat" adalah klaim OpenAI untuk tugas keputusan saja. OneMetrik mengingatkan bahwa waktu total alur kerja tetap bisa didominasi panggilan database, jaringan, atau persetujuan manual.

FAQ

Apakah Decisions API bisa dipakai dari Indonesia?

Bisa, selama akun OpenAI Anda aktif dan punya akses API. Yang perlu diperhatikan adalah lokasi pemrosesan data (AS atau Eropa) dan latensi jaringan dari server Anda.

Berapa banyak pertanyaan yang sebaiknya dimasukkan ke satu request?

Batasnya 1–400. Gabungkan pertanyaan yang memakai bukti (input) yang sama dalam satu request agar input hanya dibayar sekali. Pertanyaan untuk pesan yang berbeda tetap harus request terpisah.

Kenapa skor emosi saya 1,6, bukan bilangan bulat?

Karena score adalah rata-rata tertimbang indeks level. Nilai 1,6 berarti model condong ke "kesal" (indeks 2) tetapi masih ada peluang "sedikit kesal". Lihat array probabilities kalau Anda butuh distribusi lengkap.

Apa yang terjadi kalau pesan tidak cocok dengan pilihan mana pun?

Model tetap harus memilih salah satu nilai di choices. Itu sebabnya opsi lainnya dan pengecekan confidence penting supaya pesan aneh tidak salah rute.

Apakah API APIPedia bisa dipanggil dari agen AI lain?

Bisa. Selain REST seperti contoh di atas, APIPedia juga tersedia sebagai server MCP. Lihat panduan menghubungkan APIPedia ke Claude lewat MCP.

Sumber

Topik:

Tentang penulis

Tim APIPedia — Tim Engineering APIPedia

Tim engineering di balik APIPedia yang membangun dan memelihara API data Indonesia - ongkir, OCR KTP, data wilayah, BMKG, hingga server MCP - dan menulis panduan integrasinya dari pengalaman langsung.