Belajar AI #34: Structured Output & Guardrails — Memaksa LLM Selalu Berformat Benar

Hari ini saya belajar topik yang kelihatan sepele tapi ternyata jadi akar masalah banyak kegagalan agent di lapangan: bagaimana caranya supaya output LLM itu selalu berformat benar. Di episode #30 kita sudah bahas evals — dan dari pengalaman saya, mayoritas eval yang gagal bukan karena modelnya bodoh, tapi karena format jawabannya berantakan. JSON-nya tidak nutup, field-nya beda dari yang diminta, kadang model malah menambahkan basa-basi sebelum JSON.

Kenapa model LLM “berbohong” soal format

Pelajaran pertama yang saya catat: LLM tidak memahami JSON sebagai struktur data. Baginya, JSON adalah deretan token yang secara statistik sering muncul bersama. Karena itu, kalau kita cuma menulis “tolong balas dalam format JSON” di prompt, tidak ada jaminan apa pun. Model bisa menghasilkan JSON valid tapi salah bentuk — field name jadi Nama, atau list berubah jadi satu string. Ini yang saya sebut “hallucination format”: bukan halusinasi fakta, tapi halusinasi struktur.

Tiga level pertahanan, dari paling murah ke paling kuat

Saya merangkum jadi tiga level, dan saya sendiri pakai level 2–3 untuk pipeline produksi.

Level 1: JSON mode / instruksi prompt

OpenAI memperkenalkan JSON mode pada November 2023 — menjamin output berupa JSON valid, tapi bukan JSON yang cocok dengan schema tertentu. Anthropic tidak punya flag JSON mode; jalurnya lewat tool_use sejak Claude 3 (Maret 2024): kita definisikan “tool” dengan skema input, lalu memaksa model memanggil tool itu. Termurah, tapi paling sering bocor di produksi.

Level 2: Structured Outputs (API terkelola)

OpenAI merilis Structured Outputs pada Agustus 2024 (tersedia di gpt-4o-2024-08-06 dan gpt-4o-mini-2024-07-18). Kita kirim JSON Schema strict, dan API mengompilasi schema itu ke dalam proses decoding sehingga output dijamin mengikuti schema. Google Gemini punya fitur serupa lewat responseSchema. Ini solusi paling praktis kalau kita pakai API berbayar — tinggal set schema, tidak perlu validasi manual.

Level 3: Constraint decoding di model sendiri

Ini yang paling menarik buat saya, karena relevan kalau kita menjalankan model open-source (topik yang sudah kita sentuh di episode #24 dan #32). Ide teknisnya: di setiap langkah generasi, library menghitung token mana yang mungkin menjaga output tetap valid sesuai grammar/schema, lalu token di luar itu di-mask dari distribusi probabilitas. Jadi model secara harfiah tidak bisa menghasilkan format yang salah — bukan diperbaiki setelah jadi, tapi dicegah saat lahir. Implementasi rujukannya adalah Outlines, yang mengkompilasi regex/JSON Schema jadi automaton yang bekerja dengan tokenizer apa pun. Ada juga XGrammar dan LM Format Enforcer yang sekelas.

Pola praktis yang saya pakai: schema-first + Pydantic

Workflow favorit saya (dan ini terasa seperti “unit test untuk output LLM” — sama filosofinya dengan evals di episode #30): definisikan Pydantic model dulu, baru prompt-nya. Skema jadi kontrak, dan invalidasi langsung melempar error yang bisa ditangkap, bukan string berantakan yang merambat ke database.

from pydantic import BaseModel, Field

class SentimentResult(BaseModel):
    label: str = Field(pattern="^(positif|negatif|netral)$")
    confidence: float = Field(ge=0, le=1)
    evidence: str

# dengan library Instructor, schema ini dipakai sebagai
# response_format -> output dijamin instance SentimentResult
# result = instructor.chat(OpenAI(), response_model=SentimentResult)
# hasilnya BUKAN string JSON yang harus di-parse manual,
# tapi objek tervalidasi lengkap dengan tipe datanya

Yang saya suka dari pola ini: validasi jadi bagian dari kontrak. Kalau model mengembalikan confidence 1.4 atau label “sangat positif” (di luar pattern), kita tahu sekarang — bukan tiga langkah kemudian saat data korup sudah masuk.

Guardrails: format itu baru permulaan

Structured output menyelesaikan masalah format. Tapi guardrails menangani masalah yang lebih luas: apakah isi jawabannya boleh keluar? Di sinilah guardrails klasik (proses sebelum & sesudah model) bertemu dengan constraint decoding: validator di sisi aplikasi untuk semantik (nilai wajar, tidak ada data rahasia), constraint decoding untuk sintaks. Untuk kasus agent yang memanggil tools — pembahasan lanjutan dari function calling di episode #27 — kombinasi ini wajib, karena output yang salah format di tengah loop agent bisa merusak state percakapan.

Yang saya pelajari hari ini

Pertama: jangan pernah mengandalkan “tolong balas dalam JSON” sebagai mekanisme — itu harapan, bukan jaminan. Kedua: makin dekat constraint-nya ke proses decoding (constraint decoding), makin kecil celah kegagalan; makin murah biayanya kalau pakai API terkelola. Ketiga: schema-first mengubah format output dari “berharap model nurut” jadi “kontrak yang divalidasi” — dan ini yang bikin pipeline AI di produksi terasa seperti software engineering biasa, bukan sihir.

Sumber

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Situs ini menggunakan Akismet untuk mengurangi spam. Pelajari bagaimana data komentar Anda diproses