Во последно време RAG понекогаш е на лош глас: некои сметаат дека е сосема едноставен (лесно е да се започне, но не и да се прошири), а други дека го надминале „агентските системи“ (кои, ако се погледнат подетално, во многу случаи брзо почнуваат многу да наликуваат на RAG…)
Во овој блог, преку еден-два разработени примери, покажуваме како се справуваме со некои вообичаени предизвици, и тоа:
Работа со мешани текстуални и нумерички податоци и зошто тие го нарушуваат наивниот RAG: клучните зборови се преклопуваат, а броевите немаат семантичко значење.
Зошто помага дизајнот на вградувања што поаѓаат од резиме: создајте кратко описно резиме за секој сегмент, па вградувајте и пребарувајте врз негова основа.
Како да создавате контекстуални резимеа: вклучете го контекстот од матичниот документ за статистиките со слична структура да останат недвосмислени.
Кога да се потпрете на код и модели на Pydantic: кога дословната содржина е важна, за поголема сигурност комбинирајте приспособен код и/или модел на Pydantic со повици до големи јазични модели (LLM).
Основи
RAG-системите овозможуваат сè, од ботови за поддршка до интерни асистенти за знаење.
Во заднина, обично ги правите следниве чекори:
Ги делите изворните документи на сегменти
Го вградувате секој сегмент во векторски простор
Ги пронаоѓате најдобрите K сегменти при пребарувањето
Создавате одговор заснован врз тие сегменти
Популарните алатки — LangChain, LlamaIndex и Filestore на OpenAI — ги прават овие чекори речиси тривијални. Но во реалните процеси ќе наидете и на податоци што не се само густ текст, со кои основниот RAG тешко се справува. Во следните делови ќе прикажеме конкретни примери на предизвици со податоците и постепено ќе го надградуваме решението со воведувањето дополнителна сложеност.
Кога податоците не се само текст (што всушност е честа појава)
Разгледајте го следниов сегмент од податоци во контекст на видеоигра:
JSON
Вградувањата функционираат благодарение на научените односи меѓу зборовите преку семантичкото значење и граматиката. Во горните податоци има мешавина од текст и броеви, при што надвор од конкретниов контекст броевите не се поврзани со зборовите. Затоа, може да се каже дека овој сегмент од податоци во основа е комбинација од донекаде описни зборови проследени со неколку случајни броеви.
Ова не би било проблем ако располагавме само со ваков вид податоци, бидејќи сепак би можеле да пребаруваме според вградувањата на неколкуте достапни описни зборови (или едноставно да користиме text-to-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”} во овој случај. Така ќе можеме прецизно да ги пронајдеме точните податоци за ликот X, дури и кога ги имаме истите податоци за ликовите Y и Z.
Сепак, подобар и поопшто применлив пристап би бил да создадеме контекстуално резиме на сегментот. Односно, наместо да создаваме резиме само на самиот сегмент од податоци, би можеле да ги внесеме и матичниот документ и сегментот за да создадеме општо контекстуално резиме. Во него би објасниле како сегментот се вклопува во матичниот документ, на пример:
Овој сегмент содржи детална статистика за … за ликот X. Сегментот се вклопува во целиот документ така што ја прикажува силата на X во брзината на нападот…
Овој сегмент содржи детална статистика за … за ликот Y. Сегментот се вклопува во целиот документ така што ги прикажува подобрените параметри на Y при користење на нејзината посебна способност…
Овој сегмент содржи детална статистика за … за ликот Z. Сегментот се вклопува во целиот документ така што ги прикажува параметрите на Z, кои добро одговараат за улогата на тенк во тимски натпревари…
Овој метод (делумно инспириран од Anthropic) можеби изгледа претеран за примерот погоре, но е многу ефикасен за сегменти што би можеле погрешно да се протолкуваат „надвор од контекст“. Освен тоа, нуди единствен пристап што функционира за сите сегменти и одржува уреден инженерски процес.


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


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


Секако, тука користиме малку повеќе токени отколку при еднократно поминување низ целата содржина, но за конкретната примена, каде што задржувањето на точната содржина е најважно, малиот дополнителен трошок сосема се исплатеше.
Ова е многу едноставно решение, но следи важно начело: кога е потребна ригорозност, не треба да се потпираме само на големи јазични модели (LLM), бидејќи тие сепак се веројатносни.
Приспособениот код, функциите и моделите на Pydantic може да се користат за предвидлив и сигурен резултат, а притоа целосно да се искористат можностите на големи јазични модели (LLM).
Изработката на решение со генеративна ВИ е инженерски предизвик исто колку што е и предизвик од областа на ВИ. Се надеваме дека овие примери ве поттикнаа да се справите со сопствените уникатни предизвици. За повеќе информации за инженерски ориентирани решенија со генеративна ВИ, прочитајте ја нашата блог-објава за дизајнирање агентски системи засновани врз насочувач.