Робоча RAG-демонстрація — одна з найпростіших речей, яку можна зібрати у 2026 році. На це вистачить вечора: розбити документи на чанки, порахувати ембедінги, скласти їх у сховище, дістати top-k, передати мовній моделі та підключити чат-інтерфейс. Виглядати буде ефектно.
Промислова RAG-система, що переживає понеділковий ранок з реальним трафіком, реальними користувачами і реальними очікуваннями, — це принципово інший артефакт. За останні два роки ми випустили півдюжини таких систем. Нижче — вісім звичок, які ми тепер вважаємо обов'язковими.
1. Складіть набір для оцінювання на третій день
Не можна покращити те, чого ви не вимірюєте. Робота з найбільшим важелем у будь-якому RAG-проєкті — це сісти разом із клієнтом і сформувати від п'ятдесяти до двохсот реальних запитань разом із відповідями, які експерт-людина вважав би правильними.
Це саме та робота, від якої клієнти найчастіше відмагаються. "Немає часу, просто збирайте систему". Ввічливо відмовте. Без набору для оцінювання кожне твердження "зараз стало краще" — це відчуття, а відчуття не масштабуються на три різні підказки, дві різні моделі та чотири стратегії пошуку.
Цей набір крутиться в CI. Будь-яка зміна пошуку, підказок, вибору моделі чи навіть токенізатора запускає переоцінку. Тренди важать більше за абсолютні числа, але ми публікуємо і те, і те. Достовірність (чи послалася відповідь на правильний документ?) і точність (чи був процитований документ справді релевантним?) — ось дві метрики, які ми відстежуємо завжди.
2. Гібридний пошук майже завжди обходить чисто щільний
Чиста векторна схожість добра для запиту "знайди семантично подібне". Вона погано шукає документи, у яких згадано конкретний код товару, конкретне ім'я людини чи конкретне повідомлення про помилку. Реальні запитання вимагають обох сценаріїв одночасно.
Наш базовий підхід такий: щільний пошук (косинус на ембедінгах) + розріджений (BM25 поверх tsvector) + об'єднання через reciprocal rank fusion. Далі — cross-encoder, який переранжує top-50. Переранжування — найдорожчий крок конвеєра; його результати ми агресивно кешуємо.
У кожному проєкті, де ми це вимірювали, гібридний пошук випереджав щільний на десять-двадцять пунктів за нашим набором. Ціна — приблизно на тридцять відсотків більше обчислень на запит і дещо складніша кодова база. Це окуповується щоразу.
3. Поділ на чанки — це продуктовий дизайн, а не препроцесинг
Наївний підхід — порізати документи на чанки фіксованого розміру з невеликим перекриттям — працює на навчальних корпусах і ламається на справжніх. Реальні корпуси мають структуру: тікети підтримки містять репліку клієнта, репліку оператора й розв'язання; контракти — статті й додатки; документація — розділи й кодові блоки.
Поважайте цю структуру. Ми випускали чанкери, які ріжуть HTML по межах <h2>, транскрипти — по репліках, а неструктуровану прозу — по семантичних межах, які виявляє менша модель. Правильна схема поділу на чанки для конкретного корпусу зрушує оцінки сильніше, ніж будь-яка одна зміна підказки.
Найскладніший випадок — довгі документи (юридичні, технічні посібники), де відповідь спирається на докази з двох різних розділів. Багатовекторне індексування — коли поряд із дрібнозернистими ембедінгами абзаців зберігаємо ще й крупнозернисті ембедінги розділів — розв'язує це елегантно. І реалізувати його як слід — боляче. Закладайте на це час.
4. Цитуй або відмовляйся
RAG-систему переслідують два режими невдачі: галюцинації та впевнено неправильні відповіді. Це не одне й те саме. Галюцинацію — коли модель вигадує те, чого в результатах пошуку немає, — виявити простіше. Впевнено неправильна відповідь гірша: модель переказує те, що є у пошуку, але воно не підходить до запитання, і користувач не має жодного сигналу їй не довіряти.
Наш універсальний запобіжник: модель мусить цитувати конкретні джерельні чанки для кожного твердження, а інтерфейс має показувати ці цитати як посилання на джерело. Якщо процитувати нічим — модель має відмовити шаблонним повідомленням. Поріг відмови ми налаштовуємо під кожного клієнта окремо: занадто строгий — і система здається марною, занадто ліберальний — і довіра падає.
Частота відмов — це KPI, а не баг. Нульова частота відмов на наборі з 250 запитань — тривожний сигнал: модель упевнено вигадує там, де не знає відповіді.
5. Межі вартості — інженерне питання, а не фінансове
Рахунки від провайдерів LLM легко випустити з-під контролю. Жорсткі межі ми ставимо з першого дня й проєктуємо архітектуру так, щоб вона їх дотримувалася:
- Агресивне кешування ембедінгів. Для заданого чанка ембедінг незмінний; кешуйте його назавжди.
- Кешування відповідей із семантичною дедуплікацією. Хеш підказки — дешевий варіант; збіг за нормалізованим ембедінгом — трохи витонченіший. У будь-якому разі двадцять-сорок відсотків трафіку RAG, орієнтованого переважно на читання, потраплятиме в кеш.
- Запасний рівень моделі. Коли місячні витрати перетинають вісімдесят відсотків бюджету, запити автоматично йдуть на дешевшу модель. Кешовані "флагманські" відповіді для цього клієнта позначаємо, щоб згодом не платити двічі.
- Межі на одного орендаря. У SaaS RAG їх треба підтримувати на рівні бази даних, а не у прикладному коді.
Цими чотирма ходами ми щоразу скорочували успадковані рахунки RAG на шістдесят-вісімдесят відсотків уже на першому проєкті. На це просто ніхто не дивився.
6. Затримку дає пошук, а не генерація
Поширене припущення: "вузьке місце — це виклик LLM". Часто це не так. Завдяки стрімінгу користувач бачить перші токени за пів секунди навіть на флагманських моделях. Чого він не бачить — але відчуває — це 1,8 секунди до того, поки відпрацьовує пошук.
Ми профілюємо наскрізну затримку трасами OpenTelemetry і знаходимо повільний крок. Зазвичай це cross-encoder на холодному кеші або запит до Postgres, що промахується повз індекс, бо хтось додав фільтр і забув про індекс. Іноді — крок ембедінгу вхідних запитів: ембедінговий API провайдера не завжди швидкий.
Оптимізуйте холодний шлях. Кешуйте гарячий.
7. Ізоляція орендарів у двох місцях
Витік даних між орендарями — найгірший можливий режим невдачі для B2B RAG. Ми вимагаємо ізоляції у двох незалежних місцях:
- На рівні SQL. Кожен запит пошуку несе ідентифікатор орендаря в
WHERE. Не опційно. Не фільтр у Python. База даних відмовляє сама. - На рівні підказки. Складач підказок явно перевіряє, що всі знайдені чанки мають один ідентифікатор орендаря. Невідповідність — виняток — користувачу повертається загальне повідомлення про помилку.
Ремінь і шлейки. Якщо один шар обійшли — інший ловить.
8. Влаштуйте систему собі сам, перш ніж це зробить клієнт
Раз на місяць на повноцінній копії продакшну ми проводимо навмисні навчальні атаки:
- Чи вдасться витягнути чанк з іншого орендаря?
- Чи вдасться змусити модель виконати інструкцію prompt-injection, заховану в знайденому документі?
- Чи вдасться вичерпати обмежувач швидкості й погіршити сервіс?
- Чи вдасться запустити дорогий шлях коду дешевими вхідними даними?
Кожну знахідку фіксуємо, термінове виправляємо, решту ставимо в чергу. До шостого місяця у продакшні система, яку регулярно атакують зсередини, помітно стійкіша за ту, яку не чіпали.
Суть
RAG — це інженерія. Її можна тестувати, вимірювати й налагоджувати. Спокуса ставитися до неї як до алхімії — спробувати, відчути, що краще, випустити — народжує системи, які працюють у демо й падають у продакшні. Інженерний підхід народжує системи, що переживають понеділковий ранок.
Вісім звичок вище не вичерпні, а правильний баланс зусиль між ними залежить від вашого корпусу й форми трафіку. Але мета-звичка — виміряти, потім змінити одну річ, потім виміряти знову — універсальна.


