
Az OCR-től a pixelekig, a fallback elvesztése nélkül
A GeekBye abbahagyta, hogy OCR-rel olvassa a képernyőképeidet, és elkezdte az igazi képet küldeni a vision modelleknek — ami a könnyű rész. A tanulságos rész az az optimalizálás, amely a vision felhasználóknál teljesen kihagyta az OCR-t, csendben eltörte a nem-vision modellekhez tartozó fallbacket, és vissza kellett vonni egy olyan verzióra, amelyben a biztonsági háló párhuzamosan fut, és semmibe sem kerül.
Van egy valódi útelágazás minden alkalmazás számára, amely engedi, hogy képernyőképet készíts valamiről, és megkérdezz róla egy AI-t. Futtathatsz OCR-t — kinyered a szöveget az eszközön, elküldesz egy apró stringet, és eldobod a képet. Vagy elküldheted a pixeleket — elküldöd az igazi képet, és hagyod, hogy egy vision-képes modell elolvassa. Az OCR olcsó és kicsi, de vak mindenre, ami nem szöveg: az elrendezésre, a diagramra, a kód behúzására, a hiba alatti piros hullámvonalra. A pixelek mindezt megőrzik, de több bájtba kerülnek, és olyan modellt igényelnek, amely lát. A GeekBye ennek az elágazásnak az OCR oldalán indult, és a v1.8.6 és a v1.8.7 során átsétált a pixelek oldalára. Ez a váltás a történet könnyű része. Az érdekes rész az az optimalizálás, amely vele jött, eltörte a fallbacket, és vissza kellett vonni.
Az előzmény: olvasd a szöveget, dobd el a képet
Eddig az ívig egy képernyőkép így vált válasszá: megnyomod a rögzítési gyorsbillentyűt, a kliens OCR-t futtat — az Apple Vision framework egy Swift VisionOCR binárison keresztül macOS-en, egy PowerShell script Windowson, mindkettő egyetlen IOcrEngine absztrakció mögött — és a kinyert szöveg egy stringbe csomagolódik, és screenshotText-ként megy a backendnek. Maga a kép sosem hagyta el a gépet. Ez hatékony, és egy sima szövegfalnál rendben van. De egy képernyőkép ritkán csak szöveg. Egy kétoszlopos elrendezésű álláshirdetés, egy diagramos programozási feladat, egy hiba egy stack trace-szel, amelynek a behúzása a kulcs. Az OCR mindezt egy soronkénti átiratba lapítja, és a modell egy soványabb kérdésre válaszol, mint amit feltettél.
A váltás: küldd a pixeleket (v1.8.6)
A v1.8.6 hozzáadta a másik utat. Egy új imageOptimizer.ts, amely a sharp-ra épül, és ezért platformfüggetlen, pontosan három dolgot csinál egy nyers képernyőképpel:
sharp(pngFilePath).resize({ width: 1920, withoutEnlargement: true }).jpeg({ quality: 80 })
Méretezd át legfeljebb 1920px szélesre (soha ne nagyíts fel egy kisebb felvételt), alakítsd a PNG-t JPEG-gé, quality 80. A kimenet nyers base64, a media type egy külön mezőben utazik, és két új kérésmezőben megy a backendnek a régi mellett: screenshotImage és screenshotImageMediaType, a screenshotText-től függetlenül. A backend most már kaphat képet, szöveget, vagy mindkettőt.
Az az optimalizálási lépés nem kozmetikai. Egy nyers képernyőkép-PNG 3–8 MB; az átméretezett JPEG nagyjából 200–500 KB. Ez egy nagyságrenddel kevesebb a feltöltésből, és a modell számláját is csökkenti — a vision modellek egy képet a felbontása szerint csempéznek, és csempénként számláznak, így a szélesség 1920-ra korlátozása és az újratömörítés közvetlenül csökkenti mind a vezetéken lévő bájtokat, mind az image tokeneket, amelyekért fizetsz, a képernyőn lévő szöveg olvashatóságának érdemi vesztesége nélkül.
Honnan tudja az alkalmazás, hogy a modell egyáltalán lát-e? A backend hirdeti. A /api/config válasz kapott egy ai.vision blokkot terv szerint kulcsolva, és a kliens minden config-lekéréskor cache-el egyetlen booleant — aiSupportsVision. Amikor a vision támogatott, optimalizáld a képet; amikor nem, ess vissza OCR-re. Tehát a képesség egy backend-tény, a kliens oldalán cache-elve, és a képernyőképenkénti döntés, hogy fusson-e vagy kimaradjon az OCR, a kliensen születik ebből a flagből. A backend továbbra is birtokolja, hogy melyik modell válaszol valójában.
Az optimalizálás, amely túl okos volt
Itt válik tanulságossá. A vision út első változata az OCR-t és a képoptimalizálást együtt futtatta, párhuzamosan. Aztán egy „perf" commit ezt egy vagy/vagy-gyá szorította: ha a terv támogatja a visiont, optimalizáld a képet, és hagyd ki teljesen az OCR-t; ha nem, futtass OCR-t, és hagyd ki a képmunkát. Papíron ez a kézenfekvő győzelem — minek kinyerni szöveget, amire a vision modellnek nincs szüksége? A changelog egyenesen reklámozza is: "OCR is skipped when the AI can read the image directly."
A probléma a fallback fogalmában rejlik. A backendnek nincs egy modellje; van egy elsődleges és egy fallback, és a fallback lehet egy nem-vision modell. Képzeld el a sorrendet: a terved elsődleges modellje támogatja a visiont, így a kliens büszkén kihagyja az OCR-t, és csak a képet küldi. Az elsődleges szolgáltató megbicsaklik. A backend visszaesik egy csak-szöveges modellre — amelynek most egy olyan képet adnak át, amelyet nem tud olvasni, és semmi szöveget, mert a kliens a szöveget a semmibe optimalizálta. A gyors út csendben törölte a biztonsági hálót. Egy redundáns gyors utat biztonságosan elhagyhatsz; nem hagyhatod el azt a dolgot, amely körül a gyors út egy rövidítés volt.
A javítás: a fallbackhez köss, ne az elsődlegeshez
A javítás az a rész, amit érdemes ellopni. Nem az volt, hogy visszatérj a mindig futó OCR-hez — az eldobná a győzelmet a gyakori esetben. Az volt, hogy a kihagyást egy második capability flaghez kösd: támogat-e a fallback visiont? A backend configja kapott egy fallbackVision blokkot; a kliens az aiFallbackSupportsVision-t cache-eli az első flag mellé. Most a döntésnek négy ága van:
- Az elsődleges és a fallback is lát → optimalizáld a képet, hagyd ki az OCR-t. A gyors, gyakori út.
- Az elsődleges lát, de a fallback vak → optimalizáld a képet és futtass OCR-t, biztosításként a fallbacknek.
- A képoptimalizálás hibát dob → OCR mint kemény fallback.
- A tervnek egyáltalán nincs visionje → csak OCR.
És a második ág — a biztosítási eset — az, ahol az utolsó lépés számít. Az OCR futtatása a képoptimalizálás után visszaadná a késleltetését a tetejére. Így a kettő együtt fut egyetlen Promise.all alatt: a képoptimalizálás és az OCR átfedik egymást, és az OCR, amely csak arra az esetre létezik, ha a backendnek szüksége van rá, azon az időn belül végez, amíg a kép amúgy is optimalizálódott. A biztonsági háló valós idő szempontjából ingyenes, valahányszor nincs használatban. Ez az egész trükk egyetlen mondatban: tartsd meg az olcsó fallbacket, kösd ahhoz, hogy a fallback szolgáltatódnak tényleg szüksége van-e rá, és párhuzamosítsd, hogy semmibe se kerüljön addig a napig, amíg meg nem ment téged.
Két kisebb csapda, amit a kihagyás kioldott
A halasztott munka agresszív kihagyásának megvan az a szokása, hogy felszínre hozza minden helyet, amely csendben feltételezte, hogy a munka megtörténik. Kettő jött elő a PR-review-ban ugyanazon a héten.
Egy sorbeli deadlock. A képernyőkép-sor akkor tekintett egy elemet „ready"-nek, amikor volt OCR-szövege. A vision képernyőképek mostantól sosem kaptak OCR-szöveget — szándékosan —, így a teljességi ellenőrzés, a hasIncompleteOCR, örökké várt, és a sor beragadt. A javítás a „ready"-t úgy definiálja újra, hogy van optimalizált képe vagy van OCR-szövege, ami az, amit a pipeline most valójában garantál.
Egy elavult capability flag. Az aiSupportsVision cache-elt, ami azt jelenti, hogy pontosan a rossz irányba avulhat el: egy felhasználó kijelentkezik, vagy egy config-lekérés meghiúsul, és a kliens abban a hitben marad, hogy még mindig küldhet képeket egy modellnek, amely már nem támogatja őket. A flag mostantól kijelentkezéskor és config-lekérési hibakor nullázódik — egy cache-elt képességnek kell hogy legyen egy története arra, amikor a dolog, amit leír, eltűnik.
Egy szó a számokról
Mert az őszinteség e sorozat lényege: a commit-üzenetek számokat hordoznak — képoptimalizálás "~30ms" szemben az OCR "~100–150ms"-mal, payloadok "~200–500 KB vs ~3–8 MB PNG" — és ezek hihetők, de a szerző saját, soron belüli becslései, nem egy benchmark kimenete. Sehol a történetben nincs mért, végponttól végpontig tartó előtte/utána késleltetés. Kezeld a sebesség érvét józan tervezési szándékként, ne bizonyított eredményként. A payload-méret csökkenése az az egy szám, amelyben megbízhatsz, mert egyenesen az átméretezésből és az újratömörítésből következik.
Három dolog, amit ez a kiadás tanított nekünk
- A pixelek küldése legyőzi a szöveg olvasását — amikor a modell lát. Az OCR eldob mindent, amit egy képernyőkép a karaktereken kívül közöl. Egy vision modell olvassa az elrendezést, a diagramot és a behúzást, és egy optimalizált JPEG ezt megengedhetővé teszi. De ez egy képességre tett fogadás, ezért van szüksége fallbackre.
- Egy gyors utat optimalizálhatsz a semmibe, a biztonsági hálót soha. Az OCR kihagyása a vision felhasználóknál helyes volt az elsődlegesre és rossz a fallbackre. A bug nem a kihagyás volt; az volt, hogy az elsődleges képessége alapján hagytad ki, amikor a fallbacké volt az, ami számított.
- Egy ingyenes biztonsági háló egy párhuzamosított. Kösd a biztosítási munkát ahhoz, hogy tényleg szükség van-e rá, aztán futtasd a gyors út mellett, hogy átfedjen. Jól csinálva a kecses leépülés semmibe sem kerül addig a napig, amíg ez az egyetlen ok, amiért a funkciód még működik.
A v1-történet előző fejezetéért lásd a click-through overlay két hibamódja (v1.8.5); a teljes ívért pedig a szoftver tökéletesre szállításának anatómiája.