27.4 Adatkezelés és reprodukálhatóság külső modellnél

27.4.1 Cél és konkrét helyzet

A Northstar szintetikus rendelési adataihoz képzeljünk el egy bővítést: minden rendeléshez tartozhat egy rövid, opcionális ügyfélmegjegyzés. A feladat: azonosítsuk, mely megjegyzések utalnak szállítási sürgősségre vagy panaszra. A bemenet egy szöveg, a kimenet egy bináris címke: shipping_related (1, ha a szöveg szállítással kapcsolatos). Az elemzési egység egy megjegyzés. A modellt egy külső provider LLM API-ján keresztül hívjuk, nem helyben. A cél: úgy kialakítani a futtatási és értékelési protokollt, hogy a későbbi elemzés megismételhető legyen.

Előzetes becslés: egy kulcsszó-alapú baseline (például a „delivery”, „shipping”, „late” szavak keresése) körülbelül 70%-os pontosságot érhet el ezen a kis szintetikus készleten. Az LLM várhatóan jobb, de a cél itt nem a maximális pontosság, hanem a reprodukálhatósági szerződés bemutatása.

27.4.2 Fogalmi magyarázat

27.4.2.1 Model version

A külső modell esetén a modellverziót explicit módon kell rögzíteni. Egy API-név, mint például gpt-4o, egy alias, amely mögött a provider idővel más modellt is kiszolgálhat. A reprodukálhatósághoz a provider által kiadott, dátumozott verzióazonosítót kell használni, például gpt-4o-2024-08-06. A futtatási naplóba a modellverzió mellett a promptverzió és a kódverzió is bekerül.

Kézi kontroll: ha két futtatás ugyanazt a promptot és bemenetet kapja, de eltérő modellverzióval, a kimenetek eltérhetnek. A verzióhármas (kód, modell, prompt) együttesen azonosítja a futtatást.

27.4.2.2 Provider boundary

A provider határánál pontosan meg kell adni, mely adatok hagyják el a helyi környezetet. A titkos adatok (API kulcsok, személyes azonosítók) nem küldhetők feleslegesen. A szöveges bemenet elküldése előtt ellenőrizni kell, hogy az adatvédelmi szerződés megengedi-e a továbbítást. A Northstar szintetikus adat nem tartalmaz valós személyes adatot, így a példában a továbbítás megengedett, de a protokollt akkor is dokumentálni kell.

27.4.2.3 Caching

A gyorsítótár csökkenti a költségeket és a latenciát, de a cache kulcsának tartalmaznia kell a modellverziót, a promptverziót és a bemenetet. Ha a cache kulcsa csak a bemenetre épül, egy modellfrissítés után a régi eredményt kapjuk vissza, ami hamis reprodukálhatóságot mutat.

27.4.2.4 Non-determinism

Az LLM-kimenetek sztochasztikusak: ugyanaz a bemenet és hőmérséklet mellett is eltérhet a kimenet. A provider általában nem garantál bitazonos kimenetet még azonos seed mellett sem. Ezért az értékeléshez ismételt futtatásokból származó mintavételi bizonytalanságot kell számolni, például a tévesztési arány konfidenciaintervallumát (lásd Predikciós, paraméter- és Monte Carlo intervallum).

27.4.2.5 Evaluation freeze

Az értékelési protokollt előre rögzíteni kell: a tesztkészlet, a referencia megoldások, a metrikák és a döntési küszöbök. A tesztkészlet nem használható a promptfejlesztés során; a promptot validációs készleten iteráljuk, és a tesztkészlet csak a végső kiértékeléskor kerül elő. Ez a Küszöb, kalibráció és ritka esemény alfejezetben bemutatott küszöbválasztási elv kiterjesztése külső modellre.

27.4.3 Teljes minimális munkapélda

A következő kód szemléltető, nem futtatott. Célja a verziókezelés, a cache és a titkok kezelésének bemutatása. A mock LLM egy egyszerű kulcsszó-alapú osztályozót szimulál, hogy ne igényeljen külső API-t.

import hashlib
import json
import os
from pathlib import Path

# Futtatási konfiguráció: kód-, modell- és promptverzió
run_config = {
    "code_version": "commit:1a2b3c4",
    "model_version": "gpt-4o-2024-08-06",
    "prompt_version": "prompt-shipping-v3",
    "temperature": 0.0,
}

# Mock LLM: csak szemlélteti az interfészt
def llm_classify(text: str, config: dict) -> str:
    # Valós hívás helyett determinisztikus szimuláció
    keywords = {"delivery", "shipping", "late", "urgent"}
    if any(k in text.lower() for k in keywords):
        return "shipping_related"
    return "other"

# Cache réteg: kulcs = modellverzió + promptverzió + bemenet hash
cache = {}

def cached_classify(text: str, config: dict) -> str:
    key = (
        config["model_version"],
        config["prompt_version"],
        hashlib.sha256(text.encode()).hexdigest(),
    )
    if key in cache:
        return cache[key]
    result = llm_classify(text, config)
    cache[key] = result
    return result

# Titokkezelés: a kulcsot környezeti változóból olvassuk, nem a kódba
api_key = os.environ.get("LLM_API_KEY")
# A kód nem írja ki az api_key értékét a naplóba.

# Példa bemenetek
examples = [
    ("Please deliver before Friday, it is urgent.", "shipping_related"),
    ("I would like to add a gift note.", "other"),
    ("The last delivery was late.", "shipping_related"),
]

# Futtatás és naplózás
results = []
for text, expected in examples:
    predicted = cached_classify(text, run_config)
    results.append({
        "text": text,
        "expected": expected,
        "predicted": predicted,
        "model_version": run_config["model_version"],
        "prompt_version": run_config["prompt_version"],
    })

# Napló kiírása (api_key nélkül)
print(json.dumps(results, indent=2))

Elméletileg várt kimenet (szemléltető, nem mért):

[
  {
    "text": "Please deliver before Friday, it is urgent.",
    "expected": "shipping_related",
    "predicted": "shipping_related",
    "model_version": "gpt-4o-2024-08-06",
    "prompt_version": "prompt-shipping-v3"
  },
  {
    "text": "I would like to add a gift note.",
    "expected": "other",
    "predicted": "other",
    "model_version": "gpt-4o-2024-08-06",
    "prompt_version": "prompt-shipping-v3"
  },
  {
    "text": "The last delivery was late.",
    "expected": "shipping_related",
    "predicted": "shipping_related",
    "model_version": "gpt-4o-2024-08-06",
    "prompt_version": "prompt-shipping-v3"
  }
]

27.4.4 Eredmény értelmezése

A mock LLM mindhárom példát helyesen osztályozza, mert a kulcsszavak egyértelműek. A valódi LLM esetén a kimenet nem determinisztikus, ezért a három példa nem elegendő az értékeléshez: több száz vagy ezer megjegyzésre és ismételt futtatásokra van szükség. A cache kulcs helyesen tartalmazza a modell- és promptverziót, így egy modellfrissítés után a régi cache bejegyzések nem használhatók fel tévesen. A naplóban az api_key nem szerepel, csak a verzióadatok.

Külső modellhívásnál a modellazonosító önmagában nem garantál bitazonos reprodukciót. Rögzítsük az API-verziót, a paramétereket, az input hashét és a visszakapott kimenetet az adatvédelmi szabályok betartásával.

27.4.5 Feladatok

  1. Értelmezés. Miért nem elegendő az API-név (például gpt-4o) a reprodukálhatósághoz? Adj egy példát arra, hogyan változhat a kimenet azonos API-név mellett.

  2. Megvalósítás. Írj egy cached_llm_call függvényt, amely a model_version, prompt_version és input_text alapján tárol és visszakeres. A cache tárolja a kimenetet és a hívás időpontját is. Feltételezheted, hogy az llm_call(text, config) függvény adott.

  3. Hibadiagnózis. Egy kolléga a cache kulcsát csak hashlib.sha256(text.encode()).hexdigest() alapján képezi. A modellfrissítés után a régi cache bejegyzések miatt a rendszer stabilitást mutat, de a valódi kimenetek megváltoztak. Mi a hiba, és hogyan javítanád?

  4. Megváltozott feltétel. A feladat többosztályossá bővül: a megjegyzések három kategóriába sorolhatók: shipping_related, billing_related, other. Hogyan módosítanád a futtatási konfigurációt és az értékelési protokollt? Milyen további adatvédelmi megfontolást igényelne, ha a megjegyzések valós ügyfélneveket tartalmaznának?

27.4.6 Megoldások és indoklás

1. Értelmezés. Az API-név egy alias, amely mögött a provider idővel frissítheti a modellt. Például a gpt-4o alias 2024 augusztusában más súlyokat jelentett, mint 2025 januárjában. Azonos prompt és bemenet mellett a kimenet eltérhet, mert a modellsúlyok megváltoztak. A reprodukálhatósághoz a provider által kiadott, dátumozott verzióazonosítót kell rögzíteni, például gpt-4o-2024-08-06.

2. Megvalósítás. A cache függvény:

import hashlib
import time

cache = {}

def cached_llm_call(text: str, config: dict) -> str:
    key = (
        config["model_version"],
        config["prompt_version"],
        hashlib.sha256(text.encode()).hexdigest(),
    )
    if key in cache:
        return cache[key]["result"]
    result = llm_call(text, config)
    cache[key] = {
        "result": result,
        "timestamp": time.time(),
    }
    return result

3. Hibadiagnózis. A cache kulcs nem tartalmazza a modellverziót és a promptverziót. Ha a modell frissül, a régi bejegyzések továbbra is érvényesnek tűnnek, mert a bemenet hash-je változatlan. A javítás: a kulcsba be kell építeni a verziókat, például (model_version, prompt_version, input_hash). Így a modellfrissítés után a cache automatikusan új bejegyzéseket hoz létre.

4. Megváltozott feltétel. A futtatási konfigurációban a promptverziót módosítani kell a többosztályos feladatra, például prompt-shipping-billing-v1. A kimenet most három címkét vehet fel, ezért az értékeléshez konfúziós mátrixot és osztályonkénti pontosságot kell számolni. A referencia megoldásokat a tesztkészleten frissíteni kell. Ha valós ügyfélnevek szerepelnének, a provider felé történő továbbítás előtt anonimizálni kell a szövegeket, vagy a modellt helyben kell futtatni. Az adatvédelmi döntést a jogi és szervezeti szabályok szerint kell meghozni, nem technikai alapon.

27.4.7 Gyakori tévedés ellenpéldával

Gyakori tévedés, hogy az API-név rögzített modellt garantál. Példa: egy kutató a gpt-4o alias-t használja, és a kísérletet 2024 augusztusában futtatja. Három hónappal később a provider frissíti a modellt ugyanazon alias alatt; a kutató a régi prompttal újra futtat, és eltérő eredményt kap. A hiba oka, hogy a modellverzió nem volt rögzítve a futtatási naplóban. A Robusztusság és korlátokkal írt modellkártya modellkártya elve itt is érvényes: a modellverzió a kártya része. Önellenőrzés: a futtatási napló tartalmazza-e a kód-, modell- és promptverziót? A cache kulcsa tartalmazza-e mindhármat?

27.4.8 Rövid összefoglaló

A külső LLM-modell használatakor a reprodukálhatóság nem automatikus. A futtatást a kód-, modell- és promptverzió hármasa azonosítja; az API-név ehhez nem elegendő. A provider határánál explicit módon döntenünk kell az adattovábbításról és a titkok kezeléséről. A gyorsítótár kulcsa legyen verzióérzékeny, különben a modellfrissítés után régi eredményeket adunk vissza. Az LLM-kimenet nem determinisztikus, ezért az értékeléshez ismételt futtatásokból számolt mintavételi bizonytalanság kell. Az értékelési protokollt előre befagyasztjuk: a tesztkészlet, a referencia megoldások és a metrikák rögzítettek. A következő lépésben ezt a protokollt alkalmazzuk a szöveges baseline-ok összehasonlítására a Szöveges baseline és ritka mátrix egységben.