Useimmat hakukentät on tarkoitettu vastaamaan yhteen kysymykseen: mitä vastaa kirjoittamani sanat? Tämä on oikea kysymys kirjastoluettelolle. Se on väärä kysymys ravitsemussovellukselle.
Kun käyttäjä avaa reseptisivun Appetecissä, kirjoitetut sanat ovat pyynnön vähiten kiinnostava osa. Todella tärkeä on näkymätön: jäljellä olevat kalorit tälle päivälle, mitä he ovat kokanneet viikko toisensa jälkeen ajattelematta sitä, ovatko he vegaaneja ja ovatko he jo ylittäneet budjettinsa illalliseen mennessä. Hakukone, joka jättää kaiken tämän huomiotta, palauttaa mielellään vastaavia reseptejä – ja aivan yhtä mielellään sabotoi syyn, miksi käyttäjä avasi sovelluksen. Tämä on arkkitehtuuri tämän kuilun kuromiseksi umpeen rakennetun mallin takana: relevanssi, joka on oikea tälle henkilölle, juuri nyt.
Miksi pelkkä tekstivastaavuus ei riitä
Malli soveltuu aina, kun sama kysely palauttaa eri tuloksia eri käyttäjille – kun "paras" riippuu kontekstista, jota käyttäjä ei koskaan kirjoittanut, ja kun omistamasi luettelo on pienempi kuin käyttäjiesi odottama luettelo. Personoitu reseptihaku käsittelee relevanssia kolmen signaalin yhdistelmänä yhden sijaan:
| Signaali | Mitä se tallentaa |
|---|---|
| Teksti | Mitä käyttäjä kirjoitti – tai aterian skannaustilassa, mitä lautasella havaittiin |
| Tavoitteeseen sopivuus | Kuinka hyvin resepti sopii käyttäjän tälle aterialle, tänään, jäljellä oleviin kaloreihin |
| Tapa | Kuinka usein tämä tietty käyttäjä todella syö tätä reseptiä |
Naiivi moottori näkee vain taulukon ensimmäisen rivin. Tämä malli yhdistää kaikki kolme yhdeksi järjestetyksi luetteloksi, sitten täyttää ulkoisesta lähteestä aina, kun paikallinen luettelo on vähissä – hiljaisesti kasvattaen luetteloa joka kerta. Tulos tuntuu vähemmän kuin tietokannan kyselystä ja enemmän ystävältä, joka tuntee jo keittiösi ja ruokavaliosi.
Miten putkisto kootaan
Järjestelmä toimii hakupipelineinä, jonka edessä on personointikerros ja takana itsensä korjaava luettelo.
Kaksi hakukonetta, yksi sopimus. Elasticsearch on ensisijainen: epätarkka kirjoitusvirheiden sietokyky, englannin kielen sananmuotojen tunnistus ("grill" löytää "grilled"), painotettu relevanssin pisteytys. Jos se on joskus tavoittamattomissa, sama kysely ohjataan MongoDB-hakuun, joka kunnioittaa samoja suodattimia ja personointisääntöjä. Haku heikkenee vian sattuessa; se ei koskaan katoa kokonaan.
Kerros, jonka käyttäjät tuntevat, mutta eivät koskaan näe. Ennen tulosten palauttamista kolme asiaa muokkaavat luetteloa. Kaloribudjetti lasketaan päivittäisestä tavoitteesta miinus kirjatut tiedot, mikä nostaa esiin reseptejä, jotka sopivat jäljellä olevaan aikaikkunaan – ja jos käyttäjä on ylittänyt tavoitteensa, luettelo lajitellaan hiljaa matalimman kaloripitoisuuden mukaan, ohjaten kohti palautumista hemmottelun sijaan. Ruokailuhistoria tiivistää viimeiset 60 päivän aterialokit "mitä todella syöt" -kartaksi, pitäen suosikit yhden napautuksen päässä etusivulla. Ruokavalion mieltymykset – kasvis, vegaani, ei-kasvis, sekä kaksi tusinaa tarkempaa tunnusta, kuten keto, gluteeniton ja proteiinipitoinen – suodattavat ja järjestävät kaiken uudelleen.
Itsestään kasvava luettelo. Kun paikalliset tulokset ovat vähissä, putkisto hakee tietoa ulkoisesta lähteestä, FatSecretistä, yhdistäen nämä tulokset samaan äärettömään vierityssyötteeseen ilman näkyvää saumaa. Se kirjoittaa sitten nämä ulkoiset reseptit takaisin paikalliseen luetteloon käyttäen LLM:ää luokitellakseen kunkin kasvis-, ei-kasvis- tai vegaaniseksi, jotta se on oikein personoitavissa seuraavalla kerralla – sama periaate, joka on hybridihakuarkkitehtuurin takana, jota olemme soveltaneet muualla, missä paikallisten ja ulkoisten tulosten on tuntava yhtenä järjestelmänä.
Kolme välimuistia, kolme tehtävää. Yhdistetyt tulosjoukot, käyttäjäkohtainen tapakartta ja ulkoisten API-kutsujen vastaukset sisältävät kukin oman itsenäisen elinkaarensa, joten personoitu, monilähdehakukysely palautuu edelleen suunnilleen yhden yksinkertaisen haun nopeudella.
Nimeämisen arvoiset kompromissit
Mikään tästä ei tullut ilmaiseksi, ja rehellisyys kustannuksista on osa sitä, mikä tekee mallista luotettavan pelkän nerokkuuden sijaan.
Relevanssi tehtiin tarkoituksellisesti epäneutraaliksi. Puhdas tekstirelevanssiin perustuva sijoitus on "reilu" tavalla, joka on tässä aktiivisesti hyödytön. Tulokset ovat tarkoituksella painotettuja tavoitteeseen sopivuuden ja tapojen suuntaan, koska hieman vähemmän tekstin perusteella täydellinen resepti, joka sopii jonkun budjettiin ja makuun, on parempi vastaus. "Miksi tämä sijoittui ensimmäiseksi?" tulee tuotepäätökseksi, ei hakukoneen oletukseksi – minkä vuoksi nuo painot ovat sovelluskoodissa, eivät omistamattomassa konfiguraatiotiedostossa.
Koko luettelon omistaminen ei ollut tavoite – sen oikean osan omistaminen oli. Harkitut ääripäät olivat täysin kuratoitu luettelo (korkea laatu, kapea kattavuus) tai täysi välityspalvelu ulkoiseen APIin (rajaton kattavuus, nolla hallintaa, nolla personointia). Järjestelmä jakaa eron: aloita omistetuilla ja käyttäjien luomilla resepteillä, täydennä ulkoisesti tarvittaessa ja sisällytä täydennetty sisältö – suuntautuen ajan myötä omistamaan juuri sen, mitä käyttäjät todella etsivät.
Häiriönsietokyky valittiin huipputehokkuuden edelle. Kahden täyden hakukoneen käyttö maksaa enemmän kuin yhden – sama häiriönsietokyky edellä -ajattelu on useimpien taustajärjestelmäpäätöstemme takana. Tämä kustannus hyväksyttiin tarkoituksella: reseptihaku on ydin, päivittäisessä käytössä oleva toiminnallisuus, jossa heikentynyt haku on pieni harmi, mutta puuttuva haku on rikkinäinen sovellus.
Personointi maksaa ennustettavuudessa. Tulokset eroavat aidosti kellonajan, kulutettujen kalorien ja historian mukaan – jopa samalle käyttäjälle aamiaisen ja illallisen välillä. Tämä on tarkoituksellista, mutta se nostaa testauksen ja tuen rimaa: "se toimii puhelimessani" menettää merkityksensä, kun oikeellisuus määritellään käyttäjäkohtaisesti, hetkittäin.
Milloin tämä malli sopii – ja milloin ei
On syytä selventää, missä tätä mallia ei pitäisi tavoitella. Se ansaitsee monimutkaisuutensa, kun tulosten pitäisi aidosti erota käyttäjäkohtaisesti kontekstin perusteella, jota he eivät koskaan kirjoittaneet, kun omistettu luettelo on pienempi kuin käyttäjät odottavat, mutta sitä voidaan rikastuttaa ulkoisesti, kun ominaisuus on riittävän usein käytetty, jotta sujuva heikkeneminen on tärkeämpää kuin minimaalinen infrastruktuuri, ja kun "relevanssi" oikeutetusti sisältää signaaleja tekstin lisäksi.
Se on väärä työkalu, kun tulokset ovat identtiset jokaiselle käyttäjälle – julkinen dokumenttihaku, SKU-haku – jossa personointi lisää kustannuksia ilman hyötyä. Se on tarpeeton, kun luettelo on riittävän pieni ja vakaa, jotta yksi moottori pelkällä suodatuksella riittää, ja se toimii sinua vastaan kaikkialla, missä tiukka, identtinen, täysin selitettävissä oleva sijoitus on tiukka vaatimus.
Todellinen tehtävä
Haun laatua käsitellään tässä personointiongelmana, ei vain vastaavuusongelmana. Reseptihaku, joka on tekstin perusteella täydellinen, mutta jättää huomiotta, että käyttäjä on vegaani, on jo ylittänyt budjettinsa ja on kokannut samat viisi illallista koko kuukauden, toimi teknisesti, mutta epäonnistui käytännössä. Tehtävänä ei koskaan ollut löytää kyselyyn sopivia reseptejä – vaan löytää resepti, jonka tämän henkilön tulisi seuraavaksi kokata. Jokainen tämän arkkitehtuurin kerros, aina kahdesta hakukoneesta hiljaiseen uudelleenjärjestelyyn, kun joku on ylittänyt budjettinsa, on olemassa tämän kuilun kuromiseksi umpeen.
Jos harkitset vastaavaa rakenna-vs-yhdistä -päätöstä haun tai tiedonhaun osalta omassa tuotteessasi, keskustelemme mielellämme siitä.
Muut blogit
1. Digitaalisen terveysalustan skaalaus Microservices-arkkitehtuurilla

