Kohët e fundit, RAG nganjëherë ka marrë një nam të keq, ose sepse njerëzit mendojnë se tani është krejtësisht i parëndësishëm (është i tillë në fillim, por jo edhe aq kur shkallëzohet), ose sepse mendojnë se është tejkaluar nga „sistemet agjentike“ (të cilat, në shumë raste, po të shohësh përtej sipërfaqes, fillojnë shumë shpejt t’i ngjajnë RAG-ut…).
Ky artikull paraqet një ose dy shembuj praktikë për të treguar si i trajtojmë disa sfida të zakonshme, konkretisht:
Trajtimi i të dhënave të përziera tekstuale dhe numerike dhe pse ato e nxjerrin në pah dobësinë e një RAG-u naiv: fjalët kyçe përplasen dhe numrat nuk mbartin kuptim semantik.
Pse ndihmon projektimi i embeddings duke nisur nga përmbledhja: krijoni një përmbledhje të shkurtër përshkruese për çdo fragment, pastaj bëni embedding dhe kërkim mbi atë përmbledhje.
Si të krijoni përmbledhje kontekstuale: përfshini kontekstin e dokumentit prind, që statistikat me strukturë të ngjashme të dallohen nga njëra-tjetra.
Kur të mbështeteni te kodi dhe modelet Pydantic: kur përmbajtja fjalë për fjalë ka rëndësi, kombinoni kod të personalizuar dhe/ose një model Pydantic me thirrje LLM për më shumë besueshmëri.
Bazat
Sistemet RAG fuqizojnë gjithçka, nga botët e mbështetjes deri te asistentët e brendshëm të njohurive.
Në prapaskenë, zakonisht:
Ndani në fragmente dokumentet burimore
Ktheni çdo fragment në embedding brenda një hapësire vektoriale
Merrni K fragmentet kryesore në çastin e kërkimit
Gjeneroni një përgjigje të kushtëzuar nga ato fragmente
Mjetet e njohura—LangChain, LlamaIndex dhe Filestore i OpenAI—i bëjnë këta hapa thuajse të parëndësishëm. Por në proceset reale do të hasni të dhëna që nuk janë vetëm tekst i dendur, dhe RAG-u bazë mund të ketë vështirësi. Në pjesët vijuese do të paraqesim shembuj konkretë të sfidave me të dhënat dhe do ta ndërtojmë gradualisht zgjidhjen, ndërsa shtohet kompleksiteti.
Kur të dhënat tuaja nuk janë vetëm tekst (gjë që ndodh shpesh)
Shqyrtoni fragmentin vijues të të dhënave në kontekstin e një loje:
JSON
Embeddings funksionojnë falë marrëdhënieve të mësuara ndërmjet fjalëve përmes kuptimit semantik dhe gramatikës. Në të dhënat më sipër kemi një përzierje teksti dhe numrash ku, jashtë këtij konteksti të saktë, numrat nuk kanë asnjë lidhje me fjalët. Pra, mund të themi se ky fragment të dhënash është pak a shumë një kombinim fjalësh disi përshkruese, të pasuara nga disa numra të rastësishëm.
Kjo në fakt nuk do të ishte problem po të ishte i vetmi lloj i të dhënave që kemi, sepse ende mund t’i merrnim sipas embeddings të atyre pak fjalëve përshkruese që kemi në dispozicion (ose thjesht të përdornim text-to-sql). Por çfarë ndodh nëse ky fragment fshihet mes shumë fragmenteve me tekst të dendur, ku shfaqen po ato fjalë? Për shembull:
JSON
Tani imagjinoni se duam të gjejmë “What is the attack range with Draconic Ascension?” Ka shumë të ngjarë që të mos e gjejmë fragmentin përkatës që duam, sepse humbet mes zhurmës së fragmenteve të tjera që përmbajnë të njëjtat fjalë kyçe.
Problemi themelor është se nuk mund t’i dallojmë mirë këto fragmente të dhënash, ndonëse përmbajnë lloje të ndryshme informacioni për të njëjtën temë. A mund t’i pasurojmë ose përmirësojmë disi? Sigurisht që mundemi:smile:
Pasurojini të dhënat duke i përmbledhur—po, e lexuat mirë
Në vend që ta kthejmë drejtpërdrejt vetë fragmentin në embedding, fillimisht mund të krijojmë një përmbledhje që përshkruan përmbajtjen e të dhënave, pastaj të bëjmë embedding dhe kërkim mbi përmbledhjen. Në hapin e gjenerimit do të përdornim sërish të dhënat origjinale të lidhura me përmbledhjen.
Pra, për dy shembujt e fragmenteve më sipër do të krijonim përmbledhje të tilla:
Statistikat e rrezes, shpejtësisë dhe dëmit të sulmit (si parazgjedhje dhe me Draconic Ascension).
Përshkrimi dhe hollësitë e Draconic Ascension, duke përfshirë kushtet e aktivizimit, efektet pamore dhe historikun.
Më pas e pasurojmë edhe kërkesën, që ta „përputhim“ me përmbledhjen. Për shembull, do ta shndërronim “What is the attack range with Draconic Ascension?” në “What is the statistics of attack range with Draconic Ascension?” Kjo është veçanërisht e rëndësishme kur kërkesa vjen nga përdorues jashtë fushave teknike, të cilët pyesin në gjuhë ~~„të lirë“~~ të zakonshme njerëzore, sepse në fund të fundit nuk është në dijeninë apo interesin e tyre si funksionon RAG-u për të maksimizuar saktësinë/rikujtimin.


Mos i nxirrni gjërat jashtë kontekstit (vlen përgjithësisht edhe në jetë)
Një skenar vijues është trajtimi i një sasie të madhe fragmentesh të dhënash që duken njësoj, si më poshtë:
Plain Text
Nëse vazhdojmë me të njëjtën qasje, imagjinoni sikur të pyesnim “what is character X’s attack range?” Me këto përmbledhje që sapo krijuam, do të luanim një lojë hamendësimi të bazuar te fati, sepse edhe ato do të dukeshin shumë të ngjashme. Atëherë, si mund t’i dallojmë?
Përgjigjja e thjeshtë është: jepni kontekst. Mund të përfshijmë thjesht në fragmentin e të dhënave një referencë për dokumentin e tij prind, p.sh. {”character”: “X”} në këtë rast. Kështu do të mund të gjenim saktë të dhënat e duhura për personazhin X, edhe kur kemi të njëjtat të dhëna për personazhet Y dhe Z.
Megjithatë, një qasje më e mirë dhe më e përgjithësueshme do të ishte krijimi i një përmbledhjeje kontekstuale të fragmentit. Pra, në vend që të krijojmë një përmbledhje vetëm të vetë fragmentit të të dhënave, mund të dërgojmë si dokumentin prind, ashtu edhe fragmentin, për të krijuar një përmbledhje të përgjithshme kontekstuale, ku shpjegojmë si lidhet fragmenti me dokumentin prind, p.sh.:
Ky fragment jep statistika të hollësishme për … të personazhit X. Fragmenti lidhet me dokumentin e plotë duke treguar fuqinë e X në shpejtësinë e sulmit…
Ky fragment jep statistika të hollësishme për … të personazhit Y. Fragmenti lidhet me dokumentin e plotë duke treguar statistikat e përforcuara të Y me aftësinë e saj të veçantë…
Ky fragment jep statistika të hollësishme për … të personazhit Z. Fragmenti lidhet me dokumentin e plotë duke treguar statistikat e Z, që e bëjnë të përshtatshëm si tank në ndeshjet ekipore…
Kjo metodë (e frymëzuar pjesërisht nga Anthropic) mund të duket e tepruar për shembullin më sipër, por është shumë efikase për fragmente që mund të keqinterpretohen „jashtë kontekstit“. Për më tepër, ajo ofron një qasje të unifikuar që funksionon për të gjitha fragmentet dhe ruan një proces inxhinierik të rregullt.


Kur duhet të jeni ~~maniak kontrolli~~ rigorozë
Zakonisht i marrim të dhënat në pjesë të plota dhe i ndajmë në fragmente për një sistem RAG. Në këtë shembull paraqesim diçka paksa ndryshe—të dhëna që janë ndarë në fragmente, por në fragmente të këqija: pjesë të rastësishme nga një fragment logjik, të cilat në fakt duhen ribashkuar. Fragment logjik do të thotë një pjesë përmbajtjeje që natyrshëm duhet të qëndrojë së bashku, si një nënndarje dokumenti ose një paragraf koherent.


Përpjekja jonë e parë me këto të dhëna është t’ia japim gjithçka një thirrjeje LLM, t’i kërkojmë t’i grupojë sipas gjykimit të vet dhe pastaj të kthejë përmbajtjen e grupuar. LLM duhet të jetë mjaft i mirë në këtë punë, apo jo? Epo, po dhe jo.
Kemi vërejtur edhe në disa raste të tjera se LLM-të priren të sillen me përtaci dhe nuk janë të besueshme kur kërkohet përmbajtja e plotë dhe e saktë, veçanërisht kur konteksti është i gjatë. Dhe kjo është krejtësisht e kuptueshme. Por kjo e bënte të papërdorshme për këtë rast specifik, sepse na duhet përmbajtja e saktë fjalë për fjalë—pa përmbledhje dhe pa anashkaluar asnjë pjesë të përmbajtjes origjinale. Nuk mund të humbasim asnjë hollësi.
Dhe sigurisht, ana pozitive ishte se e kuptoi shkëlqyeshëm semantikën dhe strukturat e fragmenteve të copëzuara. Sikur vetëm të mos refuzonte ta kthente përmbajtjen e saktë. Dreqi e mori:/
Atëherë, si mund ta shfrytëzojmë atë që një LLM bën mirë, duke shmangur atë për të cilën nuk është i besueshëm? Iu drejtuam mikut tonë të vjetër e të mirë, kodit (lexo: funksion i personalizuar Python). Dhe një modeli Pydantic „që s’mund të ishte më i thjeshtë“. Ja zgjidhja:
Kaloni me radhë nëpër pjesë, duke ruajtur një fragment logjik aktual
Për çdo pjesë, pyeteni LLM-në: a i përket kjo pjesë fragmentit logjik aktual? Përgjigju po ose jo (sipas modelit Pydantic).
Nëse po, bashkëngjiteni pjesën te fragmenti; nëse jo, nxirreni fragmentin logjik aktual si të përfunduar dhe nisni një të ri me atë pjesë.


Sigurisht, këtu përdorim pak më shumë tokenë sesa me një kalim të vetëm nëpër përmbajtjen e plotë, por për këtë rast specifik, ku ruajtja e përmbajtjes së saktë është përparësia kryesore, kostoja e vogël shtesë ia vlente plotësisht.
Kjo është një zgjidhje shumë e thjeshtë, por ndjek një parim të rëndësishëm: kur kërkohet rigorozitet, nuk duam të mbështetemi vetëm te LLM-të, sepse në fund të fundit ato janë probabilistike.
Kodi dhe funksionet e personalizuara, së bashku me modelet Pydantic, mund të përdoren për të arritur një rezultat të parashikueshëm dhe të besueshëm, duke shfrytëzuar njëkohësisht aftësitë e LLM-ve.
Ndërtimi i një zgjidhjeje me IA gjeneruese është po aq sfidë inxhinierike sa edhe sfidë e IA-së. Shpresojmë që këta shembuj t’ju kenë frymëzuar të përballeni me sfidat tuaja unike. Për të lexuar më shumë rreth zgjidhjeve të IA-së gjeneruese që vendosin inxhinierinë në plan të parë, shihni artikullin tonë mbi projektimin e sistemeve agjentike të bazuara në router.