Три слова. Три кроки. Перетвори запит на embedding, знайди схожий текст, віддай моделі.
Абревіатура звучить як довершений pipeline. І якщо зібрати систему рівно так, вона працюватиме на перших десяти тестових запитах. Покажете демо комусь і відчуєте задоволення.
А потім реальний користувач пише "бот не відповідає після того, як я налаштував вебхук" і ваш трикроковий pipeline витягує чанки про конфігурацію вебхуків, чанки про шаблони відповідей бота і чанки про таймаути інтеграції зі Slack. Кожен чанк набрав високий score за vector similarity. Жоден з них не містить відповіді. Відповідь живе у зв'язку між некоректною конфігурацією вебхука і конкретним error handler, який мовчки ковтає повідомлення.
Vector search знайшов текст, що нагадує запитання. Відповідь він пропустив.
Тут туторіали закінчуються і починається справжня робота.
Проблеми за абревіатурою
Ми у Well Digit запустили RAG pipeline у production, і кожен неочевидний збій, з яким ми стикалися, потрапляє в одну з п'яти категорій. Усі це проблеми не покриваються класичними трьомка кроками RAG.
1. Проблема класифікації.
Не кожне повідомлення користувача заслуговує на retrieval цикл. Хтось надсилає "дякую, допомогло" — і система запускає повний embedding, робить запит до vector store, дістає чанки документації про подяку і генерує абзац про те, як вона рада допомогти. Витрачені токени, витрачена latency.
Перше справжнє рішення в pipeline має бути бінарним: чи потрібен цьому input retrieval взагалі? Small talk пропускає все й іде прямо до generation. Один gate. У нашому випадку це економить обчислення приблизно на 30% повідомлень.
2. Проблема неоднозначності.
"Як налаштувати інтеграцію?" Ваш продукт підтримує шість інтеграцій. Система робить embedding запиту, витягує документацію тієї інтеграції, яка випадково виявилася найближчою у vector space, і впевнено відповідає про неправильну. Користувач не може знати, що відповідь хибна, бо система навіть не зупинилась перевірити.
Це помилка дизайну, не помилка моделі. Перед generation треба перевірити: чи retrieved документи сходяться до однієї теми, чи розпорошуються по кількох? Якщо розпорошуються — чесна відповідь — це уточнювальне запитання.
Ми вирішуємо це через graph cluster detection. Якщо matched entities потрапляють у disconnected clusters без шляху між ними у межах чотирьох хопів, запит стосується кількох непов'язаних тем. Система запитує, що саме мав на увазі користувач, замість того щоб вгадувати.
3. Проблема переповнення контексту.
Витягнули 20 релевантних чанків. Це 15 000 токенів. Запхнули їх усі в prompt. Модель читає початок, читає кінець, а через середину пропливає, не помічаючи.
Феномен "lost-in-the-middle" добре задокументований у дослідженнях. Витягувати менше чанків — не вихід (втрачаєте recall). Вихід — summarization retrieved контексту перед generation. Стиснути 15 000 токенів сирого матеріалу до 3 000 токенів сфокусованого summary. Модель дійсно обробляє весь обсяг.
Ми кешуємо ці summary за content hash. Той самий набір контексту — той самий summary. Без додаткової логіки це прибирає зайві LLM виклики на повторюваних запитаннях.
4. Проблема плоского retrieval.
Головна.
Vector similarity працює по текстовій поверхні. Знаходить чанки, які звучать як запит. Але знання мають структуру. Причини ведуть до наслідків. Передумови блокують рішення. Альтернативи відгалужуються від спільних коренів.
"Що відбувається, коли finding X пов'язаний із corrective action Y?" — це relational запитання. Жоден окремий чанк не містить відповіді, бо інформація про finding X лежить в одному документі, а corrective action Y — в іншому. Їх з'єднує зв'язок, а не лексична близькість.
Вектори не бачать зв'язків. Графи — бачать.
5. Проблема intent.
Користувач, який питає "що таке двофакторна автентифікація?" і користувач, який каже "2FA зламалась після оновлення" — потребують принципово різних стратегій retrieval. Перше — концептуальне дослідження. Друге — повідомлення про проблему. Витягувати однакові чанки для обох — отримати посередні результати для обох.
Класифікація intent перед retrieval дозволяє формувати пошук. Концептуальний запит витягує пояснювальний контент і пов'язані теми. Проблемний запит витягує симптоми, причини і відомі рішення. Та сама knowledge base. Різна логіка обходу.
Що граф додає до RAG
Knowledge graph зберігає сутності та зв'язки між ними. Структуру, не прозу.
Найпростіше пояснення: vector search каже "документи, які обговорюють схожі речі." Граф каже "речі, пов'язані з тим, про що ви запитали, і як саме вони пов'язані."
На практиці граф працює поряд з vector store. Vector search знаходить точки входу. Граф розширюється від цих точок, виявляючи пов'язані знання, до яких cosine similarity ніколи не дотягнеться.
Як це працює на практиці
Проектування Graph Schema для вашого домену
Універсальної graph schema не існує. Сутності, які ви моделюєте, і зв'язки, які визначаєте, вирішують, що граф покаже такого, чого вектори не побачать. Це доменна робота, не фреймворкова.
Два приклади.
Customer support knowledge base
Це домен, у якому ми побудували найбільше.
Сутності. Центральний тип вузла має поле kind, яке розрізняє: Problem, Solution, Concept, Feature, Configuration. Problem — щось, що ламається. Solution — як це полагодити. Concept — пояснювальне знання. Feature і Configuration описують структуру продукту.
Зв'язки. Тут schema починає працювати на вас.
CAUSES — з'єднує кореневу причину з видимим симптомом. "Invalid webhook URL" CAUSES "Bot doesn't respond." Коли користувач повідомляє симптом, граф іде назад до причини.
RESOLVED_BY — з'єднує проблему з одним або кількома рішеннями. "Bot doesn't respond" RESOLVED_BY "Verify the webhook endpoint is publicly accessible." Кілька рішень на одну проблему. Модель обирає найбільш контекстуально релевантне.
DIAGNOSED_BY — з'єднує проблему з діагностичним запитанням. "Bot doesn't respond" DIAGNOSED_BY "Does the webhook URL return 200 on a GET request?" Це ребро генерує уточнювальні запитання перед тим, як система спробує дати повну відповідь.
RELATED_TO — загальна двостороння асоціація. "Webhook configuration" RELATED_TO "API key setup." Використовується для концептуального дослідження, коли користувач вивчає, а не усуває неполадки.
EVIDENCE_FOR і MENTIONS — міст між світами. Вони з'єднують чанки (vector domain) із сутностями (graph domain). Документаційний чанк є EVIDENCE_FOR Solution, або MENTIONS Feature. Без цих ребер дві системи retrieval ніколи не спілкуються одна з одною.
Intent-aware traversal. Той самий граф відповідає по-різному залежно від потреби користувача. Problem intent іде по CAUSES назад і по RESOLVED_BY вперед. HowTo intent іде по RESOLVED_BY до рішень і шукає покрокові атрибути. Concept intent широко обходить RELATED_TO, щоб побудувати карту пов'язаних знань.
Ті самі сутності. Ті самі ребра. Різні шляхи через них.
E-commerce product catalog
Інший домен. Інша схема. Той самий принцип.
Сутності. Product, Category, Feature, Specification, Review, Brand.
Зв'язки.
BELONGS_TO — Product до Category. "Running Shoe X" BELONGS_TO "Men's Athletic Footwear."
HAS_FEATURE — Product до Feature. "Running Shoe X" HAS_FEATURE "Carbon fiber plate." Коли хтось шукає "взуття з карбоновими пластинами," vector search ловить фразу. Graph traversal також знаходить кожен інший продукт із тією самою feature, включаючи ті, що описують її як "carbon-infused midsole" або "energy return plate." Різні слова — той самий вузол feature.
COMPATIBLE_WITH — Product до Product. "Running Shoe X" COMPATIBLE_WITH "Performance Insole Y." Це знання не існує в жодному окремому описі продукту. Воно живе виключно у зв'язку.
COMPARED_TO — з'єднує конкуруючі продукти. Коли хтось питає "що краще, X чи Z?", граф підіймає контекст порівняння з обох сторін.
Патерн в обох прикладах: сутності представляють речі, про які запитують користувачі. Зв'язки представляють з'єднання між ними, які жоден окремий чанк тексту не фіксує. Граф зберігає те, що знаходиться між документами.
Коли граф не потрібен
Додавання графа збільшує складність індексації, зберігання та обробки запитів. Воно виправдане, коли ваші дані мають сутності з meaningful зв'язками, коли користувачі ставлять relational запитання, і коли неоднозначність — це часта проблема.
Якщо ваш use case — пошук серед 500 FAQ-статей і повернення найрелевантнішої, vector search справляється. Не додавайте граф, бо це звучить архітектурно цікаво. Додавайте його, коли ваші користувачі ставлять запитання, які вимагають обходу зв'язків між речами.
Pipeline — це продукт
RAG — це не три кроки. Pipeline, який ми запускаємо, має шістнадцять етапів (як мінімум). Кожен із них існує тому, що конкретний збій у production цього вимагав. Classification, щоб уникнути зайвого retrieval. Intent detection, щоб сформувати стратегію. Vector search для семантичної близькості. Graph expansion для relational knowledge. Reranking, щоб об'єднати scoring signals. Summarization, щоб поважати attention limits. Ambiguity detection, щоб не відповідати впевнено і неправильно. Model selection, щоб балансувати якість і вартість.
Нічого з цього не проектувалось на дошці. Кожен stage додавався, бо щось ламалось і потрібно було зупинити це від повторення.
Трибуквена абревіатура RAG продає простоту. Робота за нею — зовсім інша історія.