
OCR:stä pikseleihin vararatkaisua menettämättä
GeekBye lakkasi lukemasta kuvakaappauksiasi OCR:llä ja alkoi lähettää itse kuvan näkömalleille — mikä on se helppo osa. Opettavainen osa on optimointi joka ohitti OCR:n kokonaan näkökäyttäjiltä, rikkoi hiljaa vararatkaisun ei-näkömalleihin, ja jouduttiin perumaan versioon jossa turvaverkko pyörii rinnakkain eikä maksa mitään.
Jokaiselle sovellukselle joka antaa sinun ottaa kuvakaappauksen jostakin ja kysyä siitä tekoälyltä on todellinen tienhaara. Voit ajaa OCR:n — eristää tekstin laitteella, lähettää pienenpienen merkkijonon ja heittää kuvan pois. Tai voit lähettää pikselit — toimittaa itse kuvan ja antaa näkökykyisen mallin lukea sen. OCR on halpa ja pieni mutta sokea kaikelle mikä ei ole tekstiä: asettelulle, kaaviolle, koodin sisennykselle, punaiselle aaltoviivalle virheen alla. Pikselit säilyttävät sen kaiken mutta maksavat enemmän tavuja ja vaativat mallin joka näkee. GeekBye aloitti tuon tienhaaran OCR-puolelta ja siirtyi v1.8.6:n ja v1.8.7:n kuluessa pikselipuolelle. Tuo vaihto on tämän tarinan helppo osa. Kiinnostava osa on optimointi joka tuli sen mukana, rikkoi vararatkaisun ja jouduttiin perumaan.
Ennen: lue teksti, pudota kuva
Tähän kaareen asti kuvakaappauksesta tuli vastaus näin: painat kaappauspikanäppäintä, asiakas ajaa OCR:n — Applen Vision-kehyksen Swift-VisionOCR-binäärin kautta macOS:llä, PowerShell-skriptin Windowsilla, molemmat yhden IOcrEngine-abstraktion takana — ja eristetty teksti kääritään merkkijonoksi ja lähetetään backendille nimellä screenshotText. Itse kuva ei koskaan poistunut koneelta. Se on tehokasta, ja pelkän tekstiseinän kohdalla se on ihan hyvä. Mutta kuvakaappaus on harvoin pelkkää tekstiä. Se on kaksipalstaisella asettelulla varustettu työpaikkailmoitus, kaaviolla varustettu koodausongelma, pinojäljellä varustettu virhe jonka sisennys on se vihje. OCR litistää sen kaiken rivi riviltä -litteroinniksi ja malli vastaa ohuempaan kysymykseen kuin siihen jonka esitit.
Vaihto: lähetä pikselit (v1.8.6)
v1.8.6 lisäsi toisen polun. Uusi imageOptimizer.ts, rakennettu sharpin päälle ja siksi alustariippumaton, tekee raa'alle kuvakaappaukselle täsmälleen kolme asiaa:
sharp(pngFilePath).resize({ width: 1920, withoutEnlargement: true }).jpeg({ quality: 80 })
Muuta kooksi enintään 1920px leveä (älä koskaan suurenna pienempää kaappausta), muunna PNG JPEG:ksi, laatu 80. Tuloste on raakaa base64:ää mediatyypin kulkiessa erillisessä kentässä, ja se ratsastaa backendille kahdessa uudessa pyyntökentässä vanhan rinnalla: screenshotImage ja screenshotImageMediaType, riippumatta kentästä screenshotText. Backend voi nyt vastaanottaa kuvan, tekstin tai molemmat.
Tuo optimointivaihe ei ole kosmeettinen. Raaka kuvakaappaus-PNG on 3–8 MB; uudelleenpakattu JPEG on karkeasti 200–500 KB. Se on suuruusluokan verran pois latauksesta, ja se leikkaa myös mallin laskua — näkömallit ruudukoittavat kuvan sen resoluution mukaan ja veloittavat ruudulta, joten leveyden rajaaminen 1920:een ja uudelleenpakkaaminen vähentää suoraan sekä johdolla kulkevat tavut että kuva-tokenit joista maksat, ilman merkittävää näytöllä olevan tekstin luettavuuden menetystä.
Mistä sovellus edes tietää voiko malli nähdä? Backend ilmoittaa sen. /api/config-vastaus sai ai.vision-lohkon joka on avaimennettu suunnitelman mukaan, ja asiakas välimuistittaa yhden totuusarvon — aiSupportsVision — jokaisella konfiguraation haulla. Kun näkö on tuettu, optimoi kuva; kun ei ole, putoa OCR:ään. Eli kyvykkyys on backendin fakta, asiakaspuolella välimuistitettu, ja kuvakaappauskohtainen päätös ajaa vai ohittaa OCR tehdään asiakkaassa tuosta lipusta. Backend omistaa yhä sen kumpi malli oikeasti vastaa.
Optimointi joka oli liian nokkela
Tässä siitä tulee opettavaista. Näköpolun ensimmäinen versio ajoi OCR:n ja kuvan optimoinnin yhdessä, rinnakkain. Sitten yksi "perf"-commit kiristi sen joko-tai-muotoon: jos suunnitelma tukee näköä, optimoi kuva ja ohita OCR kokonaan; jos ei, aja OCR ja ohita kuvatyö. Paperilla tämä on ilmeinen voitto — miksi eristää tekstiä jota näkömalli ei tarvitse? Muutosloki jopa mainostaa sitä: "OCR is skipped when the AI can read the image directly."
Ongelma on sanassa vararatkaisu. Backendilla ei ole yhtä mallia; sillä on ensisijainen ja vararatkaisu, ja vararatkaisu saattaa olla ei-näkömalli. Kuvittele sarja: suunnitelmasi ensisijainen malli tukee näköä, joten asiakas ohittaa ylpeänä OCR:n ja lähettää vain kuvan. Ensisijainen tarjoaja nikottelee. Backend putoaa pelkkä-teksti-malliin — jolle nyt ojennetaan kuva jota se ei voi lukea ja ei mitään tekstiä, koska asiakas optimoi tekstin pois. Nopea polku oli hiljaa poistanut turvaverkon. Ylimääräisen nopean polun voi pudottaa turvallisesti; ei voi pudottaa sitä minkä ympäri nopea polku oli vain oikopolku.
Korjaus: ratkaise vararatkaisun mukaan, ei ensisijaisen
Korjaus on se osa jonka kannattaa varastaa. Se ei ollut palata OCR:n jatkuvaan ajamiseen — se heittäisi pois voiton yleiselle tapaukselle. Se oli tehdä ohitus ehdolliseksi toiselle kyvykkyyslipulle: tukeeko vararatkaisu näköä? Backendin konfiguraatio sai fallbackVision-lohkon; asiakas välimuistittaa aiFallbackSupportsVision-arvon ensimmäisen lipun vieressä. Nyt päätöksellä on neljä haaraa:
- Sekä ensisijainen että vararatkaisu näkevät → optimoi kuva, ohita OCR. Nopea, yleinen polku.
- Ensisijainen näkee mutta vararatkaisu on sokea → optimoi kuva ja aja OCR, vakuutuksena vararatkaisulle.
- Kuvan optimointi heittää virheen → OCR kovana vararatkaisuna.
- Suunnitelmalla ei ole näköä lainkaan → vain OCR.
Ja toinen haara — vakuutustapaus — on se missä viimeinen siirto ratkaisee. OCR:n ajaminen kuvan optimoinnin jälkeen lisäisi sen viiveen päälle. Joten molemmat pyörivät yhdessä yhden Promise.all-kutsun alla: kuvan optimointi ja OCR limittyvät, ja OCR joka on olemassa vain siltä varalta että backend sitä tarvitsee, valmistuu sen ajan sisällä jonka kuva joka tapauksessa oli optimoinnissa. Turvaverkko on seinäkelloajassa ilmainen aina kun sitä ei käytetä. Siinä koko temppu yhdessä lauseessa: pidä halpa vararatkaisu, ratkaise sen mukaan tarvitseeko vararatkaisu-tarjoajasi sitä oikeasti, ja rinnakkaista se niin että se ei maksa mitään kunnes se pelastaa sinut.
Kaksi pienempää ansaa jotka ohitus laukaisi
Lykätyn työn aggressiivinen ohittaminen tapaa nostaa pintaan jokaisen kohdan joka hiljaa oletti että työ tapahtuisi. Kaksi tuli esiin saman viikon PR-katselmoinnissa.
Jonon lukkiutuminen. Kuvakaappausjono piti kohdetta "valmiina" kun sillä oli OCR-teksti. Näkökuvakaappaukset eivät nyt koskaan saaneet OCR-tekstiä — tarkoituksella — joten valmiustarkistus hasIncompleteOCR odotti ikuisesti ja jono jumittui. Korjaus määrittelee "valmiin" uudelleen niin että on optimoitu kuva tai on OCR-teksti, mikä on se minkä putki nyt oikeasti takaa.
Vanhentunut kyvykkyyslippu. aiSupportsVision on välimuistitettu, mikä tarkoittaa että se voi vanhentua juuri väärään suuntaan: käyttäjä kirjautuu ulos tai konfiguraation haku epäonnistuu, ja asiakas jää uskomaan että se saa yhä lähettää kuvia mallille joka ei enää tue niitä. Lippu nollautuu nyt uloskirjautumisella ja konfiguraation haun epäonnistuessa — välimuistitetulla kyvykkyydellä on oltava tarina sille kun se mitä se kuvaa katoaa.
Pari sanaa numeroista
Koska rehellisyys on tämän sarjan pointti: commit-viestit kantavat lukuja — kuvan optimointi "~30ms" versus OCR "~100–150ms," kuormat "~200–500 KB vs ~3–8 MB PNG" — ja ne ovat uskottavia, mutta ne ovat tekijän omia rivinsisäisiä arvioita, eivät benchmarkin tulostetta. Historiassa ei ole missään mitattua päästä päähän ennen/jälkeen-viivettä. Kohtele nopeusargumenttia järkevänä suunnitteluaikomuksena, ei todistettuna tuloksena. Kuorman koon pieneneminen on se yksi luku johon voit luottaa, koska se putoaa suoraan koonmuutoksesta ja uudelleenpakkaamisesta.
Kolme asiaa jotka tämä julkaisu opetti meille
- Pikselien lähettäminen voittaa tekstin lukemisen — kun malli voi nähdä. OCR heittää pois kaiken minkä kuvakaappaus viestii merkkien lisäksi. Näkömalli lukee asettelun, kaavion ja sisennyksen, ja optimoitu JPEG tekee siitä kohtuuhintaista. Mutta se on veto kyvykkyydestä, minkä vuoksi se tarvitsee vararatkaisun.
- Nopean polun voi optimoida pois, turvaverkkoa ei koskaan. OCR:n ohittaminen näkökäyttäjiltä oli oikein ensisijaiselle ja väärin vararatkaisulle. Bugi ei ollut ohitus; se oli ohittaminen ensisijaisen kyvykkyyden perusteella kun vararatkaisun oma oli se jolla oli merkitystä.
- Ilmainen turvaverkko on rinnakkaistettu turvaverkko. Ratkaise vakuutustyön mukaan tarvitaanko sitä oikeasti, aja se sitten nopean polun rinnalla niin että se limittyy. Oikein tehtynä siro heikkeneminen ei maksa mitään ennen kuin sinä päivänä se on ainoa syy miksi ominaisuutesi vielä toimii.
Edellinen luku v1-tarinassa: läpiklikattavan peittoikkunan kaksi vikatilaa (v1.8.5); ja koko kaarelle, ohjelmiston toimittamisen anatomia täydellisyyteen asti.