Ugrás a tartalomhoz
03

Structured output és megbízható model integration

Ha egy másik rendszer dolgozza fel a modell válaszát, a szabad szöveg a legrosszabb, amit adhatsz. Ebben a fejezetben megtanulod, hogyan kényszerítsd a modellt kiszámítható, sémára illeszkedő, validált kimenetre — és hogyan kezeld megbízhatóan azt is, amikor mégis mellétrafál.

🟢 Alapkompetenciák ⏱ ~14 perc olvasás Felülvizsgálva:

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ú JSONStructured output (séma)Tool calling
Formai garanciaNincs (best effort)Erős (constrained decoding)Erős
Mikor használdPrototípus, kísérletAdatkinyerés, osztályozás, extractionMűveletek, több lehetséges akció közti választás
Több lehetőség?NehézkesEgy fix alakA 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:

JSON Egy sentiment-válasz JSON Schemája
{
  "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:

Python Structured output Pydantic + OpenAI SDK-val
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.

Pydantic modell JSON Schema auto-generált Constrained decoding Validáció Pydantic Típusos objektum
A happy path: egyetlen Pydantic modell a közös forrás — belőle generálódik a séma, ehhez köti a provider a generálást (constrained decoding), a nyers választ a saját validációd is átengedi, és a végén már típusos objektummal dolgozol, string-parse nélkül.

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.

Python Beszédes mezőleírások Pydanticben
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.

Python Diszkriminált union Pydanticben
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.

Modell hívás Validáció séma + Pydantic Elfogadva típusos objektum Repair hiba vissza modellnek valid hibás max próba → hiba dobása
A retry–repair loop: minden modellhívás kimenetét a séma-validáció szűri. Ami érvényes, típusos objektumként tovább mehet; ami hibás, a konkrét validációs hibaüzenettel visszakerül a modellhez javításra — de csak egy felső próbaszámig, utána a folyamat tisztán hibát dob, nem pörög a végtelenségig.
  • 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:

Python Retry + repair validációs hibára
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.

Python A modell javasol, a kód dönt
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.

Modell javaslata Jogosultság authz Üzleti limit amount ≤ max Idempotencia nem duplikált? Végrehajtás refund Elutasítás / emberi jóváhagyás mind OK bukás
A séma a formát adja, a döntést nem: a modell javaslata három determinisztikus kapun megy át — jogosultság, üzleti limit, idempotencia. Bármelyik bukása elutasításhoz vagy emberi jóváhagyáshoz vezet; a művelet csak mindhárom átengedése után hajtódik végre.

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ípusaPéldaAjánlott kapu
Csak olvasásRendelés státusz lekérdezéseAutomatikus, séma + validáció elég
Visszafordítható írásCímke hozzáadása, draft mentéseAutomatikus, de logolva és auditálva
Pénzügyi / irreverzibilisVisszatérítés, törlés, kiküldésLimit + 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.