Miért érdekel ez téged?
A modell válasza lehetőleg ne szabad szöveg legyen, ha egy másik rendszer fogja feldolgozni. Amint az LLM kimenete egy adatbázisba, egy API-hívásba vagy egy `if`-elágazásba kerül, egyetlen elkószált mondat vagy hiányzó mező elronthatja az egész folyamatot.
Gondolj a szabad szöveges válaszra úgy, mint egy kézzel firkált cetlire: egy ember elolvassa és megérti, de egy szoftver megbotlik rajta. A structured output ezzel szemben egy kitöltendő űrlap — előre megmondod, milyen mezők vannak, milyen típusúak, és a modellnek ezt kell kitöltenie, nem szabadon fogalmaznia. Ez az a réteg, amely a 1. fejezetben megismert nemdeterminisztikus szövegjóslóból megbízható szoftverkomponenst csinál.
A jó hír: a nagy providerek ma már natívan támogatják, hogy a kimenetet egy sémához kössed. A rossz hír: a séma csak a forma garanciája — a tartalom helyességéért, a jogosultságokért és az üzleti szabályokért továbbra is a te kódod felel. Ez a fejezet mindkét oldalt lefedi.
Miért nem elég egyszerűen ráírni a promptra, hogy „adj JSON-t"? Mert a régi módszernél a hibaarány nem elhanyagolható: szabad prompttal a modell tipikusan az esetek pár százalékában valamit elront — bevezet egy magyarázó mondatot a JSON elé, markdown code fence-be teszi (```json), lezáratlan zárójelet hagy, vagy trailing vesszőt tesz. Nagy volumenben, mondjuk napi 100 000 híváson, még 1% hibaarány is napi 1000 elbukott feldolgozás — ez production rendszerben elfogadhatatlan. A séma-kikényszerítés éppen ezt a nagyságrendet nyomja a nullához közelre a formai hibáknál.
A séma kikényszerítése
Az első kérdés: hogyan éred el egyáltalán, hogy a modell ne prózát, hanem egy előre definiált struktúrát adjon vissza. Három egymásra épülő eszközöd van erre.
- JSON Schema Egy géppel olvasható leírás arról, hogyan néz ki egy érvényes JSON: milyen mezők kötelezők, milyen típusúak, milyen értékeket vehetnek fel. Ez a közös nyelv, amivel a modellnek elmondod, „ilyen alakú választ várok". A séma nem csak dokumentáció: a modell dekódolása közben tényleges korlátként működik, így a kimenet garantáltan illeszkedik a struktúrához. A séma ugyanaz a formátum, amit a REST API-k validálásához (OpenAPI) is használnak — nem AI-specifikus találmány, csak új felhasználás.
- Structured outputs A provider azon képessége, hogy a generálást a megadott JSON Schemára kényszerítse — token szinten csak olyan folytatást enged, ami a sémába belefér. Ez erősebb, mint egyszerűen megkérni a promptban, hogy „adj JSON-t": a régi módszernél a modell néha magyarázó szöveget is odabiggyesztett, vagy lezáratlan zárójelet hagyott. A structured output ezt a formai bizonytalanságot lényegében kiiktatja. A háttérben ez constrained decoding: a dekóder minden lépésnél kimaszkolja azokat a tokeneket, amelyekkel a részleges kimenet már nem tudna érvényes JSON-ná zárulni.
- Function/tool calling Ugyanaz a séma-kikényszerítés, csak műveletekre alkalmazva: megadsz a modellnek egy vagy több „függvényt" (toolt) tipizált paraméterekkel, és a modell egy strukturált tool-hívást ad vissza — a függvény nevét plusz az argumentumokat sémára illesztve. A modell nem hívja meg a függvényt, csak javasolja, milyen hívás lenne indokolt; a végrehajtás a te kódodban történik. A tool-tervezés mélységeiről a 7. Tool engineering fejezet szól.
Három szint: prompt, séma, tool
Érdemes tisztán látni, mikor melyik eszközt válaszd. A három megközelítés nem alternatíva, hanem növekvő szigorúságú lépcső — és a döntés attól függ, mit csinálsz a kimenettel.
| Prompt-alapú JSON | Structured output (séma) | Tool calling | |
|---|---|---|---|
| Formai garancia | Nincs (best effort) | Erős (constrained decoding) | Erős |
| Mikor használd | Prototípus, kísérlet | Adatkinyerés, osztályozás, extraction | Műveletek, több lehetséges akció közti választás |
| Több lehetőség? | Nehézkes | Egy fix alak | A modell választ a toolok közül |
| Tipikus formai hiba | ~1-5% | ~0% (ha a provider támogatja) | ~0% |
Nézzük meg konkrétan, hogyan néz ki egy JSON Schema egy egyszerű sentiment-osztályozó feladathoz. Ez az a leírás, amit a modell „elé teszel", és amihez a generálást köti:
{
"type": "object",
"properties": {
"sentiment": { "type": "string", "enum": ["pozitív", "semleges", "negatív"] },
"confidence": { "type": "number", "minimum": 0, "maximum": 1 }
},
"required": ["sentiment", "confidence"],
"additionalProperties": false
}
A modell ezt olvasva tudja: pontosan két mezőt kell adnia, a `sentiment` csak a három felsorolt érték egyike lehet, a `confidence` egy 0 és 1 közötti szám, és semmi mást nem tehet a válaszba (`additionalProperties: false`). A gyakorlatban ritkán írsz kézzel ilyen sémát — ehelyett Python-oldalon egy Pydantic modellt definiálsz, amiből a séma automatikusan legenerálódik:
from pydantic import BaseModel, Field
from openai import OpenAI
class Ticket(BaseModel):
category: str
priority: int = Field(ge=1, le=5)
summary: str
client = OpenAI()
resp = client.responses.parse(
model="gpt-4.1",
input="A fizetési oldal 500-as hibát dob a checkoutnál.",
text_format=Ticket,
)
ticket = resp.output_parsed # már validált Ticket objektum
print(ticket.priority) # pl. 5 (int, nem string!)
A `text_format=Ticket` alapján az SDK legenerálja a sémát, a provider ehhez köti a generálást, a válasz pedig már kész, típusos `Ticket` objektumként érkezik — nem kell stringet parse-olnod, és a `priority` garantáltan egész szám 1 és 5 között. Amit ebből tanulj: a modell világa és az alkalmazáskódod típusrendszere egyetlen forrásból (a Pydantic modellből) származik, így nincs kézi „glue code", ami elcsúszhatna.
A leírások (description) számítanak
A séma nemcsak típusokat, hanem szemantikát is közvetít. Minden mezőhöz adhatsz `description`-t, és a modell ezt tényleg olvassa — ez gyakran a legolcsóbb minőségjavító eszköz. Egy `priority` mező mellé írt „1 = enhancement, 5 = production kiesés, adatvesztés" leírás jobban kalibrálja a modellt, mint bármilyen külön prompt-mondat.
class Ticket(BaseModel):
category: str = Field(
description="A hiba fő kategóriája, pl. 'payment', 'auth', 'ui'."
)
priority: int = Field(
ge=1, le=5,
description="1 = kozmetikai, 5 = production leállás / adatvesztés."
)
summary: str = Field(
description="Egy mondatos, technikai összefoglaló, felhasználói idézet nélkül."
)
Amit ebből tanulj: a séma egyszerre kontraktus a kódnak és instrukció a modellnek. Ne pazarold el ezt a kettős szerepet üres mezőnevekkel.
Validáció és típusbiztonság
A séma-kikényszerítés a modell oldaláról jó, de production rendszerben soha ne bízz vakon abban, hogy a kimenet tényleg helyes. A saját kódodban is validálnod kell — a validáció olyan, mint a vámellenőrzés a határon: mielőtt bármilyen adat bejut a rendszeredbe, átmegy egy szigorú kapun, ami visszafordítja azt, ami nem stimmel.
- Pydantic vagy Zod validáció A Pydantic (Python) és a Zod (TypeScript) sémadefiníciós könyvtárak: egy helyen írod le az adatszerkezetet, és megkapod belőle a típusellenőrzést, a runtime-validációt és a JSON Schema-generálást. Ha a modell kimenete nem illeszkedik, azonnal jól definiált hibát kapsz, nem egy rejtett bug három réteggel lejjebb. Ez a réteg köti össze a modell világát a típusos alkalmazáskódoddal. Fontos: a provider constrained decodingja és a saját Pydantic-validációd nem redundáns — a provider a formát garantálja, te viszont üzleti invariánsokat (pl. custom validator) is ráteszel, amit a séma nem tud kifejezni.
- Enumok és diszkriminált unionök Az enum a lehetséges értékeket zárt halmazra szűkíti (pl. státusz csak „nyitott", „folyamatban" vagy „lezárt" lehet) — így a modell nem talál ki új kategóriát. A diszkriminált union több lehetséges alakzatot enged, de van egy típusmező, ami eldönti, melyik érvényes. Analógia: mint egy borítékon a feladótípus, ami megmondja, melyik űrlapot vedd elő belőle — egy `type: "refund"` üzenetnek más mezői vannak, mint egy `type: "shipping"`-nek, és a diszkriminátor egyértelműsíti, melyikkel van dolgod. A diszkriminált union különösen erős intent-routingnál: a modell egyetlen hívásban eldönti a szándékot ÉS kitölti a szándékhoz tartozó pontos mezőket.
- Schema versioning A sémáid idővel változnak: új mező kell, egy régi elavul. Ha nem verziózod őket, egy csendes módosítás visszamenőleg eltörhet minden korábban eltárolt adatot vagy futó integrációt. Verziószám (pl. `schema_version: 2`) és tudatos migrációs stratégia kell — pontosan úgy, ahogy egy adatbázis-sémát sem alakítasz át migráció nélkül. Ez különösen fontos, mert a modellek viselkedése is drift-elhet a provider frissítéseivel. Gyakorlati szabály: mező hozzáadása optional értékkel visszafelé kompatibilis, de mező eltávolítása vagy típusának változtatása breaking change — új major verzió jár érte.
- Argument validation Tool/function calling esetén a modell által javasolt argumentumokat külön, szigorúan ellenőrizni kell, mielőtt bármit végrehajtasz velük. Nem elég, hogy az `amount` szám — az is kérdés, hogy pozitív-e, a megengedett limiten belül van-e, és hogy a `user_id` egyáltalán létezik-e. A séma a formát garantálja, az argument validation az üzleti értelemben vett érvényességet. Ez az utolsó védvonal, mielőtt a modell javaslatából valódi művelet lesz.
Diszkriminált union a gyakorlatban
Tegyük fel, hogy egy support-botnak három dolgot kell tudnia: visszatérítés, szállítási kérdés, vagy általános válasz. Diszkriminált unionnal a modell egyben dönti el a szándékot és tölti ki a hozzá tartozó mezőket — nem kell külön intent-classifier és külön extractor.
from typing import Literal, Union
from pydantic import BaseModel, Field
class Refund(BaseModel):
type: Literal["refund"]
order_id: str
amount: float = Field(gt=0)
class ShippingQuery(BaseModel):
type: Literal["shipping"]
order_id: str
class GeneralReply(BaseModel):
type: Literal["general"]
message: str
class Response(BaseModel):
action: Union[Refund, ShippingQuery, GeneralReply] = Field(discriminator="type")
Amit ebből tanulj: a `type` mező a diszkriminátor, és a Pydantic ez alapján tudja, melyik alakot kell validálnia. Ha a modell `type: "refund"`-ot ad, akkor kötelező az `order_id` és a pozitív `amount` — a hibás kombináció (pl. refund `amount` nélkül) már a validációnál elbukik.
Gyakori hibák a sématervezésnél
- Túl megengedő típusok Ha egy státuszmezőt `str`-ként hagysz `enum` helyett, a modell előbb-utóbb kitalál egy új értéket (pl. „pending_review"), amit a downstream kód nem ismer. Zárd le mindig, ahol zárható a halmaz.
- Mély, egymásba ágyazott struktúrák A 4-5 szint mély séma nemcsak a modellnek nehéz, de a provider támogatottsága is gyakran korlátozott. Ha lehet, laposítsd — egy 3 szintnél mélyebb objektumfát érdemes több hívásra bontani.
- Kötelezővé tett bizonytalan mező Ha a modelltől olyat kérsz `required`-ként, amit nem tud kikövetkeztetni (pl. hiányzó adat a szövegből), akkor hallucinál egy értéket. Adj `Optional`-t vagy explicit „ismeretlen" enum-értéket, és inkább null-t kapj, mint kitalált adatot.
Amikor a modell mégis hibázik
Még a legszigorúbb sémával is előfordul, hogy a kimenet nem használható: a modell időnként érvénytelen JSON-t ad, a validáció elbukik, vagy a hálózat éppen megbicsaklik. A megbízható integráció lényege, hogy ezekre az esetekre van tervezett válaszod — nem esik szét, hanem újrapróbál, javíttat, vagy tisztán elbukik.
- Retry és repair stratégiák Ha a validáció elbukik, két lehetőséged van. A retry: egyszerűen újrafuttatod a hívást (érdemes exponenciális backoffal, ha rate limitbe futottál). A repair: visszaadod a modellnek a saját hibás kimenetét és a konkrét validációs hibaüzenetet, és megkéred, hogy javítsa. Ez olyan, mint amikor visszaadsz egy dolgozatot a hibák megjelölésével — a modell a konkrét visszajelzésből általában sikeresen korrigál. Mindig legyen felső korlát a próbálkozásokon, hogy ne pörögj végtelen ciklusban.
- Idempotency key Ha egy retry olyan műveletet ismétel meg, ami mellékhatással jár (pl. rendelés leadása, fizetés), fennáll a veszély, hogy kétszer hajtod végre. Az idempotency key egy egyedi azonosító, amit a művelethez csatolsz — a szervernek pedig egy már látott kulcsra nem szabad újra végrehajtania a műveletet. Analógia: mint egy jegyfoglalás foglalási száma — hiába nyomod meg kétszer a „foglalás" gombot, nem foglalsz két repülőjegyet. Ez a distributed systems egyik alapmintája, amiről a 13. Production architektúra is szól.
- Deterministic post-processing A modell kimenete után gyakran van determinisztikus, kódban megírt utófeldolgozás: normalizálás (kisbetűsítés, dátumformázás), kerekítés, mezők kiszámítása, egységesítés. Ne kérd a modelltől azt, amit egy megbízható függvény pontosan és ingyen elvégez. Ha a válaszból ki lehet számolni valamit, számold ki kóddal — az mindig ugyanazt adja, a modell nem feltétlenül. Konkrét példa: ne a modelltől kérd egy számla összegzését (áfa, kerekítés), csak a tételeket — az összeadást a kód végezze, mert az sosem téved.
Így néz ki egy konkrét retry-repair minta Pydantic-validációval. A lényeg, hogy a hibát strukturáltan visszaadjuk a modellnek, és van maximális próbaszám:
from pydantic import ValidationError
def extract_ticket(text, max_retries=2):
messages = [{"role": "user", "content": text}]
for attempt in range(max_retries + 1):
raw = call_model(messages, schema=Ticket) # nyers JSON string
try:
return Ticket.model_validate_json(raw) # sikeres -> visszatér
except ValidationError as e:
# a hibás választ és a konkrét hibát visszaadjuk javításra
messages.append({"role": "assistant", "content": raw})
messages.append({"role": "user",
"content": f"A séma sérült: {e}. Javítsd, csak JSON-t adj."})
raise RuntimeError("A modell nem adott valid sémát a próbálkozások után")
A minta három elvet testesít meg: a hiba nem csendes (kivétel dobódik, ha végleg nem sikerül), a modell a saját hibájából tanul (a validációs üzenetet visszakapja), és van felső korlát (nem pörög örökké). Production rendszerben ehhez még backoff, timeout és logolás társul.
Retry vs. repair: melyiket mikor?
A kettő nem ugyanarra való, és a rossz választás pénzt és latency-t éget. A hibatípus dönti el, melyiket használd.
Retry (újrafuttatás)
- Tranziens hálózati hiba, timeout, 5xx
- Rate limit (429) — backoffal
- Ritka, véletlenszerű formai csúszás
- Olcsó: nem növeled a kontextust
Repair (javíttatás)
- Szisztematikus validációs hiba (rossz enum, hiányzó mező)
- A modell „majdnem jó" választ adott
- Drágább: a hibás választ + hibaüzenetet is elküldöd
- Ha 1-2 repair után sem jó → dobj hibát, ne pörögj
Streaming és részleges kimenet
Ha a választ streameled (token-onként érkezik, hogy a felhasználó gyorsabban lásson valamit), akkor a JSON menet közben szintaktikailag érvénytelen — egy lezáratlan objektum. A validációt csak a teljes válasz megérkezése után futtathatod le. Ha közben mégis meg akarod jeleníteni a részleges kimenetet, használj partial-parse könyvtárat, ami a hiányzó zárójeleket toleránsan kezeli — de a végleges döntést mindig a teljes, validált objektumra alapozd.
Ki dönt a végrehajtásról?
A structured output és a tool calling könnyen kelti azt a látszatot, hogy a modell „intézi a dolgokat". Valójában a modell mindig csak egy javaslatot ad: egy strukturált objektumot vagy egy tool-hívást. Az, hogy ebből tényleg történik-e valami, és mi, kizárólag a te alkalmazáskódodon múlik.
Vegyünk egy példát: a modell visszaad egy `{"action": "refund", "amount": 50000, "user_id": "u_123"}` javaslatot. A séma garantálja, hogy az `amount` szám és a `user_id` string. De hogy szabad-e egyáltalán visszatéríteni, jogosult-e rá az adott felhasználó, a limiten belül van-e az összeg, és nem duplikált-e a kérés — ezt mind a normál alkalmazáskódnak kell eldöntenie, nem a modellnek. A modell javaslata a bemenet, nem a döntés.
def handle_refund(proposal: Refund, current_user):
# 1. Jogosultság: a modell javaslata itt semmit nem számít
if not current_user.can_refund(proposal.order_id):
raise PermissionError("Nincs jogosultság a visszatérítéshez")
# 2. Üzleti limit
if proposal.amount > REFUND_LIMIT:
return escalate_to_human(proposal) # emberi jóváhagyás
# 3. Idempotencia: kétszer ne fizess
if already_processed(proposal.order_id):
return "already refunded"
# csak most, minden ellenőrzés után
return payment_api.refund(proposal.order_id, proposal.amount,
idempotency_key=proposal.order_id)
Amit ebből tanulj: a séma a függvény bemenetének formáját garantálja, de a három ellenőrzés (jogosultság, limit, idempotencia) mind determinisztikus alkalmazáskód. Egyik sem bízható a modellre.
Ez nem csak jó gyakorlat, hanem biztonsági alapelv is: ha a végrehajtási jogot a modellre bízod, akkor egy megfelelően megfogalmazott rosszindulatú input (excessive agency, confused deputy) tetszőleges műveletet válthat ki. A támadási felületről és a védekezésről bővebben a 15. AI Security fejezet szól — de a mag ugyanez: a valódi biztonsági szabályokat a modellen kívül kell kikényszeríteni.
Kockázat szerinti kapuzás
Nem minden művelet egyforma kockázatú — a kontrollt a hatás súlyosságához igazítsd. Egy olvasási művelet mehet automatikusan, egy irreverzibilis pénzügyi művelethez emberi jóváhagyás kell.
| Művelet típusa | Példa | Ajánlott kapu |
|---|---|---|
| Csak olvasás | Rendelés státusz lekérdezése | Automatikus, séma + validáció elég |
| Visszafordítható írás | Címke hozzáadása, draft mentése | Automatikus, de logolva és auditálva |
| Pénzügyi / irreverzibilis | Visszatérítés, törlés, kiküldés | Limit + emberi jóváhagyás küszöb felett (human-in-the-loop) |
Összefoglalás
Ha a modell kimenetét egy másik rendszer dolgozza fel, ne szabad szöveget kérj, hanem sémára kötött, strukturált választ. A JSON Schema, a structured outputs és a tool calling adja a forma garanciáját — a promptba írt „adj JSON-t" tipikus pár százalékos hibaarányát nagyságrendekkel csökkentve; a Pydantic/Zod validáció, az enumok és a diszkriminált unionök pedig a saját kapudnál szűrik ki azt, ami mégsem stimmel. A séma verziózása és az argumentumok szigorú ellenőrzése tartja karban ezt a réteget hosszú távon, és a jó mezőleírások gyakran többet javítanak a minőségen, mint egy plusz prompt-mondat.
A megbízhatóság ott dől el, ahol a modell hibázik: tervezett retry (tranziens hibára) és repair (validációs hibára) stratégiával, felső korláttal és backoffal, idempotency key-jel a mellékhatásos műveleteknél, és determinisztikus utófeldolgozással, amely nem bízza a modellre azt, amit a kód pontosan elvégez. A legfontosabb elv viszont a jogosultságé: a modell javasol, de a végrehajtásról, az üzleti szabályokról és a jogosultságokról mindig a te kódod dönt — a kockázathoz igazított kapuzással, egészen az emberi jóváhagyásig a súlyos műveleteknél. Ezzel a réteggel a nemdeterminisztikus szövegjóslóból megbízhatóan integrálható szoftverkomponens lesz.