Последњих година RAG понекад прати лош глас: неки мисле да је потпуно тривијалан (лако га је започети, али не и проширити), док други сматрају да су га превазишли „агентски системи“ (који, када мало загребете испод површине, у многим случајевима врло брзо почну да личе на RAG…).
У овом тексту приказујемо један или два практична примера како бисмо показали како решавамо неке уобичајене изазове, и то:
Рад са мешовитим текстуалним и нумеричким подацима и зашто они ремете наивни RAG: кључне речи се преклапају, а бројеви немају семантичко значење.
Зашто помаже пројектовање уграђивања заснованих на сажетку: генеришите кратак описни сажетак за сваки сегмент, па на њему заснивајте уграђивање и претрагу.
Како генерисати контекстуалне сажетке: додајте контекст надређеног документа како би се разликовале статистике сличног облика.
Када се ослонити на кôд и Pydantic моделе: када је дослован садржај важан, ради поузданости комбинујте прилагођени кôд и/или Pydantic модел с позивима великом језичком моделу (LLM-у).
Основе
RAG системи покрећу све, од ботова за подршку до интерних асистената за знање.
У позадини обично радите следеће:
Делите изворне документе на сегменте
Уграђујете сваки сегмент у векторски простор
Преузимате најбољих K сегмената у тренутку упита
Генеришете одговор условљен тим сегментима
Популарни скупови алата — LangChain, LlamaIndex и OpenAI Filestore — чине те кораке готово тривијалним. Међутим, у стварним токовима обраде наићи ћете на податке који нису само густ текст, с којима основни RAG може имати потешкоћа. У наредним одељцима приказаћемо конкретне примере проблема с подацима и постепено надограђивати решење како будемо уводили додатну сложеност.
Када ваши подаци нису само текст (што заправо није реткост)
Размотрите следећи сегмент података у контексту видео-игре:
JSON
Уграђивања функционишу захваљујући наученим односима међу речима, заснованим на семантичком значењу и граматици. У горњим подацима имамо мешавину текста и бројева, при чему ван овог конкретног контекста бројеви немају никакву везу с речима. Стога бисмо могли рећи да је овај сегмент података углавном комбинација донекле описних речи иза којих следе насумични бројеви.
То заправо не би био проблем када бисмо имали само ову врсту података, јер бисмо и даље могли да их преузмемо на основу уграђивања неколико доступних описних речи (или бисмо једноставно користили претварање текста у SQL). Али шта ако је овај сегмент закопан међу мноштвом сегмената густог текста у којима се појављују исте речи? На пример:
JSON
Замислимо сада да желимо да пронађемо „What is the attack range with Draconic Ascension?” Врло вероватно нећемо успети да преузмемо жељени релевантни сегмент јер је дубоко сакривен у шуму других сегмената који садрже исте кључне речи.
Основни проблем је то што не можемо добро да разликујемо ове сегменте података, иако садрже различите врсте информација о истој теми. Можемо ли их некако обогатити или унапредити? Наравно да можемо:smile:
Обогатите податке тако што ћете их сажети — да, добро сте прочитали
Уместо да директно уграђујемо сам сегмент, најпре можемо генерисати сажетак који описује податке, а затим уграђивање и преузимање засновати на том сажетку. У кораку генерисања и даље бисмо користили оригиналне податке повезане са сажетком.
Тако бисмо за два горња примера сегмената генерисали сажетке попут ових:
Статистика домета, брзине и штете напада (подразумевано и уз Draconic Ascension).
Опис и појединости о способности Draconic Ascension, укључујући услове активирања, визуелне ефекте и причу света игре.
Затим проширујемо и упит како бисмо га „ускладили“ са сажетком. На пример, упит „What is the attack range with Draconic Ascension?” претворили бисмо у „What is the statistics of attack range with Draconic Ascension?” Ово је нарочито важно када упит за преузимање постављају корисници ван техничких области, и то на ~~„слободан“~~ нормалан, људски начин, јер они не знају нити их занима како RAG функционише да би се максимално повећале прецизност и потпуност резултата.


Не вадите ствари из контекста (што углавном важи и у животу)
Следећи сценарио подразумева рад с мноштвом сегмената података који изгледају исто, као у наставку:
Plain Text
Ако задржимо исти приступ, замислите да поставимо питање „what is character X’s attack range?” С овим управо генерисаним сажетцима играли бисмо игру погађања засновану на срећи, јер би и они изгледали врло слично. Како бисмо онда могли да их разликујемо?
Одговор је једноставан: обезбедите контекст. У сегмент података могли бисмо једноставно додати референцу на његов надређени документ, на пример {”character”: “X”} у овом случају. Тако бисмо сада могли прецизно да преузмемо одговарајуће податке за Karakter X чак и када имамо исте податке за Karakter Y и Z.
Међутим, бољи и уопштенији приступ био би генерисање контекстуалног сажетка сегмента. Другим речима, уместо да генеришемо сажетак само за сам сегмент података, могли бисмо да проследимо и његов надређени документ и сегмент како бисмо генерисали општи контекстуални сажетак. У њему бисмо објаснили како се овај сегмент уклапа у надређени документ, на пример:
Овај сегмент пружа детаљну статистику о… за Karakter X. Сегмент се уклапа у цео документ тако што приказује снагу лика X у брзини напада…
Овај сегмент пружа детаљну статистику о… за Karakter Y. Сегмент се уклапа у цео документ тако што приказује побољшану статистику лика Y уз њену посебну способност…
Овај сегмент пружа детаљну статистику о… за Karakter Z. Сегмент се уклапа у цео документ тако што приказује статистику лика Z, који је погодан за улогу тенка у тимским мечевима…
Овај метод (делимично инспирисан компанијом Anthropic) може деловати претерано за горњи пример, али је веома ефикасан за сегменте који би могли бити погрешно протумачени „ван контекста“. Уз то, нуди јединствен приступ који функционише за све сегменте и одржава уредан инжењерски ток обраде.


Када треба да будете ~~опседнути контролом~~ темељни
Податке обично добијамо у целинама, а затим их делимо на сегменте за RAG систем. У овом примеру приказујемо нешто мало другачије — податке који јесу подељени на сегменте, али лоше: то су насумични делови логичке целине које заправо треба поново груписати. Логички сегмент је део садржаја који природно треба да остане на окупу, попут пододељка документа или смислено заокруженог пасуса.


Наш први покушај био је да све податке проследимо великом језичком моделу (LLM-у), затражимо да их групише како сматра прикладним и потом врати груписани садржај. Велики језички модел (LLM) би требало да буде прилично добар у томе, зар не? Па, и да и не.
Утврдили смо, и у неколико других прилика, да се велики језички модели (LLM-ови) често понашају лењо и нису поуздани када захтевате потпун и дослован садржај, нарочито ако је контекст дугачак. Што има савршеног смисла. Али то је било неприхватљиво за овај конкретан случај употребе јер нам је садржај потребан дословно, реч по реч — без сажетака и без изостављања било ког дела оригинала. Не смемо пропустити ниједан детаљ.
Наравно, позитивна страна била је то што је одлично разумео семантику и структуру разбијених сегмената. Само ако не одбије да дословно понови садржај. Дођавола:/
Како онда можемо искористити оно у чему је велики језички модел (LLM) добар, а избећи оно у чему није поуздан? Окренули смо се старом добром пријатељу — коду (читај: прилагођеној Python функцији). И Pydantic моделу који „не може бити једноставнији“. Ево решења:
Пролазите кроз одељке и притом одржавајте текући логички сегмент
За сваки одељак питајте велики језички модел (LLM): да ли овај одељак припада текућем логичком сегменту? Одговор треба да буде да или не (према Pydantic моделу).
Ако је одговор потврдан, додајте одељак сегменту; ако није, избаците завршени текући логички сегмент, па започните нови тим одељком.


Наравно, овде користимо нешто више токена него при једном пролазу кроз цео садржај, али за конкретан случај употребе у којем је очување дословног садржаја главни приоритет, тај мали додатни трошак се и те како исплатио.
Ово је врло једноставно решење, али следи важно начело: када је потребна строгост, не треба се ослањати искључиво на велике језичке моделе (LLM-ове), јер су они ипак пробабилистички.
Прилагођени кôд и функције, као и Pydantic модели, могу се користити за постизање предвидљивог и поузданог резултата, уз истовремено искоришћавање могућности великих језичких модела (LLM-ова).
Израда решења заснованог на генеративној вештачкој интелигенцији подједнако је инжењерски изазов колико и изазов у области вештачке интелигенције. Надамо се да су вас ови примери подстакли да се ухватите укоштац са својим јединственим изазовима. Да бисте прочитали више о инжењерски оријентисаним решењима генеративне вештачке интелигенције, погледајте наш текст о пројектовању агентских система заснованих на усмеравању.