Ugrás a tartalomhoz
18

Szoftverfejlesztési tudás

Az LLM-modellek varázsa könnyen elfeledteti, hogy amit építesz, az a nap végén egy szoftverrendszer. A megbízhatóságot, a sebességet és a biztonságot ugyanaz a mérnöki alaptudás adja, mint bármely más production alkalmazásnál — plusz néhány AI-specifikus fogás, főleg a tesztelésben.

🔵 Alkalmazásépítés ⏱ ~16 perc olvasás Felülvizsgálva:

Miért érdekel ez téged?

Egy jó AI engineer továbbra is elsősorban jó software engineer. Az LLM csak egy újabb — nemdeterminisztikus, fizetős, hálózaton túli — komponens a rendszeredben; minden más köré ugyanaz a mérnöki tudás épül, ami eddig is elválasztotta a demót a production rendszertől.

Ez a fejezet szándékosan checklist jellegű: nem új elméletet tanít, hanem összeszedi azt az alapot, amire a többi fejezet épül. Ha ezek megvannak, az AI-réteg (RAG, agentek, evalok) egy erős házra kerül rá. Ha hiányoznak, a legokosabb prompt sem ment meg egy szétcsúszó deploytól vagy egy lekezeletlen timeouttól.

A gyakorlatban ez így néz ki: egy Jupyter notebookban 20 perc alatt összerakott RAG-demó lenyűgöző. Ugyanaz a logika production alá helyezve viszont hirtelen felveti a kérdéseket, amiket ez a fejezet jár körbe — mi történik, ha 200 kérés érkezik egyszerre, ha a provider 429-cel válaszol, ha a válasz 40 másodpercig streamel egy elszakadó mobilkapcsolaton, ha holnap kicserélik alattad a modellt. A demó és a production közötti szakadékot nem a modell mélyebb ismerete hidalja át, hanem a klasszikus mérnöki fegyelem.

Erős alap kell

Ezt a listát tekintsd önellenőrzésnek: nem kell mindegyikben világbajnoknak lenned, de mindegyiket értened kell annyira, hogy tudatosan használd. Csoportokba rendeztem őket aszerint, hogy a rendszer melyik rétegét szolgálják.

Nyelvek és aszinkronitás

  • Python Az AI engineering lingua francája: itt van a legtöbb provider SDK, adatfeldolgozó és ML könyvtár. Backend, glue-kód, eval pipeline — jó eséllyel ebben írod. Ismerd a type hinteket, a dataclass/Pydantic modelleket és az asyncio-t; ez a három együtt visz el a demótól a stabil szolgáltatásig.
  • TypeScript A frontend és az edge/serverless réteg nyelve, és egyre több agent SDK itthon van. A típusosság a streaming UI-nál és a tool-sémáknál is sokat segít: a zod-séma egyszerre validál futásidőben és ad statikus típust. Ha a chat-felületet és a backendet is te viszed, valószínűleg Python + TypeScript párost fogsz használni.
  • Async / await Az LLM-hívás lassú, hálózaton túli művelet — egy válasz jellemzően 1–30 másodperc, tool-használó agenteknél percek. A párhuzamos, nem blokkoló futás nem opció, hanem alapfeltétel: párhuzamos retrieval, több modellhívás egyszerre, streaming. Egy szinkron kódban egyetlen 20 másodperces hívás blokkolja a teljes worker-t; asyncben ugyanaz a worker közben több száz kérést kiszolgál.

A különbség kézzelfogható: ha egy kérés öt független forrásból húz kontextust, a szinkron megoldás sorban várja ki mind az ötöt (5 × 200ms = 1s), az async pedig egyszerre indítja őket (~200ms). Az alábbi minta a leggyakoribb async-nyereséget mutatja — a párhuzamos retrievalt.

Python Párhuzamos retrieval asyncio.gather-rel
import asyncio

async def retrieve(source, query):
    # mindegyik hálózati hívás; egyenként ~200ms
    return await source.search(query)

async def gather_context(sources, query):
    # az 5 keresés EGYSZERRE fut, nem sorban
    results = await asyncio.gather(
        *(retrieve(s, query) for s in sources)
    )
    return [chunk for r in results for chunk in r]

# szinkron: ~1000ms, async: ~200ms — ugyanaz a kód, 5x gyorsabb

Mit tanulj belőle: az async nem díszítés, hanem a latency egyik legolcsóbb eszköze. Ahol több független I/O-művelet van (retrieval, több tool, több modell), ott a gather szinte ingyen adja a gyorsulást — cserébe a hibakezelést gondosan kell megírni, mert bármelyik ág elszállhat.

API- és adatréteg

  • FastAPI (vagy hasonló) Aszinkron, típusos API-framework, amiből percek alatt lesz streamelő végpont és automatikus OpenAPI séma-dokumentáció. A választás nem szent (Litestar, Express, Hono is jó), a mintázatok viszont közösek: aszinkron handler, dependency injection, séma-alapú validáció a határon.
  • Pydantic / Zod Séma-validáció Pythonban, illetve TypeScriptben. Ez a határőr a modell strukturált kimenete és az alkalmazáskód között (lásd 3. Structured output). Nemcsak validál: dokumentál, típust ad, és a hibaüzenete pontosan megmondja, melyik mező rossz — ezt vissza is adhatod a modellnek javításra.
  • SQL Az adat 90%-a végül relációs táblában landol. Indexek, join-ok, lekérdezés-tervek (EXPLAIN ANALYZE) olvasása — az AI-appod adatrétege is ezen áll vagy bukik. Egy hiányzó index a chat-history lekérdezésén észrevétlenül 10ms-ból 2s-ot csinál, mihelyt a tábla milliós lesz.
  • REST A rendszerek közti kommunikáció közös nyelve: erőforrások, HTTP-igék, státuszkódok, idempotencia. A providerek és a tool-jaid nagy része is így beszél. Az idempotency key különösen fontos AI-nál: ha egy drága generálást retry-olsz, ne fizess érte kétszer és ne hozz létre két rekordot.
  • WebSocket / SSE A tokenről tokenre érkező válasz kézbesítésének csatornái. SSE (Server-Sent Events) az egyszerű, egyirányú streamre, WebSocket a kétirányú, valós idejű (pl. hang, félbeszakítás) interakcióra. A legtöbb szöveges chat-hez az SSE bőven elég és sokkal egyszerűbb üzemeltetni.
  • OAuth / OIDC A ki vagy? és mit tehetsz? kérdések szabványos megválaszolása. Scoped tokenek nélkül az agent-tooljaid tárva-nyitva állnak (lásd 15. AI Security). Egy agent nevében indított tool-hívásnál a felhasználó jogosultságát kell átvinni, nem egy mindenható service accountét.

A streaming a legjellemzőbb AI-specifikus API-minta: a felhasználó ne 20 másodpercig nézzen egy pörgő ikont, hanem lássa, ahogy épül a válasz. FastAPI-ban ez pár sor.

Python Streaming végpont FastAPI-val (SSE)
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

@app.post("/chat")
async def chat(req: ChatRequest):
    async def token_stream():
        async for chunk in llm.stream(req.messages):
            # SSE formátum: minden esemény "data: ..." sorral
            yield f"data: {chunk.text}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(token_stream(),
                             media_type="text/event-stream")

Mit tanulj belőle: a streaming nemcsak UX-fícsör, hanem az észlelt latency legerősebb eszköze — az első token 300ms-en belül megjelenhet, miközben a teljes válasz 20 másodperc. Cserébe fel kell készülnöd a félbeszakadt streamre: mi történik, ha a kliens elnavigál a 700. tokennél? (Lásd 19. Product- és UX-ismeretek.)

A két streaming-csatorna közti választás gyakori döntési pont; az alábbi táblázat összefoglalja, mikor melyik jó.

SSEWebSocket
IrányEgyirányú (szerver → kliens)Kétirányú, teljesen duplex
Tipikus use caseToken-streaming, progressValós idejű hang, félbeszakítás, kollaboráció
ProtokollSima HTTP, auto-reconnectKülön upgrade, saját heartbeat kell
ÜzemeltetésEgyszerű (proxy, CDN barát)Bonyolultabb (sticky session, skálázás)
Mikor válaszd?Klasszikus szöveges chatVoice agent, élő közbevágás

Infrastruktúra és üzemeltetés

  • Docker Reprodukálható futtatókörnyezet: a nálam működött nem elfogadható válasz, ha a konténer mindenhol ugyanaz. Az AI-függőségek (CUDA, torch, modellkönyvtárak) miatt itt duplán számít — egy pinnelt base image és lockfile nélkül a build két hónap múlva már mást húz le.
  • Kubernetes Konténerek skálázása, öngyógyítás, rolling deploy. Az inference terhelése sajátos (hosszú kérések, GPU-igény, lökésszerű forgalom), de a kiindulás ugyanaz a K8s-tudás (lásd 16. AI Platform). A HPA-t (autoscaler) itt nem CPU-ra, hanem queue-hosszra vagy egyidejű kérésre érdemes kötni.
  • Terraform Infrastructure-as-code: a felhőerőforrásaidat verziózott, felülvizsgálható kódként kezeled, nem kézzel kattintgatod. Kevesebb kézi hiba, több reprodukálhatóság — és egy code review a terraform plan diffjén megfogja azt, amit egy konzolos kattintás nem.
  • CI/CD Automatikus build, teszt és deploy. AI-rendszernél a pipeline-ba eval gate is kerül: ha a merge candidate eval-eredménye a baseline alá esik (pl. a válaszminőség 92%-ról 85%-ra), a pipeline megállítja a kiadást (lásd 16. AI Platform). A prompt- és modellváltás így nem csúszik észrevétlenül productionbe.

Adattárolás és üzenetkezelés

  • Queue-k Aszinkron, megbízható munkavégzés: a hosszú agent-futásokat sorba teszed, nem a HTTP-kérésben várakoztatod. Backpressure, retry, dead-letter queue — mind innen jön. Ha egy agent-feladat 3 percig fut, semmiképp ne egy szinkron HTTP-kapcsolat tartsa nyitva; tedd queue-ba, adj vissza egy job ID-t, és streameld/pollold az eredményt.
  • Redis Gyors in-memory tár cache-hez, rate limithez, sessionhöz és elosztott lockhoz. Egy semantic vagy response cache backendje gyakran ez — egy jól eltalált prompt-cache 30–60%-kal is leviheti a modellhívások számát és költségét ismétlődő kérdéseknél.
  • PostgreSQL A megbízható, mindenevő relációs adatbázis — a pgvector kiterjesztéssel ráadásul vektorkeresésre is jó, így sokszor a RAG-indexed is elfér benne (lásd 4. RAG). Amíg pár millió chunk alatt vagy, ne vezess be külön vektoradatbázist: a Postgres egyszerűbb, tranzakciós, és egy helyen van a metaadattal.
  • Object storage Nagy, strukturálatlan fájlok (feltöltött PDF-ek, képek, hangfájlok, generált artifactok) otthona, pl. S3 vagy kompatibilis. Olcsó (~0,02 USD/GB/hó nagyságrend), gyakorlatilag korlátlanul skálázódó, presigned URL-lel közvetlenül a kliensnek adható — a nagy fájlt sose a saját szolgáltatásodon vezesd át.

A tárolási réteg leggyakoribb kezdő hibája, hogy egyetlen eszközbe akarnak mindent belegyömöszölni. Az alábbi döntési szempontok segítenek szétválasztani, mi hová való.

Melyik tárba tedd?

  • Redis: rövid életű, gyorsan kell — session, rate-limit számláló, response cache, lock. Ha elveszik, nem tragédia.
  • PostgreSQL: tartós, tranzakciós, lekérdezhető — user, üzenet-history, audit log, és pgvectorral a RAG-index is.
  • Object storage: nagy bináris — feltöltött dokumentumok, generált képek/hang, eval-artifactok.
  • Queue: nem adat, hanem munka — háttérfeladat, batch-generálás, hosszú agent-futás.

Gyakori hibák

  • Nagy PDF-et base64-ben az adatbázisba tolni object storage helyett.
  • Chat-historyt csak Redisben tartani — egy restart után eltűnik.
  • Rate-limitet az app memóriájában számolni, így több replikánál értelmét veszti.
  • 3 perces agent-futást szinkron HTTP-kérésben várakoztatni queue helyett.

Biztonság és megfigyelhetőség

  • Distributed tracing Egy kérés útjának végigkövetése szolgáltatásokon át, egyetlen trace ID mentén. AI-rendszernél a span-ekhez modell-, token- és tool-adatok is kerülnek (lásd 14. Observability). Egy lassú válasznál a trace azonnal megmutatja, a retrieval, a reranker vagy a modellhívás vitte-e el a 4 másodpercet.
  • Testing A determinisztikus kódod (validáció, üzleti szabályok, elágazások) klasszikus tesztelése. Az AI-specifikus részt lentebb külön kibontom — ott van a nehéz része, mert a modell kimenete futásról futásra változhat.
  • Observability Logok, metrikák, trace-ek együtt: nemcsak mi romlott el?, hanem miért? is megválaszolható. Egyetlen request-duration metrika kevés — AI-nál külön mérd a token/kérést, a költséget, a cache hit rate-et és a tool-hibaarányt.
  • Cloud security IAM, hálózati szegmentáció, titokkezelés, least privilege. Az AI-tool-ok pont annyi jogot kapjanak, amennyi feltétlen kell — se többet. Egy „olvasd ki az adatot” tool sose kapjon írási jogot, mert egy prompt injection akkor is csak olvasni tud (lásd 15. AI Security).

A felsorolt rétegek nem különálló pontok, hanem egyetlen kérés útján állnak össze rendszerré. Az alábbi ábra egy tipikus AI-kérés folyamát mutatja a klienstől a modellig és vissza — látszik, hol lép be a cache, a queue és a retrieval.

Client API · FastAPI Retrieval pgvector LLM provider Redis cache Queue · async job cache hit → kész hosszú job → 202 token-stream vissza (SSE)
Egy tipikus AI-kérés útja: az API először a cache-t nézi (találat esetén azonnal válaszol), a hosszú feladatot queue-ba teszi, egyébként retrievalt végez és az LLM providert hívja — a válasz pedig tokenről tokenre streamel vissza a kliensnek. A checklist minden eleme ezen az úton kap szerepet.

Tesztelés: így tesztelj AI-t tartalmazó rendszert

A klasszikus tesztelés egy alapfeltevésen áll: ugyanaz a bemenet ugyanazt a kimenetet adja. Egy LLM ezt a feltevést azonnal felrúgja — ugyanarra a promptra máskor mást válaszol, drágán, lassan, és a provider csendes frissítése bármikor megváltoztathatja a viselkedést. A megoldás nem az, hogy feladod a tesztelést, hanem hogy kigyomlálod a nemdeterminizmust minden szintről, ahol lehet, és külön kezeled azt a réteget, ahol nem lehet.

End-to-end eval nemdeterminisztikus Tool-mock agent döntése + argumentumok Schema / contract teszt interfész-illeszkedés Unit / integration teszt determinisztikus · mock modell kevés · drága statisztikai, ritkán sok · olcsó CI-ben, minden commit
Az AI-kiegészített tesztpiramis: alul sok, olcsó, determinisztikus teszt (unit, integration, schema, contract, tool-mock rögzített válaszokkal), felül kevés, drága, statisztikai end-to-end eval. A nemdeterminizmust lefelé kigyomlálod, felül szembenézel vele.

Gondolj rá úgy, mint egy időjárásfüggő alkatrészt tartalmazó gépre: a rögzített alkatrészeket precízen leméred, az időjárás-érzékeny részt pedig zárt kamrában, előre rögzített körülmények között teszteled — és külön, statisztikai méréssel figyeled, hogyan viselkedik a valós, változó időben.

A determinisztikus réteg tesztjei

Ez a réteg pont ugyanolyan, mint bármely más appban — és a hibáid nagy része itt fogható meg a legolcsóbban, még mielőtt egyetlen tokent is elégetnél.

  • Unit tesztek A determinisztikus kódod (parser, validáció, üzleti szabály, prompt-összeállító függvény) izolált tesztelése LLM-hívás nélkül. Például: a context-assembler tényleg a legfrissebb 5 üzenetet és a top-3 chunkot rakja-e össze, a megadott token-budget alatt marad-e? Ez gyors (ms), ingyenes és a hibáid nagy részét itt éred el.
  • Integration tesztek Több komponens együttműködése: az adatbázis, a queue, a cache és az API-réteg tényleg összeér-e. A modell helyére itt jellemzően mock vagy rögzített válasz kerül, hogy a teszt gyors és megismételhető maradjon. Konténerizált függőségekkel (pl. testcontainers) éles Postgres/Redis ellen futtatod, nem in-memory hamisítvánnyal.
  • Contract tesztek Azt igazolják, hogy két rendszer (pl. a te szolgáltatásod és egy tool-szerver, vagy a model gateway) által megosztott interfész stabil marad. Ha a provider vagy egy MCP-szerver (lásd 8. Protokollok) megváltoztatja a szerződést, ez a teszt bukik el, nem a felhasználói élményben.
  • Schema tesztek A modell strukturált kimenete tényleg illeszkedik-e a Pydantic/Zod sémára: kötelező mezők, enumok, típusok, tartományok. Ez a mesterséges intelligencia kimenete és az alkalmazáslogika közti sorompó automatikus ellenőrzése (lásd 3. Structured output). A séma nemcsak a happy path-ot ellenőrzi: teszteld a hiányzó mezőt és a rossz enumot is, mert éles hívásnál ezek elő fognak jönni.
  • Tool mockok Az agent tool-jait tesztben kicseréled kiszámítható, gyors változatra, hogy ne igazi API-t hívj (fizetős, lassú, mellékhatásos). Így külön tudod tesztelni, hogy az agent a helyes toolt hívja-e a helyes argumentumokkal — pl. hogy egy „foglald le a repülőt” intent tényleg a book_flight toolt hívja, nem a search_flight-ot.

A nemdeterminizmus kiiktatása

Ahol nem tudod kikerülni a modellt, ott legalább rögzítsd a viselkedését, hogy a saját kódodat gyorsan és megismételhetően tesztelhesd.

  • Recorded provider responses Egyszer elmented egy valós modellválaszt (VCR/cassette minta), és utána a teszt ezt játssza vissza élő hívás helyett. Így a teszt ingyenes, ezredmásodperc alatt fut, és minden futáskor pontosan ugyanazt adja — ez a determinizmus egyik alappillére. A rögzített választ frissítsd, amikor tudatosan modellt/promptot váltasz, és a diffet reviewold.
  • Deterministic fixture-ök Rögzített bemenetek és elvárt kimenetek, amelyekre a logikádat építed. Ahol determinizmust erőltethetsz (temperature=0, seed, pinnelt modellverzió, rögzített időbélyeg, befagyasztott „mai dátum”), ott erőltesd. Vigyázat: a temperature 0 sem garantál bitre azonos kimenetet, csak csökkenti a szórást — a schema-assertion megbízhatóbb, mint a szó szerinti egyezés.

Az alábbi minta a két technikát köti össze: a hívást rögzített válasszal helyettesíti, de a sémát és az üzleti elvárást élesben ellenőrzi.

Python Rögzített válasz + séma-teszt: determinisztikus AI-teszt
# A modellhívást mockoljuk egy KORÁBBAN RÖGZÍTETT válasszal,
# a sémát viszont élesben validáljuk — a teszt gyors és determinisztikus.
def test_extract_ticket(monkeypatch):
    recorded = load_fixture("ticket_response.json")  # rögzített provider-válasz
    monkeypatch.setattr(llm, "call", lambda **_: recorded)

    ticket = extract_ticket("Nem tudok belépni, 500-as hibát kapok")

    assert ticket.priority in {"low", "medium", "high"}  # enum a sémából
    assert ticket.category == "auth"                       # determinisztikus elvárás
    assert 0.0 <= ticket.confidence <= 1.0                  # tartomány

Mit tanulj belőle: ne a szó szerinti szöveget assertáld (az flaky lesz), hanem a strukturális invariánsokat — enum-tagság, tartomány, kötelező mezők. Ezek akkor is állnak, ha a modell másképp fogalmaz.

Tool-mock a gyakorlatban

Az agentnél a legérdekesebb kérdés nem az, hogy a tool jól fut-e, hanem hogy az agent egyáltalán a helyes toolt hívja-e. Ezt a döntést kell izoláltan tesztelni.

Python Tool-hívás verifikálása mockkal
def test_agent_refunds_via_correct_tool(monkeypatch):
    calls = []
    fake_refund = lambda **kw: calls.append(("refund", kw)) or {"ok": True}
    monkeypatch.setattr(tools, "issue_refund", fake_refund)

    run_agent("Kérek visszatérítést a 12345-ös rendelésre")

    assert len(calls) == 1                       # pontosan egyszer hívott toolt
    assert calls[0][0] == "refund"               # a HELYES toolt
    assert calls[0][1]["order_id"] == "12345"    # a HELYES argumentummal

Mit tanulj belőle: a tool-mock nemcsak gyorsít és spórol, hanem megfogja a legveszélyesebb agent-hibát: a rossz mellékhatást. Egy éles issue_refund teszt közben valódi pénzt utalna — a mock ezt kizárja, miközben a döntést így is verifikálod.

Szembenézés a nemdeterminizmussal

Van egy réteg, ahol nincs értelme kikerülni a bizonytalanságot: itt eloszlásként méred a minőséget, nem egyetlen igaz/hamis assertionként.

  • End-to-end evalok Itt már nem kikerülöd a nemdeterminizmust, hanem szembenézel vele: a teljes rendszert valós vagy realisztikus feladatokon futtatod, és eloszlásként méred a minőséget (pl. „a 200 tesztkérdés 91%-ára helyes a válasz”), nem egyetlen assertionként. Ez a klasszikus teszt és az eval találkozási pontja (lásd 11. Evals).
  • Production trace replay Valós production trace-eket (lásd 14. Observability) veszel elő, és visszajátszod őket egy változás után: nem rontottad-e el azt, ami eddig működött? A valós forgalom a legjobb regressziós adathalmaz — a felhasználók olyan bemeneteket adnak, amiket te sose találnál ki.
  • Chaos testing Szándékosan hibát injektálsz: leesik a provider, megszakad a hálózat, elszáll egy tool, 500ms helyett 30s a válasz. Azt teszteled, hogy a rendszered elegánsan degradál-e (fallback modell, retry, részleges eredmény), nem pedig összeomlik. Egy AI-appnál ez különösen fontos, mert a külső provider elérhetetlensége nem ha, hanem mikor kérdés.
  • Rate-limit és timeout tesztek A provider rate limitje (HTTP 429) és a lassú válasz nem kivétel, hanem üzemszerű állapot. Teszteld, hogy a retry-backoff, a circuit breaker és a timeout-kezelés tényleg működik-e — mielőtt a production forgalom teszteli le helyetted. Konkrétan: 429-re exponenciális backoff jitterrel, és egy felső határ, ami után elegánsan feladod, nem végtelenségig próbálkozol.

A teljes kép egy AI-kiegészített tesztpiramis: alul sok olcsó, determinisztikus teszt, felül kevés drága, statisztikai eval. A rétegek szerepét az alábbi táblázat foglalja össze.

RétegMit mér?Determinisztikus?Hol fut?
Unit / integrationSaját kód, glue, adatrétegIgen (mock modell)Minden commitnál, CI
Schema / contractInterfész-illeszkedésIgenCI
Tool-mockAgent döntése, argumentumokIgen (rögzített válasz)CI
End-to-end evalVálaszminőség eloszláskéntNem — statisztikaiEval gate, nightly
Chaos / rate-limitDegradáció, ellenállóságRészbenStaging, időszakos

Gyakori hibák és döntési szempontok

A mérnöki alapok hiánya ritkán látszik a demón; a production-ben viszont mindig előjön. Az alábbi lista a leggyakoribb csapdákat és a hozzájuk tartozó döntéseket gyűjti össze — ezeket érdemes a rendszertervezéskor, nem az első incidensnél végiggondolni.

Gyakori hibák

  • Szinkron hívás mindenre: egy 20s-os LLM-hívás blokkolja a workert, és 10 párhuzamos kérésnél összeáll a szolgáltatás.
  • Nincs timeout: egy beragadt provider-kapcsolat percekig fogja a connection poolt, míg minden elfogy.
  • Naiv retry: azonnali, korlátlan újrapróbálkozás 429-re csak tovább üti a providert (retry storm).
  • Modellverzió nincs pinnelve: egy csendes provider-frissítés éjjel megváltoztatja a viselkedést, és senki sem tudja, miért.
  • Titok a kódban vagy promptban: az API-kulcs a repóban vagy a system promptban landol.
  • Nincs költség-megfigyelés: egy elszabadult agent-loop egy éjszaka alatt több száz dollárt éget el észrevétlenül.

Döntési szempontok

  • Sync vagy async? Ha van hálózati I/O (és AI-nál mindig van), async — és a hosszú feladat queue-ba.
  • Cache-eljek? Ha ismétlődnek a kérések és a válasz stabil, igen (Redis) — jelentős költség- és latency-nyereség.
  • Saját inference vagy API? Kezdd managed API-val; self-hosting csak akkor, ha a volumen vagy az adatszabályozás indokolja (lásd 16. AI Platform).
  • Fallback stratégia? Dönts előre: másik modell, cache-elt válasz, vagy elegáns hibaüzenet — de sose néma összeomlás.
  • Idempotencia kell? Ha a művelet drága vagy mellékhatásos (fizetés, e-mail), igen — idempotency key-jel.
Python Robusztus provider-hívás: timeout + backoff + fallback
import asyncio, random

async def call_with_resilience(messages, retries=3, timeout=15):
    for attempt in range(retries):
        try:
            return await asyncio.wait_for(
                primary_llm.call(messages), timeout=timeout)
        except (RateLimitError, asyncio.TimeoutError):
            if attempt == retries - 1:
                break
            # exponenciális backoff jitterrel — nem retry storm
            await asyncio.sleep(2 ** attempt + random.random())
    # elegáns degradáció: olcsóbb/gyorsabb fallback modell
    return await fallback_llm.call(messages)

Mit tanulj belőle: a három védelmi réteg — timeout, jitteres backoff, fallback — együtt teszi a rendszert ellenállóvá. Egyik sem elég önmagában: a timeout nélkül beragadsz, backoff nélkül retry stormöt indítasz, fallback nélkül pedig a felhasználó üres kezet kap.

A három réteg együtt egy jól definiált vezérlési folyamot ad ki. Az alábbi ábra a döntéseket és az átmeneteket mutatja: mikor próbálkozol újra, és mikor váltasz fallbackre.

Hívás timeout=15s Hiba? 429 / timeout Retry-limit elérve? Backoff + jitter → újrapróbál Válasz kész Fallback modell nincs hiba hiba nem limit: igen
Az ellenálló provider-hívás vezérlési folyama: siker esetén kész a válasz; 429 vagy timeout esetén, amíg a retry-limit alatt vagy, jitteres backoff után újrapróbálsz — a limit elérésekor pedig fallback modellre váltasz, sose néma összeomlásra.

Összefoglalás

Ez a fejezet a rendszered gerincéről szól: az LLM izgalmas, de a megbízhatóságot a klasszikus szoftvermérnöki alapok adják — nyelvek és async, tiszta API- és adatréteg, konténerizált és kódként kezelt infrastruktúra, megbízható tárolás és üzenetkezelés, végül biztonság és megfigyelhetőség. Egy jó AI engineer ezekben otthon van, és az AI-réteget erre a házra építi rá.

A tesztelés az a pont, ahol az AI-rendszer tényleg más: a nemdeterminizmust kigyomlálod, ahol lehet (rögzített válaszok, mockok, deterministic fixture-ök, séma- és contract-tesztek), és szembenézel vele, ahol nem (end-to-end evalok, production trace replay, chaos- és rate-limit-tesztek). A tesztpiramis alján sok olcsó, determinisztikus teszt, a tetején kevés drága, statisztikai eval.

A production-t végül nem a modell mélyebb ismerete hozza el, hanem a fegyelem: minden külső hívásnak timeout, backoff és fallback; minden drága műveletnek idempotencia és költség-megfigyelés; minden kiadásnak eval gate. Ha ezt következetesen csinálod, a modell körüli bizonytalanságot mérhető, üzemeltethető szoftverré szelídíted — és pontosan ez választja el a demót a production rendszertől.