Чому більшість ботів «чат із документами» не працюють

Бізнесовий AI-агент корисний рівно настільки, наскільки добре він знаходить потрібні документи в момент, коли надходить запитання. Модель загального призначення ніколи не бачила вашої HR-політики, прайс-листа чи звітів про інциденти, тож заповнює прогалини правдоподібними здогадками.

Уявіть, що працівник питає внутрішнього асистента: «Скільки днів відпустки я можу перенести на наступний рік?» «Гола» модель відповідає: «зазвичай п'ять днів». Відповідь звучить упевнено й розумно — і вона хибна, якщо ваша політика каже десять.

У підтримці, продажах чи юридичній роботі такі відповіді коштують грошей і довіри. Retrieval-augmented generation (RAG) розв'язує проблему: спершу знаходить релевантні фрагменти документів, а потім змушує модель відповідати саме на їхній основі. У статті пройдемо весь шлях: індексація, пошук, генерація, перехід від пайплайна до агента і доведення того, що результат працює.

Що таке RAG простими словами

RAG ділить роботу на два пайплайни: офлайновий, що індексує ваші документи, і онлайновий, що відповідає на запитання з цього індексу. Ідея походить зі статті Lewis et al. 2020 року з Facebook AI Research: автори поєднали нейронний пошук по Вікіпедії з генератором тексту і перевершили моделі, що покладалися лише на власні ваги, у задачах, які потребують знань (Lewis et al., 2020).

Офлайн-пайплайн (запускається щоразу, коли змінюється документ):

  1. Ваші документи → парсинг і очистка
  2. Розбиття на фрагменти (chunks) з метаданими
  3. Обчислення ембедингів і запис в індекс (вектори + повнотекстовий пошук)

Онлайн-пайплайн (запускається на кожне запитання користувача):

  1. Запитання користувача → гібридний пошук по індексу з фільтром за правами доступу
  2. Reranking топ-20 кандидатів
  3. Топ 5–8 фрагментів → LLM формує відповідь із посиланнями на джерела

Онлайн-частина читає лише індекс, відфільтрований за правами доступу користувача.

Бізнесові аргументи тут практичні, а не академічні. Ваші знання змінюються щотижня: переіндексація документа займає секунди, тоді як донавчання моделі триває дні й однаково не дає посилання на джерело. Крім того, RAG дозволяє застосовувати права доступу на етапі пошуку, тож модель ніколи не побачить фрагмент, який користувачеві читати не дозволено.

Крок 1: Індексація — парсинг, збагачення, розбиття

Якість пошуку здебільшого визначається на етапі індексації, задовго до того, як хтось введе запитання. Важливі три речі: чистий текст, корисні метадані та розумні фрагменти.

Парсинг. PDF, презентації та скановані договори втрачають структуру, коли їх сплющують у звичайний текст. Зберігайте заголовки, нумерацію списків і таблиці, бо «Розділ 4.2» часто саме те, що робить фрагмент знаходжуваним. Конвертуйте таблиці в Markdown або в одне речення на рядок, інакше числа відірвуться від назв колонок.

Метадані. Кожен фрагмент має містити джерело, шлях розділу, дату останнього оновлення, власника і групу доступу. Метадані забезпечують фільтри («лише політики, чинні у 2026 році»), посилання на джерела і перевірку прав.

Розбиття. Спершу діліть за структурою (заголовки, абзаци), потім за розміром, з невеликим перекриттям, щоб речення, розрізане на межі, вціліло в обох сусідніх фрагментах. Кілька сотень токенів на фрагмент із перекриттям 10–15% — розумна відправна точка; налаштовуйте її на власному наборі для оцінки, а не довіряйте дефолтам.

from dataclasses import dataclass

@dataclass
class Chunk:
    text: str
    source: str
    section: str
    updated: str
    acl_group: str

def chunk_document(doc, max_chars=2000, overlap=200):
    chunks = []
    for section in doc.sections:          # спершу ділимо за заголовками
        text, start = section.text, 0
        while start < len(text):
            end = min(start + max_chars, len(text))
            chunks.append(Chunk(
                # додаємо «хлібні крихти», щоб фрагмент мав сенс сам по собі
                text=f"{doc.title} > {section.path}\n\n{text[start:end]}",
                source=doc.url,
                section=section.path,
                updated=doc.updated_at,
                acl_group=doc.acl_group,
            ))
            if end == len(text):
                break
            start = end - overlap
    return chunks

Зверніть увагу на «хлібні крихти» на початку кожного фрагмента. Фрагмент «ліміт — 30 днів» сам по собі марний; з префіксом «Політика повернень > Клієнти з ЄС» він відповідає на реальне запитання.

Крок 2: Пошук — гібридний пошук, reranking, контекст

Чистого векторного пошуку для бізнесових документів недостатньо. Ембединги добре вловлюють зміст, але розмивають точні токени: артикули, коди помилок, номери пунктів договору і назви продуктів. Ранжування за ключовими словами сімейства BM25 знаходить їх точно (Robertson & Zaragoza, 2009), тож продакшен-системи запускають обидва пошуки й об'єднують результати.

Стандартний спосіб об'єднання — Reciprocal Rank Fusion (RRF). Кожен документ отримує суму 1 / (k + rank) по всіх списках результатів, де в оригінальній статті k = 60 (Cormack et al., 2009). RRF не потребує калібрування оцінок між двома рушіями, тож це безпечний вибір за замовчуванням.

Ось усе разом у PostgreSQL з розширенням pgvector. Тут повнотекстове ранжування Postgres заміщує BM25; пошукові рушії на кшталт Elasticsearch чи OpenSearch використовують BM25 нативно.

-- $1 = ембединг запиту, $2 = текст запиту, $3 = групи доступу користувача
WITH semantic AS (
  SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) AS rnk
  FROM chunks
  WHERE acl_group = ANY($3)
  ORDER BY embedding <=> $1
  LIMIT 50
),
keyword AS (
  SELECT id, ROW_NUMBER() OVER (
           ORDER BY ts_rank(tsv, plainto_tsquery('simple', $2)) DESC) AS rnk
  FROM chunks
  WHERE tsv @@ plainto_tsquery('simple', $2)
    AND acl_group = ANY($3)
  ORDER BY ts_rank(tsv, plainto_tsquery('simple', $2)) DESC
  LIMIT 50
)
SELECT c.id, c.text, c.source,
       COALESCE(1.0 / (60 + s.rnk), 0)
     + COALESCE(1.0 / (60 + k.rnk), 0) AS rrf_score
FROM semantic s
FULL OUTER JOIN keyword k ON s.id = k.id
JOIN chunks c ON c.id = COALESCE(s.id, k.id)
ORDER BY rrf_score DESC
LIMIT 20;

Фільтр доступу стоїть в обох підзапитах. Права перевіряє база даних, а не прохання до моделі «тримати секрети». Для українськомовних документів зверніть увагу на конфігурацію повнотекстового пошуку: стандартна поставка PostgreSQL не має українського стемера, тож використовуйте конфігурацію simple, окремий словник або зовнішній пошуковий рушій (документація PostgreSQL: full text search).

Топ-20 кандидатів потім ідуть у reranker — cross-encoder, що читає запитання і кожен фрагмент разом і переупорядковує їх за релевантністю (Nogueira & Cho, 2019). До мовної моделі доходять лише найкращі п'ять-вісім фрагментів.

Ще одне дешеве покращення — contextual retrieval. Перед індексацією LLM пише одне-два речення про те, де фрагмент розташований у документі, і цей опис додається до фрагмента і для ембедингів, і для пошуку за ключовими словами. У тестах Anthropic це скоротило частку невдалих пошуків у топ-20 на 35% лише з контекстними ембедингами, на 49% — з доданим контекстним BM25 і на 67% — з reranking'ом поверх (Anthropic, 2024).

Крок 3: Генерація — відповідь із джерел або «я не знаю»

Промпт генерації має одне завдання: змусити модель вважати знайдені фрагменти єдиним джерелом правди. Для цього потрібні три правила: пронумеровані джерела, посилання для кожного твердження і явний дозвіл відмовитися відповідати.

Ти — внутрішній асистент компанії Acme Corp.
Відповідай ЛИШЕ на основі джерел нижче. Позначай кожне твердження як [n].
Якщо в джерелах немає відповіді, відповідай
«Я не знайшов цього в нашій документації» і підкажи, до кого звернутися.
Якщо джерела суперечать одне одному, віддавай перевагу найсвіжішому
і згадай про суперечність.

<sources>
[1] HR-політика > Відпустки > Перенесення (оновлено 2026-03-01)
    Працівники можуть перенести до 10 невикористаних днів відпустки...
[2] HR FAQ > Відпустки (оновлено 2024-11-15)
    Можна перенести до 5 днів...
</sources>

Запитання: Скільки днів відпустки я можу перенести?

З таким промптом модель відповідає «до 10 днів [1]» і зазначає, що старіший FAQ [2] досі каже про п'ять. Друге речення цінне: воно підказує власнику документа, що виправити.

Порядок фрагментів теж має значення. Liu et al. показали, що моделі значно краще використовують інформацію на початку і в кінці довгого контексту, ніж у середині (Liu et al., 2023). Ставте найсильніші фрагменти першими і не запихайте 40 фрагментів «про всяк випадок»: більше контексту часто означає гірші відповіді, а не кращі.

Посилання на джерела — ще й продуктова функція. Коли користувач може перейти до конкретного абзацу, він довіряє правильним відповідям і сам помічає хибні.

Від пайплайна до агента

RAG-пайплайн відповідає на одне запитання одним пошуком; агент вирішує, що шукати, де і чи достатньо в нього вже інформації. На практиці перехід означає дати моделі пошук як інструмент, який вона може викликати багаторазово, разом із кількома іншими.

Типовому бізнесовому агенту вистачає кількох інструментів:

  • search_docs(query, filters) — гібридний пошук, описаний вище, з фільтрами за метаданими, які модель заповнює сама.
  • get_document(id) — повний документ, коли фрагмента замало для контексту.
  • query_crm(customer_id) — структуровані факти, які ніколи не можна вгадувати з тексту.
  • create_ticket(...) — дія з наслідками, що виконується лише після підтвердження користувача.

Ось як виглядає цикл на реальному запитанні підтримки:

Користувач: Чи може клієнт #4471 ще отримати повернення за березневе замовлення?

Агент -> query_crm(4471)
         Клієнт з ЄС, замовлення від 2026-03-12, тариф Pro
Агент -> search_docs("строк повернення клієнти ЄС", {doc_type: "policy"})
         [1] Політика повернень > ЄС: повернення протягом 30 днів від покупки
Агент -> search_docs("винятки повернення тариф Pro")
         релевантних фрагментів немає

Агент:  Ні. 30-денний строк повернення для клієнтів з ЄС
        сплив 11 квітня [1]. Винятків для тарифу Pro я не знайшов;
        команда білінгу може погодити його вручну.

Агент поєднав структуровані дані з текстом політики і запустив другий пошук, коли перший залишив прогалину. А ще чесно визнав, чого документи не покривають, замість того щоб вигадати виняток.

Дослідження вказують у тому самому напрямку. Self-RAG навчає моделі вирішувати, коли шукати, і критикувати власні чернетки (Asai et al., 2023). Для запитань, що охоплюють увесь корпус, як-от «які теми повторюються в усіх звітах про інциденти?», GraphRAG будує граф знань із підсумками спільнот замість пошуку окремих фрагментів (Edge et al., 2024). А щоб підключати інструменти з різних систем без самописного «клею», існує відкритий стандарт Model Context Protocol.

Одне правило не змінюється ніколи: агент діє з правами користувача, а не з власними. Кожен виклик інструмента несе ідентичність користувача, а кожен фільтр застосовується в коді.

Крок 4: Вимірюйте до запуску

RAG ламається тихо: хибна відповідь із посиланням виглядає точнісінько як правильна. Тому до запуску зберіть еталонний набір (golden set) зі 100–200 реальних запитань — для кожного очікувана відповідь і документ, що її містить. Беріть їх із тікетів підтримки, робочих чатів і логів пошуку, а не з уяви.

Потім вимірюйте два шари окремо, бо погана відповідь може походити з будь-якого з них:

Шар Метрика На яке питання відповідає
Пошук Recall@k Чи є правильний фрагмент серед топ-k результатів?
Пошук MRR Наскільки високо стоїть перший правильний фрагмент?
Генерація Faithfulness Чи підкріплене кожне твердження знайденими джерелами?
Генерація Answer relevance Чи відповідає відповідь на поставлене запитання?
Наскрізно Refusal accuracy Чи каже система «я не знаю», коли документи мовчать?

Фреймворки на кшталт RAGAS автоматизують оцінку faithfulness і релевантності за допомогою LLM у ролі судді (Es et al., 2023). Перш ніж довіряти судді, звірте його з кількома десятками людських оцінок. Проганяйте повний набір після кожної зміни розбиття, ембедингів, reranker'а чи промпту і зберігайте результати, щоб порівнювати версії.

Типові помилки та чек-лист запуску

Більшість провалених RAG-проєктів падають через дані й процеси, а не через модель. Звичні підозрювані — застарілі чи дубльовані документи, що суперечать одне одному, таблиці, сплющені в шум, і права доступу, що перевіряються в промпті замість запиту до бази. Найдорожча помилка — відсутність еталонного набору, коли кожну зміну оцінюють «на око».

RAG — це 80% data engineering і 20% prompt engineering. Команди, які думають навпаки, застрягають на пілотах.

Перш ніж відкрити агента користувачам, перевірте, що:

  • Кожен документ має власника і дату оновлення, а застарілі версії видалені з індексу
  • Кожен фрагмент містить джерело, шлях розділу і групу доступу
  • Гібридний пошук із reranking'ом протестовано проти чисто векторного на ваших власних запитаннях
  • Промпт вимагає посилань на джерела і явно дозволяє «я не знаю»
  • Еталонний набір зі 100+ реальних запитань проганяється після кожної зміни
  • Запитання, знайдені фрагменти та відгуки користувачів логуються для розбору

Починайте з малого: один відділ, один набір документів, один еталонний набір. Вузький агент, що посилається на джерела і визнає свої прогалини, заробляє більше довіри, ніж широкий, що вгадує, — і саме ця довіра дозволяє потім його розширювати. Як ця логіка працює в клієнтській підтримці, ми розбираємо у статті AI-агенти для підтримки: де вони працюють, а де ні.

Коли RAG — не ваше рішення

  • Якщо знання вашої компанії — у головах людей, а не в документах
  • Якщо документи оновлюються щохвилини і ви не готові до індексації в реальному часі
  • Якщо потрібна точність у числах (фінансові розрахунки, залишки, KPI) — тут краще structured retrieval з SQL-агентом поверх ваших даних

Пов'язані гайди

Коли пайплайн запрацював, три теми визначають, чи переживе він продакшен: як ви міряєте якість (Як оцінювати LLM-застосунки), як підключаєте агента до бізнес-систем (MCP для бізнесу) і скільки коштує його робота (Оптимізація витрат на LLM). Знайдені документи — теж недовірений ввід; див. Безпека AI-агентів.

Детальніше про шар пошуку — у статтях ембеддинги та чанкінг і порівняння векторних баз даних; про вибір між пошуком і донавчанням — у статті fine-tuning, RAG чи промпти.

Джерела

  1. Lewis et al. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS 2020.
  2. Robertson & Zaragoza (2009). The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval.
  3. Cormack, Clarke & Büttcher (2009). Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods. SIGIR 2009.
  4. Nogueira & Cho (2019). Passage Re-ranking with BERT.
  5. Anthropic (2024). Introducing Contextual Retrieval.
  6. Liu et al. (2023). Lost in the Middle: How Language Models Use Long Contexts.
  7. Asai et al. (2023). Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection.
  8. Edge et al. (2024). From Local to Global: A Graph RAG Approach to Query-Focused Summarization.
  9. Es et al. (2023). RAGAS: Automated Evaluation of Retrieval Augmented Generation.
  10. pgvector — open-source пошук за векторною схожістю для PostgreSQL.
  11. Model Context Protocol — відкритий стандарт для підключення AI-агентів до інструментів і даних.
  12. PostgreSQL. Full text search: dictionaries.