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.
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.
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ó.
| SSE | WebSocket | |
|---|---|---|
| Irány | Egyirányú (szerver → kliens) | Kétirányú, teljesen duplex |
| Tipikus use case | Token-streaming, progress | Valós idejű hang, félbeszakítás, kollaboráció |
| Protokoll | Sima HTTP, auto-reconnect | Külön upgrade, saját heartbeat kell |
| Üzemeltetés | Egyszerű (proxy, CDN barát) | Bonyolultabb (sticky session, skálázás) |
| Mikor válaszd? | Klasszikus szöveges chat | Voice 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 plandiffjé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
pgvectorkiterjeszté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.
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.
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_flighttoolt hívja, nem asearch_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.
# 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.
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éteg | Mit mér? | Determinisztikus? | Hol fut? |
|---|---|---|---|
| Unit / integration | Saját kód, glue, adatréteg | Igen (mock modell) | Minden commitnál, CI |
| Schema / contract | Interfész-illeszkedés | Igen | CI |
| Tool-mock | Agent döntése, argumentumok | Igen (rögzített válasz) | CI |
| End-to-end eval | Válaszminőség eloszlásként | Nem — statisztikai | Eval gate, nightly |
| Chaos / rate-limit | Degradáció, ellenállóság | Részben | Staging, 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.
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.
Ö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.