
Od OCR k pixelom bez straty záložnej cesty
GeekBye prestal čítať tvoje snímky obrazovky cez OCR a začal posielať samotný obraz vidiacim modelom — čo je tá ľahká časť. Poučná časť je optimalizácia, ktorá preskakovala OCR úplne pre používateľov s vidiacim modelom, potichu lámala záložnú cestu k modelom bez videnia a musela byť vrátená späť do verzie, kde záchranná sieť beží paralelne a nestojí nič.
Každá aplikácia, ktorá ti dovolí spraviť snímku obrazovky a spýtať sa na ňu AI, stojí pred skutočným rázcestím. Môžeš spustiť OCR — vytiahnuť text na zariadení, poslať maličký reťazec a obraz zahodiť. Alebo môžeš poslať pixely — odoslať samotný obraz a nechať model, ktorý vidí, aby si ho prečítal. OCR je lacný a malý, no slepý voči všetkému, čo nie je text: voči rozloženiu, diagramu, odsadeniu kódu, červenej vlnovke pod chybou. Pixely toto všetko zachovajú, ale stoja viac bajtov a vyžadujú model, ktorý vidí. GeekBye začal na strane OCR tohto rázcestia a naprieč v1.8.6 a v1.8.7 prešiel na stranu pixelov. To prepnutie je ľahká časť tohto príbehu. Zaujímavá časť je optimalizácia, ktorá prišla s ním, zlomila záložnú cestu a musela byť vrátená.
Predtým: prečítaj text, zahoď obraz
Do tohto oblúka sa snímka obrazovky stávala odpoveďou takto: stlačíš skratku na zachytenie, klient spustí OCR — Apple framework Vision cez swiftový binárny súbor VisionOCR na macOS, PowerShell skript na Windows, oba za jednou abstrakciou IOcrEngine — a vytiahnutý text sa zabalí do reťazca a pošle na backend ako screenshotText. Samotný obraz nikdy neopustil stroj. To je efektívne a pre stenu čistého textu to stačí. Ale snímka obrazovky je zriedka len text. Je to pracovná ponuka s dvojstĺpcovým rozložením, programátorská úloha s diagramom, chyba so stack trace, ktorej odsadenie je tou stopou. OCR splošti toto všetko do prepisu riadok po riadku a model odpovie na chudobnejšiu otázku, než akú si položil.
Prepnutie: pošli pixely (v1.8.6)
v1.8.6 pridalo druhú cestu. Nový imageOptimizer.ts, postavený na sharp a preto multiplatformový, robí surovej snímke obrazovky presne tri veci:
sharp(pngFilePath).resize({ width: 1920, withoutEnlargement: true }).jpeg({ quality: 80 })
Zmenši na najviac 1920px šírku (nikdy nezväčšuj menšie zachytenie), preveď PNG na JPEG, kvalita 80. Výstup je surový base64 s typom média neseným v samostatnom poli a putuje na backend v dvoch nových poliach požiadavky popri starom: screenshotImage a screenshotImageMediaType, nezávisle od screenshotText. Backend teraz môže dostať obraz, text alebo oboje.
Ten krok optimalizácie nie je kozmetický. Surová PNG snímka obrazovky má 3–8 MB; zmenšený JPEG je zhruba 200–500 KB. To je o rád menej pri nahrávaní a zároveň to orezáva účet za model — vidiace modely delia obraz na dlaždice podľa jeho rozlíšenia a účtujú za dlaždicu, takže obmedzenie šírky na 1920 a prekomprimovanie priamo znižujú aj bajty na drôte, aj tokeny obrazu, za ktoré platíš, bez akejkoľvek výraznej straty čitateľnosti textu na obrazovke.
Ako vôbec aplikácia vie, či model dokáže vidieť? Backend to ohlasuje. Odpoveď /api/config získala blok ai.vision kľúčovaný podľa plánu a klient pri každom načítaní konfigurácie kešuje jedinú logickú hodnotu — aiSupportsVision. Keď je videnie podporované, optimalizuj obraz; keď nie je, siahni po OCR. Takže schopnosť je fakt na strane backendu, kešovaný na strane klienta, a rozhodnutie pre každú snímku, či spustiť alebo preskočiť OCR, sa robí na klientovi z tohto príznaku. Backend stále vlastní to, ktorý model naozaj odpovie.
Optimalizácia, ktorá bola priveľmi šikovná
Tu sa to stáva poučným. Prvá verzia vidiacej cesty spúšťala OCR a optimalizáciu obrazu spolu, paralelne. Potom „perf" commit to stiahol do buď-alebo: ak plán podporuje videnie, optimalizuj obraz a preskoč OCR úplne; ak nie, spusti OCR a preskoč prácu na obraze. Na papieri je to zjavná výhra — načo vyťahovať text, ktorý vidiaci model nepotrebuje? Changelog to dokonca inzeruje: „OCR is skipped when the AI can read the image directly."
Problém je slovo fallback. Backend nemá jeden model; má hlavný a záložný, a záložný môže byť bez videnia. Predstav si tú postupnosť: hlavný model tvojho plánu vidí, takže klient hrdo preskočí OCR a pošle len obraz. Hlavný poskytovateľ zaškytne. Backend sa prepne na model iba pre text — ktorému teraz podajú obraz, čo ho nevie prečítať, a žiadny text, pretože klient text preč zoptimalizoval. Rýchla cesta potichu vymazala záchrannú sieť. Nadbytočnú rýchlu cestu môžeš bezpečne zahodiť; nemôžeš zahodiť to, čo rýchla cesta obchádzala skratkou.
Liek: podmieň to záložným modelom, nie hlavným
Liek je tá časť, ktorú sa oplatí ukradnúť. Nebolo ním vrátiť sa k vždy spúšťanému OCR — to by zahodilo výhru pre bežný prípad. Bolo ním spraviť preskočenie podmieneným druhým príznakom schopnosti: vidí záložný model? Konfigurácia backendu získala blok fallbackVision; klient kešuje aiFallbackSupportsVision vedľa prvého príznaku. Teraz má rozhodnutie štyri vetvy:
- Hlavný aj záložný obidva vidia → optimalizuj obraz, preskoč OCR. Rýchla, bežná cesta.
- Hlavný vidí, ale záložný je slepý → optimalizuj obraz a spusti OCR, ako poistku pre záložný.
- Optimalizácia obrazu vyhodí výnimku → OCR ako tvrdá záloha.
- Plán nemá videnie vôbec → len OCR.
A druhá vetva — prípad s poistkou — je miesto, kde záleží na poslednom kroku. Spustiť OCR po optimalizácii obrazu by pridalo jeho latenciu navrch. Takže oba bežia spolu pod jedným Promise.all: optimalizácia obrazu a OCR sa prekrývajú a OCR, ktoré existuje len pre prípad, že ho backend potrebuje, skončí v čase, počas ktorého sa obraz aj tak optimalizoval. Záchranná sieť je zadarmo z hľadiska reálneho času vždy, keď sa nepoužije. Celý trik v jednej vete: zachovaj lacnú zálohu, podmieň ju tým, či ju tvoj záložný poskytovateľ naozaj potrebuje, a paralelizuj ju tak, aby nestála nič, kým ťa nezachráni.
Dve menšie pasce, ktoré preskočenie nastražilo
Agresívne preskakovanie odloženej práce má vo zvyku vyniesť na povrch každé miesto, ktoré potichu predpokladalo, že sa tá práca stane. Dve vyšli najavo v PR review v tom istom týždni.
Zaseknutie fronty. Fronta snímok považovala položku za „pripravenú", keď mala text OCR. Snímky pre videnie teraz text OCR nikdy nedostali — zámerne — takže kontrola úplnosti hasIncompleteOCR čakala navždy a fronta zamrzla. Liek predefinuje „pripravená" na má optimalizovaný obraz alebo má text OCR, čo pipeline teraz naozaj zaručuje.
Zastaraný príznak schopnosti. aiSupportsVision je kešovaný, čo znamená, že môže zastarať presne zlým smerom: používateľ sa odhlási alebo načítanie konfigurácie zlyhá a klient ostane v presvedčení, že stále smie posielať obrazy modelu, ktorý ich už nepodporuje. Príznak sa teraz resetuje pri odhlásení a pri zlyhaní načítania konfigurácie — kešovaná schopnosť musí mať scenár pre chvíľu, keď to, čo opisuje, zmizne.
Slovo o číslach
Keďže poctivosť je zmyslom tejto série: commit správy nesú čísla — optimalizácia obrazu „~30ms" oproti OCR „~100–150ms", náklady „~200–500 KB vs ~3–8 MB PNG" — a sú vierohodné, ale sú to autorove vlastné odhady vpísané do kódu, nie výstup benchmarku. Nikde v histórii nie je zmeraná end-to-end latencia pred a po. Ber argument o rýchlosti ako rozumný návrhový zámer, nie dokázaný výsledok. Zníženie veľkosti nákladu je to jediné číslo, ktorému môžeš veriť, pretože vypadne rovno zo zmenšenia a prekomprimovania.
Tri veci, ktoré nás toto vydanie naučilo
- Poslať pixely bije čítanie textu — keď model vidí. OCR zahodí všetko, čo snímka obrazovky komunikuje okrem znakov. Vidiaci model číta rozloženie, diagram a odsadenie a optimalizovaný JPEG to robí cenovo dostupným. Ale je to stávka na schopnosť, a preto potrebuje zálohu.
- Rýchlu cestu môžeš zoptimalizovať preč, záchrannú sieť nikdy. Preskakovanie OCR pre používateľov s videním bolo správne pre hlavný model a nesprávne pre záložný. Bug nebol v preskočení; bol v tom, že sa preskakovalo podľa schopnosti hlavného modelu, keď záležalo na schopnosti záložného.
- Zadarmo je taká záchranná sieť, ktorá je paralelizovaná. Podmieň poistnú prácu tým, či je naozaj potrebná, a potom ju spusti popri rýchlej ceste, aby sa prekrývali. Spravená správne, elegantná degradácia nestojí nič až do dňa, keď je jediným dôvodom, prečo tvoja funkcia stále funguje.
Pre predchádzajúcu kapitolu príbehu v1 — dva režimy zlyhania prekrytia prepúšťajúceho kliknutia (v1.8.5); a pre celý oblúk — anatómiu dodávania softvéru do dokonalosti.