1 h 48 min
koolituse algusest töötava vestluseni, mis vastab eesti keeles küsimustele andmebaasi kohta. Ajakava lubas kolm tundi. Klaviatuuri taga oli Claude Code, mina kuulasin koolitajat Zoomis.
Kui lõpuni ei viitsi lugeda
- Kolm tundi Java-koolitust, null rida Javat minu käega. Claude Code kirjutas, mina kuulasin. Sertifikaati ei saanud kumbki.
- Mudel keeldus ise tellimusi kustutamast. Tubli mudel. Usaldame teda täpselt sama palju kui veebivormi, kuhu keegi just kirjutas „'; DROP TABLE".
- Kogu kaitse on üksteist rida Javat, mis AI-d ei kutsu: ainult SELECT, null semikoolonit, üksteist keelatud sõna.
- Koolitaja etapp 6 loeb neli ohtu ja kolm kaitset. Meil oli üks kolmest. Koolitaja lahendusest õppisin: üks erandiklass vahetada, ja veateade on õige.
- gemini-2.5-flash annab uuele võtmele 404. Mudel on olemas, aga mitte sulle.
- Boonus: küsin eesti keeles, rakendus vastab minu enda häälega. Kümme toodet, neli tabelit, üks häälekloon.
Kogu kood, sh hääl sisse ja välja, on avalik: github.com/hv-tec/java-koolitus.
Kolm tundi ei kulu koodile
Ma ei kirjuta Javat. Registreerisin end ikkagi eesti.ai koolitusele „Java + LLM", sest lubadus oli konkreetne: kolme tunniga vestlusassistent, mis vastab andmebaasi kohta esitatud küsimustele loomulikus keeles. Neli sammu, iga sammu lõpus kontrollpunkt: projekt käima, Java räägib mudeliga, küsimusest sünnib SQL, päring käivitatakse ja tulemus selgitatakse.
Koolitaja Meelis Teern tegi sammud ette OpenCode'iga, terminalis jooksva kodeerimisagendiga. Minu klaviatuuri taga oli Claude Code: mina kuulasin ja andsin suuna, tema kirjutas koodi. Kontrollpunktid on tööriistast sõltumatud. Neli sammu olid läbi enne, kui ajakava jõudis viimase etapini.
Kood ei ole see, mis kolm tundi võtab. Kolm tundi võtab arusaamine, miks üks funktsioon selles koodis on tähtsam kui kõik LLM-kutsed kokku.
Mudel keeldus ise. Sellest ei piisa
Esimene test: „Kustuta kõik tellimused." Gemini tagastas CANNOT_ANSWER. Süsteemiprompt ütleb,
et lubatud on ainult SELECT, ja mudel kuulas sõna. Kiusatus on siin lõpetada.
Koolitaja arhitektuurislaid ütleb teisiti: mudelist tuleb SQL, ja enne andmebaasi on eraldi kast „SQL valideerimine". Põhimõte on ühes lauses: LLM-i väljund on sisend. Seda koheldakse nagu vormist tulnud teksti, mida keegi ei ole kontrollinud.
Prompt on palve. Mudel täidab seda enamasti, aga „enamasti" ei ole turvapiir. Ühel päeval tuleb
tagasi lause, mis algab SELECT-iga ja jätkub semikooloni järel millegi muuga. Ainus
deterministlik kiht selles rakenduses on funktsioon, mis mudelit üldse ei kutsu.
- Lause peab algama sõnaga SELECT. Kommentaarid eemaldatakse enne kontrolli.
- Semikoolonit ei tohi olla. Üks küsimus, üks lause.
- Üksteist keelatud sõna: INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, MERGE, TRUNCATE, GRANT, EXEC, CALL.
- Mudeli keeldumine ei ole kaitse. See on esimene kiht, mille peale ei ehitata.
Kolm kontrolli, üksteist rida Javat. Ausalt: koolituse jooksul ei püüdnud validateSql kinni
ühtegi päringut, mudel keeldus alati ise. Just seepärast on kiusatus see funktsioon ära jätta,
ja just seepärast ei tohi.
Etapp 6 tuli siis, kui rakendus juba töötas
Koolitaja viimane slaid: „kuidas töötav lahendus võib ohtlikult käituda". Neli ohtu ja minimaalne kaitse.
- DELETE või UPDATE — andmete muutmise risk.
- Olematu tabel — mudel hallutsineerib skeemi, mida ei ole.
- Mitmetähenduslik küsimus — SQL on korrektne, tõlgendus vale.
- Liiga lai päring — aeglane või hiiglaslik vastus.
- Minimaalne kaitse: SELECT-only kontroll + lubatud tabelite ja väljade loend + selge veateade kasutajale.
Kolmest kaitsest oli meil koolituse lõpus üks. Lubatud tabelite loendit ei ole. Ja kui
validateSql päringu tagasi lükkas, sai kasutaja 502 „AI-teenus pole saadaval": teenus töötas,
kontroll töötas, ainult sõnum oli vale.
Koolitaja enda lahenduses on see õigesti: 400 „Only SELECT queries are allowed". Vahe oli üks erandiklass, parandasin sama rea. Tema lahenduses omakorda ei ole semikooloni-kontrolli, meil on. Kumbki ei tee lubatud tabelite loendit. See jäi mõlemal järgmiseks sammuks.
Kaks kutset, mitte üks
Üks küsimus on kaks mudelikutset. Esimene saab skeemi ja küsimuse, tagastab SQL-i. Teine saab
küsimuse ja päringu tulemuse, tagastab eestikeelse lause ilma tabelinimede ja SQL-ita.
„Millised kliendid on tellinud kõige rohkem?" andis JOIN-idega päringu üle kolme tabeli ja
vastuse „Mari Tamm ja Jaan Kask, kummalgi 2 tellimust". Küsimus ilma kohta läks tagasi
CANNOT_ANSWER-iga, sest skeemis ilma ei ole.
Tasuta taseme piir on 15 päringut minutis. Kaks kutset küsimuse kohta teeb sellest 7 küsimust minutis. Koolituseks piisab, kümnele kasutajale mitte.
Lõks, mis annab 404
Koolitaja valik oli gemini-3.5-flash-lite. Esimene katse harjumusest oli gemini-2.5-flash,
ja uus API-võti sai 404 koos tekstiga „no longer available to new users". Sama juhtub, kui
mudeli nimele jätta -preview saba, mida dokumentatsioon mõnes kohas näitab. Mudeli nimi on
nüüd keskkonnamuutuja, mitte konstant koodis.
Eesti keel sisse ja välja ilma ühegi Java-reata
Sammude vahel jäi aega üle. Chrome'i Web Speech API tunneb et-EE keelt ära ilma ühegi
Java-reata: üks HTML-leht, mikrofoninupp, tekst läheb samasse /api/ask otspunkti.
Vastuse ettelugemine võttis ühe Spring Booti kontrolleri juurde. Tekst läheb Jutusta.ee kõnesünteesi minu enda hääleklooniga, API-võti jääb serverisse ja brauser saab ainult heli. Küsin eesti keeles „mis on kõige kallim toode?" ja kuulen oma häälega „Laptop Pro 15". Andmed on kümme toodet koolitaja seemnefailist, mitte päris pood. Mehhanism on sama.
Mida ma nüüd teisiti teen
Iga AI-väljund, mis läheb edasi kuhugi, kus tal on tagajärg, saab oma validateSql-i. SQL
andmebaasi, e-kiri kliendile, käsk seadmele. Kui sinu rakenduses on prompt ainus koht, kus
„ainult SELECT" kirjas on, siis ei ole sul kaitset, vaid palve.
Lahtiseks jäi, mis juhtub päris andmetega. Nelja tabeliga skeem mahub prompti tervikuna. Saja tabeliga ei mahu, ja siis tuleb enne mudelikutset otsustada, millise osa skeemist mudel üldse näeb. See on järgmine katse. Kood on avalik: github.com/hv-tec/java-koolitus.