Navigimi kryesor

Shembuj praktikë të zgjidhjeve të personalizuara RAG

Shembuj nga bota reale tregojnë si sistemet e përshtatura të gjenerimit të pasuruar nga marrja e informacionit zgjidhin probleme komplekse të njohurive të ndërmarrjeve.

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.

Ndërtimi i zgjidhjeve të personalizuara RAG

Bazat

Sistemet RAG fuqizojnë gjithçka, nga botët e mbështetjes deri te asistentët e brendshëm të njohurive.

Në prapaskenë, zakonisht:

  1. Ndani në fragmente dokumentet burimore

  2. Ktheni çdo fragment në embedding brenda një hapësire vektoriale

  3. Merrni K fragmentet kryesore në çastin e kërkimit

  4. 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 gjërat ndërlikohen

  1. 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

{ "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, } }} 

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

{"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." } }}

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:

  1. 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:

  1. Statistikat e rrezes, shpejtësisë dhe dëmit të sulmit (si parazgjedhje dhe me Draconic Ascension).

  2. 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.

Diagram që ilustron se kur gjërat ndërlikohen.

  1. 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

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

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.:

  1. 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…

  2. 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ë…

  3. 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.

Diagram që ilustron se kur gjërat ndërlikohen.

  1. 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.

Diagram që ilustron se kur gjërat ndërlikohen.

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ë.

Diagram që ilustron se kur gjërat ndërlikohen.

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.

Përmbyllje

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.

Autor

Cynthia Yu