RAG saa nykyään toisinaan huonon maineen: sitä pidetään joko täysin yksinkertaisena (alkuun pääseminen on helppoa, skaalaaminen ei niinkään) tai sen ajatellaan jääneen ”agenttijärjestelmien” jalkoihin (vaikka pintaa raaputtamalla monet niistä alkavat muistuttaa RAGia hyvin nopeasti…).
Tässä blogikirjoituksessa käymme parin käytännön esimerkin avulla läpi yleisiä haasteita:
Tekstin ja numeerisen datan käsittely ja se, miksi naiivi RAG ei siinä toimi: avainsanat menevät päällekkäin, eikä numeroilla ole semanttista merkitystä.
Miksi tiivistelmiin perustuvat upotukset auttavat: luo jokaisesta osasta lyhyt kuvaus ja tee upotus sekä haku sen perusteella.
Kontekstisidonnaisten tiivistelmien luominen: sisällytä ylätason dokumentin konteksti, jotta samankaltaiset tilastot voidaan erottaa toisistaan.
Milloin kannattaa hyödyntää koodia ja Pydantic-malleja: kun sisältö on säilytettävä sanatarkasti, luotettavuutta voi parantaa yhdistämällä mukautetun koodin ja/tai Pydantic-mallin suuren kielimallin kutsuihin.
Perusteet
RAG-järjestelmät toimivat niin tukibottien kuin sisäisten tietämysavustajienkin perustana.
Konepellin alla tehdään yleensä seuraavaa:
Pilko lähdedokumentit osiin
Muunna jokainen osa vektoriavaruuden upotukseksi
Nouda kyselyn yhteydessä K parasta osaa
Luo vastaus näiden osien perusteella
Suositut työkalut, kuten LangChain, LlamaIndex ja OpenAI Filestore, tekevät näistä vaiheista lähes triviaaleja. Todellisissa käsittelyputkissa vastaan tulee kuitenkin muutakin kuin tiivistä tekstiä, ja silloin yksinkertainen RAG voi joutua vaikeuksiin. Seuraavissa osioissa esittelemme konkreettisia datahaasteita ja rakennamme ratkaisua vaiheittain monimutkaisuuden lisääntyessä.
Kun data ei ole pelkkää tekstiä (varsin tavallinen tilanne)
Tarkastellaan seuraavaa peliaiheista dataosaa:
JSON
Upotukset toimivat sanojen semanttisten ja kieliopillisten suhteiden oppimisen ansiosta. Yllä oleva data sisältää tekstiä ja numeroita, mutta tämän nimenomaisen kontekstin ulkopuolella numeroilla ei ole yhteyttä sanoihin. Voisikin sanoa, että tämä dataosa on lähinnä yhdistelmä jossain määrin kuvailevia sanoja ja niitä seuraavia satunnaisia numeroita.
Tämä ei olisi ongelma, jos kaikki datamme olisi samanlaista, sillä voisimme edelleen hakea tietoa muutamien kuvailevien sanojen upotusten perusteella (tai käyttää vain tekstistä SQL:ksi -ratkaisua). Entä jos tämä osa on hautautunut lukuisten tekstitiheiden osien joukkoon, joissa esiintyvät samat sanat? Esimerkiksi:
JSON
Kuvitellaan, että haluamme hakea vastauksen kysymykseen “What is the attack range with Draconic Ascension?” Todennäköisesti emme löydä haluamaamme osaa, koska se hukkuu samoja avainsanoja sisältävien muiden osien kohinaan.
Perusongelma on, ettemme pysty erottamaan näitä dataosia kunnolla toisistaan, vaikka ne sisältävät samasta aiheesta erityyppistä tietoa. Voisimmeko jotenkin rikastaa tai parantaa dataa? Totta kai voimme:smile:
Rikasta dataa tekemällä siitä tiivistelmä – kyllä, luit oikein
Sen sijaan, että tekisimme upotuksen suoraan itse osasta, voimme ensin luoda sen sisältöä kuvaavan tiivistelmän ja tehdä upotuksen sekä haun tiivistelmän perusteella. Vastauksen luonnissa käyttäisimme silti tiivistelmään linkitettyä alkuperäistä dataa.
Yllä esitetyistä kahdesta osasta voisimme siis luoda esimerkiksi seuraavat tiivistelmät:
Hyökkäyksen kantaman, nopeuden ja vahingon tilastot (oletusarvot ja Draconic Ascensionin kanssa).
Draconic Ascensionin kuvaus ja tiedot, kuten aktivointiehdot, visuaaliset tehosteet ja taustatarina.
Lisäksi täydennämme kyselyä, jotta se vastaa paremmin tiivistelmää. Esimerkiksi kysymys “What is the attack range with Draconic Ascension?” muutettaisiin muotoon “What is the statistics of attack range with Draconic Ascension?” Tämä on erityisen tärkeää, kun hakukyselyn esittävät muiden kuin teknisten alojen käyttäjät ~~”vapaamuotoisesti”~~ tavallisella ihmiskielellä. Heidän ei tarvitsekaan tietää tai välittää siitä, miten RAG maksimoi tarkkuuden ja saannin.


Älä irrota asioita asiayhteydestään (pätee elämään yleisemminkin)
Seuraavaksi tarkastellaan tilannetta, jossa käsiteltävänä on valtava määrä samalta näyttäviä dataosia, kuten alla:
Plain Text
Jos jatkamme samalla tavalla, kuvitellaan kysymys “what is character X’s attack range?” Näiden juuri luotujen, keskenään hyvin samankaltaisten tiivistelmien kanssa joutuisimme arvailemaan tuurilla. Miten voisimme siis erottaa ne toisistaan?
Yksinkertainen vastaus: lisäämällä kontekstia. Voisimme lisätä dataosaan viittauksen sen ylätason dokumenttiin, tässä tapauksessa esimerkiksi {”character”: “X”}. Näin voisimme hakea tarkasti oikeat tiedot hahmosta X, vaikka meillä olisi samat tiedot myös hahmoista Y ja Z.
Parempi ja yleiskäyttöisempi ratkaisu olisi kuitenkin luoda osasta kontekstisidonnainen tiivistelmä. Sen sijaan, että tekisimme tiivistelmän vain dataosasta, voisimme syöttää sekä ylätason dokumentin että osan kontekstisidonnaisen tiivistelmän luontia varten. Tiivistelmässä kuvaisimme, miten osa liittyy ylätason dokumenttiin, esimerkiksi näin:
Tämä osa sisältää yksityiskohtaisia tilastoja … hahmosta X. Osa liittyy koko dokumenttiin osoittamalla X:n vahvuuden hyökkäysnopeudessa…
Tämä osa sisältää yksityiskohtaisia tilastoja … hahmosta Y. Osa liittyy koko dokumenttiin esittelemällä Y:n erikoiskyvyn tehostamat ominaisuudet…
Tämä osa sisältää yksityiskohtaisia tilastoja … hahmosta Z. Osa liittyy koko dokumenttiin osoittamalla, että Z:n ominaisuudet sopivat hyvin tankiksi joukkueotteluissa…
Tämä (osin Anthropicin inspiroima) menetelmä voi vaikuttaa yllä olevan esimerkin kannalta ylimitoitetulta. Se on kuitenkin erittäin tehokas osille, jotka voisi tulkita väärin ”asiayhteydestään irrotettuina”. Lisäksi se tarjoaa kaikille osille sopivan yhtenäisen lähestymistavan ja pitää käsittelyputken siistinä.


Kun on oltava ~~kontrollifriikki~~ täsmällinen
Yleensä saamme datan kokonaisuuksina ja pilkomme sen osiin RAG-järjestelmää varten. Tässä esimerkissä tilanne on hieman toinen: data on pilkottu huonoihin osiin. Ne ovat satunnaisia katkelmia loogisista kokonaisuuksista, jotka on koottava uudelleen yhteen. Looginen osa tarkoittaa sisältökokonaisuutta, jonka kuuluu luontevasti pysyä yhdessä, kuten dokumentin alaosiota tai yhtenäistä kappaletta.


Ensimmäisellä yrityksellä syötimme kaiken datan suurelle kielimallille, pyysimme sitä ryhmittelemään sisällön parhaaksi katsomallaan tavalla ja palauttamaan ryhmitellyn sisällön. Suuren kielimallin pitäisi olla tässä melko hyvä, vai mitä? No, kyllä ja ei.
Olemme havainneet useissa muissakin tilanteissa, että suuret kielimallit oikovat ja ovat epäluotettavia, kun sisällön on säilyttävä kokonaisena ja täsmällisenä – etenkin kontekstin ollessa pitkä. Se on täysin ymmärrettävää. Tässä käyttötapauksessa se oli kuitenkin ratkaiseva este, sillä tarvitsimme sisällön täsmälleen sanasta sanaan: ei tiivistelmiä eikä yhdenkään alkuperäisen kohdan ohittamista. Yksikään yksityiskohta ei saanut jäädä pois.
Vastauksen myönteinen puoli oli tietenkin se, että malli ymmärsi rikkinäisten osien merkitykset ja rakenteet erinomaisesti. Kunhan se vain suostuisi toistamaan sisällön täsmälleen. Hemmetti:/
Miten voisimme hyödyntää suuren kielimallin vahvuuksia joutumatta kärsimään sen epäluotettavuudesta? Käännyimme vanhan kunnon ystävämme eli koodin puoleen (lue: mukautettu Python-funktio). Lisäksi käytimme ”tätä yksinkertaisemmaksi ei pääse” -Pydantic-mallia. Ratkaisu on seuraava:
Käy osiot läpi yksi kerrallaan ja pidä samalla yllä nykyistä loogista osaa
Kysy jokaisen osion kohdalla suurelta kielimallilta: kuuluuko tämä osio nykyiseen loogiseen osaan? Vastauksen on oltava kyllä tai ei Pydantic-mallin mukaisesti.
Jos kyllä, liitä osio nykyiseen osaan. Jos ei, palauta valmistunut looginen osa ja aloita osiosta uusi.


Tämä käyttää tietenkin hieman enemmän tokeneita kuin koko sisällön käsittely yhdellä kertaa. Tässä käyttötapauksessa täsmällisen sisällön säilyttäminen oli kuitenkin tärkeintä, joten pieni lisäkustannus kannatti.
Ratkaisu on hyvin yksinkertainen, mutta se noudattaa tärkeää periaatetta: kun vaaditaan täsmällisyyttä, emme halua luottaa yksinomaan suuriin kielimalleihin, sillä ne ovat pohjimmiltaan probabilistisia.
Mukautetulla koodilla ja funktioilla sekä Pydantic-malleilla voidaan saavuttaa ennustettava ja luotettava lopputulos hyödyntäen silti suurten kielimallien kykyjä.
Generatiivisen tekoälyratkaisun rakentaminen on yhtä lailla suunnittelu- kuin tekoälyhaaste. Toivomme näiden esimerkkien innostaneen sinua tarttumaan omiin ainutlaatuisiin haasteisiisi. Lue lisää suunnittelulähtöisistä generatiivisen tekoälyn ratkaisuista blogikirjoituksestamme, joka käsittelee reitittimiin perustuvien agenttijärjestelmien suunnittelua.