RAG з Ollama: як навчити AI відповідати по твоїх документах — від пайплайну до продакшну

Оновлено:
RAG з Ollama: як навчити AI відповідати по твоїх документах — від пайплайну до продакшну

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.

RAG з Ollama: як навчити AI відповідати по твоїх документах — від пайплайну до продакшну

🎯 Вибір моделі для ембедингів: 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.

📎 Джерела

  1. LlamaIndex: Introduction to RAG — офіційна документація
  2. Real Python: LlamaIndex in Python — A RAG Guide — практичний туторіал
  3. Medium: LlamaIndex for Beginners 2025 — від нуля до продакшну
  4. DEV Community: RAG з LlamaIndex, ChromaDB і Ollama
  5. Prem AI: LangChain vs LlamaIndex 2026 — Production RAG Comparison
  6. Contabo: LlamaIndex vs LangChain — Which One to Choose in 2026
  7. Ollama: nomic-embed-text — характеристики ембединг моделі
  8. Ollama: mxbai-embed-large — альтернативна ембединг модель

Останні статті

Читайте більше цікавих матеріалів

Spring AI + pgvector: 6 помилок які я зробив будуючи RAG для блогу

Spring AI + pgvector: 6 помилок які я зробив будуючи RAG для блогу

Перша година після підключення Spring AI — і застосунок не стартує. NoUniqueBeanDefinitionException: expected single matching bean but found 2: ollamaChatModel, openAiChatModel. Гугл каже додати spring.ai.openai.chat.enabled=false. Не працює. Документація мовчить. Це була тільки...

RAG з Ollama: як навчити AI відповідати по твоїх документах — від пайплайну до продакшну

RAG з Ollama: як навчити AI відповідати по твоїх документах — від пайплайну до продакшну

RAG з Ollama: навчи AI відповідати по твоїх документах У тебе є документи — PDF, статті, нотатки, база знань. Ти хочеш задавати питання і отримувати відповіді саме по цих документах, а не по загальних знаннях моделі. І все це — локально, без відправки даних у хмару....

Comet проти Safari та Chrome: чи варто переходити на AI-браузер у 2026

Comet проти Safari та Chrome: чи варто переходити на AI-браузер у 2026

Щороку з'являються десятки нових браузерів — і майже всі зникають непомітно. Але Comet від Perplexity — інший випадок. Це не чергова косметична надбудова над Chrome. Це спроба переосмислити саму роль браузера у твоєму житті. Спойлер: Comet не замінить Safari чи Chrome для...

Браузер Comet від Perplexity вийшов на iOS

Браузер Comet від Perplexity вийшов на iOS

Ми звикли до того, що браузер — це просто вікно в інтернет. Ти відкриваєш сторінку, читаєш, закриваєш. Але що, якщо браузер сам читає сторінку за тебе, знаходить потрібне і виконує завдання? Саме таку ідею просуває Perplexity зі своїм новим браузером Comet, який 18...

Контекстне вікно LLM: чому AI забуває і скільки це коштує

Контекстне вікно LLM: чому AI забуває і скільки це коштує

Ти коли-небудь помічав, що ChatGPT або Claude на початку розмови пам'ятає все ідеально, а через годину починає плутати деталі або перепитувати те, що ти вже пояснював? Це не баг — це фундаментальне обмеження, яке визначає, скільки AI може "тримати в голові" одночасно. Називається воно...

Ollama на 8 ГБ RAM: які моделі працюють у 2026

Ollama на 8 ГБ RAM: які моделі працюють у 2026

Маєш ноутбук з 8 ГБ оперативної пам'яті і хочеш запустити AI локально? Ця стаття — розбір: що працює, що ледь тягне, а що навіть не варто завантажувати. Без ілюзій, з конкретними моделями та командами для кожної задачі. Якщо ще не знайомий з Ollama — почни з вступної статті про те, що таке...