Чому більшість ботів «чат із документами» не працюють
Бізнесовий AI-агент корисний рівно настільки, наскільки добре він знаходить потрібні документи в момент, коли надходить запитання. Модель загального призначення ніколи не бачила вашої HR-політики, прайс-листа чи звітів про інциденти, тож заповнює прогалини правдоподібними здогадками.
Уявіть, що працівник питає внутрішнього асистента: «Скільки днів відпустки я можу перенести на наступний рік?» «Гола» модель відповідає: «зазвичай п'ять днів». Відповідь звучить упевнено й розумно — і вона хибна, якщо ваша політика каже десять.
У підтримці, продажах чи юридичній роботі такі відповіді коштують грошей і довіри. Retrieval-augmented generation (RAG) розв'язує проблему: спершу знаходить релевантні фрагменти документів, а потім змушує модель відповідати саме на їхній основі. У статті пройдемо весь шлях: індексація, пошук, генерація, перехід від пайплайна до агента і доведення того, що результат працює.
Що таке RAG простими словами
RAG ділить роботу на два пайплайни: офлайновий, що індексує ваші документи, і онлайновий, що відповідає на запитання з цього індексу. Ідея походить зі статті Lewis et al. 2020 року з Facebook AI Research: автори поєднали нейронний пошук по Вікіпедії з генератором тексту і перевершили моделі, що покладалися лише на власні ваги, у задачах, які потребують знань (Lewis et al., 2020).
Офлайн-пайплайн (запускається щоразу, коли змінюється документ):
- Ваші документи → парсинг і очистка
- Розбиття на фрагменти (chunks) з метаданими
- Обчислення ембедингів і запис в індекс (вектори + повнотекстовий пошук)
Онлайн-пайплайн (запускається на кожне запитання користувача):
- Запитання користувача → гібридний пошук по індексу з фільтром за правами доступу
- Reranking топ-20 кандидатів
- Топ 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 чи промпти.
Джерела
- Lewis et al. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS 2020.
- Robertson & Zaragoza (2009). The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval.
- Cormack, Clarke & Büttcher (2009). Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods. SIGIR 2009.
- Nogueira & Cho (2019). Passage Re-ranking with BERT.
- Anthropic (2024). Introducing Contextual Retrieval.
- Liu et al. (2023). Lost in the Middle: How Language Models Use Long Contexts.
- Asai et al. (2023). Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection.
- Edge et al. (2024). From Local to Global: A Graph RAG Approach to Query-Focused Summarization.
- Es et al. (2023). RAGAS: Automated Evaluation of Retrieval Augmented Generation.
- pgvector — open-source пошук за векторною схожістю для PostgreSQL.
- Model Context Protocol — відкритий стандарт для підключення AI-агентів до інструментів і даних.
- PostgreSQL. Full text search: dictionaries.