Створення RAG з нуля
Моя перша версія ШІ сказала мені, що я продаю гідравлічний екскаватор. Я не продаю екскаватори. Вона з повною впевненістю надала мені вигадану ціну та вигаданий опис.
Це був той момент, коли я перестав довіряти лише промптам. Я перебудував систему з одним правилом: вона відповідає лише на основі каталогу, або не відповідає взагалі.
Ось як я побудував надійну систему RAG (Retrieval-Augmented Generation), використовуючи Postgres та Python.
Підготовка даних Більшість туторіалів пропускають найважчу частину: очищення даних. Я розділив свій процес на два етапи:
- Етап 1: Завантаження HTML-файлів на диск. Я зберігаю метадані у вигляді коментаря на початку кожного файлу. Це робить процес ідемпотентним. Якщо файл уже існує, я його пропускаю.
- Етап 2: Офлайн-парсинг цих файлів. Це перетворює HTML на чистий каталог у форматі JSON.
Після парсингу я перевіряю наповненість полів. Якщо таке поле, як вага або ціна, порожнє, я дізнаюся про це негайно. Справжня робота полягає саме в отриманні чистих даних.
Частина з ШІ
Я перетворюю кожен продукт на блок тексту та конвертую його у вектор за допомогою моделі bge-m3. Ці вектори я зберігаю в Postgres, використовуючи розширення pgvector.
Для пошуку продуктів я використовую гібридний підхід:
- Семантичний пошук: використовує вектори для пошуку продуктів, що відповідають змісту вашого запиту.
- Структуровані фільтри: я використовую LLM, щоб перетворити запит на кшталт "Siemens motors under €2000" на JSON. Це дозволяє мені виконувати SQL-запит із точними фільтрами за брендом та ціною.
Один SQL-запит обробляє як нечіткий пошук, так і жорсткі фільтри. Це дозволяє тримати все в синхронізації.
Запобіжники Хороша RAG-система має знати, коли варто промовчати. Я використовую два рівні захисту, щоб запобігти галюцинаціям:
- Поріг схожості: кожен результат отримання має певний бал. Якщо бал нижчий за встановлений ліміт, я відкидаю ці результати. Якщо жоден результат не проходить перевірку, система каже "не знайдено", навіть не звертаючись до LLM. Модель не зможе галюцинувати, якщо вона взагалі не бачить даних.
- Суворий системний промпт: я вказую моделі відповідати лише на основі наданих продуктів. Якщо продукти не мають відношення до запиту, вона має відмовити.
Поріг робить погану поведінку неможливою. Промпт лише просить про хорошу поведінку. Використовуйте обидва методи.
Підсумок
- Збирайте ретельно.
- Очищуйте чесно.
- Векторизуйте просто.
- Відмовляйте за дизайном.
Саме здатність відмовити робить систему надійною. Довіра базується на архітектурі, а не на проханні до моделі бути «ввічливою».
Побудова RAG з нуля: Збір, Очищення, Ембединг, Пошук
RAG (Retrieval-Augmented Generation) — це техніка, яка дозволяє великим мовним моделям (LLM) отримувати доступ до зовнішніх даних, що не входили до їхнього початкового набору навчання. Це вирішує проблему галюцинацій та застарілої інформації, надаючи моделі контекст із ваших власних документів.
Побудова системи RAG можна розділити на чотири основні етапи: Збір (Collect), Очищення (Clean), Ембединг (Embed) та Пошук (Retrieve).
1. Збір (Collect)
Перший крок — це збір даних, які ви хочете використовувати як знання для вашої моделі. Джерела можуть бути різними:
- Документи: PDF, Word (.docx), текстові файли (.txt).
- Веб-сторінки: HTML-файли або дані, отримані через веб-скрапінг.
- Бази даних: SQL або NoSQL бази даних.
- API: Дані, отримані з зовнішніх сервісів.
Головне завдання на цьому етапі — зібрати якомога більше релевантної інформації у форматі, який можна обробити.
2. Очищення (Clean)
Сирі дані зазвичай "брудні". Вони можуть містити зайвий шум, який заважатиме моделі правильно розуміти контекст. Очищення включає:
- Видалення зайвих символів: HTML-тегів, спеціальних символів, зайвих пробілів або невидимих знаків.
- Видалення дублікатів: Щоб уникнути повторення однієї й тієї ж інформації.
- Форматування: Приведення тексту до єдиного стандарту (наприклад, видалення заголовків і підписів, які не несуть змісту).
Чисті дані = кращі результати. Якщо ви подасте "сміття" на етапі ембедингу, модель отримає "сміття" на виході.
3. Ембединг (Embed)
Після того, як дані очищені, їх потрібно перетворити на формат, який розуміє комп'ютер — на числа. Це робиться за допомогою процесу, який називається ембедингом.
Процес виглядає так:
- Розбиття на фрагменти (Chunking): Оскільки LLM мають обмеження на обсяг тексту (context window), довгі документи потрібно розбити на менші частини — чанки (chunks).
- Векторизація: Кожен чанк пропускається через модель ембедингів (наприклад, від OpenAI або HuggingFace), яка перетворює текст на вектор (довгий список чисел). Ці вектори представляють семантичне значення тексту.
Ці вектори потім зберігаються у спеціальній векторній базі даних (Vector Database), такій як Pinecone, Chroma або FAISS.
4. Пошук (Retrieve)
Це фінальний етап, який відбувається під час запиту користувача. Коли користувач ставить запитання:
- Запит перетворюється на вектор: Запитання користувача проходить через ту саму модель ембедингів, що й ваші документи.
- Семантичний пошук: Система порівнює вектор запиту з векторами у вашій базі даних. Вона шукає ті чанки, чиї вектори є найбільш схожими на вектор запиту (зазвичай за допомогою косинусної схожості).
- Передача контексту: Найбільш релевантні фрагменти тексту передаються разом із запитом користувача до LLM.
Тепер LLM отримує не просто запитання, а запитання + фактичну інформацію з ваших документів, що дозволяє їй надати точну та обґрунтовану відповідь.