
Strukturált kimenet anélkül, hogy szépen megkérnéd a modellt
Közzétéve: 2025. október 21.
A legtöbb cég, amelyik LLM feature-t visz ki ügyfelekhez, nem chatbotot szállít. Egy gombot szállít, ami egy dolgot csinál kiszámíthatóan - összefoglal egy dokumentumot, megír egy választ -, mert egy szabad szöveges mező egyenes meghívó a felhasználóknak, hogy bármit megkérdezzenek a modelltől, és egy LLM, ami bármire válaszol, pontosan az a kiszámíthatatlanság, amit a legtöbb termék nem engedhet meg magának.
Az összefoglalás az egyszerűbb eset. Adj a modellnek egy világos instrukciót, kérj egy összefoglalót valamilyen szólimittel, és a legtöbbször kapsz is valami használhatót. A gond ott kezdődik, amikor a kimenetnek olyan struktúrája kell legyen, amit egy downstream rendszer tényleg fel tud dolgozni - nem csak olvasható szöveg, hanem valami, amit egy UI vagy egy adatbázis emberi ellenőrzés nélkül be tud fogadni.
A kedves kérés működik, amíg nem
Az első kézenfekvő lépés, hogy a struktúrát a promptban kéred. Mondd meg a modellnek, hogy bontsa szekciókra a szöveget, minden szekciót csomagoljon tagekbe, adjon hozzá címet és linket vissza a forráshoz - és pontosan ezt fogja csinálni, a legtöbbször. Kérj HTML-szerű tageket markdown helyett, és szívesen legenerál section, title és link blokkokat, amik egy kis, jól formázott dokumentumnak tűnnek.
Ha az LLM-ek úgy viselkednének, ahogy megszoktuk a szoftvertől, ezzel vége is lenne a történetnek. Nem így viselkednek. Tedd fel ugyanazt a jól specifikált kérdést százszor, és néhány válasz ki fog hagyni egy taget, rosszul fog beágyazni valamit, vagy teljesen kicsúszik a formátumból - nem azért, mert a modell félreértette az instrukciót, hanem mert ez egy valószínűségi folyamat, nem egy parser. Éles környezetben pedig „száz esetből tíz rossz forma" egyenlő egy elromlott UI-val, egy parser exceptionnel, vagy egy olyan adatbázis-írással, aminek mezői nem is léteznek.
Az ösztönös reakció, hogy egyszerűen újrapróbálod, amíg tiszta parse-ot nem kapsz. Ez működik, de minden sikertelen próbálkozás tokent és időt visz el, és a providerek egyre keményebb rate limitet szabnak, így az újrapróbálkozás-loop nem ingyen biztosíték - hanem egy sor, amiben állsz.
A forma legyen nem alkuképes
A megoldás nem egy jobb prompt. A megoldás az, hogy a megkötést kiveszed a promptból, és beteszed magába a decoding folyamatba.
A tool calling az, amihez a legtöbben elsőként nyúlnak. Definiálsz egy schemát - egy function nevet, egy típusos paraméterhalmazt, hogy melyik kötelező -, és ezt csatolod a request mellé. A modell nem csak megpróbál olyan JSON-t írni, ami történetesen illeszkedik a schemára; az API megkötést tesz arra, milyen tokent egyáltalán kiadhat a következő lépésben, így a kimenet generálás közben validálódik a schema szerint, nem utólag.
tool = {
"name": "create_section",
"description": "Create a blog section with title, body, and link",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string"},
"body": {"type": "string"},
"link": {"type": "string", "format": "uri"},
},
"required": ["title", "body", "link"],
},
}
Hívd meg a modellt ezzel a tool-lal, és ami visszajön, egy strukturált argumentum objektum lesz, nem egy string, amiről reménykedned kell, hogy tisztán parse-olható.
A háttérben ez azért működik, mert a modell egyszerre egy tokent generál, és egy schema lefordítható arra a megkötésre, hogy melyik token legális az adott pozícióban - gyakorlatilag egy véges állapotgép, ami a modell nyers valószínűségei és a ténylegesen kiadott token közé ékelődik. Egy magas valószínűségű, de rossz típusú tokent kizárnak, ami marad, és megfelel a schemának, abból választ a modell. Ugyanez a mechanizmus áll az OpenAI strukturált json_schema response formátuma mögött is - a különbség inkább abban van, hova kerül a payload a válaszban, nem abban, hogyan működik a kikényszerítés.
Nem a schema a lényeg, hanem hogy leteszed a reménykedést
Ehhez nem kell, hogy a modell okosabb legyen. Az kell, hogy abbahagyd a formátum-betartás kérésként kezelését, és API-határon kikényszerített dologként kezeld - ugyanaz az ösztön, ami miatt egy REST endpointon validálod a bemenetet, ahelyett hogy a kliensre bíznád, hogy jól formázott JSON-t küld.
Ha még mindig regexet írsz, hogy megments egy kicsit hibás választ, vagy retry-loopot építesz és azt reziliensnek hívod, az általában azt jelzi, hogy a megkötés egy réteggel lejjebb van a helyén - a schemában, nem a szavakban, amikkel kedvesen megkérted a modellt.
