AI & Otomatisasi

Structured Output untuk Otomatisasi AI - Schema Adalah Kontrak, Bukan Pemeriksaan Keamanan

Structured Output untuk Otomatisasi AI - Schema Adalah Kontrak, Bukan Pemeriksaan Keamanan

Sebuah language model dapat mengembalikan JSON yang rapi alih-alih sebuah paragraf. Ini terasa seperti langkah penting menuju otomatisasi yang andal: semua key tersedia, type tampak benar, dan parser tidak perlu lagi menyelamatkan code block yang terpotong. Namun, apa sebenarnya yang menjadi lebih andal?

Respons terstruktur dapat memudahkan software berkomunikasi dengan model yang tidak selalu pasti. Respons seperti ini tidak membuat setiap nilai menjadi benar, setiap tindakan yang diminta menjadi tepat, atau setiap side effect menjadi aman. Karena itu, pertanyaan yang berguna bukanlah, “Apakah model mengembalikan JSON yang valid?” Melainkan, “Gate apa saja yang harus dilewati permintaan ini sebelum aplikasi boleh bertindak?”

Perbedaan ini paling penting ketika output berhenti menjadi teks dan mulai menjadi otoritas: menerbitkan artikel, mengirim pesan, mengubah file, atau memanggil administrative API. Desain yang konservatif membiarkan model mengusulkan tindakan dengan struktur yang sempit, sementara application code yang deterministik memutuskan apakah usulan tersebut boleh dilanjutkan.

Lima Pertanyaan Berbeda di Balik Kata “Valid”

Kata valid sering memuat terlalu banyak arti. Sebuah pipeline otomatisasi kecil perlu memisahkan setidaknya lima pertanyaan:

  1. Bisakah data di-parse? Apakah respons tersebut merupakan JSON yang valid?
  2. Apakah sesuai kontrak? Apakah field wajib tersedia, nilainya memiliki type yang diharapkan, dan pilihannya berada dalam batas yang dideklarasikan?
  3. Apakah masuk akal saat ini? Apakah draft yang dirujuk ada, locale didukung, dan jadwal yang diminta masih tersedia?
  4. Apakah actor ini boleh melakukannya? Apakah user atau service memiliki otorisasi atas resource dan operasi tersebut, serta apakah tindakannya memerlukan persetujuan?
  5. Bisakah dieksekusi dengan aman? Apakah retry akan menggandakan efek, apakah penggunaan resource dibatasi, dan apakah kegagalan akan meninggalkan state yang dapat dipahami?

RFC 8259 mendefinisikan JSON sebagai format pertukaran data dengan grammar yang kecil. Lolos dari grammar itu hanya menjawab pertanyaan pertama. Hal tersebut tidak menjelaskan apakah sebuah slug menunjuk ke draft yang nyata atau apakah caller boleh menerbitkannya.

Validasi JSON Schema dapat menjawab sebagian pertanyaan kedua melalui assertion seperti type, required, enum, dan batas panjang. Ini berguna. Kontrak yang menolak bentuk tidak terduga jauh lebih baik daripada mencari token seperti [PUBLISH] di dalam prosa. Namun, ini tetap satu lapisan, bukan seluruh keputusan.

Schema Mempersempit Percakapan

Bayangkan sebuah assistant yang boleh mengusulkan tindakan terhadap draft artikel yang sudah ada. Kontrak yang sengaja dibuat kecil dapat terlihat seperti ini:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": ["request_review", "schedule"]
    },
    "draft_id": {
      "type": "integer",
      "minimum": 1
    },
    "locale": {
      "type": "string",
      "enum": ["en", "id", "de"]
    },
    "scheduled_at": {
      "type": ["string", "null"]
    },
    "reason": {
      "type": "string",
      "minLength": 1,
      "maxLength": 500
    }
  },
  "required": ["action", "draft_id", "locale", "scheduled_at", "reason"],
  "additionalProperties": false
}

Enum mencegah model menciptakan tindakan seperti delete_everything. Batas integer menolak identifier negatif. Allowlist locale menjaga interface tetap berada dalam bahasa yang memang didukung aplikasi. additionalProperties: false membuat field yang tidak sengaja atau dikarang terlihat, bukan menerimanya secara diam-diam.

API model saat ini menunjukkan dua pola yang berhubungan. OpenAI mendokumentasikan respons model yang dibatasi schema, sedangkan Anthropic mendokumentasikan definisi tool dengan input_schema. API dan fitur schema yang didukung dapat berubah, sehingga aplikasi harus mengikuti dokumentasi provider dan model yang benar-benar digunakannya. “Strict” tidak seharusnya dianggap sebagai satu perilaku portabel yang sama pada setiap implementasi.

Gagasan yang lebih tahan lama bersifat provider-neutral: definisikan usulan machine-readable terkecil yang dibutuhkan aplikasi. Jangan menyediakan general shell command ketika tugas hanya perlu memilih antara review dan penjadwalan. Vocabulary yang sempit mengurangi ambiguitas bagi model maupun executor.

Bentuk Bukanlah Kebenaran

Sekarang perhatikan usulan yang sesuai schema berikut:

{
  "action": "schedule",
  "draft_id": 417,
  "locale": "de",
  "scheduled_at": "2026-09-10T00:00:00+07:00",
  "reason": "The translation is complete."
}

Bentuknya mungkin sempurna sementara setiap kesimpulan operasionalnya salah. Draft 417 mungkin tidak ada. Terjemahan German mungkin masih kehilangan satu paragraf. Timestamp tersebut mungkin sudah berlalu ketika diproses. Artikel lain mungkin sudah mengisi slot harian. Caller mungkin hanya memiliki permission untuk review. Kalimat dalam reason tetap merupakan teks hasil generasi, bukan bukti.

Batas ini berasal dari cara kerja schema. Spesifikasi inti JSON Schema menyatakan bahwa sebuah instance hanya dapat gagal pada assertion yang tersedia di schema. Schema tidak dapat menolak aturan yang tidak dinyatakan. Bahkan format string yang dinyatakan pun memiliki nuansa: spesifikasi validasi menjelaskan bahwa format dapat hanya menjadi annotation, bergantung pada vocabulary dan implementasi, serta pemeriksaan sintaksis tidak membuktikan bahwa entity yang dirujuk benar-benar ada.

Karena itu, application code memerlukan tahap validasi semantik setelah validasi schema. Dalam contoh ini, tahap tersebut dapat memuat draft berdasarkan ID, membandingkan locale dan status sebenarnya, memeriksa kalender publikasi, serta memastikan terjemahan dan media yang diwajibkan tersedia. Semua ini merupakan query biasa terhadap state yang otoritatif. Meminta model untuk “memeriksa ulang” dengan menghasilkan jawaban lain bukanlah hal yang setara.

Perlakukan Usulan sebagai Input yang Tidak Tepercaya

Respons model dapat dipengaruhi oleh user prompt, dokumen yang diambil, hasil tool, atau output model sebelumnya. Aplikasi tidak boleh menganggap asal yang terstruktur sebagai asal yang tepercaya. Panduan OWASP tentang improper output handling menyarankan agar output model diperlakukan seperti input yang tidak tepercaya dan divalidasi sebelum mencapai fungsi downstream.

Prinsip tersebut mengarah pada aturan engineering yang sudah dikenal:

  • Temukan identifier melalui application code; jangan mengubah string hasil generasi menjadi fragmen SQL.
  • Petakan nama tindakan yang diizinkan ke fungsi tertentu; jangan meneruskan teks model ke shell atau eval().
  • Encode teks hasil generasi sesuai destination context; teks berbentuk HTML yang valid menurut schema tidak otomatis menjadi HTML yang aman.
  • Tolak field dan nilai yang tidak dikenal alih-alih menebak maksud model.
  • Kembalikan validation error sebagai data, bukan sebagai izin untuk mencoba fallback yang lebih berkuasa.

Ini bukan argumen untuk menolak structured output. Justru inilah alasan structured output berguna: object yang sempit lebih mudah divalidasi dan diarahkan dengan aman dibandingkan prosa bebas. Kekeliruannya adalah membiarkan data contract sekaligus berperan sebagai security boundary yang memang tidak dirancang untuk itu.

Otorisasi Harus Berada di Luar Model

Sebuah prompt dapat memberi tahu model tindakan apa yang diizinkan, tetapi prompt bukan access-control system. Executor harus mengautentikasi caller yang sebenarnya dan menegakkan policy menggunakan server-side state. Jika seorang user hanya boleh mengedit draft miliknya, database query atau service layer harus mempertahankan batas ownership tersebut terlepas dari ID yang diusulkan model.

Panduan OWASP tentang excessive agency memisahkan tiga pengurangan yang berguna: minimalkan functionality yang tersedia, minimalkan permission downstream, dan minimalkan autonomy. Lookup read-only sebaiknya menggunakan identity read-only. Tool yang menjadwalkan satu draft yang telah disetujui tidak seharusnya juga menyediakan operasi update atau delete yang arbitrer. Tindakan berisiko tinggi dapat berhenti pada state review sampai seseorang menyetujui target dan efek yang tepat.

Persetujuan manusia tidak otomatis berguna. Tombol samar yang meminta seseorang “menyetujui output AI” mendorong klik refleks. Checkpoint yang bermakna menampilkan tindakan yang diusulkan, target, perubahan penting, sumber otoritas, dan konsekuensi. Persetujuan juga sebaiknya kedaluwarsa jika draft dasarnya berubah; jika tidak, seseorang mungkin menyetujui satu versi sementara sistem mengeksekusi versi lain.

Pisahkan Usulan, Keputusan, dan Efek

Workflow yang bersih tidak bergerak langsung dari respons model menuju side effect. Workflow tersebut mencatat state yang berbeda:

model response
  -> parsed proposal
  -> schema accepted
  -> policy decision
  -> awaiting approval (when required)
  -> execution attempt
  -> succeeded or failed

Pemisahan ini membuat kegagalan tidak terlalu misterius. Penolakan schema berarti kontrak tidak terpenuhi. Penolakan policy berarti permintaan memiliki bentuk yang benar tetapi tidak diizinkan. Kegagalan eksekusi berarti operasi yang disetujui mengalami masalah operasional. Menggabungkan ketiganya menjadi “AI gagal” menyembunyikan bagian sistem yang perlu diperbaiki.

Decision record sebaiknya merujuk pada identifier yang stabil dan versi object yang diperiksa. Untuk write yang menimbulkan konsekuensi, idempotency key atau aturan uniqueness yang setara dapat mencegah retry mengulangi efek logis yang sama. Ini penting karena timeout bersifat ambigu: client dapat kehilangan respons setelah server sebenarnya telah menyelesaikan tindakan.

Retry juga perlu dibatasi. Mengulang usulan yang malformed atau dilarang membuang resource dan dapat menyamarkan serangan. Mengulang kegagalan jaringan sementara mungkin masuk akal, tetapi hanya ketika operasi tersebut repeat-safe dan sistem dapat membedakan “belum dicoba” dari “selesai tetapi respons hilang.”

Log Perlu Menjelaskan Keputusan Tanpa Menjadi Kebocoran Baru

Audit trail sebaiknya dapat menjawab pertanyaan praktis: actor terautentikasi mana yang memulai permintaan, konfigurasi model mana yang menghasilkan usulan, versi schema mana yang diterapkan, aturan mana yang lolos atau gagal, siapa yang menyetujuinya, tool mana yang berjalan, dan apa final state-nya?

Itu tidak mengharuskan penyimpanan setiap prompt dan respons selamanya. Raw context dapat memuat personal data, credential, dokumen privat, atau instruksi berbahaya. Utamakan structured event field, redact secret, batasi retention, dan batasi akses. Simpan informasi yang cukup untuk merekonstruksi keputusan, bukan salinan tanpa batas dari semua hal yang dilihat model.

Versioning juga penting. Jika schema atau policy berubah, reviewer di kemudian hari perlu mengetahui aturan mana yang mengevaluasi usulan lama. Pelajaran yang lebih luas ini sejalan dengan kerangka lifecycle dalam NIST AI 600-1: risk management berada di seluruh tahap design, development, use, dan evaluation. Semua itu tidak dapat dipadatkan menjadi satu opsi API pada waktu generasi.

Checklist Review Singkat

  • Apakah respons di-parse dengan JSON parser, bukan executable evaluation?
  • Apakah aplikasi memvalidasi menggunakan schema dan dialect tepat yang ingin didukungnya?
  • Apakah tindakan, identifier, ukuran, dan collection dibatasi dengan sempit?
  • Apakah code deterministik memeriksa business state saat ini setelah validasi schema?
  • Apakah downstream service menegakkan autentikasi, ownership, dan least privilege?
  • Apakah tindakan yang menimbulkan konsekuensi memerlukan persetujuan yang spesifik dan dapat diperiksa?
  • Apakah timeout atau retry dapat mengulangi side effect?
  • Apakah failure state terpisah dan log tetap berguna tanpa menyimpan secret secara berlebihan?
  • Apakah perubahan schema, policy, dan tool diuji dengan usulan invalid maupun valid?

Sebuah Kontrak, Bukan Putusan

Structured output menyelesaikan masalah integrasi yang nyata. Ia mengganti parsing prosa yang rapuh dengan kontrak eksplisit dan dapat menjaga respons model tetap berada dalam kumpulan bentuk yang jauh lebih kecil. Itu merupakan peningkatan yang berarti.

Batasnya juga sama penting: schema dapat menetapkan bahwa sebuah usulan tampak seperti schedule draft 417. Schema tidak dapat dengan sendirinya menetapkan bahwa draft 417 ada, lengkap, dimiliki caller, belum berubah, sesuai dengan editorial policy hari ini, atau patut diterbitkan sekarang.

Otomatisasi yang andal dibangun dengan menyusun jaminan-jaminan sederhana. Parse datanya. Validasi bentuk yang dideklarasikan. Periksa realitas dan policy dengan code deterministik. Tegakkan otoritas di downstream. Minta persetujuan ketika konsekuensinya layak diberi friction. Kemudian eksekusi secara terbatas dan repeat-safe. Model boleh mengusulkan langkah berikutnya, tetapi aplikasi harus tetap bertanggung jawab untuk memutuskan apakah langkah tersebut diizinkan.

References