AI · Automatizacija · Inženjering

Zašto prompt radi u testu, a pada uživo

Piše Lazar MilićevićOctober 31, 20267 min čitanja
Code on a monitor illustrating why a prompt works in testing but fails in live production

Prvi put sam to video na pipeline-u koji je prošao sve test primere bez greške, a prvog dana u produkciji izbacio izlaz koji parser nije mogao da pročita (u jednom mom projektu). Prompt je bio isti, model isti. Razlika je bila u tome što je produkcija bila prljava na načine koje test skup nije pokrivao. Ovo je skup tehnika kojima danas pretvaram LLM iz lutrije u komponentu koja se ponaša predvidivo.

Test skup laže, a produkcija ne

Prompt prolazi u testu jer su testni ulazi čisti, kratki i napisani od strane nekoga ko zna šta sistem očekuje. Produkcija šalje duže, neuredne i neočekivane ulaze, i tu pada ono što je izgledalo stabilno.

Konkretno, razlike koje sam najčešće viđao:

  • Dužina ulaza. Test primer ima tri paragrafa, pravi ulaz ima četrdeset stranica emailova u lancu.
  • Šum. Potpisi, pravne napomene, citirani odgovori, HTML ostaci.
  • Jezik i mešavina jezika. Srpski i engleski u istoj rečenici, ćirilica i latinica u istom dokumentu.
  • Prazni i degenerisani slučajevi. Prazno polje, jedna reč, ulaz koji uopšte nije ono što pipeline očekuje.

Zato prvi korak nije pisanje boljeg prompta, nego pravljenje test skupa koji liči na produkciju. Ja ga punim stvarnim ulazima iz logova (uz anonimizaciju), a posebno čuvam one na kojima je sistem već pao. Svaki produkcioni incident postaje jedan red u tom skupu. Posle nekoliko meseci taj skup vredi više od bilo kog prompta, jer je to jedini način da znaš da li promena zaista nešto popravlja.

OpenAI u dokumentaciji za Structured Outputs preporučuje isto: koristi evals da odrediš strukturu izlaza koja najbolje radi za tvoj slučaj upotrebe (Structured model outputs). Ja bih to proširio na ceo prompt, ne samo na šemu.

Context engineering je veći problem od prompt engineeringa

Kad sistem postane agent ili pipeline sa više koraka, sama formulacija instrukcije prestaje da bude glavni izvor grešaka. Glavni izvor postaje ono što u kontekst uđe pored instrukcije. Anthropic u svom inženjerskom blogu upravo to opisuje: context engineering kao pojam koji je u primenjenoj AI zamenio prompt engineering kao glavni fokus, uz tezu da kontekst treba tretirati kao dragocen, ograničen resurs (Effective context engineering for AI agents).

Ovo se slaže sa onim što vidim u praksi. Pouzdanost ne pada zato što je prompt loše napisan, nego zato što je kontekst prenatrpan. Dve stvari mi pomažu da to razumem.

Prva je rad "Lost in the Middle" (Liu et al.), objavljen u TACL 2024: performanse modela su često najbolje kad se relevantna informacija nalazi na početku ili na kraju ulaza, a značajno opadaju kad je u sredini dugog konteksta (arXiv, ACL Anthology). Druga je Chroma izveštaj "Context Rot", koji zaključuje da modeli ne koriste kontekst ravnomerno i da pouzdanost opada kako ulaz postaje duži (Context Rot).

Praktična posledica: duži kontekst nije besplatan, čak i kad ga model "podržava". Moja pravila:

  1. Kritične instrukcije idu na početak, a ključno ograničenje ponovim na kraju. Ne zato što model "zaboravlja", nego zato što pozicija utiče na to koliko se informacija koristi.
  2. Ono što nije potrebno za ovaj korak ne ulazi u kontekst. Ne "za svaki slučaj".
  3. Retrieval vraća manje, ali bolje. Pet dobrih odlomaka bije dvadeset osrednjih.

Progresivno otkrivanje umesto istovara svega odjednom

Najveći skok pouzdanosti u agentnim sistemima dobio sam kad sam prestao da unapred ubacujem sve što bi agentu moglo zatrebati. Anthropic to opisuje kao progressive disclosure: agent samostalno pronalazi i preuzima podatke i postepeno otkriva relevantan kontekst istraživanjem (izvor).

U praksi to izgleda ovako. Umesto da u prompt stavim ceo dokument, stavim kratak indeks (naslovi, identifikatori, jednorečenični opisi) i alat kojim agent traži konkretan deo. Agent plaća tokene samo za ono što stvarno pročita.

Pristup Prednost Cena
Sve u promptu Jednostavno, jedan poziv Šum, rast konteksta, slabija pažnja na bitno
Indeks + alat za dohvat Mali kontekst, fokus Više poziva, treba dobro napisan alat
Hibrid (osnovno unapred, ostalo na zahtev) Ravnoteža Treba odlučiti šta je "osnovno"

Trade-off je stvaran: više koraka znači veću latenciju i više mesta gde nešto može da pođe po zlu. Za kratke, dobro ograničene zadatke istovar svega u prompt je sasvim u redu. Za dugotrajne zadatke gde kontekst raste, progresivno otkrivanje je skoro uvek bolje.

Za dugačke sesije koristim i kompakciju (sažimanje istorije kad kontekst naraste), koju Anthropic navodi kao prvu polugu context engineeringa za koherentnost u zadacima sa dugim horizontom (izvor). Pazim samo da sažetak čuva odluke i otvorena pitanja, a ne prepričava razgovor.

Izlaz mora biti ograničen šemom, ne molbom

Najjeftinija popravka pouzdanosti koju znam: prestani da moliš model za JSON u tekstu prompta, i traži ga kroz mehanizam API-ja. "Vrati samo validan JSON" u promptu je molba. Šema je ograničenje.

OpenAI razlikuje JSON mode i Structured Outputs: oba daju validan JSON, ali samo Structured Outputs garantuje poštovanje šeme (dokumentacija). Anthropic ima ekvivalent: structured outputs garantuju odgovore koji poštuju šemu putem constrained decodinga (Claude Platform Docs).

Dva upozorenja iz dokumentacije koja sam naučio na teži način:

  • Podržan je samo podskup JSON Schema. OpenAI vraća grešku ako uz strict: true pošalješ nepodržanu šemu (izvor). Kod Anthropica, na primer, spoljni $ref nije podržan (izvor). Šemu zato treba testirati kao i svaki drugi kod, a ne pretpostaviti da radi.
  • Prefill nije put za strukturu. Za neke modele prefill odgovora vraća grešku 400, a dokumentacija preporučuje structured outputs ili instrukcije u system promptu (Messages API). Ne gradi pipeline na triku koji može nestati.

Ono što šema ne rešava je važnije od onoga što rešava. Garancija šeme znači da je oblik ispravan, ne da je sadržaj tačan. Model može da vrati savršeno validan JSON sa izmišljenim vrednostima. Zato šemu pišem tako da guši prostor za izmišljanje: jasna imena ključeva, opisi polja (OpenAI to i preporučuje), enum umesto slobodnog teksta gde god je moguće, i eksplicitna vrednost za "ne znam" umesto da model mora da pogađa.

# Primer: polje koje model ne mora da izmišlja
schema = {
    "type": "object",
    "properties": {
        "kategorija": {
            "type": "string",
            "enum": ["racun", "ponuda", "reklamacija", "nepoznato"],
            "description": "Tip dokumenta. 'nepoznato' ako nije jasno."
        },
        "iznos": {
            "type": ["number", "null"],
            "description": "Ukupan iznos ako je eksplicitno naveden, inače null."
        }
    },
    "required": ["kategorija", "iznos"],
    "additionalProperties": False
}

Ovo je ilustracija pristupa, ne gotova šema za tvoj slučaj. Tačna pravila za opciona polja i required proveri u dokumentaciji provajdera koji koristiš, jer se razlikuju.

Tretiraj neuspeh kao normalan ishod

Ni sa šemom ne dobijaš garanciju da će svaki poziv uspeti. OpenAI-jev zvanični primer odvojeno obrađuje skraćen odgovor (finish_reason jednak length), odbijanje modela (refusal) i prazan sadržaj (izvor). Ako to ne obrađuješ u kodu, tvoj sistem pada upravo na onim slučajevima koji se u testu ne pojave.

Moj minimum za svaki LLM poziv u produkciji:

  1. Proveri zašto je generisanje stalo. Skraćen izlaz nije validan izlaz, čak ni kad je parsiran deo ispravan.
  2. Obradi refusal kao poseban ishod, ne kao grešku parsiranja.
  3. Ograniči broj ponavljanja. Claude Agent SDK validira izlaz prema šemi i ponovo šalje upit pri neslaganju, a kad se pokušaji iscrpe vraća grešku (dokumentacija). Ista logika važi i za tvoj kod: retry bez gornje granice je način da platiš račun za petlju.
  4. Definiši šta sistem radi kad ne uspe. Red za ručni pregled, fallback vrednost, alarm. Odluku donosi unapred, ne u 3 ujutru.

Jedna napomena o poverenju: Agent SDK validira izlaz i može da vrati grešku kad pokušaji presahnu, pa ne pretpostavljam savršen izlaz. Zato ni ja ne gradim logiku koja računa na to. Uvek validiram sam, bez obzira na to šta provajder obećava.

Šta bih ja uradio

Ako danas imaš prompt koji radi u testu a pada uživo, ovo je redosled kojim bih išao:

  1. Prvo napravi test skup od stvarnih ulaza, uključujući sve na čemu je sistem već pao. Bez toga svaka "popravka" je nagađanje.
  2. Skrati i očisti kontekst pre nego što dirneš formulaciju. Najčešće problem nije u rečima instrukcije nego u onome što stoji oko nje.
  3. Pređi sa molbe na ograničenje. Structured outputs umesto "vrati JSON", enum umesto slobodnog teksta, eksplicitno "nepoznato".
  4. Obradi neuspeh u kodu: skraćeno, odbijeno, prazno, ograničeni retry, definisan fallback.
  5. Tek onda pipaj prompt, i to jednu promenu po jednu, merenu na test skupu.

Jedna stvar koju bih izbegao: preterano preskriptivne prompte sa dugim spiskovima pravila. Anthropic primećuje da pametniji modeli zahtevaju manje preskriptivnog inženjeringa (izvor), a moje iskustvo je da dugi spiskovi pravila često samo dodaju šum i kontradikcije. Svaki put kad dodam pravilo, pitam se da li ga zaista pokreće neki red u test skupu, ili ga dodajem iz straha.

Predvidiv LLM nije onaj sa savršenim promptom, nego onaj oko koga je izgrađen sistem koji pretpostavlja da će greške dolaziti. Ako radiš na nečem sličnom i želiš da razmenimo iskustva ili pogledamo tvoj pipeline zajedno, javi se na lazar-milicevic.com/#contact.

Često postavljana pitanja

Zašto prompt radi u testu, a pada u produkciji?

Test ulazi su obično čisti, kratki i napisani od nekoga ko zna šta sistem očekuje. Produkcija šalje duže i neuređene ulaze: potpise, pravne napomene, HTML ostatke, mešavinu jezika i pisama, kao i prazna ili degenerisana polja. Zato prvi korak nije pisanje boljeg prompta, nego pravljenje test skupa koji liči na stvarni saobraćaj.

Kako da napravim test skup koji stvarno liči na produkciju?

Punim ga stvarnim ulazima iz logova, uz anonimizaciju, i posebno čuvam one na kojima je sistem već pao. Svaki produkcioni incident postaje jedan novi red u skupu. Posle nekoliko meseci taj skup vredi više od bilo kog prompta, jer je jedini pouzdan način da znaš da li promena zaista nešto popravlja.

Šta je context engineering i po čemu se razlikuje od prompt engineeringa?

Context engineering je pristup u kome se fokus pomera sa formulacije instrukcije na to šta sve ulazi u kontekst modela, uz tezu da je kontekst dragocen i ograničen resurs. Kod agenata i višekoraknih pipeline-a glavni izvor grešaka često nije loše napisan prompt, nego prenatrpan kontekst. Praktično, u kontekst stavljam samo ono što je potrebno za konkretan korak.

Zašto duži kontekst smanjuje pouzdanost LLM-a?

Rad "Lost in the Middle" pokazuje da performanse modela često opadaju kada je relevantna informacija u sredini dugog ulaza, a najbolje su kada je na početku ili na kraju. Chroma izveštaj "Context Rot" zaključuje da modeli ne koriste kontekst ravnomerno i da pouzdanost opada kako ulaz raste. Zato kritične instrukcije stavljam na početak, ključno ograničenje ponavljam na kraju, a retrieval ograničavam na nekoliko dobrih odlomaka.

Kako da dobijem pouzdan JSON izlaz iz LLM-a?

Umesto da u tekstu prompta molim model da vrati validan JSON, zahtev postavljam kroz mehanizam API-ja. Structured outputs, kod OpenAI-ja i kod Anthropic-a, garantuju poštovanje šeme, dok JSON mode garantuje samo validan JSON. Treba imati na umu da je podržan samo podskup JSON Schema, pa šemu prilagođavam tim ograničenjima.

Lazar Milicevic

Lazar Milićević

Senior Technical Engineer. Gradim AI automatizaciju, GenAI/LLM sisteme i cloud arhitekturu — autonomne sisteme koji rade dok spavaš. Osnivač BizFlowAI.

Gradiš nešto teško sa AI-jem ili automatizacijom? Otvoren sam za razgovor.

Javi se

← Svi postovi