RAG з Ollama: навчи AI відповідати по твоїх документах
У тебе є документи — PDF, статті, нотатки, база знань. Ти хочеш задавати питання
і отримувати відповіді саме по цих документах, а не по загальних знаннях моделі.
І все це — локально, без відправки даних у хмару.
Саме для цього існує RAG. У цій статті — пояснення концепції ,
візуальна схема пайплайну і покроковий Python-приклад який реально працює.
А також патерни з продакшну яких немає в документації.
📚 Зміст статті
🎯 Що таке RAG — і чому це не донавчання
Коротка відповідь:
RAG (Retrieval-Augmented Generation) — це спосіб дати моделі доступ
до твоїх документів без зміни самої моделі. Перед кожним запитом
система знаходить релевантні фрагменти з твоєї бази і додає їх
у контекст. Модель відповідає на основі цих фрагментів —
а не тільки того що вивчила при навчанні.
LLM навчені на трильйонах токенів з інтернету — але не на твоїх
внутрішніх документах, не на твоїй кодовій базі і не на статтях
які ти написав минулого тижня. RAG закриває цей розрив.
Аналогія: юрист і справа
Уяви досвідченого юриста. Він знає закони, прецеденти, загальну практику —
все що вивчив за роки. Але коли йому дають нову справу — він не відповідає
по пам'яті. Він читає матеріали справи, виділяє ключові факти,
і тільки потім формулює позицію спираючись на конкретні документи.
LLM без RAG — це той самий юрист якого просять відповідати
без доступу до матеріалів справи. Він щось скаже, але це буде
загальна думка, а не відповідь по твоєму конкретному кейсу.
RAG дає йому ці матеріали.
Чому не донавчання
Fine-tuning — це перенавчання моделі на твоїх даних. Звучить логічно,
але на практиці це дорого, повільно і негнучко. Якщо твої документи
змінюються — треба перенавчати знову. Якщо зробив помилку в даних —
модель «запам'ятала» неправильне.
RAG не чіпає модель взагалі. Документи оновив — переіндексував за хвилини.
Помилку знайшов — виправив у джерелі. Модель при цьому залишається тією самою.
Саме тому для більшості задач де потрібно «відповідати по документах»
RAG є правильним вибором, а не fine-tuning.
RAG vs Fine-tuning: коли що вибирати
| Критерій |
RAG |
Fine-tuning |
| Складність реалізації |
Низька — кілька днів |
Висока — тижні |
| Обчислювальні ресурси |
Мінімальні — CPU достатньо |
Потрібен GPU, значні витрати |
| Оновлення знань |
Переіндексація — хвилини |
Перенавчання — дні |
| Прозорість |
✔️ Видно які документи використані |
❌ Важко пояснити звідки відповідь |
| Помилка в даних |
Виправив джерело — готово |
Перенавчання з нуля |
| Підходить для |
База знань, документи, FAQ, пошук |
Зміна стилю, тону, формату відповідей |
| Не підходить для |
Зміна поведінки самої моделі |
Часто оновлювані документи |
Коли RAG не вирішить задачу
RAG — не срібна куля. Є кілька сценаріїв де він не допоможе:
- ⚠️ Короткі запити (1-2 слова) — semantic search
на коротких запитах менш точний ніж keyword search.
Потрібен fallback.
- ⚠️ Питання виходить за межі документів — якщо
відповіді немає в базі, модель або скаже «не знаю» або
почне галюцинувати. Промпт має явно забороняти друге.
- ⚠️ Треба змінити стиль або поведінку моделі —
тут RAG не допоможе, це завдання для fine-tuning або
детального системного промпту.
Висновок розділу: RAG — правильний вибір коли
потрібно відповідати по конкретних документах, і ці документи
змінюються або оновлюються. Fine-tuning потрібен коли треба
змінити саму поведінку моделі, а не розширити її знання.
🎯 Як влаштований пайплайн: від документа до відповіді
Коротка відповідь:
RAG-пайплайн розділений на дві незалежні фази. Індексація —
виконується один раз або при оновленні документів:
документ → чанки → ембединги → vector store.
Пошук і генерація — при кожному запиті користувача:
питання → ембединг → similarity search → промпт з контекстом → відповідь.
Якщо розуміти де що відбувається — налагодження стає в рази простішим.
Одна з перших речей яку я зрозумів на практиці: модель ніколи
не «читає» весь документ цілком. Вона бачить тільки кілька
фрагментів які система вважає найрелевантнішими. Якість відповіді
прямо залежить від якості цього вибору.
Фаза 1 — Індексація
Індексація відбувається один раз — або за розкладом при оновленні контенту.
Мета: перетворити людський текст у форму яку vector store може шукати.
Крок 1. Читання документа
Вхідні формати: PDF, HTML, Markdown, plain text, веб-сторінки.
На цьому кроці головне — отримати чистий текст без розмітки.
HTML-теги, metadata PDF, службові символи — все це шум який погіршує
якість ембедингів. Jsoup для Java, BeautifulSoup для Python —
стандартні інструменти очищення.
Крок 2. Чанкінг — розбиття на фрагменти
Документ розбивається на менші частини — чанки. Чому не зберігати цілий документ?
Дві причини. По-перше, ембединг-моделі мають ліміт токенів:
nomic-embed-text — 8192 токени, all-minilm — 256. Стаття на 5000 слів
просто не вміститься. По-друге, пошук точніший на менших фрагментах:
якщо зберегти цілу статтю як один вектор — вектор усереднює всі теми статті
і стає менш специфічним.
Стандарт: 512 токенів з overlap 50 токенів. Overlap — це навмисне перекриття
між сусідніми чанками щоб думка не обривалася на межі фрагменту.
Крок 3. Ембединги
Кожен чанк перетворюється в числовий вектор — список з 768 чисел
(для nomic-embed-text). Цей вектор кодує смисл тексту,
а не конкретні слова. Саме тому «веб-розробка під ключ»
і «як зробити сайт» матимуть схожі вектори навіть без спільних слів.
Саме в цьому і є перевага semantic search над LIKE-пошуком.
Крок 4. Vector Store
Вектори і оригінальний текст чанків зберігаються у векторній базі:
pgvector (розширення для PostgreSQL), Chroma, FAISS, Milvus або інша.
Якщо в проєкті вже є PostgreSQL — pgvector є природним вибором:
один інстанс бази, нічого нового не встановлювати.
Фаза 2 — Пошук і генерація
Ця фаза запускається при кожному запиті і займає секунди.
Користувач вводить питання — і через кілька кроків отримує відповідь.
Крок 5. Ембединг запиту
Питання користувача також перетворюється в вектор —
тією самою ембединг-моделлю що використовувалась при індексації.
Це принципово: якщо індексував через nomic-embed-text,
запит теж має йти через nomic-embed-text.
Різні моделі дають несумісні вектори — і пошук буде нерелевантним.
Крок 6. Similarity Search
Vector store знаходить N найближчих векторів до вектора запиту.
«Найближчий» — це cosine similarity або dot product між векторами.
Результат: топ-5 (або скільки задаш) найрелевантніших чанків.
Similarity threshold фільтрує результати нижче порогу схожості —
зазвичай починають з 0.5 і коригують по логах.
Крок 7. Формування промпту
Знайдені чанки вставляються в системний промпт як контекст.
Якість цього промпту напряму впливає на якість відповіді.
Мінімум що має бути в системному промпті:
Відповідай ТІЛЬКИ на основі наданого контексту.
Якщо відповіді немає в контексті — чесно скажи про це.
Не вигадуй інформацію якої немає в документах.
Контекст:
{знайдені чанки}
Без явної заборони на вигадування — модель буде галюцинувати.
Це не баг конкретної моделі, це поведінка LLM загалом.
Крок 8. Генерація відповіді
LLM (Ollama з llama3.3, mistral або іншою моделлю) генерує відповідь
спираючись на контекст з попереднього кроку.
Важлива деталь: показуй користувачу джерела — які конкретно статті
або документи використала система. Це і прозорість, і спосіб
перевірити де модель взяла інформацію.
Висновок розділу: Два незалежних процеси —
індексація і пошук+генерація. Більшість проблем RAG виникають
або на кроці чанкінгу (неправильний розмір), або на кроці
similarity search (неправильний threshold), або в системному промпті
(немає заборони на галюцинації). Розберемо кожен детально далі.
🎯 Інструменти: LlamaIndex vs LangChain vs ручний підхід
Коротка відповідь:
У 2026 році вибір між LlamaIndex і LangChain — це не про «кращий»,
а про те що ти будуєш. LlamaIndex спеціалізований на роботі з документами
і дає кращу якість пошуку з меншим кодом. LangChain — для складних агентних
систем де RAG є одним із компонентів. Ручний підхід через Spring AI або
прямий REST API — якщо тебе не влаштовує Python або потрібен максимальний
контроль над кожним кроком.
Детальний розбір 2026 року зазначає:
порівняння «LangChain = оркестрація, LlamaIndex = дані» яке повторюють
більшість статей — вже застаріло. Обидва фреймворки зближуються
і покривають схожий функціонал. Вибір тепер залежить від
того де ти починаєш і що є основою твого застосунку.
LlamaIndex — якщо в центрі документи і пошук
LlamaIndex побудований навколо ідеї що головна проблема —
надійно з'єднати LLM з твоїми даними. Все інше — агенти, пайплайни,
оркестрація — йде поверх цього фундаменту.
Незалежні бенчмарки показують:
LlamaIndex на 40% швидший за LangChain на document retrieval tasks
і у 2025 році отримав +35% до точності пошуку.
Причина: вбудовані абстракції для chunking, re-ranking і context assembly
налаштовані під retrieval — а не під загальні пайплайни.
Плюси:
- ✔️ Найпростіший старт: 10 рядків для working RAG
- ✔️ Вбудований re-ranking, hybrid search (semantic + keyword)
- ✔️ Широкий список data connectors через LlamaHub: PDF, HTML,
Notion, Google Docs, S3, бази даних
- ✔️ Гнучкий chunking: SentenceSplitter, TokenTextSplitter,
SemanticSplitter — міняєш одним рядком
- ✔️ Нативна інтеграція з Ollama через окремі пакети
Мінуси:
- ⚠️ Для складних агентних систем з багатьма зовнішніми інструментами —
менш зручний ніж LangChain
- ⚠️ Документація менш повна ніж у LangChain для нетипових сценаріїв
Коли вибирати: є документи, PDF, база знань —
потрібно задавати питання і отримувати відповіді. Блог, FAQ, внутрішня
документація компанії, технічна довідка.
LangChain — якщо RAG є частиною складнішої системи
LangChain будувався навколо ідеї що побудова з LLM — це проблема
workflow: є вхідні дані, є вихідні, між ними — моделі, інструменти,
пам'ять і зовнішні джерела які потрібно скомпонувати.
У 2026 році
це означає LangGraph — направлений граф де вузли це функції,
а ребра — переходи між станами. RAG в LangChain — один із інструментів
агента, а не центральна абстракція.
Плюси:
- ✔️ Найбільша екосистема і спільнота — 220% зростання GitHub stars
за 2024 рік
- ✔️ LangGraph для складних stateful агентів з розгалуженням і циклами
- ✔️ LangSmith — observability, tracing, evaluation out-of-the-box
- ✔️ Мультимодальна підтримка сильніша ніж у LlamaIndex
- ✔️ Кращий для conversational пам'яті через довгі розмови
Мінуси:
- ⚠️ Крутіша крива навчання — більше концепцій для простого RAG
- ⚠️ Для чистого document retrieval — більше коду ніж у LlamaIndex
- ⚠️ Часті breaking changes: оригінальний LangChain замінено LangGraph
для продакшн-сценаріїв
Коли вибирати: AI-агент який викликає зовнішні API,
пише код, виконує SQL-запити — і при цьому ще й шукає по документах.
RAG тут не головна задача, а один із інструментів агента.
Ручний підхід — максимальний контроль без фреймворку
Ollama REST API напряму + будь-яка vector DB + власна логіка.
Саме цей підхід лежить в основі Spring AI для Java, де фреймворк
надає готові абстракції але ти контролюєш кожну деталь пайплайну.
Плюси:
- ✔️ Повний контроль над кожним кроком — chunking, scoring,
fallback логіка
- ✔️ Немає залежності від Python — будь-яка мова через REST API
- ✔️ Немає breaking changes від зовнішнього фреймворку
- ✔️ Можна реалізувати кастомну логіку яку фреймворки не підтримують
Мінуси:
- ⚠️ Більше коду — chunking, batch індексацію, fallback пишеш сам
- ⚠️ Немає вбудованого re-ranking і hybrid search
- ⚠️ Більше часу на реалізацію ніж з LlamaIndex
Коли вибирати: Java/Spring Boot проєкт (Spring AI),
нестандартні вимоги до пайплайну, або коли фреймворк додає більше
складності ніж вирішує проблем.
Матриця вибору
| Задача |
Рекомендація |
| Відповіді по PDF і документах — простий старт |
LlamaIndex |
| Семантичний пошук по блогу або базі знань |
LlamaIndex |
| Внутрішня документація компанії, FAQ |
LlamaIndex |
| AI-агент що викликає API і інструменти |
LangChain / LangGraph |
| Складний multi-step workflow з пам'яттю |
LangChain / LangGraph |
| Java / Spring Boot проєкт |
Spring AI (ручний підхід) |
| Кастомна логіка fallback і дедуплікації |
Ручний підхід |
| Швидкий прототип будь-якою мовою |
Прямий Ollama REST API |
Чи можна комбінувати
Так — і це поширена практика в продакшні.
Contabo описує типовий стек 2026 року:
LlamaIndex як knowledge layer (індексація і пошук),
LangChain як orchestration layer (агенти і workflow),
і n8n або власний сервіс як workflow engine.
Але для більшості проєктів один фреймворк краще ніж два —
менше залежностей, простіша підтримка.
Висновок розділу: Якщо задача — «відповідати по документах»,
починай з LlamaIndex. Якщо будуєш складного AI-агента де RAG один
із інструментів — LangChain. Якщо на Java або потрібен повний контроль —
Spring AI або прямий REST API до Ollama.
🎯 Вибір моделі для ембедингів: nomic-embed-text і альтернативи
Коротка відповідь:
nomic-embed-text — стандартний вибір для Ollama: легка (274 МБ),
швидка, вектор 768 вимірів. Але є важливе обмеження: погано працює
з короткими запитами і нелатинськими мовами. Для українського тексту
і коротких запитів — потрібен fallback або інша модель.
Ембединг-модель — це фундамент RAG. Погана ембединг-модель зробить
весь пайплайн непотрібним незалежно від якості LLM.
Я дізнався про це не з документації — а коли запит «LLM»
повертав нерелевантну статтю зі score 0.63,
а правильна не потрапляла навіть в топ-5.
Що таке ембединги — аналогія з фотографією
Уяви що ти зберігаєш фотографії в різних форматах.
Маленьке фото 100×100 пікселів — займає мало місця, але деталі розмиті,
схожі обличчя важко розрізнити. Фото 4000×3000 — займає набагато більше,
але кожна деталь чітка і унікальна.
З ембедингами — та сама логіка. Вектор — це «фотографія» тексту в числовому просторі.
Розмірність вектора (384, 768, 1024) — це як роздільна здатність.
Більший вектор кодує більше деталей і нюансів смислу — і пошук точніший.
Але більший вектор займає більше місця в базі і повільніше генерується.
all-minilm з вектором 384 — це JPEG 100×100: легкий і швидкий,
але губить деталі. nomic-embed-text з вектором 768 — JPEG 1920×1080:
хороший баланс. mxbai-embed-large з вектором 1024 — RAW-формат:
максимальна деталізація, але коштує вдвічі дорожче по RAM.
І ще одна важлива деталь по аналогії з фото: якщо ти зберіг всі фотографії
в одному форматі — не можна порівнювати їх з фотографіями в іншому форматі.
Так само з ембедингами: якою моделлю індексував — тією
ж моделлю потрібно ембедити запит.
Поміняєш модель — переіндексуй всю базу заново.
Порівняння ембединг-моделей для Ollama
| Модель |
Розмір |
Вектор |
Контекст |
Коли підходить |
Команда |
| nomic-embed-text |
274 МБ |
768 |
8192 токени |
Стандартний вибір, довгі документи |
ollama pull nomic-embed-text |
| mxbai-embed-large |
669 МБ |
1024 |
512 токени |
Висока точність пошуку, короткі чанки |
ollama pull mxbai-embed-large |
| all-minilm |
46 МБ |
384 |
256 токени |
Слабке залізо, швидкість важливіша за точність |
ollama pull all-minilm |
Реальні обмеження nomic-embed-text
З реального досвіду при індексації 500+ статей на чотирьох мовах —
nomic-embed-text має конкретні слабкі місця яких немає в офіційній документації:
- ⚠️ Короткі запити (1-2 слова) — запит «LLM» повертав
score 0.63 для нерелевантної статті, а правильна не потрапляла
навіть в топ-5. Векторний простір моделі не вміє добре розрізняти
короткі однослівні запити
- ⚠️ Нелатинські мови — українська і кирилиця загалом
дають нижчу якість порівняно з англійським текстом.
Запит «LLM vs RAG у 2026» знаходить правильно (score 0.69),
але короткий україномовний запит — вже проблема
- ⚠️ Змішаний контент — якщо документи на кількох мовах
одночасно, якість пошуку падає: ембединги англійського
і українського тексту живуть у різних «зонах» векторного простору
Практичний висновок: коли достатньо nomic-embed-text
nomic-embed-text підходить добре якщо:
- ✔️ Документи переважно англійською
- ✔️ Запити 3+ слів (семантичний пошук)
- ✔️ Потрібен великий контекст — 8192 токени достатньо для довгих статей
Потребує fallback або заміни якщо:
- ⚠️ Документи переважно нелатинськими мовами
- ⚠️ Короткі пошукові запити (1-2 слова) — додай keyword fallback
- ⚠️ Потрібна максимальна точність — розглянь mxbai-embed-large
Висновок розділу: Вибір ембединг-моделі — це компроміс
між точністю, розміром і швидкістю — як вибір формату і роздільної здатності фото.
nomic-embed-text — правильний старт. Але знай його обмеження
і завжди тестуй на реальних запитах своєї аудиторії
перш ніж вважати що пошук «працює».
🎯 Ключові рішення при реалізації RAG — і чого уникати
Коротка відповідь:
Універсального working коду для RAG не існує — кожен проєкт має свою
архітектуру, свою мову, свою модель зберігання і свої вимоги до пошуку.
Але є кілька рішень які потрібно прийняти в будь-якому RAG-проєкті —
і типові пастки в кожному з них.
Туторіали показують як запустити RAG за 10 хвилин.
Продакшн показує що більшість цього часу ти витратиш
на вирішення проблем яких у туторіалі немає.
Рішення 1 — Як читати і очищати документи
Незалежно від мови і фреймворку — на вході є «брудний» текст.
PDF містить колонтитули, номери сторінок, артефакти форматування.
HTML містить теги, атрибути, JavaScript, рекламні блоки.
Весь цей шум потрапляє в ембединги і знижує якість пошуку.
Що важливо вирішити заздалегідь:
- ✔️ Який формат є основним і як його чистити
- ✔️ Чи потрібно зберігати структуру (заголовки, секції)
або достатньо plain text
- ✔️ Що робити з порожніми або дуже короткими документами
Типова пастка: ігнорувати очищення на старті — і отримати
ембединги де 30% контенту це «Copyright 2024» і «Сторінка 1 з 15».
Рішення 2 — Розмір чанка і стратегія розбиття
Немає правильного розміру чанка для всіх випадків.
512 токенів — стандарт для статей. 256 — для технічної документації
де кожен абзац незалежний. 1024 — якщо важливо зберегти контекст
між абзацами.
Що важливо вирішити:
- ✔️ Розбивати по токенах або по реченнях — різний результат
на межах чанків
- ✔️ Який overlap потрібен — 0 якщо чанки незалежні,
50-100 токенів якщо думки переходять між абзацами
- ✔️ Чи потрібно зберігати метадані в кожному чанку —
title, url, category, locale — для фільтрації пізніше
Типова пастка: встановити розмір чанка один раз і більше не перевіряти.
Перевір реальні чанки після розбиття — і ти побачиш де думки обриваються.
Рішення 3 — Ідентифікатори чанків
Здається дрібницею — але стає критичним при переіндексації.
Якщо використовувати рандомні UUID при кожній індексації —
база накопичує дублікати. Через місяць один документ
може бути представлений 10 версіями одних і тих самих чанків.
Правильне рішення — детермінований ID на основі document_id і порядкового номера чанка.
При переіндексації той самий чанк отримує той самий ID і перезаписується,
а не дублюється. Як це реалізувати — в Java через
UUID.nameUUIDFromBytes(), в Python через hashlib.md5().
Конкретна реалізація залежить від стеку — принцип однаковий.
Рішення 4 — Стратегія оновлення індексу
Як визначати що потрібно переіндексувати?
Два підходи:
- ✔️ Timestamp-based: зберігати
indexed_at
і переіндексовувати де updated_at > indexed_at.
Просто і надійно — але є підводний камінь:
якщо @PreUpdate або тригер оновлює updated_at
при saveAll() — і indexed_at стає застарілим
відразу після запису. Рішення: встановлювати indexed_at
трохи у майбутнє — now() + 10 секунд.
- ✔️ Hash-based: зберігати хеш контенту і переіндексовувати
тільки якщо хеш змінився. Точніше, але складніше в реалізації.
Рішення 5 — Batch індексація
Спроба проіндексувати 500 документів одним запитом закінчується
або таймаутом Ollama або OOM. Batch по 10-20 документів —
і при падінні продовжуєш з місця зупинки,
а не з початку. Саме тому indexed_at важливіший
ніж здається — він дозволяє шедулеру підхопити звідки впав.
Рішення 6 — LAZY loading і транзакції
Якщо використовуєш ORM (JPA, SQLAlchemy, ActiveRecord) —
будь обережний з lazy-loaded зв'язками під час індексації.
Доступ до post.getTranslations() поза транзакцією
дає LazyInitializationException в JPA або
DetachedInstanceError в SQLAlchemy.
Рішення: JOIN FETCH у запиті або явне завантаження
всього необхідного всередині транзакції.
Чого уникати — коротко
- ❌ Не починай з «робочого прикладу з туторіалу» напряму в продакшн —
там немає fallback, немає batch, немає ідемпотентності
- ❌ Не ігноруй очищення тексту перед індексацією
- ❌ Не використовуй рандомні UUID для чанків якщо плануєш переіндексацію
- ❌ Не встановлюй threshold раз і назавжди — логуй score
і коригуй на реальних запитах
- ❌ Не вважай що vector search замінить keyword search повністю —
для коротких запитів потрібен fallback
- ❌ Не забувай що зміна ембединг-моделі вимагає повної переіндексації
Висновок розділу: RAG — це не «скопіював і запустив».
Це шість архітектурних рішень кожне з якими має свої пастки.
Прийми їх свідомо до початку реалізації — і заощадиш
від кількох годин до кількох днів дебагу.
🎯 Продакшн патерни: що не пишуть в туторіалах
Коротка відповідь:
Різниця між «hello world» RAG і продакшн RAG — чотири патерни:
fallback на keyword search, ідемпотентна індексація,
дедуплікація результатів і batch обробка.
Жоден з них не згадується в базових туторіалах.
Кожен з них ти відкриваєш через реальну проблему.
Я відкрив всі чотири патерни не через документацію —
а через конкретні інциденти в продакшні.
Fallback — коли запит «Spring» повертав нерелевантні результати.
Ідемпотентність — коли після місяця роботи шедулера
одна стаття була в базі 47 разів.
Дедуплікація — коли топ-5 результатів містив 4 чанки з однієї статті.
Batch — коли Ollama зависала на 500 документах.
Патерн 1 — Fallback: vector → keyword
Vector search побудований на семантичній схожості.
Він добре знаходить «веб-розробка під ключ» за запитом «як зробити сайт».
Але на запиті «Spring» або «RAG» — схожість розмивається по всьому
векторному просторі і точність падає.
Правило яке працює на практиці: запити довжиною 1-2 слова
направляти на keyword search, 3+ слів — на vector search.
Якщо vector search повернув порожній результат — fallback на keyword.
Якщо vector search впав з помилкою — fallback на keyword.
Користувач не помічає різниці, але завжди отримує відповідь.
Реалізація залежить від стеку — але принцип незмінний:
два провайдери, фасад який вирішує який використати,
і автоматичний перехід між ними.
Патерн 2 — Ідемпотентна індексація
Шедулер запускає індексацію щоночі.
Якщо при кожному запуску генерувати рандомний UUID для чанків —
через місяць та сама стаття представлена 30 версіями одних і тих самих чанків.
Пошук повертає дублікати, якість падає, база росте без причини.
Рішення: детермінований ID на основі ідентифікатора документа
і порядкового номера чанка. При переіндексації той самий чанк
отримує той самий ID і перезаписується — а не додається як новий запис.
Конкретна реалізація залежить від мови — в Java це
UUID.nameUUIDFromBytes(),
в Python — hashlib.md5() через uuid.UUID(bytes=...).
Принцип однаковий.
Патерн 3 — Дедуплікація результатів
Vector search повертає топ-N чанків — не топ-N документів.
Якщо одна стаття добре відповідає запиту — вона може дати
4 чанки з 5 у топ-5. Користувач отримає чотири посилання
на одну й ту саму сторінку.
Рішення: після отримання результатів — дедуплікація по document_id.
Для кожного документа залишаєш тільки чанк з найвищим score.
Результат: топ-5 завжди містить 5 різних документів,
а не 5 фрагментів одного.
Важливо: метадані кожного чанку мають містити document_id —
без цього дедуплікація неможлива. Продумай структуру метаданих
до початку індексації.
Патерн 4 — Batch індексація з відновленням
Ollama обробляє ембединги послідовно через CPU або GPU.
Запит на 500 документів одночасно — це або таймаут,
або OOM, або просто зависання без помилки.
Рішення: batch по 10-20 документів. Але важливіше інше —
після кожного batch одразу зберігати indexed_at
для оброблених документів. Якщо процес впав на batch 7 з 50 —
при наступному запуску шедулер підхопить з batch 8,
а не почне спочатку. Це і є різниця між «запустив і сподіваюся»
і надійною індексацією.
Висновок розділу: Чотири патерни — fallback,
ідемпотентність, дедуплікація і batch з відновленням —
це різниця між прототипом який працює на демо
і системою яка працює в продакшні місяцями.
Реалізація в кожному стеку своя — принципи однакові для всіх.
🎯 Типові проблеми: чанкінг, threshold, галюцинації
Три класи проблем у RAG — і всі три виявляються тільки на реальних запитах,
не на синтетичних тестах. Неправильний чанкінг: модель отримує фрагменти
без контексту. Неправильний threshold: або забагато шуму або нічого не знаходить.
Галюцинації: модель вигадує навіть маючи контекст — через слабкий промпт
або нерелевантні чанки що все ж потрапили в топ.
Найважливіше що я зрозумів: більшість проблем RAG
невидимі без логування score. Якщо ти не бачиш
яке значення схожості повертає кожен результат —
ти не можеш діагностувати чому пошук дає погані відповіді.
Додай логування score одразу — і зекономиш години дебагу.
Проблема 1 — Чанкінг: як зрозуміти що щось не так
Симптом: модель дає відповіді які здаються частково правильними —
є якийсь зв'язок з питанням, але деталі неправильні або обрізані.
Причина: чанки розбиті в невдалих місцях і думка обривається на межі фрагменту.
Діагностика: виведи кілька реальних чанків після розбиття і прочитай їх.
Якщо чанк починається або закінчується посередині речення — розмір або стратегія розбиття неправильні.
- ✔️ Короткі документи, статті блогу — 512 токенів, overlap 50
- ✔️ Технічна документація з довгими секціями — 1024 токени, overlap 100
- ✔️ Код — 256-512 токенів, розбивай по функціях а не по рядках
- ✔️ Overlap — не економ: 50-100 токенів перекриття гарантують
що думка не загубиться на межі чанків
Проблема 2 — Similarity threshold: як знайти правильне значення
Симптом А: модель відповідає впевнено але неправильно —
threshold занизький, нерелевантні чанки потрапляють в контекст.
Симптом Б: модель постійно каже «не знайдено» навіть на очевидних питаннях —
threshold зависокий, нічого не проходить фільтр.
Діагностика: логуй score кожного результату — і дивись на розподіл.
Якщо правильні результати мають score 0.55-0.65 — threshold 0.7 відрізає їх всіх.
Якщо нерелевантні результати мають score 0.55 — threshold 0.5 пропускає шум.
- ✔️ Початкова точка: 0.5 — і коригуй по логах реальних запитів
- ✔️ Короткі запити (1-2 слова) — знижуй до 0.4 або краще використовуй fallback
- ✔️ Довгі семантичні запити — 0.65-0.7 дає чистіший результат
- ✔️ Якщо всі результати нижче порогу — повертай «не знайдено»
замість порожнього контексту
Проблема 3 — Галюцинації навіть з контекстом
Симптом: модель дає впевнену відповідь яка не відповідає жодному
з наданих чанків — або комбінує факти з різних джерел неправильно.
Причина найчастіше не в моделі — а в промпті або в тому
що нерелевантний контекст все ж потрапив в топ.
Три рівні захисту:
- ✔️ Жорсткий системний промпт — явна заборона вигадувати:
«Відповідай ТІЛЬКИ на основі наданого контексту.
Якщо відповіді немає — скажи про це чесно.
Не комбінуй інформацію з різних джерел якщо вони суперечать одне одному»
- ✔️ Score-based фільтр — якщо максимальний score
серед результатів нижче 0.5, не передавай контекст в LLM взагалі.
Повертай «не знайдено» — краще чесна відповідь ніж впевнена галюцинація
- ✔️ Показуй джерела — список статей або документів
звідки взята відповідь. Користувач сам перевірить
і повідомить якщо щось не так
Висновок розділу: Всі три проблеми діагностуються
однаково — через логування score і читання реальних чанків
які потрапляють в контекст. Додай це з першого дня —
і більшість питань «чому RAG дає погані відповіді»
отримають очевидну відповідь.
❓ Часті питання (FAQ)
Чи потрібен GPU для RAG з Ollama?
Ні. GPU прискорює генерацію відповідей і ембединги, але не є обов'язковим.
На CPU ембединги через nomic-embed-text генеруються повільніше,
але цілком реально для продакшну з помірним навантаженням.
На Apple Silicon (M1/M2/M3) — швидко навіть без окремого GPU.
Детальніше які моделі реально працюють на обмеженому залізі —
Ollama на 8 ГБ RAM: які моделі працюють у 2026.
Скільки документів можна проіндексувати?
Обмежень немає — залежить від розміру vector store і RAM.
pgvector справляється з мільйонами векторів без проблем.
На практиці для блогу з 500 статей — це ~2500 чанків по 512 токенів,
займає кілька МБ в PostgreSQL. Генерація ембедингів через Ollama
на такому обсязі займає 15-30 хвилин на CPU — прийнятно для нічного шедулера.
RAG чи звичайний повнотекстовий пошук?
Не «або», а «і». RAG краще на семантичних запитах з 3+ слів де важливий зміст —
«як оптимізувати запити в PostgreSQL» знайде статтю про індекси
навіть без точного збігу слів. Повнотекстовий пошук краще на коротких запитах
(1-2 слова), іменах і точних збігах. Оптимальна стратегія —
vector search з автоматичним fallback на FTS.
Як оновлювати індекс при зміні документів?
Зберігай indexed_at для кожного документа і запускай
переіндексацію де updated_at > indexed_at.
Детермінований ID через hash дозволяє перезаписувати чанки без дублікатів.
Важливий нюанс: якщо ORM оновлює updated_at автоматично
при saveAll() — встановлюй indexed_at
трохи у майбутнє щоб шедулер не переіндексовував все заново щоразу.
Яку модель Ollama вибрати для RAG?
Для ембедингів — nomic-embed-text як стандарт, mxbai-embed-large
якщо потрібна вища точність. Для генерації відповідей — llama3.3:8b
на 8 ГБ RAM або qwen2.5:14b якщо є 16 ГБ.
Повне порівняння моделей з бенчмарками і рекомендаціями по задачах —
Яку модель Ollama вибрати у 2026: порівняння Llama, Qwen, DeepSeek і Mistral.
Чи є Java-рішення для RAG з Ollama?
Так — Spring AI з нативною підтримкою Ollama і pgvector.
Реальний кейс реалізації RAG для блогу на Spring AI з конкретними
помилками і рішеннями яких немає в документації —
у статті Як я будував RAG для webscraft.org:
Spring AI + pgvector + реальний досвід.
З чого почати якщо ніколи не працював з Ollama?
Спочатку розберися з самою платформою — що таке Ollama,
як вона влаштована і які задачі вирішує.
Що таке Ollama і навіщо запускати AI локально у 2026 —
огляд без жаргону з якого зручно починати.
Після цього RAG стає логічним наступним кроком.
✅ Висновки
RAG — одна з тих технологій яка виглядає складно до першої реалізації
і очевидно після. Пайплайн простий концептуально: документ → чанки →
ембединги → пошук → відповідь. Складність починається в деталях —
і більшість цих деталей ти знайдеш не в документації,
а через реальні інциденти в продакшні.
Те що я виніс з реалізації для блогу :
- ✔️ Починай з nomic-embed-text — але одразу логуй score
на реальних запитах. Для нелатинських мов і коротких запитів
потрібен fallback — дізнаєшся про це не з документації
- ✔️ Детермінований UUID для чанків — не рандомний.
Без цього переіндексація множить дублікати і ти не одразу зрозумієш чому
- ✔️ Fallback на keyword search — vector search
не замінює звичайний пошук, він доповнює його
- ✔️ Batch по 10-20 документів з збереженням indexed_at —
щоб при падінні продовжувати з місця зупинки, а не з початку
- ✔️ Жорсткий системний промпт — без явної заборони
вигадувати модель буде галюцинувати. Це не баг конкретної моделі
З Ollama весь стек безкоштовний і працює локально —
жодного документа не йде в хмару. Для блогу, внутрішньої документації
або корпоративної бази знань це і є правильна архітектура.
Якщо працюєш на Java і Spring Boot — наступна стаття саме про це.
Реальний кейс реалізації RAG для webscraft.org на Spring AI + pgvector:
які помилки я зробив, які підводні камені знайшов і що б зробив інакше —
Як я будував RAG для webscraft.org: Spring AI + pgvector + реальний досвід.
Якщо ще не знайомий з Ollama —
почни з огляду Ollama 2026.
📎 Джерела
- LlamaIndex: Introduction to RAG — офіційна документація
- Real Python: LlamaIndex in Python — A RAG Guide — практичний туторіал
- Medium: LlamaIndex for Beginners 2025 — від нуля до продакшну
- DEV Community: RAG з LlamaIndex, ChromaDB і Ollama
- Prem AI: LangChain vs LlamaIndex 2026 — Production RAG Comparison
- Contabo: LlamaIndex vs LangChain — Which One to Choose in 2026
- Ollama: nomic-embed-text — характеристики ембединг моделі
- Ollama: mxbai-embed-large — альтернативна ембединг модель
Ключові слова:
RAGRetrieval-Augmented Generationкраулинг AIGoogle AI OverviewsPerplexitySEO під RAG