Головна / Статті

Створення асистента Slack RAG без окремої векторної бази даних

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

Видавець: REACTAPP.TOP9 хв читання1961 слів

Джерела: Lessons We Learned Building a RAG Assistant Without a Separate Vector Database

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

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

Цікавою є архітектура. Rocky працює на самій StarRocks. Фрагменти документації, пошук за ключовими словами, отримання векторів та оцінка схожості знаходяться у одній OLAP-таблиці, тож асистент, який відповідає на запитання щодо бази даних, також працює на цій самій базі даних. Усе це складається приблизно з 600 рядків коду на Python, однієї таблиці та ключа API Gemini.

Основи RAG

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

Тому все залишилося в StarRocks. Фрагменти документів та їхні 768-вимірні ембеддинги Gemini зберігаються у звичайній OLAP-таблиці з використанням моделі PRIMARY KEY, а пошук здійснюється за допомогою SQL-запиту. Принцип полягає у тому, що якщо ваша аналітична база даних вже підтримує стовпці масивів та функції косинусової схожості, вам не потрібна окрема система даних для векторного пошуку.

Таблиця виглядає як будь-яка інша:

CREATE TABLE docs (
    id     BIGINT NOT NULL,
    path   VARCHAR(512),
    `index` INT,
    `text`  STRING,
    vector ARRAY<FLOAT>   -- 768-dim Gemini embedding
) ENGINE = OLAP
PRIMARY KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1;

А отримання даних здійснюється за допомогою одного запиту з використанням косинусової схожості:

SELECT path, `index`, `text`,
       approx_cosine_similarity([0.012, -0.034, ...], vector) AS similarity
FROM docs
ORDER BY similarity DESC
LIMIT 8;

Немає SDK, немає бібліотеки клієнта та немає окремої системи. З боку Rocky „база даних векторів“ — це просто ще одна таблиця, яку він вже може запитувати.

Від події в Slack до конкретної відповіді

Процес спеціально розроблений мінімалістичним чином. Згадка @Rocky у Slack активує бота, який передає логіку обробки до Gemini 3 Flash за допомогою вбудованих функцій виклику. Модель вирішує, чи шукати інформацію в документації, чи запитувати Google, і може виконувати до десяти кроків виклику інструментів за один раз.

  1. Slack надсилає повідомлення. Бібліотека slack_bolt для Python отримує подію через режим Socket та виділяє запит користувача разом із контекстом розмови.
  2. Gemini аналізує та використовує інструменти. Модель отримує системний запит із суворими правилами чесності та два інструменти: search_starrocks_doc — для пошуку векторів на клієнтській стороні, та google_search — для пошуку на серверській стороні за допомогою вбудованого пошуковика Gemini.
  3. Пошук відбувається всередині StarRocks. Інструмент пошуку включає запит із параметром gemini-embedding-001 та значенням task_type=RETRIEVAL_QUERY, а потім виконує SQL-запит на косинусову схожість, описаний раніше.
  4. Модель пише відповідь. Gemini об’єднує отримані фрагменти у відповідь у форматі Markdown, яку Rocky перетворює на формат mrkdwn для Slack перед публікацією.
  5. Дані телеметрії надсилаються для моніторингу. Кожен виклик моделі, використання інструментів та підрахунок токенів стають елементами OpenTelemetry span, причому thread_ts використовується як ідентифікатор сеансу.

Бот складається приблизно з 600 рядків коду на Python у одному файлі. Додаткові інструменти для розділення на частини, генерації ембеддингів та створення індексу додають ще кілька тисяч рядків, а зберігання, пошук та оцінка схожості залишаються на розсуд StarRocks.

Чому ця стек-технологія підходить для легкого додатку RAG

Три варіанти дозволяють зберегти невеликі обсяги ресурсів під час роботи, водночас забезпечуючи корисні відповіді.

Таблиця основного ключа разом із Stream Load для заміни документів

Корпус даних не є потоковим завданням. Коли документи змінюються, весь каталог docs/en/ знову розділяється на частини, генеруються нові ембеддинги, а вся таблиця перезавантажується за допомогою Stream Load:

curl --location-trusted -u root: \
     -H "format: json" \
     -H "strip_outer_array: true" \
     -T docs_with_embedding.json \
     -XPUT http://host:8030/api/starsage/docs/_stream_load

Одна HTTP-запит може завантажити близько 5 000 рядків, кожен з яких містить масив чисел з плаваючою комою довжиною 768 вимірів. У такому масштабі метод Stream Load ідеально підходить: немає потреби у підтримці брокера, немає необхідності запускати конвеєр Flink, а також немає потреби у постійній інфраструктурі для отримання даних. Для тисяч чи десятків тисяч рядків із вбудованими векторами це найпростіший спосіб.

Модель первинного ключа гарантує атомарну заміну даних. Запити, які надходять під час завантаження, бачать послідовну копію даних, а не напівоновлену базу. Саме це є основною причиною вибору таблиці з первинним ключем замість таблиці з ключем дублікатів. Йдеться не про оновлення даних на рівні окремих рядків, оскільки обсяг роботи полягає у повній перебудові даних. Система з таблицею первинного ключа та методом Stream Load забезпечує атомарність кожного завантаження, тож Rocky подає або стару базу даних, або повністю оновлену, але ніколи їхню суміш.

Отримання даних як інструмент, а не заповнення контексту

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

На практиці повідомлення на кшталт „Привіт, Rocky“ зовсім не спричиняє пошуку та коштує майже нічого. Складне запитання, наприклад про те, як поводиться функція GROUP_CONCAT у версіях 3.5 та 4.0, спричиняє кілька викликів: модель запитує кожну версію окремо та порівнює результати. Кількість викликів для пошуку обмежена вісьмома за один раунд. Серверна частина Gemini з функцією google_search обробляє інформацію з приміток до версій та проблем у GitHub, які не входять до документації, а Rocky охоплює широку сферу без окремої системи збору даних з вебу.

Кеш ембеддингів для прогнозованої вартості

У спільноті часто постають однакові запитання. Кілька разів на тиждень задають питання про те, як додати індекс у вигляді бітмапу та як розгорнути систему на Kubernetes. Локальний кеш SQLite, який використовує текст запиту та значення field task_type як ключі (оскільки RETRIEVAL_QUERY та RETRIEVAL_DOCUMENT генерують різні вектори), усуває зайві виклики функцій ембеддингу, тому популярні запити не коштують нічого.

Щодо моделей LLM, то токени вхідних даних, які зберігаються у кеші Gemini, обходяться у 25 відсотків від стандартної ціни. Завдяки кешу ембеддингів вартість кожної відповіді становить приблизно 0,001 долара, що є прийнятним для проекту, фінансованого спільнотою та який працює необмежений час.

Шість уроків з реального використання

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

1. Зміни у формулюванні запитів ефективніші за налаштування для усунення галюцинацій

У ранніх версіях виникали неправильні назви дзеркал Red Hat, вигадувалися URL-адреси з примітками до випуску та фальшиві номери версій, через що учасники спільноти потрапляли на сторінки 404. Для бота для запитань та відповідей неправильна відповідь, навіть якщо вона дається впевнено, гірша, ніж відсутність відповіді.

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

2. Виправте розділення на частини перед тим, як працювати з пошуком

У документації StarRocks є багато рядків імпорту MDX, таблиць Markdown, які охоплюють кілька частин, та дуже довгих операторів SQL DDL. Введення такої необробленої інформації у систему ембеддингів призводить до некоректних результатів. У процес розділення на частини було вкладено значні зусилля:

  • Ієрархічне розділення за заголовками. Фрагменти утворюються біля заголовків Markdown, причому весь шлях до заголовка зберігається як метадані, що дозволяє моделі зрозуміти, де саме фрагмент належить у ієрархії документа.
  • Фільтрація шуму. Рядки імпорту MDX видаляються, а блоки коду довжиною понад 4 000 символів відкидаються, оскільки вони знижують якість семантичного сигналу без позитивного впливу на відповіді.
  • Простіші таблиці результатів. Приклади результатів у форматі таблиць у стилі MySQL скорочуються до структурованих описів замість сирих ASCII-матриць.

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

3. RAG — це інструмент, а не потік обробки даних

Коли отримання даних є викликом функції, а не вбудованою частиною кожного запиту, модель може пропустити цей крок, якщо він не потрібен. Привітання, додаткові уточнення та прості фактичні запитання ніколи не торкаються векторного індексу. Поєднання інструменту з боку клієнта (search_starrocks_doc) та інструменту з боку сервера (google_search), що повністю підтримується SDK Gemini з версії 1.70, також забезпечує Rocky альтернативний спосіб отримання контенту поза корпусом даних без використання спеціальних скрейперів.

4. Дотримуйтесь моделі сеансів платформи

Треди в Slack мають ідентифікатори thread_ts. Rocky використовує їх безпосередньо як ідентифікатор сеансу, зберігаючи історію у JSON-файлі з такою ж назвою та включаючи шість найновіших повідомлень у кожному запиті:

contents = []
for turn in history[-MAX_CONTEXT_MESSAGES:]:
    contents.append({"role": turn["role"], "parts": [{"text": turn["text"]}]})
contents.append({"role": "user", "parts": [{"text": query}]})

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

Одна з постійних претензій користувачів — це те, що „Rocky не пам’ятає“. Це виникає через те, що в Slack Rocky відповідає лише тоді, коли його згадують за допомогою @, що створює враження забудькуватості. Це проблема взаємодії з продуктом, а не технічна.

5. Розпочніть вимірювання рано

Після запуску команда хотіла отримати відповіді на базові запитання. Скільки запитань обробив Rocky цього тижня? Де концентрувалися виклики інструментів за тематикою? Скільки коштувала типова відповідь? Які відповіді були відкладені, виправлені чи потребували подальшого розгляду? Без слідів система була чорною скринькою, тому згодом був доданий повний шар OpenTelemetry. Урок полягає у необхідності додавати механізми відстеження ще до запуску. Без них використання токенів, витрати та закономірності викликів інструментів залишаються невидимими, а оптимізація витрат, визначення причин хибних результатів та аналіз слабких запитань стають набагато складнішими.

6. Агресивно використовувати кеш та відстежувати кожну копійку

API для вбудовування здаються недорогими за один виклик, але часті запитання від спільноти швидко накопичуються. Кешування даних SQLite разом із системою ціноутворення на кешовані токени Gemini зберігають собівартість на рівні майже 0,001 долара за кожну відповідь. Для проекту, який підтримується спільнотою, це різниця між «можливістю функціонувати необмежено» та «необхідністю обговорення бюджету».

Чесні компроміси

Rocky — це легкий асистент типу RAG, а не система підтримки у продакшені, тож варто зазначити кілька обмежень:

  • Галюцинації зменшуються, але не усуваються повністю. Механізми контролю запитів допомагають, але не гарантують точності. Найкраще працюють добре задокументовані теми, тоді як крайні випадки та особливості певних версій все ще можуть спричинити проблеми.
  • Довгі відповіді. Як і більшість ботів типу RAG, він надмірно пояснює. Планується додати можливість отримувати короткі відповіді у форматі короткого огляду.
  • Відсутня спільна пам’ять між потоками. Контекст знаходиться всередині одного потоку Slack, тому користувач, який вчора запитував про матеріалізовані перегляди, сьогодні починає з нуля.
  • Ще немає ескалації до людини. Після кількох невдалих спроб система має автоматично позначити адміністратора, але цей механізм ще не впроваджений.
  • Обмежені можливості для усунення проблем. Для більш глибокої діагностики потрібно використовувати інструменти для відлову помилок з відкритим кодом, які проект нещодавно оприлюднив.
  • Усі ці пункти є активними елементами плану розвитку, сформованими на основі відгуків спільноти.

    Що показує Rocky

    Rocky — це невеликий проект: 600 рядків коду на Python, одна таблиця та один ключ API, проте він дозволяє досягати більших цілей. Для легких AI-додатків аналітична база даних із стовпцями масивів та функціями подібності може усунути потребу у окремій векторній базі даних. Одна таблиця відповідає за зберігання документів, пошук за ключовими словами та отримання векторів, тож кількість елементів, якими потрібно керувати, залишається невеликою, і існує лише одна система, а не дві.

    Бот щодня обробляє справжній трафік спільноти, і, за даними проекту, користувачі в середньому оцінюють його на 4,2 з 5, причому вартість кожної відповіді становить менше десятої частки цента. Для тих, хто створює подібного помічника в певній галузі та хоче просту інфраструктуру, підхід із однією таблицею вартий уваги, особливо якщо у системі вже використовується StarRocks.

    Пов’язана література

  • Гібридний пошук зображень у масштабі: SigLIP, SPLADE, YOLOv8 та описи від Gemini — проектування системи пошуку мільйона зображень, яка поєднує щільні та розріджені ембеддинги зі виявленням об’єктів, розпізнаванням ентитетів, безпечним пошуком та описами, створеними за допомогою LLM.