Якість RAG у часі індексації: типи фрагментів, очищення та мітки для пошуку
Якість RAG у час індексації: створення фрагментів тексту, таблиць та зображень, усунення шуму від парсера та додавання міток для пошуку, написаних LLM, що приблизно вдвічі зменшило кількість невдалих пошуків.
Де демо-версія перетворюється на систему для продакшну
Парсер RAG передає впорядкований список вузлів, і кожен вузол знає, з якої частини він походить. Усе, що відбувається з цього моменту до моменту, коли модель читає запит, — це робота під час створення індексу, і саме цей етап відрізняє демо-версію від того, що можна запустити в продакшні. Цей процес складається з трьох етапів. Вузли перетворюються на частини, яких існує три види з власними інструментами для їх створення. Частини очищуються від матеріалу, який парсер ретельно витягнув, але який модель не повинна бачити. Крім того, кожна частина отримує коротку генеровану мітку від мовної моделі. Під час внутрішньої оцінки банківських документів ці мітки приблизно вдвічі зменшили кількість невдалих пошуків, що робить їх найбільшою користю у всьому процесі обробки.
Це також найменш привабливий аспект. Майже ніхто не пише про видалення шуму, і саме тому існує стільки перемог у цій категорії. Наведені нижче цифри походять з невеликої внутрішньої оцінки, а не з ретельних тестів, тож їх слід сприймати саме так.
Три види фрагментів, три інструменти створення
Простий підхід використовує один інструмент для розділення тексту: текст розділяється, і все. Реальні документи містять принаймні три види контенту, які поводяться зовсім по-різному. Абзац прози, таблиця з тарифами та скріншот — кожен з них потребує окремої обробки, а спроба пройти через один і той самий інструмент розділення призводить до появи неповних таблиць та неправильних кроків обробки.
Фрагменти тексту відповідають структурі
Розділення за кількістю символів виключається. Натомість спочатку вводиться кожен рядок — як заголовок, як нумерований крок чи як звичайне речення, — а правила об’єднання залежать від цих типів. Це запобігає приєднанню нумерованого кроку до нерелевантного абзацу. Занадто довгі уривки розрізаються біля кінця речень, а розрізання посеред речення є останнім засобом. Сусідні фрагменти трохи перекриваються, тому процедура, що перетинає межу, залишається зрозумілою з обох сторін.
Таблиці залишаються цілими
Таблиці створюються окремо, оскільки їхній зміст полягає у їхньому форматуванні. Програма знаходить рядок заголовків, повторює його у кожній частині, якщо таблицю потрібно розділити, та впорядковує рядки. Вона також відхиляє таблиці, які не містять реального контенту, здебільшого це зміст книги, який парсери виводять у вигляді акуратної сітки заголовків та номерів сторінок.
У попередній версії цієї системи була проблема саме тут. Змістові таблиці виявлялися за допомогою примітивного тесту, багато рядків закінчувалися невеликими числами. Таблиці з тарифами також підпадали під це опис, тож справжній графік тарифів міг зникнути з індексу без жодних попереджень. Новий тест є більш точним: на справжній сторінці змісту номери сторінок постійно зростають, тоді як суми тарифів, такі як 35 доларів, 30 доларів та 7 доларів, можуть зменшуватися чи збільшуватися. Таблиця вважається частиною індексу лише тоді, коли кінцеві числа ніколи не знижуються. Тепер графіки тарифів залишаються, сторінки змісту все одно з’являються, і одна умова усунула цю помилку.
Зображення обробляються по одному
Кожне зображення може коштувати грошей, тому кожне з них пробує три варіанти по черзі та зупиняється на першому, який спрацює.
- У режимі безкоштовного оброблення використовується текст, який вже було вилучено парсером. Якщо у документі є придатний підпис чи анотація, яка не є місцем для заміни, наприклад „image“, то використовується саме цей текст, і жодна модель розпізнавання зображень не запускається. У багатьох документах від третини до половини зображень проходять через цей шлях, що є справжньою економією.
- Платний шлях викликає модель розпізнавання зображень. Це застосовується, коли немає придатного тексту, а зображення більше за маленький розділювач. Чітко сформульоване запитання змушує модель спочатку класифікувати зображення. Якщо це логотип чи орнамент, модель відповідає одним словом «DECORATIVE», а цей фрагмент відкидається. Інакше вона заповнює два поля. У полі SCREEN йде один рядок з поясненням призначення екрана, наприклад, екран для введення даних про вихідні дзвінки для ділових рахунків, сформульований так, щоб він знаходився поблизу запитань користувача у векторному просторі. У полі CONTENT перелічуються всі позначки полів, кнопки та видимий код у порядку читання, щоб пошуковики могли ввести саме ті рядки, які вони можуть використати. Таким чином скріншот можна знайти за призначенням та за написаним на ньому текстом.
- Альтернативний шлях використовує заголовок. Якщо обидва способи зазнають невдачі, але розділ відомий, зберігається місце заміни, яке вказує, що це за зображення та де воно знаходиться, щоб його можна було отримати через відповідний розділ під час запиту.
У перевірці розміру є невеликий недолік. Дуже маленькі зображення пропускають обробку за допомогою моделі на основі порогових значень ширини та висоти, але обидві величини мають бути маленькими, тому дуже тонка та дуже широка лінія-розділник все одно потрапляє до моделі. Це марнує трохи ресурсів на декор, не впливаючи на точність, і це є у списку справ.
У діаграмі показано три шляхи до зображень у порядку їх використання. Модель обробляє зображення один раз під час індексації, а кожен наступний запит обробляється безкоштовно.
Уточнення того, що правильно зробив парсер
Саме вірність оригіналу робить парсери тут джерелом проблем. Вони витягують усе зі сторінки: позначку „КОНФІДЕНЦІЙНО“, яка повторюється на 200 сторінках, номери сторінок, інформацію про авторські права у нижній частині сторінки, речення, розірване через переход на нову сторінку. Кожен з цих елементів стає окремим фрагментом, і всі вони є зайвим багажем. Цей зайвий багаж також спотворює ранжування, адже запит користувача може так само легко знайти банер, як і певну процедуру.
Тож як тільки утворюються фрагменти, вони проходять процедуру очищення, під час якої кожен крок спрямований на певний тип зайвого матеріалу, який дійсно зустрічається у реальних документах. У такому порядку:
- Фільтрація стандартних елементів видаляє прості номери сторінок, позначки версій та рядки з посиланнями на кшталт „Зв’яжіться з нами“ чи „Дізнайтеся більше“ біля початку. Набір правил змінюється залежно від типу файлу, адже PDF-файли, документи Word та презентації мають свій власний тип зайвого багажу.
- Видаліть повторювані елементи. Текст, який ідентично з’являється на багатьох сторінках, вважається зайвим. Фраза „КОНФІДЕНЦІЙНО, ТІЛЬКИ ДЛЯ ВНУТРШНЬОГО ВИКОРИСТАННЯ“ на п’ятнадцяти сторінках підлягає видаленню, тоді як фраза „Дивіться сторінку 3“, яка зустрічається двічі на одній сторінці, залишається, оскільки вона не охоплює кількох сторінок.
- Виправте пошкоджені фрагменти. Частина тексту, яка починається посеред речення або з неповного пункту, була розділена не в правильному місці. Якщо її можна об’єднати з попередньою частиною в тому ж розділі, вони зливаються. Якщо ні, їй присвоюється позначка як продовженню, щоб її підпис відображав відповідний контекст.
- Злучіть самотні фрагменти. Фрагмент довжиною 40 символів, що залишився на межі розділу, може бути поглинутий сусіднім фрагментом, але лише тоді, коли обидва належать до точно такого ж розділу та мають однакову назву. Об’єднання фрагментів між розділами неможливе, а короткий випадковий елемент, який справді існує окремо, залишається без змін.
- Функція видалення дублікатів об’єднує речення, яке повторюється під одним заголовком, у один блок. Якщо воно з’являється під двома заголовками, обидві копії зберігаються, оскільки завдяки наявності шляху до розділу вони містять різну інформацію. Зображення повністю звільнені від цього правила, адже два схожі екрани все одно залишаються двома окремими скріншотами.
Жодне з цих правил не є складним. Кожне з них виникло внаслідок спостереження за тим, як справжній документ перевершує простішу версію, і разом вони пояснюють, чому запит потрапляє до процедури, а не до розділу з інформацією про авторські права.
Етикет пошуку
Наступний крок, який приносить найбільшу користь, але який більшість систем ігнорують, полягає у наступному. Візьмемо простий блок, наприклад:
Введіть суму та натисніть F4.
Він містить шлях до свого розділу — Wire Transfer > Initiation > Amount Entry, що вже допомагає. Однак перед вбудовуванням модель мови також створює короткий позначковий текст довжиною від 18 до 22 слів, який зберігається разом із фрагментом. Для цього фрагмента позначка виглядає приблизно так:
Крок процедури: введіть суму переказу, потім натисніть F4, щоб підтвердити та розпочати банківський переказ.
Розгляньмо ефект. Без позначки інструкція знаходиться у просторі ембеддингів поруч із усіма іншими кроками, які пропонують ввести значення та натиснути клавішу. З позначкою вона опиняється поруч із інструкціями щодо банківських переказів та підтверджень, що є словниковим запасом, який використовує справжній користувач. Перефразоване запитання на кшталт «Як підтвердити суму при банківському переказі?» тоді знаходить саме цю частину тексту. Під час внутрішньої оцінки позначки допомогли скоротити кількість невдалих пошуків майже вдвічі. Це був невеликий тест на одному корпусі даних, тож сприймайте це як сильну підказку, а не як універсальний закономірний ефект.
Ця техніка походить від методу Contextual Retrieval компанії Anthropic, який показав, що розміщення речення контексту, специфічного для кожного фрагмента, перед кожним з них перед індексуванням значно зменшує кількість невдалих пошуків. Тут її адаптували для контенту, де важливі точні ідентифікатори. Коди транзакцій та клавіші функцій мають копіюватися без змін та ніколи не перефразуватися, а кожна мітка починається зі слова, що визначає роль, наприклад „Крок процедури“ чи „Таблиця посилань“, щоб фрагменти групувалися з відповідними типами запитань.
Що бачить автор міток
Якістю мітки визначається контекст, який надається моделі, що її створює. Одного лише фрагмента недостатньо, адже саме він втратив свій сенс під час видалення з сторінки. Тому кожен запит на створення мітки містить кілька елементів:
- сам текст фрагмента
- шлях до розділу та його заголовок
- заголовки стовпців, коли фрагмент — це таблиця
- попередній та наступний фрагменти, щоб показати, що їх оточує
- короткий огляд усього документа для загального уявлення
Огляд документа отримується в результаті окремого запиту, який надсилається перед початком міткування. Він бере зразки фрагментів з початку, середини та кінця, а також одну таблицю та один зображення, і повертає два-три речення з назвою типу документа, його аудиторії та пов’язаних продуктів та кодів. Кожен запит на міткування потім включає цей огляд. Фразу „ввести суму“ слід міткувати по-різному у посібнику з банківських переказів та у документі з інформацією про кредит, і саме завдяки огляду модель визначає, який документ вона читає.
Сусідні елементи потребують уважнішого розгляду, адже поняття „попередній та наступний“ не означає, що вони фізично знаходяться поруч. Елемент, який знаходиться поруч, може належати до зовсім іншої процедури. Тому сусіди обираються за критерієм релевантності у трьох рівнях, береться перший, який підходить:
- найближчий елемент у тому ж шляху розділу
- в іншому випадку — найближчий елемент у тому ж батьківському розділі
- в іншому випадку — найближчий елемент під тією самою заголовковою статтею
Кандидати скануються зсередини поточного елемента, а все, що знаходиться поза відповідним розділом, ігнорується. Таким чином модель бачить наступний елемент, який справді належить до цього. Кожен сусідний елемент скорочується до кількох сотень символів, що забезпечує достатній контекст без необхідності передачі повних елементів.
У прикладі F4 модель бачить, що попередній фрагмент у її розділі стосувався вибору одержувача, а наступний — підтвердження переказу. Це дозволяє їй створювати чіткий позначник щодо введення суми банківського переказу, а не розпливчастий огляд. Добре підібрані сусідні фрагменти забезпечують точність позначників.
На малюнку показано, як модель позначників читає сусідні фрагменти з того ж розділу, потім формує одне конкретне речення, яке зберігається поруч із фрагментом та використовується в обох шинах пошуку.
Чому це доступно
Три характеристики роблять цей підхід практичним та не дуже дорогим.
- Одноразова вартість. Позначник генерується під час індексації та зберігається, тому запити ніколи не оплачують його.
- Низька одиницева вартість. Невелика, швидка модель позначає фрагменти групами по 25, кілька груп працюють одночасно, і витрати становлять менше долара за фрагмент.
Мітка знаходиться у власному полі, окремо від тіла частини. Це розділення має значення пізніше, під час пошуку, коли мітка отримує додаткову вагу у пошуку за ключовими словами. Наразі важливо те, що найдорожчі операції обробки виконуються ще до того, як хтось почне пошук, і саме це дозволяє працювати запитам у перефразованому вигляді.
Детермінізм та версіоновані запити
Генерація міток є детерміністичною: температура нульова, а насіння фіксоване, що означає, що повторна індексація документа дає такі самі мітки. Саме це підтримує твердження про те, що витрати сплачуються лише один раз. Повторна індексація ніколи не змінює вектори, вона лише створює їх заново.
Детермінізм також залежить від версіонування запиту. Семя забезпечує ідентичний результат лише для ідентичного вхідного даних, а текст інструкцій є частиною цих даних. Якщо змінити запит під час різних версій, кожна наступна переіндексація дасть різні мітки, тож індекс непомітно ділиться на дві генерації, і ніщо про це не повідомляє. Тому запит зберігається з тегом версії, завантажується за допомогою цього тега під час індексації та змінюється лише через контрольоване, навмисне оновлення. Відтворюваність між версіями потрібно створювати вручну, адже саме семя цього не забезпечить.
Вбудовування та зберігання
Після очищення, розміщення та позначення остаточним кроком під час індексації є створення вектора. Один модель вбудовування охоплює всі типи фрагментів: text-embedding-3-large, скорочений до 1536 вимірів, тож текст, таблиці та скріншоти можуть порівнюватися з запитом у одному просторі.
У ембеддер вводиться складений рядок у фіксованій послідовності: структурний префікс, мітка, а потім основний текст. Префікс ґрунтується на вже відомих фактах щодо цього фрагмента. До них належать тип документа, визначений за шляхом до розділу (наприклад, графік тарифів чи процедура банківського переказу), а також заголовок та шлях до розділу; щодо зображень — сторінка та їхнє розташування. Позначення фрагмента, схожого на процедуру, як процедури спрямовує його до запитань, які починаються зі слова «як».
На цьому етапі до тексту також додається шлях до розділу, де у старій версії була помилка. Раніше префікс записувався у фрагмент під час очищення, і будь-яка подальша зміна могла додати його ще раз, тихо пошкодивши вектор. У поточному коді будь-який збережений префікс видаляється, а потім створюється новий під час вбудовування. Він знаходиться в одному місці та додається лише один раз, безпосередньо перед тим, як модель його прочитає, що усунуло цілу групу помилок через подвійні префікси.
Готовий вектор записується до сховища разом із тілом фрагмента, міткою, структурними метаданими та стовпцем повного тексту, з якого здійснюється пошук за ключовими словами. Після цього документ стає активним. Якщо якийсь крок процесу зазнає помилки, документ позначається як відхилений, а причина зберігається для діагностики; при цьому попередня активна версія залишається доступною для пошуку протягом усього часу. Напівіндексований документ ніколи не є видимим.
Як виглядає завершена рядок
Щоб зробити це конкретнішим, ось частина «Введіть суму та натисніть F4» у її первісному вигляді в магазині. Кожне поле в ній походить з попереднього кроку.
text : "Enter amount and press F4."
(built + cleaned + section-prefixed)
retrieval_context : "Procedure step entering the amount
and pressing F4 during wire transfer
initiation." (the label)
chunk_type : TEXT
section_path : "Wire Transfer > Initiation >
Amount Entry" (Part 2)
preceding_heading : "Amount Entry"
table_headers : null (TABLE only)
page_start / end : 12 / 12
image_object_key : null (IMAGE only)
embedding_model : text-embedding-3-large
embedding_dim : 1536
embedding : [ ...1536 floats... ] (body+label)
text_search_vector: label weighted high, body lower
У блоку таблиці заголовки стовпців заповнюють місця, де у блоку тексту є значення null. Блок зображення зберігає ключ blob, тип MIME та позицію на сторінці; дані CONTENT знаходяться у полі text, а рядок SCREEN — у полі retrieval_context. Усі три типи блоків мають однакову структуру рядка. Завантаження даних здійснюється за допомогою двох стовпців: embedding для векторного пошуку та text_search_vector для пошуку за ключовими словами; кожен з цих стовпців формується на основі основного тексту та етикетки. Інші стовпці визначають місцезнаходження блоку, відображають його або обмежують доступ лише відповідному користувачеві.
Це весь результат обробки під час створення індексу: такі рядки, по одному на кожен блок, після очищення, позначення та векторизації, чекають на перше запитання.
Пов’язана література
- Практичні нотатки: перехід RAG у продакшн: проблеми на всьому етапі з реальних прикладів — Детальний огляд Практичних нотаток: перехід RAG у продакшн: проблеми на всьому етапі з реальних прикладів: контракти, перевірки та шаблони коду для команд, які впроваджують цю модель.
- Десять „тихих“ помилок у системах RAG у продакшні та способи їх виправлення — Від розміру та перекриття фрагментів до гібридних методів пошуку, RRF, переранкінгу, вибору ембеддингів та оцінок у CI: десять помилок у процесі отримання даних, які призводять до неправильних відповідей RAG, та способи їх усунення.