Pagrindinė navigacija

Praktiniai individualiai pritaikytų RAG sprendimų pavyzdžiai

Realūs pavyzdžiai rodo, kaip individualiai pritaikytos paieška papildyto generavimo sistemos sprendžia sudėtingas įmonių žinių problemas.

Pastaruoju metu RAG kartais vertinamas nepalankiai: vieni mano, kad tai visiškai paprasta (pradėti paprasta, išplėsti – ne taip lengva), o kiti – kad jį pakeitė „agentinės sistemos“ (nors dažnai vos pasigilinus jos labai greitai ima priminti RAG...).

Šiame tinklaraščio įraše pateikiame kelis praktinius pavyzdžius, parodančius, kaip sprendžiame dažnas problemas:

  • Mišrių tekstinių ir skaitinių duomenų apdorojimas ir kodėl jie trikdo primityvų RAG: raktažodžiai sutampa, o skaičiai neturi semantinės reikšmės.

  • Kodėl verta pirmiausia kurti santraukas įterpiniams: sugeneruokite trumpą kiekvieno fragmento aprašą, tada pagal jį kurkite įterpinį ir vykdykite paiešką.

  • Kaip generuoti kontekstines santraukas: įtraukite pirminio dokumento kontekstą, kad būtų galima atskirti panašios formos statistinius duomenis.

  • Kada pasikliauti kodu ir „Pydantic“ modeliais: kai svarbu tiksliai išsaugoti turinį, patikimumui užtikrinti derinkite pasirinktinį kodą ir (arba) „Pydantic“ modelį su LLM iškvietomis.

Individualių RAG sprendimų kūrimas

Pagrindai

RAG sistemos naudojamos visur – nuo klientų aptarnavimo robotų iki vidinių žinių asistentų.

Paprastai jos veikia taip:

  1. Suskaidote pirminius dokumentus į fragmentus

  2. Atvaizduojate kiekvieną fragmentą vektorinėje erdvėje

  3. Gaunate K tinkamiausių fragmentų užklausos metu

  4. Generuojate tais fragmentais pagrįstą atsakymą

Populiarūs įrankių rinkiniai – „LangChain“, „LlamaIndex“, OpenAI „Filestore“ – šiuos veiksmus paverčia beveik elementariais. Tačiau realiose duomenų apdorojimo grandinėse pasitaiko ne vien tankaus teksto, todėl baziniam RAG gali kilti sunkumų. Tolesniuose skyriuose konkrečiais pavyzdžiais parodysime duomenų keliamus iššūkius ir, didėjant sudėtingumui, palaipsniui kursime sprendimą.

Kai viskas tampa sudėtingiau

  1. Kai jūsų duomenys nėra vien tekstas (o taip nutinka dažnai)

Panagrinėkime tokį žaidimų srities duomenų fragmentą:

JSON

{ "Attack": { "Range": { "default": 5, "with_Draconic_Ascension": 5.5, }, "Speed": { "default": "2 seconds", "with_Draconic_Ascension": "2.2 seconds", }, "Damage": { "default": 10, "with_Draconic_Ascension": 12, } }} 

Įterpiniai veikia dėl išmoktų semantine reikšme ir gramatika paremtų žodžių sąsajų. Pirmiau pateiktuose duomenyse sumaišytas tekstas ir skaičiai, o už šio konkretaus konteksto ribų skaičiai nėra susiję su žodžiais. Taigi galima sakyti, kad šis duomenų fragmentas iš esmės yra kelių aprašomųjų žodžių ir po jų pateiktų atsitiktinių skaičių derinys.

Tai nekeltų problemų, jei turėtume tik tokio tipo duomenis, nes vis tiek galėtume juos gauti pagal kelių esamų aprašomųjų žodžių įterpinius (arba tiesiog naudoti „text-to-SQL“). Tačiau kas nutiktų, jei šis fragmentas būtų paslėptas tarp daugybės tankaus teksto fragmentų, kuriuose kartojasi tie patys žodžiai? Pavyzdžiui:

JSON

{"Draconic_Ascension": { "description": "Transform into dragon form. Attack range and speed are increased by 10%, damage is boosted by 20%.", "details": { "activation_conditions": "Can only be activated when HP is below 50%or Fury meter is full.", "visual_effects": "Wings unfurl, scales shimmer with embers, voice lines change to echoing growls.", "lore": "An ancient bloodline awakens. The bearer of the mark channels the soul of the last Flamewing Wyrm, becoming a living storm of fire and fury." } }}

Dabar įsivaizduokime, kad norime gauti atsakymą į klausimą „What is the attack range with Draconic Ascension?“ Labai tikėtina, kad norimo fragmento nerasime, nes jis paskęs tarp kitų fragmentų, kuriuose yra tie patys raktažodžiai.

Esminė problema ta, kad šiuos duomenų fragmentus sunku atskirti, nors juose pateikiama skirtingo pobūdžio informacija ta pačia tema. Ar galėtume kaip nors juos praturtinti ar patobulinti? Žinoma, galime :smile:

  1. Praturtinkite duomenis juos apibendrindami – taip, perskaitėte teisingai

Užuot tiesiogiai kūrę paties fragmento įterpinį, pirmiausia galime sugeneruoti santrauką, apibūdinančią duomenis, o tada pagal ją kurti įterpinį ir vykdyti paiešką. Generuodami atsakymą vis tiek naudotume su santrauka susietus pradinius duomenis.

Taigi dviem pirmiau pateiktiems fragmentams sugeneruotume maždaug tokias santraukas:

  1. Atakos nuotolio, greičio ir žalos statistika (įprasta ir naudojant „Draconic Ascension“).

  2. „Draconic Ascension“ aprašas ir išsami informacija, įskaitant aktyvinimo sąlygas, vaizdo efektus ir priešistorę.

Tada užklausą taip pat papildome, kad ji „atitiktų“ santrauką. Pavyzdžiui, klausimą „What is the attack range with Draconic Ascension?“ pakeistume į „What is the statistics of attack range with Draconic Ascension?“ Tai ypač svarbu, kai paieškos užklausą pateikia techninių žinių neturintys naudotojai, kalbantys ~~„laisvu stiliumi“~~ įprasta žmonių kalba, nes jiems nebūtina žinoti ar rūpintis, kaip veikia RAG ir kaip maksimaliai padidinti tikslumą bei aprėptį.

Schema, iliustruojanti, kada viskas tampa sudėtingiau.

  1. Neištraukite informacijos iš konteksto (tai apskritai praverčia gyvenime)

Toliau panagrinėkime atvejį, kai tenka tvarkyti daugybę vienodai atrodančių duomenų fragmentų, pavyzdžiui:

Plain Text

# Chunk one{ "Attack": { "Range": { "default": 9, }, ... }}# Chunk two{ "Attack": { "Range": { "default": 6, }, ... }}# Chunk three{ "Attack": { "Range": { "default": 7, }, ... }}

Jei laikytumės to paties metodo, įsivaizduokite klausimą „what is character X’s attack range?“ Su ką tik sugeneruotomis santraukomis tektų kliautis sėkme ir spėlioti, nes jos taip pat atrodytų labai panašiai. Kaip galėtume jas atskirti?

Atsakymas paprastas – pateikti kontekstą. Į duomenų fragmentą galėtume tiesiog įtraukti nuorodą į pirminį dokumentą, pavyzdžiui, šiuo atveju {”character”: “X”}. Tuomet galėtume tiksliai gauti reikiamus personažo X duomenis, net jei turėtume tokius pačius personažų Y ir Z duomenis.

Vis dėlto geresnis ir universalesnis būdas – sugeneruoti kontekstinę fragmento santrauką. Kitaip tariant, užuot generavę tik paties duomenų fragmento santrauką, galėtume pateikti ir jo pirminį dokumentą, ir patį fragmentą, kad būtų sukurta bendra kontekstinė santrauka. Joje paaiškintume, kaip fragmentas siejasi su pirminiu dokumentu, pavyzdžiui:

  1. Šiame fragmente pateikiama išsami personažo X ... statistika. Šis fragmentas papildo visą dokumentą, parodydamas personažo X pranašumą pagal atakos greitį...

  2. Šiame fragmente pateikiama išsami personažo Y ... statistika. Šis fragmentas papildo visą dokumentą, parodydamas specialiuoju gebėjimu sustiprintus personažo Y rodiklius...

  3. Šiame fragmente pateikiama išsami personažo Z ... statistika. Šis fragmentas papildo visą dokumentą, parodydamas, kad personažo Z rodikliai puikiai tinka tanko vaidmeniui komandinėse rungtynėse...

Šis metodas (iš dalies įkvėptas „Anthropic“) pirmiau pateiktam pavyzdžiui gali atrodyti perteklinis, tačiau jis labai veiksmingas dirbant su fragmentais, kuriuos be konteksto galima klaidingai suprasti. Be to, tai universalus visiems fragmentams tinkamas metodas, leidžiantis išlaikyti tvarkingą inžinerinę apdorojimo grandinę.

Schema, iliustruojanti, kada viskas tampa sudėtingiau.

  1. Kai reikia būti ~~kontrolės maniaku~~ kruopščiam

Paprastai gauname vientisus duomenis ir suskaidome juos į fragmentus RAG sistemai. Šiame pavyzdyje parodome kiek kitokį atvejį: duomenys jau suskaidyti, tačiau netinkamai – tai atsitiktinės loginio fragmento dalys, kurias iš tiesų reikia vėl sujungti. Loginis fragmentas – tai turinio dalis, kurią natūralu laikyti drauge, pavyzdžiui, dokumento poskyris arba nuosekli pastraipa.

Schema, iliustruojanti, kada viskas tampa sudėtingiau.

Pirmiausia pabandėme visus šiuos duomenis pateikti per vieną LLM iškvietą, paprašyti juos sugrupuoti tinkamiausiu būdu ir grąžinti sugrupuotą turinį. LLM turėtų tai atlikti gana gerai, tiesa? Ir taip, ir ne.

Čia ir keliais kitais atvejais pastebėjome, kad LLM linkę tinginiauti ir nėra patikimi, kai reikia viso tikslaus turinio, ypač esant ilgam kontekstui. Tai visiškai suprantama. Tačiau šiuo konkrečiu atveju tai buvo nepriimtina, nes mums reikėjo visiškai tikslaus turinio, žodis po žodžio – be santraukų ir nepraleidžiant nė menkiausios pradinio turinio dalies. Negalime praleisti nė vienos detalės.

Žinoma, geroji pusė buvo ta, kad LLM puikiai suprato suskaidytų fragmentų semantiką ir struktūrą. Jei tik neatsisakydavo tiksliai atkartoti turinio. Po velnių :/

Kaip galėtume pasinaudoti LLM stiprybėmis, bet išvengti sričių, kuriose jis nepatikimas? Kreipėmės į seną gerą draugą – kodą (kitaip tariant, individualią „Python“ funkciją). Ir į paprastesnį už paprastą „Pydantic“ modelį. Štai sprendimas:

  • Nuosekliai pereikite per dalis, kartu išlaikydami dabartinį loginį fragmentą

  • Ties kiekviena dalimi paklauskite LLM: ar ši dalis priklauso dabartiniam loginiam fragmentui? Atsakymas turi būti „taip“ arba „ne“ (pagal „Pydantic“ modelį).

  • Jei taip, prijunkite dalį prie fragmento. Jei ne, išveskite užbaigtą dabartinį loginį fragmentą ir pradėkite naują nuo šios dalies.

Schema, iliustruojanti, kada viskas tampa sudėtingiau.

Žinoma, čia sunaudojame šiek tiek daugiau žetonų nei vienu kartu apdorodami visą turinį, tačiau šiuo konkrečiu atveju, kai svarbiausia tiksliai išsaugoti turinį, nedidelės papildomos išlaidos tikrai atsipirko.

Tai labai paprastas sprendimas, tačiau jis paremtas svarbiu principu: kai būtinas kruopštumas, nenorime pasikliauti vien LLM, nes jie vis dėlto yra tikimybiniai.

Pasirinktinis kodas ar funkcijos ir „Pydantic“ modeliai padeda pasiekti nuspėjamą bei patikimą rezultatą ir kartu išnaudoti LLM galimybes.

Apibendrinimas

Generatyviojo DI sprendimo kūrimas yra tiek pat inžinerinis, kiek ir dirbtinio intelekto iššūkis. Tikimės, kad šie pavyzdžiai įkvėpė jus imtis savų unikalių iššūkių. Daugiau apie inžinerija grindžiamus generatyviojo DI sprendimus skaitykite mūsų tinklaraščio įraše apie maršruto parinktuvais grindžiamų agentinių sistemų projektavimą.

Autorius

Cynthia Yu