Модуль 03: RAG (Збагачена Пошукова Генерація)

March 2, 2026 · View on GitHub

Зміст

Відеоогляд

Перегляньте цю живу сесію, де пояснюють, як почати з цього модуля:

RAG with LangChain4j - Live Session

Чому ви навчитеся

В попередніх модулях ви навчилися вести розмови з ШІ та ефективно структурувати ваші пропорти. Але існує фундаментальне обмеження: мовні моделі знають лише те, що навчилися під час тренування. Вони не можуть відповідати на питання про політики вашої компанії, документацію проекту чи будь-яку інформацію, яку не тренували.

RAG (Збагачена Пошукова Генерація) вирішує цю проблему. Замість того, щоб намагатися навчити модель вашій інформації (що дорого та непрактично), ви даєте їй змогу шукати у ваших документах. Коли хтось ставить запитання, система знаходить релевантну інформацію і включає її у пропорт. Модель відповідає, базуючись на отриманому контексті.

Уявіть, що RAG дає моделі довідкову бібліотеку. Коли ви ставите запитання, система:

  1. Запит користувача — Ви ставите запитання
  2. Ембеддінг — конвертує ваше запитання у вектор
  3. Пошук по векторах — знаходить схожі фрагменти документів
  4. Збір контексту — додає релевантні фрагменти до пропорта
  5. Відповідь — LLM генерує відповідь на основі контексту

Це закріплює відповіді моделі на ваших реальних даних, замість покладання на знання під час тренування або вигадки відповідей.

Вимоги

  • Пройдено Модуль 00 - Швидкий старт (для прикладу Easy RAG, який буде використаний пізніше в цьому модулі)
  • Пройдено Модуль 01 - Вступ (розгорнуті ресурси Azure OpenAI, включаючи модель ембеддінгу text-embedding-3-small)
  • Файл .env у кореневій директорії з обліковими даними Azure (створений командою azd up у Модулі 01)

Примітка: Якщо ви не пройшли Модуль 01, спочатку дотримуйтесь інструкцій розгортання там. Команда azd up розгортає як модель чат GPT, так і модель ембеддінгу, яку використовує цей модуль.

Розуміння RAG

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

What is RAG

Ця діаграма показує відмінність між стандартною LLM (яка гадує на основі даних тренування) і LLM із підтримкою RAG (яка спершу звертається до ваших документів).

Ось як частини з’єднуються цілком. Запит користувача проходить через чотири етапи — ембеддінг, пошук за векторами, збір контексту та генерація відповіді — кожен будується на попередньому:

RAG Architecture

Ця діаграма показує повний конвеєр RAG — запит користувача проходить через ембеддінг, пошук за векторами, збір контексту та генерацію відповіді.

Решта модуля покроково розглядає кожен етап із кодом, який ви можете запускати та змінювати.

Який підхід RAG використовується в цьому підручнику?

LangChain4j пропонує три способи реалізації RAG, кожен із різним рівнем абстракції. Діаграма нижче порівнює їх поруч:

Three RAG Approaches in LangChain4j

Ця діаграма порівнює три підходи RAG у LangChain4j — Easy, Native та Advanced — показуючи їх ключові компоненти та коли використовувати кожен.

ПідхідЩо він робитьКомпроміс
Easy RAGАвтоматично все підключається через AiServices та ContentRetriever. Ви анотуєте інтерфейс, приєднуєте отримувача, і LangChain4j обробляє ембеддінг, пошук та складання пропорта за лаштунками.Мінімум коду, але ви не бачите, що відбувається на кожному етапі.
Native RAGВи виконуєте виклик моделі ембеддінгу, шукаєте в сховищі, будуєте пропорт і генеруєте відповідь самі — по одному явному кроку за раз.Більше коду, але кожен етап видно і його можна змінювати.
Advanced RAGВикористовує фреймворк RetrievalAugmentor із плагінами для трансформерів запитів, маршрутизаторів, ранжувальників і інжекторів контенту для виробничих конвеєрів.Максимальна гнучкість, але суттєво більша складність.

Цей підручник використовує Native підхід. Кожен крок конвеєра RAG — ембеддінг запиту, пошук у векторному сховищі, збір контексту і генерація відповіді — явно прописаний у RagService.java. Це зроблено навмисне: як навчальний ресурс, важливіше, щоб ви бачили і розуміли кожен етап, ніж мінімізували код. Коли ви освоїтесь із тим, як усе працює, можна перейти до Easy RAG для швидких прототипів або Advanced RAG для виробничих систем.

💡 Уже бачили Easy RAG в дії? Модуль швидкого старту містить приклад Document Q&A (SimpleReaderDemo.java), який використовує Easy RAG — LangChain4j автоматично робить ембеддінг, пошук і формування пропортів. Цей модуль робить крок далі, відкриваючи той конвеєр, щоб ви могли бачити і контролювати кожен етап самі.

Діаграма нижче показує конвеєр Easy RAG з прикладу швидкого старту. Зверніть увагу, як AiServices і EmbeddingStoreContentRetriever приховують всю складність — ви завантажуєте документ, приєднуєте отримувача та отримуєте відповіді. Native підхід у цьому модулі відкриває кожен прихований крок:

Easy RAG Pipeline - LangChain4j

Ця діаграма показує конвеєр Easy RAG з SimpleReaderDemo.java. Порівняйте його з Native підходом у цьому модулі: Easy RAG приховує ембеддінг, отримання і формування пропорта за AiServices і ContentRetriever — ви завантажуєте документ, додаєте отримувача і отримуєте відповіді. Native підхід у цьому модулі відкриває цей конвеєр, щоб ви викликали кожен етап (ембеддінг, пошук, збір контексту, генерація) самі, отримуючи повний огляд і контроль.

Як це працює

Конвеєр RAG у цьому модулі складається з чотирьох етапів, які виконуються послідовно кожного разу, коли користувач ставить питання. Спершу завантажений документ розбирається і розбивається на фрагменти. Ці фрагменти конвертуються у векторні ембеддінги і зберігаються, щоб їх можна було математично порівнювати. Коли приходить запит, система виконує семантичний пошук, щоб знайти найрелевантніші фрагменти, і в кінці передає їх як контекст LLM для генерації відповіді. Нижче кожен етап розглядається детально з прикладами коду та діаграмами. Почнемо з першого кроку.

Обробка документів

DocumentService.java

Коли ви завантажуєте документ, система розбирає його (PDF або простий текст), додає метадані, такі як ім’я файлу, а потім розбиває на фрагменти — менші частини, що зручно містяться у контекстному вікні моделі. Ці фрагменти трохи перекриваються, щоб контекст на межах не губився.

// Проаналізуйте завантажений файл і обгорніть його у документ LangChain4j
Document document = Document.from(content, metadata);

// Розділіть на шматки по 300 токенів з перекриттям у 30 токенів
DocumentSplitter splitter = DocumentSplitters
    .recursive(300, 30);

List<TextSegment> segments = splitter.split(document);

Нижче наведена діаграма, що наочно показує цей процес. Зверніть увагу, що кожен фрагмент має спільні токени з сусідніми — 30-токенне перекриття гарантує, що важливий контекст не буде пропущений між межами:

Document Chunking

Ця діаграма показує розбиття документа на фрагменти по 300 токенів з 30-токенним перекриттям, зберігаючи контекст на межах фрагментів.

🤖 Спробуйте з GitHub Copilot Chat: Відкрийте DocumentService.java і запитайте:

  • "Як LangChain4j розбиває документи на фрагменти і чому перекриття важливе?"
  • "Який оптимальний розмір фрагменту для різних типів документів і чому?"
  • "Як обробляти документи кількома мовами або з особливим форматуванням?"

Створення ембеддінгів

LangChainRagConfig.java

Кожен фрагмент конвертується у числове подання, зване ембеддінгом — фактично конвертер значень у числа. Модель ембеддінгу не є "розумною", як чат-модель; вона не виконує інструкції, не міркує і не відповідає на питання. Вона може лише відобразити текст у математичному просторі, де схожі значення опиняються близько одне до одного — "автомобіль" біля "машини", "політика повернення грошей" біля "поверніть мої гроші". Уявіть чат-модель як людину, з якою можна спілкуватися; а модель ембеддінгу – як дуже хорошу систему архівування.

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

Embedding Model Concept

Ця діаграма показує, як модель ембеддінгу конвертує текст у числові вектори, розміщуючи схожі значення — як "машина" і "автомобіль" — біля одне одного у векторному просторі.

@Bean
public EmbeddingModel embeddingModel() {
    return OpenAiOfficialEmbeddingModel.builder()
        .baseUrl(azureOpenAiEndpoint)
        .apiKey(azureOpenAiKey)
        .modelName(azureEmbeddingDeploymentName)
        .build();
}

EmbeddingStore<TextSegment> embeddingStore = 
    new InMemoryEmbeddingStore<>();

Діаграма класів нижче показує два окремі потоки в конвеєрі RAG і класи LangChain4j, які їх реалізують. Потік загрузки (виконується один раз під час завантаження) розбиває документ, створює ембеддінги фрагментів і зберігає їх через .addAll(). Потік запиту (виконується кожен раз, коли користувач ставить питання) створює ембеддінг запиту, шукає в сховищі через .search() і передає знайдений контекст чат-моделі. Обидва потоки взаємодіють через спільний інтерфейс EmbeddingStore<TextSegment>:

LangChain4j RAG Classes

Ця діаграма показує два потоки в конвеєрі RAG — загрузку і запит — та їх взаємозв’язок через спільний EmbeddingStore.

Після збереження ембеддінгів схожий контент природньо групується у векторному просторі. Візуалізація нижче показує, як документи за суміжними темами опиняються близько один до другого, що робить можливим семантичний пошук:

Vector Embeddings Space

Ця візуалізація показує, як пов’язані документи групуються в 3D векторному просторі, утворюючи окремі кластери за темами, як Технічна документація, Бізнес правила та FAQ.

Коли користувач виконує пошук, система виконує чотири кроки: один раз створює ембеддінги документів, кожного разу ембеддить запит, порівнює вектор запиту з усіма збереженими векторами за косинусною схожістю та повертає топ-K найвищих за балом фрагментів. Діаграма нижче демонструє кожен крок і класи LangChain4j, що беруть участь:

Embedding Search Steps

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

Семантичний пошук

RagService.java

Коли ви ставите запитання, воно також конвертується в ембеддінг. Система порівнює ембеддінг вашого запитання з усіма ембеддінгами фрагментів документів. Знаходить ті, що мають найбільш схожу суть — не лише сумісні ключові слова, а справжню семантичну схожість.

Embedding queryEmbedding = embeddingModel.embed(question).content();

EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder()
    .queryEmbedding(queryEmbedding)
    .maxResults(5)
    .minScore(0.5)
    .build();

EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest);
List<EmbeddingMatch<TextSegment>> matches = searchResult.matches();

for (EmbeddingMatch<TextSegment> match : matches) {
    String relevantText = match.embedded().text();
    double score = match.score();
}

Нижче наведена діаграма, яка порівнює семантичний пошук і традиційний пошук за ключовими словами. Пошук за ключовим словом "транспорт" пропускає фрагмент про "автомобілі та вантажівки", а семантичний пошук розуміє, що це одне й те саме, і повертає його з високим балом:

Semantic Search

Ця діаграма порівнює пошук за ключовими словами і семантичний пошук, показуючи, як семантичний пошук отримує концептуально пов’язаний контент навіть коли точні ключові слова відрізняються. Під капотом схожість вимірюється за допомогою косинусної схожості — по суті питання "чи спрямовані ці дві стрілки в одному напрямку?" Два шматки тексту можуть використовувати абсолютно різні слова, але якщо вони мають однаковий зміст, їх вектори вказують в один бік і оцінка близька до 1.0:

Косинусна схожість

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

🤖 Спробуйте з GitHub Copilot Chat: Відкрийте RagService.java і запитайте:

  • "Як працює пошук за схожістю з використанням впроваджень і що визначає оцінку?"
  • "Який поріг схожості слід використовувати і як це впливає на результати?"
  • "Як мені обробляти випадки, коли релевантні документи не знайдені?"

Генерація відповіді

RagService.java

Найрелевантніші шматки збираються у структурований prompt, що включає явні інструкції, отриманий контекст і запит користувача. Модель читає ці конкретні шматки і відповідає на основі цієї інформації — вона може використовувати лише те, що перед нею, що запобігає галюцинаціям.

String context = matches.stream()
    .map(match -> match.embedded().text())
    .collect(Collectors.joining("\n\n"));

String prompt = String.format("""
    Answer the question based on the following context.
    If the answer cannot be found in the context, say so.

    Context:
    %s

    Question: %s

    Answer:""", context, request.question());

String answer = chatModel.chat(prompt);

Нижче наведена діаграма, яка показує цю збірку у дії — найвищо оцінені шматки з кроку пошуку вставляються у шаблон prompt, а OpenAiOfficialChatModel генерує обґрунтовану відповідь:

Збірка контексту

Ця діаграма показує, як найвищо оцінені шматки збираються в структурований prompt, що дозволяє моделі генерувати обґрунтовану відповідь на основі ваших даних.

Запуск застосунку

Перевірка розгортання:

Переконайтеся, що файл .env існує в кореневій директорії з обліковими даними Azure (створений під час Модуля 01). Запустіть це з директорії модуля (03-rag/):

Bash:

cat ../.env  # Повинно показувати AZURE_OPENAI_ENDPOINT, API_KEY, DEPLOYMENT

PowerShell:

Get-Content ..\.env  # Повинно показувати AZURE_OPENAI_ENDPOINT, API_KEY, DEPLOYMENT

Запуск застосунку:

Примітка: Якщо ви вже запускали всі застосунки за допомогою ./start-all.sh з кореневої директорії (як описано в Модулі 01), цей модуль вже працює на порту 8081. Ви можете пропустити команди запуску нижче і перейти безпосередньо за адресою http://localhost:8081.

Опція 1: Використання Spring Boot Dashboard (Рекомендується для користувачів VS Code)

Dev-контейнер включає розширення Spring Boot Dashboard, яке надає візуальний інтерфейс для керування всіма застосунками Spring Boot. Ви можете знайти його у панелі активності зліва у VS Code (шукайте іконку Spring Boot).

За допомогою Spring Boot Dashboard ви можете:

  • Побачити всі доступні застосунки Spring Boot у робочій області
  • Запускати/зупиняти застосунки одним кліком
  • Переглядати логи застосунків у реальному часі
  • Моніторити статус застосунків

Просто натисніть кнопку відтворення поряд із "rag", щоб запустити цей модуль, або запустіть усі модулі одночасно.

Spring Boot Dashboard

Цей скріншот показує Spring Boot Dashboard у VS Code, де ви можете запускати, зупиняти та моніторити застосунки у візуальному режимі.

Опція 2: Використання shell-скриптів

Запустіть усі веб-застосунки (модулі 01-04):

Bash:

cd ..  # З кореневого каталогу
./start-all.sh

PowerShell:

cd ..  # З кореневої директорії
.\start-all.ps1

Або запустіть тільки цей модуль:

Bash:

cd 03-rag
./start.sh

PowerShell:

cd 03-rag
.\start.ps1

Обидва скрипти автоматично завантажують змінні середовища з кореневого файлу .env і будуть збирати JAR-файли, якщо вони відсутні.

Примітка: Якщо ви хочете збирати всі модулі вручну перед запуском:

Bash:

cd ..  # Go to root directory
mvn clean package -DskipTests

PowerShell:

cd ..  # Go to root directory
mvn clean package -DskipTests

Відкрийте http://localhost:8081 у вашому браузері.

Щоб зупинити:

Bash:

./stop.sh  # Цей модуль лише
# Або
cd .. && ./stop-all.sh  # Всі модулі

PowerShell:

.\stop.ps1  # Тільки цей модуль
# Або
cd ..; .\stop-all.ps1  # Усі модулі

Використання застосунку

Застосунок надає веб-інтерфейс для завантаження документів і постановки питань.

Інтерфейс RAG Застосунку

Цей скріншот показує інтерфейс застосунку RAG, де ви завантажуєте документи і ставите запитання.

Завантаження документа

Почніть із завантаження документа — TXT-файли найкраще підходять для тестування. У цій директорії надається sample-document.txt, який містить інформацію про функції LangChain4j, реалізацію RAG та найкращі практики — ідеально для тестування системи.

Система обробляє ваш документ, розбиває його на шматки та створює впровадження для кожного шматка. Це відбувається автоматично при завантаженні.

Постановка питань

Тепер ставте конкретні запитання про зміст документа. Спробуйте щось фактичне, що чітко вказано в документі. Система шукає релевантні шматки, включає їх у prompt і генерує відповідь.

Перевірка посилань на джерела

Зверніть увагу, що кожна відповідь містить посилання на джерела з оцінками схожості. Ці оцінки (від 0 до 1) показують, наскільки релевантний кожен шматок вашому запитанню. Вищі оцінки означають кращі збіги. Це дає змогу перевірити відповідь за джерельним матеріалом.

Результати запиту RAG

Цей скріншот показує результати запиту з згенерованою відповіддю, посиланнями на джерела та оцінками релевантності для кожного витягнутого шматка.

Експериментуйте з питаннями

Спробуйте різні типи питань:

  • Конкретні факти: "Яка основна тема?"
  • Порівняння: "Яка різниця між X та Y?"
  • Підсумки: "Підсумуйте ключові моменти про Z"

Спостерігайте, як оцінки релевантності змінюються залежно від того, наскільки добре ваше питання відповідає змісту документа.

Ключові поняття

Стратегія розбиття на шматки

Документи розбиваються на шматки по 300 токенів з 30 токенами перекриття. Такий баланс забезпечує достатній контекст у кожному шматку, щоб бути змістовним, при цьому зберігаючи розмір достатньо малим, щоб можна було включити кілька шматків у prompt.

Оцінки схожості

Кожен витягнутий шматок має оцінку схожості між 0 та 1, що показує, наскільки він відповідає запитанню користувача. Нижче наведена діаграма, що візуалізує діапазони оцінок і як система використовує їх для фільтрації результатів:

Оцінки схожості

Ця діаграма показує діапазони оцінок від 0 до 1, з мінімальним порогом 0.5, що фільтрує нерелевантні шматки.

Оцінки варіюються від 0 до 1:

  • 0.7-1.0: Дуже релевантні, точна відповідність
  • 0.5-0.7: Релевантні, хороший контекст
  • Нижче 0.5: Відфільтровані, занадто відмінні

Система витягує лише шматки вище мінімального порогу для забезпечення якості.

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

Режими відмов впроваджень

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

Зберігання в оперативній пам’яті

Цей модуль використовує зберігання в оперативній пам’яті для простоти. При перезапуску застосунку завантажені документи втрачаються. У продуктивних системах використовують персистентні векторні бази даних, такі як Qdrant або Azure AI Search.

Управління контекстним вікном

Кожна модель має максимальний розмір контекстного вікна. Ви не можете включити всі шматки великого документа. Система витягує топ N найбільш релевантних шматків (за замовчуванням 5), щоб залишатися в межах лімітів і водночас надавати достатній контекст для точних відповідей.

Коли RAG має значення

RAG не завжди є правильним підходом. Наведений нижче довідник допоможе визначити, коли RAG додає цінність, а коли простіші підходи — як включення контенту безпосередньо в prompt або покладання на вбудовані знання моделі — достатні:

Коли використовувати RAG

Ця діаграма показує довідник прийняття рішень, коли RAG додає цінність, а коли достатні простіші підходи.

Наступні кроки

Наступний модуль: 04-tools - AI агенти з інструментами


Навігація: ← Попередній: Модуль 02 - Інженерія промптів | Назад до головної | Далі: Модуль 04 - Інструменти →


Відмова від відповідальності: Цей документ було перекладено за допомогою сервісу автоматичного перекладу Co-op Translator. Хоч ми і прагнемо до точності, просимо враховувати, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ рідною мовою слід вважати авторитетним джерелом. Для критично важливої інформації рекомендовано звертатися до професійного людського перекладу. Ми не несемо відповідальності за будь-які непорозуміння чи неправильні тлумачення, що виникли внаслідок використання цього перекладу.