Пошук уроків, статей та іншого контенту
Як поєднати пошук по власних документах із генерацією тексту LLM, щоб отримати відповіді, обґрунтовані реальними даними, а не лише пам'яттю моделі.
RAG (Retrieval-Augmented Generation, генерація з доповненням пошуком) — це підхід, за якого мовна модель перед формуванням відповіді отримує релевантну інформацію з зовнішнього джерела.
Замість того щоб покладатися лише на знання, отримані під час навчання, модель працює за таким принципом:
Отримує запит користувача.
Шукає пов’язані фрагменти у власних документах або базах даних.
Додає знайдений контекст до запиту.
Генерує відповідь на основі запиту та знайдених даних.
Наприклад, чат-бот для внутрішньої документації компанії може шукати інформацію в:
інструкціях;
технічній документації;
базі знань;
договорах;
FAQ;
системі підтримки;
звітах і внутрішніх політиках.
Без RAG мовна модель може не знати цих документів або використовувати застарілу інформацію. RAG дає їй доступ до актуального контексту під час виконання запиту.
Великі мовні моделі мають кілька важливих обмежень.
Модель не знає документи, які з’явилися після завершення її навчання. Вона також не має автоматичного доступу до приватних даних організації.
Якщо моделі бракує інформації, вона може сформувати правдоподібну, але неправильну відповідь. Це називають галюцинацією.
Наприклад, модель може:
вигадати пункт внутрішньої політики;
назвати неіснуючий API;
приписати документу твердження, яких у ньому немає;
використати застарілу версію інструкції.
Модель може обробити лише певний обсяг тексту за один запит. Якщо передати їй усю базу документів, це:
збільшить вартість запиту;
сповільнить відповідь;
ускладнить пошук потрібної інформації;
може перевищити максимальний розмір контексту.
RAG вирішує цю проблему, передаючи моделі не всю колекцію, а лише кілька найбільш релевантних фрагментів.
Типова RAG-система складається з двох етапів:
Підготовка документів — виконується заздалегідь.
Обробка запиту — виконується щоразу, коли користувач ставить запитання.
Загальна схема виглядає так:
Документи
↓
Очищення та розбиття на фрагменти
↓
Створення embeddings
↓
Векторне сховище
Запит користувача
↓
Embedding запиту
↓
Пошук схожих фрагментів
↓
Формування контексту
↓
LLM
↓
ВідповідьПеред пошуком документи потрібно перетворити на зручний для обробки формат.
Джерелами можуть бути:
текстові файли;
HTML-сторінки;
PDF-документи;
записи з бази даних;
Markdown-файли;
документи з корпоративного сховища;
повідомлення з системи підтримки.
На цьому етапі важливо не лише витягнути текст, а й зберегти метадані:
назву документа;
автора;
дату оновлення;
версію;
тип документа;
ідентифікатор джерела;
права доступу.
Метадані знадобляться під час фільтрації результатів і формування посилань на джерела.
Документи часто містять шум:
повторювані заголовки;
номери сторінок;
службові символи;
дублікати;
навігаційні елементи;
некоректно розпізнаний текст;
зайві пробіли.
Якість очищення безпосередньо впливає на якість пошуку. Якщо текст витягнуто неправильно, модель може отримати неповний або спотворений контекст.
Великі документи потрібно поділити на менші частини, які називають чанками (chunks).
Один чанк може містити:
кілька абзаців;
один розділ;
пункт інструкції;
опис окремої функції;
питання та відповідь із FAQ.
Занадто великі фрагменти містять багато зайвої інформації. Занадто малі можуть втратити контекст.
На практиці часто використовують фрагменти розміром у кілька сотень токенів із частковим перекриттям. Перекриття допомагає не втрачати зміст на межі двох фрагментів.
Наприклад, якщо речення починається в одному чанку, а його пояснення — у наступному, перекриття збільшує ймовірність, що обидві частини будуть доступні під час пошуку.
Однак універсального розміру чанка не існує. Його потрібно підбирати з урахуванням структури даних:
для технічної документації корисно зберігати цілі розділи;
для FAQ — окремі пари «питання — відповідь»;
для юридичних документів — цілі пункти та підпункти;
для вихідного коду — функції або класи, а не випадкові ділянки тексту.
Після розбиття документи потрібно перетворити на числові представлення — embeddings.
Embedding — це вектор чисел, який описує зміст тексту. Тексти зі схожим значенням мають близькі вектори, навіть якщо використовують різні слова.
Наприклад, такі запити можуть бути семантично близькими:
«Як змінити пароль?»
«Де оновити облікові дані для входу?»
Звичайний пошук за ключовими словами може не побачити зв’язку між цими формулюваннями. Семантичний пошук, заснований на embeddings, має більше шансів знайти потрібний фрагмент.
Для кожного чанка зберігають:
сам текст;
його embedding;
метадані;
ідентифікатор документа;
позицію в документі.
Коли користувач ставить запит, система створює embedding і для нього. Потім порівнює вектор запиту з векторами документів.
Для оцінювання схожості використовують, зокрема:
косинусну схожість;
скалярний добуток;
евклідову відстань.
Зазвичай важливе не саме числове значення, а порядок результатів: які фрагменти найбільш схожі на запит.
Embeddings зберігають у векторному сховищі. Воно оптимізоване для пошуку найближчих векторів серед великої кількості записів.
Векторне сховище має підтримувати:
додавання embeddings;
пошук найближчих векторів;
фільтрацію за метаданими;
оновлення та видалення документів;
масштабування колекції.
Під час пошуку можна поєднати семантичну схожість із фільтрами. Наприклад:
шукати лише в документах певного відділу;
враховувати тільки актуальну версію;
обмежити результати певним типом документа;
перевірити права користувача на доступ до джерела.
Це важливо для безпеки. Не можна просто виконати семантичний пошук по всій базі, якщо різні користувачі мають різні права доступу.
Коли надходить новий запит, система виконує кілька кроків.
Спочатку можна визначити:
мову запиту;
його тип;
потрібний час або версію документа;
фільтри;
чи потрібно виконати пошук взагалі.
Деякі запити не потребують зовнішніх документів. Наприклад, прохання перефразувати вже наданий текст може бути оброблене без RAG.
Запит перетворюється на embedding тією самою або сумісною моделлю, яка використовувалася для документів.
Система шукає найближчі фрагменти у векторному сховищі. Наприклад, вона може отримати десять або двадцять кандидатів.
Початковий векторний пошук швидкий, але не завжди ідеально визначає релевантність. Тому результати можна передати окремій моделі-reranker, яка точніше порівняє запит із кожним фрагментом.
Після повторного ранжування до LLM передають лише найкращі результати.
Знайдені фрагменти об’єднують у контекст. До нього часто додають позначки джерел:
[Джерело 1: Інструкція з доступу]
Користувач може змінити пароль у розділі «Налаштування профілю»...
[Джерело 2: Політика безпеки]
Пароль потрібно оновлювати не рідше одного разу на 180 днів...LLM отримує:
запит користувача;
знайдений контекст;
інструкції щодо формату відповіді;
правила роботи з відсутньою інформацією.
У системній інструкції варто явно зазначити, що модель має:
використовувати наданий контекст;
не вигадувати відсутні факти;
повідомляти, якщо інформації недостатньо;
за можливості вказувати джерела;
розрізняти факти та припущення.
Нижче наведено концептуальний приклад. Конкретні функції embed, search і generate залежать від обраних моделей та інфраструктури.
def answer_question(question):
# Перетворюємо запит на вектор
query_vector = embed(question)
# Знаходимо релевантні фрагменти з урахуванням прав доступу
chunks = search(
vector=query_vector,
limit=8,
filters={"access_group": "support"}
)
# Вибираємо найкращі фрагменти після повторного ранжування
ranked_chunks = rerank(question, chunks)
context_chunks = ranked_chunks[:4]
context = "\n\n".join(
f"[Джерело {index + 1}]\n{chunk.text}"
for index, chunk in enumerate(context_chunks)
)
prompt = f"""
Ти відповідаєш на запитання користувача на основі наведеного контексту.
Не вигадуй фактів, яких немає в контексті.
Якщо відповіді недостатньо, прямо скажи про це.
Контекст:
{context}
Запитання:
{question}
"""
return generate(prompt)Цей приклад не реалізує повний production-процес, але демонструє основну ідею: пошук відбувається до генерації, а результат пошуку стає контекстом для LLM.
Семантичний пошук добре знаходить документи за змістом, але не завжди ефективний для точних термінів.
Наприклад, у запиті можуть бути:
назва функції;
номер помилки;
ідентифікатор договору;
назва продукту;
версія пакета;
артикул товару.
Для таких випадків корисний гібридний пошук, який поєднує:
пошук за embeddings;
пошук за ключовими словами;
фільтрацію за метаданими.
Результати двох підходів об’єднують і повторно ранжують. Це дає змогу одночасно враховувати і значення тексту, і точні збіги.
Користувацький запит може бути нечітким або містити зайві слова. Перед пошуком окрема модель може перетворити його на більш придатну пошукову форму.
Наприклад:
«А що робити, якщо воно не працює після оновлення?»
→ «Проблеми після оновлення клієнтського застосунку та способи їх усунення»Однак переписування не повинно змінювати зміст запиту або додавати непідтверджені деталі.
Складне питання можна розділити на кілька простіших:
1. Які умови повернення товару?
2. Який строк повернення?
3. Які документи потрібні?Для кожного підзапиту система виконує окремий пошук, а потім об’єднує результати.
Одне питання можна переформулювати кількома способами й виконати пошук для кожної версії. Це допомагає, якщо користувач використав нетипову термінологію.
Іноді корисно зберігати документи на кількох рівнях:
короткий фрагмент для точного пошуку;
більший батьківський розділ для передачі контексту;
повний документ як джерело.
Спочатку система знаходить малий фрагмент, а потім додає до контексту ширший розділ, якому він належить.
RAG-система може повертати не лише текст відповіді, а й:
назву документа;
номер розділу;
сторінку;
дату оновлення;
фрагмент, на якому базується відповідь.
Це підвищує довіру до системи та спрощує перевірку результату.
RAG і fine-tuning розв’язують різні задачі.
RAG підходить, коли потрібно:
працювати з приватними документами;
часто оновлювати дані;
показувати джерела;
швидко додавати нові документи;
відповідати на основі актуальної інформації.
Донавчання моделі використовують, коли потрібно:
змінити стиль відповідей;
навчити модель певному формату;
адаптувати її до спеціальної термінології;
покращити виконання повторюваної задачі.
Fine-tuning сам по собі не є надійним способом зберігати часто змінювані факти. Для таких даних RAG зазвичай підходить краще.
Ці підходи також можна поєднувати: fine-tuning відповідає за стиль і поведінку моделі, а RAG — за доступ до актуальних знань.
Якість потрібно перевіряти окремо на різних етапах.
Потрібно з’ясувати:
чи знаходить система потрібний документ;
чи потрапляє правильний фрагмент у top-k результатів;
чи не переважають нерелевантні документи;
чи працюють фільтри доступу;
чи знаходяться документи за синонімами.
Якщо потрібний фрагмент не знайдено, навіть найкраща LLM не зможе надійно відповісти.
Потрібно перевірити:
чи відповідає текст знайденому контексту;
чи не додала модель вигаданих деталей;
чи відповідає відповідь на конкретне питання;
чи не пропущені важливі умови;
чи правильно оформлені цитати.
Хороша система не повинна відповідати впевнено на будь-яке питання. Якщо в документах немає потрібної інформації, вона має повідомити про це або передати запит оператору.
Для тестування варто використовувати:
питання, на які є відповідь;
питання, на які відповіді немає;
неоднозначні питання;
запити з помилками;
питання про застарілі версії;
спроби отримати заборонені дані.
Причини:
невдалий розмір чанків;
слабка модель embeddings;
надто широкий пошук;
відсутність повторного ранжування;
неякісне очищення документів.
Що можна зробити:
змінити стратегію розбиття;
додати гібридний пошук;
використовувати reranking;
зменшити або збільшити кількість результатів;
зберігати структурні метадані.
Потрібно:
зберігати версію документа;
додавати дату оновлення;
видаляти або архівувати застарілі чанки;
фільтрувати результати за актуальністю;
передавати моделі інформацію про пріоритет новіших документів.
Витягування тексту з PDF може зруйнувати порядок колонок, заголовків і приміток. Для таких документів потрібно окремо перевіряти якість парсингу.
Іноді доцільно зберігати не лише текст, а й:
структуру таблиці;
заголовки;
зв’язок із підписами;
порядок сторінок;
координати фрагментів.
Це може бути наслідком:
занадто великого контексту;
суперечливих фрагментів;
поганого формату prompt;
неправильного порядку джерел;
нечітких інструкцій.
Корисно передавати менше, але якісніших фрагментів і чітко відокремлювати джерела від інструкцій.
Якщо до сховища потрапляють дублікати, меню, номери сторінок та інший шум, результати пошуку стають менш точними.
Поділ документа кожні N символів може розірвати заголовок, список або важливе пояснення. Краще враховувати структуру документа.
Більше тексту не завжди означає кращу відповідь. Нерелевантні фрагменти можуть заплутати модель і збільшити витрати.
Перевіряти права доступу лише після генерації відповіді небезпечно. Користувач не повинен отримати фрагменти заборонених документів навіть як частину внутрішнього контексту.
Фільтрацію потрібно виконувати до передачі результатів моделі.
RAG зменшує ризик галюцинацій, але не гарантує їх відсутність. Модель може:
неправильно інтерпретувати контекст;
об’єднати несумісні твердження;
проігнорувати джерело;
додати правдоподібну, але відсутню деталь.
Для критичних сценаріїв потрібні перевірка фактів, цитати, правила відмови та участь людини.
Без версій неможливо зрозуміти, чи використала система актуальну інструкцію. Метадані документа мають бути частиною дизайну RAG-системи, а не додаватися після появи проблем.
Якщо система дала неправильну відповідь, потрібно з’ясувати, де виникла помилка:
документ не був проіндексований;
потрібний фрагмент не знайдено;
пошук повернув неправильні результати;
контекст сформовано некоректно;
модель неправильно використала контекст.
Без такого поділу складно зрозуміти, що саме потрібно виправляти.
RAG добре підходить для:
чат-ботів за внутрішньою документацією;
пошуку по технічних матеріалах;
автоматизації першої лінії підтримки;
відповідей за корпоративними політиками;
аналізу великої колекції документів;
роботи з часто оновлюваними даними;
систем, де важливо показувати джерела.
RAG може бути надлишковим, якщо:
даних дуже мало;
відповідь не залежить від зовнішніх документів;
достатньо звичайного пошуку;
користувачу потрібен точний запис із бази даних, а не сформульоване пояснення.
Для числових операцій, транзакцій і складних фільтрів часто краще використовувати структуровані запити до бази даних, а не покладатися лише на пошук у тексті.
RAG поєднує пошук і генерацію:
Документи очищають і розбивають на фрагменти.
Для фрагментів створюють embeddings.
Embeddings зберігають у векторному сховищі.
Запит користувача перетворюють на embedding.
Система знаходить релевантні фрагменти.
Знайдений контекст передають мовній моделі.
Модель формує відповідь, бажано з посиланнями на джерела.
RAG не навчає модель нових фактів у звичайному розумінні. Він дає моделі доступ до потрібної інформації саме під час виконання запиту.
Надійність такої системи залежить не лише від обраної LLM, а й від якості документів, розбиття на чанки, embeddings, пошуку, фільтрації доступу, формування контексту та перевірки відповідей.