Outlines: Structured Generation dari LLM dengan Constrained Decoding

# Outlines: Structured Generation dari LLM dengan Constrained Decoding Salah satu tantangan terbesar saat bekerja dengan Large Language Models (LLM) adalah mendapatkan output yang terstruktur dan val...

By Ruby Abdullah · · tutorial
OutlinesLLMStructured GenerationConstrained DecodingPython

Outlines: Structured Generation dari LLM dengan Constrained Decoding

Salah satu tantangan terbesar saat bekerja dengan Large Language Models (LLM) adalah mendapatkan output yang terstruktur dan valid secara konsisten. Ketika Anda membutuhkan JSON yang valid, enum dari pilihan tertentu, atau format spesifik lainnya, LLM seringkali menghasilkan output yang tidak sesuai format. Outlines hadir untuk menyelesaikan masalah ini dengan pendekatan constrained decoding di level token, menjamin output selalu valid secara struktural.

Bagaimana Constrained Decoding Bekerja?

Sebelum membahas Outlines, penting untuk memahami perbedaan dua pendekatan structured generation:

Pendekatan Retry-Based (seperti Instructor)

Library seperti Instructor menggunakan pendekatan "generate-then-validate":

  • LLM menghasilkan output bebas
  • Output di-parse dan divalidasi
  • Jika gagal, prompt diperbaiki dan LLM dipanggil ulang
  • Ulangi sampai output valid atau batas retry tercapai
  • Masalahnya: biaya API meningkat, latensi tidak dapat diprediksi, dan tidak ada jaminan konvergensi.

    Pendekatan Constrained Decoding (Outlines)

    Outlines menggunakan pendekatan yang fundamentally berbeda:

  • Sebelum generasi, Outlines membangun finite-state machine (FSM) atau regular expression automaton dari schema yang diinginkan
  • Pada setiap langkah token generation, Outlines menghitung token mana saja yang valid berdasarkan state saat ini
  • Probabilitas token yang tidak valid diset ke nol (masked)
  • LLM hanya bisa memilih dari token yang valid
  • Hasilnya: output dijamin valid 100% tanpa perlu retry, dengan overhead komputasi yang minimal.

    Instalasi

    Instalasi Outlines cukup straightforward:

    pip install outlines
    

    Untuk menggunakan dengan model transformers lokal:

    pip install outlines[transformers]
    

    Untuk integrasi dengan llama.cpp:

    pip install outlines[llamacpp]
    

    Untuk integrasi dengan vLLM:

    pip install outlines[vllm]
    

    JSON Generation dengan Pydantic Models

    Fitur paling populer dari Outlines adalah kemampuan menghasilkan JSON yang valid berdasarkan Pydantic model.

    Contoh Dasar

    import outlines
    

    from pydantic import BaseModel, Field

    from typing import List, Optional

    from enum import Enum

    Definisikan schema dengan Pydantic

    class Alamat(BaseModel):

    jalan: str = Field(description="Nama jalan dan nomor")

    kota: str = Field(description="Nama kota")

    provinsi: str = Field(description="Nama provinsi")

    kodepos: str = Field(description="Kode pos 5 digit")

    class Karyawan(BaseModel):

    nama: str = Field(description="Nama lengkap karyawan")

    usia: int = Field(ge=18, le=65, description="Usia karyawan")

    email: str = Field(description="Alamat email")

    departemen: str = Field(description="Departemen kerja")

    gaji: float = Field(gt=0, description="Gaji bulanan")

    alamat: Alamat

    keahlian: List[str] = Field(description="Daftar keahlian")

    Load model

    model = outlines.models.transformers("microsoft/Phi-3-mini-4k-instruct")

    Buat generator dengan schema JSON

    generator = outlines.generate.json(model, Karyawan)

    Generate data terstruktur

    prompt = """Buatkan data karyawan fiktif untuk perusahaan teknologi di Jakarta.

    Karyawan ini adalah seorang senior data engineer."""

    result = generator(prompt)

    print(type(result)) #

    print(result.nama)

    print(result.departemen)

    print(result.modeldumpjson(indent=2))

    Nested dan Complex Schemas

    from pydantic import BaseModel, Field
    

    from typing import List, Optional

    from enum import Enum

    import outlines

    class Prioritas(str, Enum):

    TINGGI = "tinggi"

    SEDANG = "sedang"

    RENDAH = "rendah"

    class Status(str, Enum):

    TODO = "todo"

    INPROGRESS = "inprogress"

    REVIEW = "review"

    DONE = "done"

    class SubTugas(BaseModel):

    judul: str

    selesai: bool

    class Tugas(BaseModel):

    id: int

    judul: str = Field(maxlength=100)

    deskripsi: str

    prioritas: Prioritas

    status: Status

    assignee: str

    estimasijam: float = Field(gt=0)

    subtugas: List[SubTugas]

    tags: List[str]

    class SprintPlan(BaseModel):

    namasprint: str

    durasihari: int = Field(ge=7, le=30)

    tujuan: str

    tugaslist: List[Tugas]

    model = outlines.models.transformers("microsoft/Phi-3-mini-4k-instruct")

    generator = outlines.generate.json(model, SprintPlan)

    prompt = """Buatkan sprint plan untuk tim development yang sedang mengerjakan

    fitur rekomendasi produk menggunakan machine learning. Sprint ini berdurasi

    2 minggu dengan 3 tugas utama."""

    sprint = generator(prompt)

    print(sprint.modeldumpjson(indent=2))

    Regex-Guided Generation

    Outlines memungkinkan Anda mengontrol format output menggunakan regular expression:

    import outlines
    
    

    model = outlines.models.transformers("microsoft/Phi-3-mini-4k-instruct")

    Generate nomor telepon Indonesia

    phonepattern = r"\+62[0-9]{9,12}"

    generator = outlines.generate.regex(model, phonepattern)

    result = generator("Berikan nomor telepon Indonesia: ")

    print(result) # contoh: +628123456789

    Generate tanggal format ISO

    datepattern = r"[0-9]{4}-[0-1][0-9]-[0-3][0-9]"

    generator = outlines.generate.regex(model, datepattern)

    result = generator("Tanggal hari ini dalam format ISO: ")

    print(result) # contoh: 2026-04-30

    Generate alamat email

    emailpattern = r"[a-z0-9.%+-]+@[a-z0-9.-]+\.[a-z]{2,}"

    generator = outlines.generate.regex(model, emailpattern)

    result = generator("Email kontak customer service: ")

    print(result)

    Generate format khusus: kode produk

    productcodepattern = r"[A-Z]{3}-[0-9]{4}-[A-Z]{2}"

    generator = outlines.generate.regex(model, productcodepattern)

    result = generator("Kode produk untuk laptop gaming: ")

    print(result) # contoh: LAP-2026-GM

    Generate IPv4 address

    ippattern = r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"

    generator = outlines.generate.regex(model, ippattern)

    result = generator("IP address server database: ")

    print(result)

    Choice dan Enum Generation

    Ketika Anda ingin membatasi output ke pilihan tertentu:

    import outlines
    
    

    model = outlines.models.transformers("microsoft/Phi-3-mini-4k-instruct")

    Sentiment analysis dengan pilihan terbatas

    sentiments = ["positif", "negatif", "netral"]

    generator = outlines.generate.choice(model, sentiments)

    reviews = [

    "Produk ini sangat bagus! Kualitasnya luar biasa.",

    "Pelayanannya buruk, pengiriman sangat lambat.",

    "Produknya biasa saja, sesuai harga.",

    ]

    for review in reviews:

    prompt = f"Analisis sentimen review berikut: '{review}'\nSentimen: "

    sentiment = generator(prompt)

    print(f"Review: {review[:50]}... -> {sentiment}")

    Klasifikasi kategori tiket support

    categories = [

    "billing",

    "technicalissue",

    "featurerequest",

    "accountmanagement",

    "generalinquiry"

    ]

    generator = outlines.generate.choice(model, categories)

    ticket = "Saya tidak bisa login ke akun saya setelah reset password"

    prompt = f"Klasifikasikan tiket support berikut: '{ticket}'\nKategori: "

    category = generator(prompt)

    print(f"Kategori: {category}")

    Deteksi bahasa

    languages = ["indonesian", "english", "malay", "javanese", "sundanese"]

    generator = outlines.generate.choice(model, languages)

    text = "Kulo badhe tumbas sega goreng"

    prompt = f"Deteksi bahasa dari teks berikut: '{text}'\nBahasa: "

    language = generator(prompt)

    print(f"Bahasa terdeteksi: {language}")

    Type-Guided Generation

    Outlines juga mendukung generation berdasarkan tipe data Python:

    import outlines
    
    

    model = outlines.models.transformers("microsoft/Phi-3-mini-4k-instruct")

    Generate integer

    intgenerator = outlines.generate.format(model, int)

    result = intgenerator("Berapa jumlah penduduk Jakarta dalam jutaan? ")

    print(f"Hasil (int): {result}, tipe: {type(result)}")

    Generate float

    floatgenerator = outlines.generate.format(model, float)

    result = floatgenerator("Berapa nilai pi sampai 5 desimal? ")

    print(f"Hasil (float): {result}, tipe: {type(result)}")

    Generate boolean

    boolgenerator = outlines.generate.format(model, bool)

    result = boolgenerator("Apakah Python adalah bahasa compiled? ")

    print(f"Hasil (bool): {result}, tipe: {type(result)}")

    Integrasi dengan Berbagai Model

    Menggunakan Transformers (Hugging Face)

    import outlines
    
    

    Model dari Hugging Face

    model = outlines.models.transformers(

    "microsoft/Phi-3-mini-4k-instruct",

    device="cuda" # atau "cpu"

    )

    Dengan konfigurasi sampling

    generator = outlines.generate.json(model, MySchema)

    result = generator(

    prompt,

    maxtokens=1024,

    temperature=0.7,

    topp=0.9

    )

    Menggunakan llama.cpp

    import outlines
    
    

    Model GGUF lokal via llama.cpp

    model = outlines.models.llamacpp(

    "path/to/model.gguf",

    nctx=4096,

    ngpulayers=35

    )

    generator = outlines.generate.json(model, MySchema)

    result = generator(prompt)

    Menggunakan vLLM

    import outlines
    
    

    Model dengan vLLM untuk inferensi cepat

    model = outlines.models.vllm(

    "microsoft/Phi-3-mini-4k-instruct",

    tensorparallelsize=1

    )

    generator = outlines.generate.json(model, MySchema)

    Batch generation - sangat efisien dengan vLLM

    prompts = [

    "Ekstrak informasi dari: Budi, 30 tahun, Jakarta",

    "Ekstrak informasi dari: Siti, 25 tahun, Surabaya",

    "Ekstrak informasi dari: Ahmad, 35 tahun, Bandung",

    ]

    results = generator(prompts)

    for r in results:

    print(r.modeldumpjson())

    Contoh Praktis: Pipeline Ekstraksi Data dengan Output Valid

    Berikut contoh lengkap membangun pipeline ekstraksi data yang menjamin output selalu valid:

    import outlines
    

    from pydantic import BaseModel, Field, validator

    from typing import List, Optional

    from enum import Enum

    import json

    Schema untuk ekstraksi informasi invoice

    class MataUang(str, Enum):

    IDR = "IDR"

    USD = "USD"

    EUR = "EUR"

    SGD = "SGD"

    class ItemInvoice(BaseModel):

    namaitem: str = Field(description="Nama produk atau jasa")

    kuantitas: int = Field(ge=1, description="Jumlah item")

    hargasatuan: float = Field(gt=0, description="Harga per unit")

    total: float = Field(gt=0, description="Total harga item")

    class InfoPerusahaan(BaseModel):

    nama: str = Field(description="Nama perusahaan")

    alamat: str = Field(description="Alamat perusahaan")

    npwp: Optional[str] = Field(None, description="Nomor NPWP jika tersedia")

    class Invoice(BaseModel):

    nomorinvoice: str = Field(description="Nomor invoice")

    tanggal: str = Field(description="Tanggal invoice format YYYY-MM-DD")

    penerbit: InfoPerusahaan

    penerima: InfoPerusahaan

    items: List[ItemInvoice]

    subtotal: float = Field(gt=0)

    pajakpersen: float = Field(ge=0, le=100)

    totalpajak: float = Field(ge=0)

    grandtotal: float = Field(gt=0)

    matauang: MataUang

    catatan: Optional[str] = None

    Inisialisasi model dan generator

    model = outlines.models.transformers("microsoft/Phi-3-mini-4k-instruct")

    invoicegenerator = outlines.generate.json(model, Invoice)

    Teks invoice untuk diekstrak

    invoicetext = """

    INVOICE #INV-2026-0042

    Dari:

    PT Teknologi Maju Indonesia

    Jl. Sudirman No. 123, Jakarta Selatan

    NPWP: 01.234.567.8-901.000

    Kepada:

    CV Sumber Makmur

    Jl. Gatot Subroto No. 456, Bandung

    Tanggal: 15 April 2026

    Item:

  • Jasa Konsultasi IT - 40 jam @ Rp 500.000 = Rp 20.000.000
  • Lisensi Software Enterprise - 5 unit @ Rp 2.000.000 = Rp 10.000.000
  • Pelatihan Karyawan - 2 sesi @ Rp 3.000.000 = Rp 6.000.000
  • Subtotal: Rp 36.000.000

    PPN (11%): Rp 3.960.000

    Total: Rp 39.960.000

    Catatan: Pembayaran paling lambat 30 hari setelah tanggal invoice.

    """

    Ekstraksi data - dijamin menghasilkan output valid

    prompt = f"""Ekstrak semua informasi dari invoice berikut ke dalam format terstruktur:

    {invoicetext}

    Data invoice:"""

    invoicedata = invoicegenerator(prompt)

    Output dijamin valid dan bertipe Invoice

    print(f"Nomor Invoice: {invoicedata.nomorinvoice}")

    print(f"Penerbit: {invoicedata.penerbit.nama}")

    print(f"Penerima: {invoicedata.penerima.nama}")

    print(f"Jumlah item: {len(invoicedata.items)}")

    print(f"Grand Total: {invoicedata.matauang.value} {invoicedata.grandtotal:,.0f}")

    print(f"\nFull JSON:")

    print(invoicedata.modeldumpjson(indent=2))

    Pipeline batch processing

    class HasilEkstraksi(BaseModel):

    nama: str

    jabatan: Optional[str] = None

    perusahaan: Optional[str] = None

    email: Optional[str] = None

    telepon: Optional[str] = None

    lokasi: Optional[str] = None

    persongenerator = outlines.generate.json(model, HasilEkstraksi)

    Data kartu nama untuk diekstrak

    kartunamalist = [

    """Dr. Andi Prasetyo, M.Sc.

    Chief Technology Officer

    PT Inovasi Digital Nusantara

    andi.prasetyo@inovasi.co.id

    +62 812-3456-7890

    Jakarta, Indonesia""",

    """Sarah Chen, MBA

    Regional Sales Director

    GlobalTech Solutions Pte Ltd

    sarah.chen@globaltech.sg

    +65 9123 4567

    Singapore""",

    """Bambang Widodo

    Senior Data Scientist

    Bank Central Digital

    b.widodo@bcd.co.id

    021-5551234

    Surabaya, Indonesia""",

    ]

    print("\n=== Batch Extraction Results ===")

    for i, kartu in enumerate(kartunamalist):

    prompt = f"Ekstrak informasi kontak dari kartu nama berikut:\n{kartu}\n\nData:"

    result = persongenerator(prompt)

    print(f"\n--- Kartu Nama {i+1} ---")

    print(f"Nama: {result.nama}")

    print(f"Jabatan: {result.jabatan}")

    print(f"Perusahaan: {result.perusahaan}")

    print(f"Email: {result.email}")

    print(f"Telepon: {result.telepon}")

    print(f"Lokasi: {result.lokasi}")

    Pipeline Klasifikasi Dokumen

    import outlines
    

    from pydantic import BaseModel, Field

    from typing import List

    from enum import Enum

    class KategoriDokumen(str, Enum):

    KONTRAK = "kontrak"

    INVOICE = "invoice"

    PROPOSAL = "proposal"

    LAPORAN = "laporan"

    SURAT = "surat"

    MEMO = "memo"

    class TingkatUrgensi(str, Enum):

    URGENT = "urgent"

    NORMAL = "normal"

    RENDAH = "rendah"

    class KlasifikasiDokumen(BaseModel):

    kategori: KategoriDokumen

    urgensi: TingkatUrgensi

    ringkasan: str = Field(maxlength=200)

    katakunci: List[str] = Field(maxlength=5)

    departementujuan: str

    perlutindakan: bool

    tindakandisarankan: Optional[str] = None

    model = outlines.models.transformers("microsoft/Phi-3-mini-4k-instruct")

    classifier = outlines.generate.json(model, KlasifikasiDokumen)

    dokumen = """

    MEMO INTERNAL

    Perihal: Permintaan Anggaran Darurat untuk Perbaikan Server

    Kepada seluruh jajaran manajemen,

    Dengan ini kami informasikan bahwa server utama data center mengalami

    kerusakan hardware kritis pada tanggal 28 April 2026. Dibutuhkan

    penggantian segera untuk 3 unit SSD enterprise dan 2 unit RAM ECC.

    Estimasi biaya: Rp 85.000.000

    Deadline: Maksimal 3 hari kerja

    Mohon persetujuan segera mengingat dampak terhadap operasional perusahaan.

    Hormat kami,

    Tim IT Infrastructure

    """

    prompt = f"Klasifikasikan dokumen berikut:\n\n{dokumen}\n\nKlasifikasi:"

    hasil = classifier(prompt)

    print(hasil.modeldumpjson(indent=2))

    Tips dan Best Practices

    1. Desain Schema yang Baik

    # Gunakan Field untuk dokumentasi dan validasi
    

    class DataPelanggan(BaseModel):

    nama: str = Field(

    description="Nama lengkap pelanggan",

    minlength=2,

    maxlength=100

    )

    usia: int = Field(

    ge=0, le=150,

    description="Usia dalam tahun"

    )

    email: str = Field(

    description="Alamat email valid"

    )

    2. Batasi Ukuran Output

    # Gunakan maxtokens untuk efisiensi
    

    generator = outlines.generate.json(model, MySchema)

    result = generator(prompt, max_tokens=512)

    3. Pilih Model yang Tepat

    Untuk structured generation, model yang lebih kecil seringkali sudah cukup karena Outlines membantu "mengarahkan" output. Model 3B-7B parameter biasanya sudah memberikan hasil yang baik untuk tugas ekstraksi sederhana.

    4. Gunakan Enum untuk Pilihan Terbatas

    # Lebih baik menggunakan Enum daripada string bebas
    

    class Sentimen(str, Enum):

    POSITIF = "positif"

    NEGATIF = "negatif"

    NETRAL = "netral"

    Daripada

    class HasilAnalisis(BaseModel):

    sentimen: str # bisa menghasilkan nilai apapun

    5. Pertimbangkan Performa

    # Cache generator untuk penggunaan berulang
    

    model = outlines.models.transformers("model-name")

    generator = outlines.generate.json(model, MySchema)

    Generator sudah membangun FSM saat inisialisasi

    Panggilan berikutnya lebih cepat karena FSM sudah di-cache

    for prompt in prompts:

    result = generator(prompt)

    Kesimpulan

    Outlines mengubah cara kita berinteraksi dengan LLM untuk menghasilkan output terstruktur. Dengan pendekatan constrained decoding, Anda mendapatkan:

    • Jaminan 100% output selalu valid sesuai schema
    • Tanpa retry, mengurangi biaya dan latensi
    • Integrasi fleksibel dengan berbagai model dan format output
    • Performa optimal dengan overhead komputasi minimal

    Untuk proyek yang membutuhkan output terstruktur dari LLM, seperti pipeline ekstraksi data, klasifikasi dokumen, atau structured data generation, Outlines adalah pilihan yang sangat direkomendasikan dibandingkan pendekatan retry-based tradisional.

    Artikel Terkait

    Tutorial MergeKit: Menggabungkan Model LLM untuk Membuat Model AI Kustom

    Tutorial MergeKit: Menggabungkan Model LLM untuk Membuat Model AI Kustom Model bahasa besar (LLM) open-source terus berk...

    Tutorial Semantic Router: Routing Cerdas untuk Aplikasi LLM

    Tutorial Semantic Router: Routing Cerdas untuk Aplikasi LLM Semantic Router adalah library Python yang memungkinkan Anda...

    Tutorial Lengkap Unstructured: Preprocessing Dokumen untuk RAG dan Aplikasi LLM

    Tutorial Lengkap Unstructured: Preprocessing Dokumen untuk RAG dan Aplikasi LLM Dalam membangun aplikasi Retrieval-Augme...

    Tutorial Lengkap Llama Stack: Framework Resmi Meta untuk Membangun Aplikasi AI dengan Model Llama

    Tutorial Lengkap Llama Stack: Framework Resmi Meta untuk Membangun Aplikasi AI dengan Model Llama Llama Stack adalah fra...