Усі уроки · 44
Шлях 3 · Дорожня карта AI-інженера·Урок 16 з 33

Способи реалізації RAG

SDK напряму, LangChain, LlamaIndex, Haystack або повністю вручну: компроміси між контролем, зручністю та прив’язкою до інструмента — і мінімальний RAG у ~30 рядків на Python без API-ключів.

РівеньСереднійЧас~15 хв

Навіщо це вам, навіть якщо ви ніколи не напишете такий код

Раз на кілька тижнів хтось питає мене: «LangChain чи LlamaIndex?» — ніби один із них і є правильною відповіддю. Обирати між ними варто за тим, що кожен варіант вирішує за вас: будь-яке рішення, яке бібліотека ухвалює від вашого імені, вам потім доведеться або терпіти, або скасовувати.

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

Кілька термінів, які трапляться далі: векторна БД — база даних, яка зберігає ембеддинги (списки чисел, що передають зміст) і знаходить найближчі до вашого запитання (як це працює). Фреймворк — це набір готових «кубиків»: завантажувачі, роздільники, пошукові компоненти (retrievers). Це зручно, але фреймворк нав’язує свої рішення.

П’ять способів побудувати той самий пайплайн

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

  • Повністю вручну — API ембеддингів або локальна модель, numpy, ваші власні рядки промптів. Повний контроль і повна відповідальність.
  • SDK напряму — SDK провайдера моделі плюс клієнт векторної БД. Стандартний вибір для реальної експлуатації: мінімум магії, легко зрозуміти, що відбувається.
  • LangChain — універсальний фреймворк для LLM. Величезна екосистема, безліч інтеграцій, шари абстракції на кілька рівнів углиб.
  • LlamaIndex — фреймворк, націлений саме на завантаження даних і пошук по них. Часто найшвидший шлях до доброго прототипу RAG.
  • Haystack — фреймворк компанії deepset, збудований навколо пайплайнів, зі строгою типізацією. Популярний у робочих системах пошуку та відповідей на запитання.

Кожен варіант реалізує одні й ті самі шість кроків. Різниця — у тому, скільки всього він ховає, скільки вирішує за вас і наскільки важко від нього піти:

Шлях Що ви пишете Що він ховає Складність відходу Обирайте, коли
Повністю вручну виклики ембеддингів, сортування, рядки промптів нічого немає — усе ваше ви вчитеся або пайплайн невеликий і нестандартний
SDK напряму + векторна БД кілька сотень рядків вашого власного сполучного коду нічого немає ви випускаєте вузькоспеціалізований продукт
LangChain ланцюжки з готових компонентів розбиття, пошук, складання промпту середня — її типи розповзаються вашим кодом вам потрібна широта інтеграцій
LlamaIndex індекси та пошукові рушії (query engines) завантаження даних, розбиття, типові налаштування пошуку середня потрібен «чат із цими 500 PDF» — цього тижня
Haystack типізовані компоненти пайплайна з’єднання компонентів, частину пошуку середня платформа пошуку та відповідей на запитання, над якою працює команда

Ці варіанти відрізняються обсягом рішень, які за вас уже хтось ухвалив. Тому запитати варто так: які з цих готових рішень я згоден успадкувати?

Ручний варіант від початку до кінця

Уся ідея, стиснута у два невеликі файли: «іграшкова» модель ембеддингів і «іграшкова» LLM (ключі API не потрібні — важлива механіка), а потім сам цикл RAG:

minimal-rag
1# Крихітні заглушки замість справжньої моделі ембеддингів і LLM.
2# Замініть ці дві функції викликами справжніх API — решта не зміниться.
3import math
4 
5def embed(text):
6 # Справжня модель ембеддингів перетворює текст на довгий список чисел
7 # (сотні або тисячі), у якому закодовано його зміст.
8 # Ми підробляємо її чотирма числами: як часто трапляються слова "refund",
9 # "password" і "plan", плюс довжина тексту.
10 # Для розуміння механіки вистачає; для справжнього пошуку — ні.
11 words = text.lower().split()
12 return [
13 words.count(#98c379">"refund") / (len(words) + 1),
14 words.count(#98c379">"password") / (len(words) + 1),
15 words.count(#98c379">"plan") / (len(words) + 1),
16 len(words) / 100,
17 ]
18 
19def llm(prompt):
20 # Справжня LLM читає промпт і пише відповідь.
21 # Наша лише друкує те, що їй передали, — щоб ви точно побачили,
22 # які фрагменти дійшли до моделі. У цьому вся суть RAG:
23 # керуєте тим, що потрапляє сюди, — керуєте відповіддю.
24 for line in prompt.splitlines():
25 if line.startswith(#98c379">"[") or line.startswith("Q:"):
26 print(line)
27 return #98c379">"Answer grounded in the chunks above."
28 
29def cosine(a, b):
30 # «Косинусна схожість»: наскільки близькі два списки чисел?
31 # 1.0 — майже однакові, 0.0 — жодного зв’язку.
32 # Саме так пошук вирішує, які фрагменти залишаться.
33 dot = sum(x * y for x, y in zip(a, b))
34 na = math.sqrt(sum(x * x for x in a))
35 nb = math.sqrt(sum(x * x for x in b))
36 return dot / (na * nb) if na and nb else 0.0

Читайте дві вкладки по порядку: mocks.py — це актори (підроблена модель ембеддингів і підроблена LLM), rag.py — сама п’єса.

Якщо код — не ваша мова, ось та сама історія простими словами:

  1. Три речення зображають довідковий центр вашої компанії.
  2. Індексація (один раз, офлайн): кожне речення перетворюється на список чисел — ембеддинг, — який приблизно передає його зміст. Ви зберігаєте і речення, і його числа. Це ваш індекс, у якому можна шукати.
  3. Запитання проходить точно таке саме перетворення. Та сама лінійка з обох боків — тому це працює.
  4. Пошук: порівняйте числа запитання з числами кожного фрагмента й залиште три найближчі. Жодного «розуміння» тут немає — лише арифметика над списками чисел.
  5. Складання: вставте ці фрагменти над запитанням — так, ніби ви простягнули людині сторінку нотаток і сказали: «відповідай, користуючись лише цим і нічим іншим».
  6. Генерація: модель читає нотатки й пише відповідь.

Ось і весь RAG: шість ходів, жоден з яких сам по собі не хитрий.

У реальній експлуатації embed і llm стають справжніми викликами API, а відсортований список — запитом до векторної бази, але форма коду не змінюється. У цьому й суть: RAG — звичайний пайплайн, нічого складного в ньому немає. А якість пайплайна визначає крок 4: якщо пошук передасть моделі не ті три фрагменти, відповідь не врятує ні фреймворк, ні модель.

Фреймворки: сильні й слабкі сторони

LangChain дає змогу запуститися за хвилини: завантажувачі документів, роздільники, обгортки над векторними сховищами, ланцюжки. Ціна — глибина абстракції: коли якість пошуку кульгає, ви можете виявити, що налагоджуєте фреймворк замість свого пайплайна. Його сильна сторона — широта: один API поверх десятків провайдерів.

LlamaIndex починає з боку даних. Індекси, пошукові рушії (query engines) і розумні типові налаштування RAG становлять основу продукту. Для завдання «чат із цими 500 PDF» він часто виявляється найшвидшим шляхом. Ціна того самого роду, що й у LangChain: ви приймаєте його абстракції.

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

У всіх трьох є спільна проблема: вони швидко змінюються й при цьому ламають сумісність. Зафіксуйте версію, перед оновленням читайте список змін (changelog) і тримайте власну тонку обгортку навколо тих небагатьох викликів, які ви використовуєте, — тоді несумісна зміна зачепить один файл, а не тридцять.

Прив’язка реальна, але неглибока

Прив’язка до фреймворка в RAG — це здебільшого прив’язка до понять (завантажувачі, пошукові компоненти, ланцюжки). Чужої інфраструктури за ними немає. Усе підключається до тих самих «цеглинок»: API ембеддингів, векторне сховище й API генерації. Під час відходу від фреймворка переписують сполучний код, а дані лишаються на місці.

Як обирати

Правило вибору, яке не застаріло:

  1. Вивчаєте ідею? Зберіть усе вручну один раз, як у прикладі вище. Вам більше ніколи не доведеться гадати, що ховає абстракція пошукового компонента (retriever).
  2. Випускаєте вузькоспеціалізований продукт? SDK напряму плюс клієнт векторної БД. Більшість RAG-систем у реальній експлуатації влаштовано саме так — кілька сотень рядків, які ви повністю розумієте.
  3. Швидко робите прототип на різношерстих документах? LlamaIndex або LangChain. Прийміть їхні абстракції, щоб виграти кілька днів.
  4. Велика платформа пошуку та відповідей на запитання, над якою працює команда? Структура Haystack окупає себе.

Фреймворк завантажує дані, а за якість відповідаєте ви

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

Перевірте себе

Ви переходите на іншу модель ембеддингів. Що потрібно зробити?

Яку частину RAG не робить за вас жоден фреймворк?

Розробник-одинак випускає вузький продукт. Порада цього уроку:

Підсумок

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

Якщо якийсь із шести кроків усе ще здається туманним, пройдіть урок RAG-пайплайн крок за кроком ще раз — це ті самі шість кроків, по одному.