Show HN: Outlines - Alat yang Menjamin LLM Menghasilkan Output Terstruktur seperti JSON yang Valid
(github.com/normal-computing)- Outlines adalah alat output terstruktur untuk LLM, yang memungkinkan penentuan tipe output yang diinginkan saat proses generasi sehingga data dijamin sesuai tepat dengan struktur tersebut
- Alih-alih memperbaiki output yang salah setelah generasi dengan parsing, regex, atau kode rapuh, alat ini langsung memaksakan struktur yang valid pada tahap generasi
- Cara penggunaannya adalah dalam bentuk
model(prompt, output_type), dan memungkinkan penentuan output yang selaras dengan sistem tipe Python sepertiLiteral["Yes", "No"],int, atau model Pydantic - Jenis output yang didukung mencakup pilihan yang telah ditentukan sebelumnya, Function Calls berbasis signature fungsi, skema JSON/Pydantic, pola regex, dan pemaksaan struktur berbasis grammar
- Integrasi model dibagi menjadi dukungan server vLLM·Ollama, dukungan model lokal transformers·llama.cpp, serta dukungan API OpenAI·Gemini·Dottxt
- Contoh alur kerja meliputi mengubah email pelanggan menjadi tiket layanan, mengubah deskripsi produk menjadi data kategori, mengembalikan informasi terstruktur atau
"I don't know"dari deskripsi acara yang tidak lengkap, mengklasifikasikan dokumen ke kategori yang telah ditentukan, dan mengubah permintaan rapat dalam bahasa alami menjadi parameter fungsi - Template prompt mendukung
outlines.Template.from_stringberbasis Jinja serta pemuatan dari file, untuk memisahkan prompt yang kompleks dari kode dan menggunakannya kembali - Instalasi tersedia melalui
pip install outlines, dan.txt APIsaat ini ditandai sebagai dalam tahap early access
1 komentar
Komentar Hacker News
Pustaka ini tampaknya memakai ide sederhana yang sangat bagus: secara mekanisme, ia memask sebagian ruang kosakata dan melangkah secara efisien pada tiap tahap waktu.
Namun, sebagai orang yang pernah memakai pustaka yang menambahkan struktur keluaran di atas LLM dasar, saya penasaran apakah model dasar seperti Llama2 benar-benar bekerja dengan baik.
Dari pengalaman saya, jawabannya lebih dekat ke “sama sekali tidak”, dan agar benar-benar berfungsi, diperlukan cukup banyak instruction tuning yang disesuaikan untuk penggunaan tertentu.
Selain itu, melakukan masking ruang keadaan secara post-hoc selama generasi pada model yang sudah di-instruction-tune pada akhirnya mengubah distribusi generasi, sehingga secara intuitif terasa bertentangan karena bisa saja merugikan instruction tuning.
Untuk poin kedua, jika tujuannya misalnya membuat model hanya menghasilkan JSON, itu 100% bisa dilakukan dengan membatasi token keluaran mana yang boleh dan tidak boleh dipakai.
Karena bisa memakai GPU di M2 Mac, saya menggunakan versi MLC melalui plugin https://github.com/simonw/llm-mlc.
Makalah “Guiding Language Models of Code with Global Context using Monitors”(https://arxiv.org/abs/2306.10763) mengusulkan Monitor Guided Decoding, yang menghubungkan LLM dengan analisis statis untuk mengarahkan model agar menghasilkan kode yang konsisten secara tipe.
Tanpa fine-tuning apa pun, mengarahkan generasi level token dengan analisis statis pada titik-titik tertentu meningkatkan kualitas kode yang dihasilkan secara besar, baik dari sisi kemampuan dikompilasi maupun kesesuaian dengan jawaban benar. Bahkan model yang sangat kecil (1.1B) menghasilkan lebih banyak kode yang bisa dikompilasi dibanding model yang jauh lebih besar (175B), sekaligus meningkatkan kesesuaian jawaban.
Perbedaan utamanya hanya fungsi generasinya adalah manusia, bukan LLM. Saya tidak melihat alasan mengapa perantara manusia itu tidak boleh dihilangkan.
Dalam kode tradisional, kondisi batas secara harfiah lebih mirip kasus khusus kecil, tetapi pada LLM kita tidak tahu apa yang akan membuatnya melenceng ke arah aneh, dan kode parsing harus menanggung kekacauan itu.
Dengan kata lain, rasanya proporsi kasus yang harus diperlakukan sebagai kondisi batas meningkat drastis.
GPT-4 bisa dibuat mengembalikan JSON valid hanya dengan memasukkan contoh di pesan sistem, dan berhasil sembilan dari sepuluh kali.
Namun tetap saja probabilistik, dan sembilan dari sepuluh belum cukup.
Kadang ia juga membuat respons halusinasi seperti
{"key1": "value1", "key2": "value2" for i in range(n)}.Jika diprompt ulang bersama pesan error parsing, biasanya percobaan kedua akan beres.
Namun escaping tanda kutip ganda dan karakter baris baru kurang stabil. Meski diberi beberapa contoh, hanya sekitar separuh yang di-escape dengan benar, dan walaupun error escaping diprompt ulang, tingkat keberhasilannya tetap sekitar 50%.
Prompt terkait ada di sini: https://github.com/microsoft/TypeChat/blob/c45460f4030938da3...
Saya melihat pendekatan berbasis tata bahasa seperti yang tampak di sini, atau cara seperti https://github.com/ggerganov/llama.cpp/pull/1773, sebagai solusi yang jauh lebih elegan.
Saya paham orang-orang menginginkan JSON, tetapi bagi saya itu mirip mencoba membuat kucing berenang. Pada akhirnya bisa saja berhasil, tetapi itu bukan kecenderungan alaminya.
Kesalahan utamanya adalah tidak menyediakan jalan keluar. Karena LLM berusaha menemukan jawaban yang benar, jika kita memasukkan teks-teks dan memintanya mengembalikan data terstruktur tetapi ada teks yang kosong, ia jadi sulit menentukan jawaban yang benar dan halusinasi pun muncul.
Solusinya adalah membuat jalan keluar, misalnya menambahkan boolean seperti
textIsMissingsebagai salah satu argumen. Jika pola kegagalan seperti ini diperhitungkan, semuanya berjalan mulus.Saya mencoba ulang berkali-kali dan bahkan meminta perbaikan lanjutan, tetapi tetap tidak lolos validasi; tidak sekali pun ia menghasilkan JSON yang 100% valid, jadi akhirnya saya menyerah.
Pertama, tidak perlu memasukkan terlalu banyak contoh ke prompt, sehingga memakai lebih sedikit token.
Kedua, lebih sedikit terdampak masalah lupa.
Keunggulan kecilnya, kita bisa mengontrol secara tepat dari mana keluaran yang diinginkan harus dimulai. Namun secara keseluruhan, saya menganggapnya fitur tambahan yang bagus, bukan sesuatu yang sangat esensial.
Sebagian besar kekuatan LLM ada pada distribusi probabilitas yang terkalibrasi dari responsnya, dan teknik ini tampaknya mungkin membuang kemampuan itu. Saya bertanya-tanya mengapa ini dianggap cukup
Sebagai contoh sederhana, anggap keluaran LLM yang mungkin hanya “hello world”, “food”, “hello”, dan “good day”, dan semuanya berprobabilitas sama saat tidak ada prompt. Anggap tata bahasanya hanya memberi batasan bahwa harus ada spasi di suatu tempat dalam keluaran
Jika kita mengambil sampel keluaran LLM sampai lolos tata bahasa, kita akan mendapatkan “hello world” dan “good day” dengan probabilitas yang sama. Namun jika menerapkan teknik dari situs web itu, “hello world” akan muncul dua kali lebih sering daripada “good day”
Masalah intinya adalah, meskipun sebuah prefiks jawaban mungkin memiliki peluang yang amat rendah untuk berujung pada respons yang valid, teknik ini—dengan asumsi berhasil—akan membentuk respons valid dari prefiks tersebut. Jika cukup independen di tempat yang tepat, mungkin tidak masalah, tetapi pada model autoregresif, kesalahan yang berkorelasi cepat menumpuk
Kalau dibatasi pada JSON: saat LLM membuat respons yang tidak memenuhi skema, apakah ia menghasilkan lebih banyak atau lebih sedikit kesalahan faktual, halusinasi, string terpotong, penghilangan tokoh utama, dan sebagainya? Jika tingkat kesalahan faktual terkait dengan tingkat kesalahan skema secara tidak sepele, jalur ini lebih berbahaya daripada kelihatannya. Melihat bagaimana kata tertentu atau frasa yang menempel bisa sangat memengaruhi keluaran LLM, detail seperti kepatuhan skema juga tampaknya sangat mungkin merembet ke karakteristik keluaran lainnya
Saya mencoba memikirkan contoh “prefiks jawaban punya peluang sangat rendah untuk berujung pada respons valid, tetapi tekniknya entah bagaimana menyusun respons valid sehingga menimbulkan masalah”, tetapi belum terpikir. Kalau ada contoh yang bagus, itu bisa menjadi pertanyaan riset yang menarik
Terkait hal ini, LLama.cpp bulan lalu mengimplementasikan sampling berbasis tata bahasa
https://news.ycombinator.com/item?id=36819906
https://github.com/ggerganov/llama.cpp/pull/1773
Cara kami jauh lebih efisien. llama.cpp menelusuri seluruh kosakata, sekitar 50 ribu token, untuk membuat mask di setiap langkah
Kami membuat indeks saat inisialisasi, lalu pada setiap langkah cukup melakukan lookup dictionary untuk menyusun mask. Ini menukar kecepatan dengan memori, dan sampling-nya secepat sampling standar
Melihat banyaknya makalah terkait, kemungkinan ada beberapa tempat lain juga. Poin dari ini dan pekerjaan saat ini adalah menyediakan panduan dengan biaya sangat rendah; untuk kasus regex sudah diimplementasikan cukup lama, lalu kemudian diperluas ke JSON
Terima kasih sudah membuat hal seperti ini; mekanisme kerjanya terasa seperti ide yang sangat jelas, jadi mengejutkan bahwa platform pihak pertama belum melakukannya
Selain JSON, saya penasaran bagaimana ini bisa dipakai untuk pekerjaan lain yang membutuhkan input terstruktur
Bisa jadi ini akibat sumber daya developer yang terbatas. Kalau fitur dasar seperti ini tidak ada pada teknologi berusia 10 tahun, itu mengejutkan; tetapi untuk teknologi AI yang sedang dalam perlombaan senjata, masih kurangnya fitur kenyamanan terasa bisa dimaklumi
Saya kurang paham apa bedanya ini dengan proyek-proyek berikut
https://github.com/1rgs/jsonformer
https://github.com/newhouseb/clownfish
https://github.com/mkuchnik/relm
https://github.com/ggerganov/llama.cpp/pull/1773
https://github.com/Shopify/torch-grammar
Secara umum, sistem panduan berbasis logit seperti ini sangat banyak, dan alasan utamanya tidak mendapat traction besar adalah karena model-model dengan performa terbaik berada di balik REST API yang tidak memungkinkan pendekatan sedetail ini
Karena model-model itu jauh lebih baik, orang biasanya cukup puas dengan meminta ulang sampai format yang benar keluar. Dalam pengalaman saya, kegagalan seperti itu juga cukup jarang pada GPT-4
Kami menelusuri kosakata satu kali saat inisialisasi untuk membuat indeks, lalu setelah itu generasi secepat generasi standar
Dengan kata lain, apakah setiap kali LLM menghasilkan satu token, ia memperbarui “mask” bias logit agar token berikutnya hanya bisa berupa token JSON yang valid? Sangat keren
Saya tidak yakin bagaimana metode ini benar-benar bisa menjamin 100%
Edit: makalahnya menjelaskan bagian ini dengan lebih hati-hati
Apakah Brandon Willard ini Brandon Willard breakdancer dari Detroit?
Edit: benar! https://brandonwillard.github.io/
remilouf, karena Anda mengatakan latar belakang Anda adalah “pemrograman probabilistik, relasional, dan simbolik”, saya rasa Anda memahami bahwa menghasilkan teks dari tata bahasa reguler, tata bahasa bebas konteks, atau pada dasarnya tata bahasa di level apa pun bukanlah masalah
Misalnya, dalam Prolog, sebuah bahasa relasional, ini sangat mudah dilakukan dengan notasi Definite Clause Grammars selama tata bahasanya diberikan
Menurut saya pendekatan ini mengharuskan pengguna menyediakan tata bahasa. Kalau begitu, saya penasaran apa keunggulan memakai LLM untuk menghasilkan teks
Mengapa tidak cukup menjalankan tata bahasa sebagai generator untuk membuat teks yang diinginkan? Dengan begitu, usaha dan biaya besar untuk melatih LLM sejak awal bisa dikurangi. Jika tujuannya hanya menghasilkan teks terstruktur, saya juga mempertanyakan mengapa LLM, yang merupakan model bahasa alami, diperlukan
Alasan LLM diperlukan meskipun yang diinginkan hanya teks terstruktur adalah untuk mem-parsing teks tak terstruktur yang ditulis manusia dan mengembalikan data terstruktur yang bisa dipakai mesin
Jika permintaannya adalah “apa 10 digit pertama pi?” dan respons dibatasi dengan regex
"[0-9]+\.[0-9]+", tujuannya adalah mendapatkan jawaban sebenarnya, 3.1415926535, bukan string acak seperti “1.2346789” yang sekadar cocok dengan polaMenarik, dan baru-baru ini kami juga membuat tool serupa yang membatasi keluaran llama agar cocok dengan interface TypeScript[1]
Saya sangat yakin jaminan format keluaran akan menjadi penting selama beberapa dekade ke depan saat LLM dipakai untuk kasus nyata yang bukan sekadar main-main
[1] https://github.com/ggerganov/llama.cpp/discussions/2494