FAQ-база знаний
FAQ-база знаний — это специализированный режим RAG для вопросно-ответных датасетов: типичный кейс — база ответов службы поддержки, где у каждой темы есть один канонический ответ и несколько вариантов формулировки вопроса.
В отличие от обычной индексации документов, где текст режется на чанки фиксированного размера, FAQ-режим понимает структуру записи: каждый вариант формулировки превращается в отдельную точку в Qdrant с одним и тем же faq_id — это даёт лучшее семантическое покрытие на коротких запросах пользователя.
Когда использовать FAQ вместо документов
| Используйте FAQ | Используйте документы |
|---|---|
| Структурированные Q&A пары (вопрос → ответ) | Длинные тексты, статьи, инструкции |
| Каждый ответ — самодостаточный, привязан к конкретному вопросу | Ответ собирается из нескольких фрагментов |
| У вопроса бывает несколько формулировок | Один источник истины — текст документа |
| Нужны фильтры по категориям при поиске | Достаточно семантической близости |
| Нужна дедупликация (один ответ — один результат) | Нужны разные фрагменты как контекст |
Формат данных FAQ
FAQ-датасет — это JSON-массив записей. Каждая запись описывает одну тему: ID, варианты формулировки вопроса, ответ и опциональные метаданные.
Структура записи
[
{
"faq_id": "payout-delay-001",
"question_variants": [
"Долгий вывод средств",
"Почему так долго выводят деньги",
"Когда придут деньги"
],
"answer": "Если запрос на рассмотрении у Администрации, это может занять до 3 рабочих дней. Обычно деньги поступают в течение одной минуты, но в редких случаях обработка платежа может занимать до 30 минут.",
"category": "payouts",
"metadata": {}
},
{
"faq_id": "promo-no-credit-001",
"question_variants": [
"Дайте промокод",
"Хочу промокод",
"Где получить бонус"
],
"answer": "Промокоды и бонусы выдаются за участие в действующих акциях. Список акций — в разделе «Акции» вашего личного кабинета.",
"category": "bonuses"
}
]
Поля записи
| Поле | Тип | Обязательное | Назначение |
|---|---|---|---|
faq_id | string | да | Стабильный уникальный идентификатор записи. При повторной заливке датасета записи с тем же faq_id обновляются на месте, новые — добавляются, отсутствующие в новой загрузке — удаляются (full-sync). |
question_variants | string[] | да, ≥1 непустой | Варианты формулировки вопроса. Каждый вариант становится отдельной точкой в Qdrant, эмбеддится отдельно. |
answer | string | да | Канонический ответ. Не индексируется, хранится в payload. Возвращается LLM как результат поиска. |
category | string | нет | Категория для фильтрации на стороне поиска. Если задана — позволяет ограничить scope retrieval'а на конкретные темы. |
metadata | object | нет | Произвольные дополнительные поля (например, priority, language). Копируются в payload Qdrant как есть. |
faq_idID должен быть стабильным — это ключ для обновлений и дедупликации. Не используйте автогенерируемые UUID при каждой выгрузке: повторная заливка создаст дубликаты вместо обновления существующих записей. Используйте человекочитаемые ID вроде payout-delay-001, promo-no-credit-001.
Чем больше формулировок одного и того же вопроса вы перечислите в question_variants, тем точнее система найдёт релевантный ответ на нестандартный запрос пользователя. Дубликаты на выдаче исключаются автоматически — см. раздел про дедупликацию ниже.
Индексация FAQ — нода FAQ_CHUNKING
Для индексации FAQ-данных используется специальная нода FAQ Chunking (faq_chunking). Она парсит JSON-массив, валидирует каждую запись и режет на чанки по вариантам вопроса.
Структура пайплайна индексации FAQ
Start → S3 Source → FAQ Chunking → OpenAI Embedding → Vector Store
FAQ-коллекция в Qdrant создана с размерностью 1536 (соответствует модели OpenAI text-embedding-3-small). Локальная модель BAAI/bge-small-en-v1.5 (384) не подходит — Qdrant отвергнет upsert с ошибкой dimension mismatch. Также OpenAI лучше работает с многоязычным контентом (русский, английский и т.д.).
В отличие от обычного пайплайна, между S3 Source и FAQ Chunking нет ноды Document Parser. FAQ Chunking сам парсит JSON напрямую из байтов. Если поставить Document Parser в этот пайплайн — валидатор отклонит граф с ошибкой document_parser (outputs text) → faq_chunking (expects file_bytes).
Что делает нода FAQ Chunking
Для каждой записи в JSON-массиве:
- Валидирует обязательные поля (
faq_id,question_variants,answer) - Создаёт по одной точке Qdrant на каждый
question_variant:textточки — сам вариант вопроса (то, что эмбеддится)- В payload точки кладутся
faq_id,answer,category,question_variantи любые поля изmetadata
- Генерирует детерминированный point ID на основе
(faq_id, variant_index)— повторная заливка тех же записей перезапишет точки на месте, без дубликатов - Помечает все точки одним
run_id(UUID прогона) — после успешного апсерта все точки с тем жеsource_idно другимrun_idудаляются (full-sync)
Параметров у ноды нет — поведение полностью определяется содержимым входного JSON.
Коллекция Qdrant
FAQ-данные хранятся в отдельной коллекции faq_chunks (изолированной от knowledge_base, где живут обычные документы). Это позволяет использовать другие модели эмбеддингов и payload-индексы для FAQ без влияния на остальную базу знаний.
В ноде Vector Store при настройке FAQ-пайплайна обязательно укажите:
| Параметр | Значение | Назначение |
|---|---|---|
| collection_name | faq_chunks | Записывать в FAQ-коллекцию, а не в общую knowledge_base |
Поиск по FAQ — настройки Vector Search
Для retrieval из FAQ-базы используется обычный пайплайн поиска, но на ноде Vector Search заполняются специальные FAQ-поля:
Start → OpenAI Query Embedding → Vector Search → Reranker
FAQ-параметры на ноде Vector Search
| Параметр | Значение для FAQ | Назначение |
|---|---|---|
| collection_name | faq_chunks | Искать в FAQ-коллекции, а не в основной |
| dedupe_by | faq_id | Server-side группировка результатов по faq_id — N вариантов одной записи схлопываются в один результат |
| return_field | answer | На выходе подменить text на payload.answer — LLM получит ответ, а не формулировку вопроса |
| filters.category | ["payouts", "bonuses"] (опционально) | Закрепить scope поиска за списком категорий. LLM не видит этот фильтр — он задаётся владельцем пайплайна. |
Зачем нужна дедупликация (dedupe_by: faq_id)
В FAQ-коллекции одна запись = N точек (по числу question_variants). Без дедупликации запрос «когда придут деньги» может вернуть в top-3 три разных варианта одной и той же FAQ — и LLM получит три копии одного ответа в контексте, потеряв два слота из трёх.
С dedupe_by: faq_id Qdrant группирует точки по faq_id и возвращает по одной (с максимальным score) на каждую уникальную запись. Top-3 = 3 разных ответа.
Зачем return_field: answer
В FAQ-точке Qdrant поле text содержит формулировку вопроса (то, что эмбеддилось), а полный ответ — в payload.answer. Если оставить return_field пустым, LLM получит сами вопросы, а не ответы — и попытается на них «отвечать», что приведёт к мусору в выдаче.
С return_field: answer система автоматически подменяет text результата на содержимое payload.answer. В метаданных результата сохраняется matched_question — формулировка, которая фактически совпала с запросом (полезно для отладки и для подсказки LLM, на какой именно вопрос он отвечает).
Pinned-фильтры по категориям
Если вы хотите, чтобы конкретный агент искал ответы только в определённых категориях FAQ — задайте filters.category на ноде Vector Search в его retrieval-пайплайне:
filters:
category:
- payouts
- bonuses
Этот фильтр применяется на уровне Qdrant-запроса до выдачи результатов. LLM никак не управляет им и даже не знает о его существовании — это инструмент владельца пайплайна для разделения scope разных агентов.
Чтобы создать FAQ-агента для другой темы (например, для регистрации) — создайте отдельный retrieval-пайплайн с другим набором filters.category и привяжите его к соответствующему агенту.
Рекомендуемые значения порогов для FAQ
FAQ-варианты обычно короткие (несколько слов до короткой фразы), поэтому absolute scores эмбеддингов ниже, чем на длинных текстах. Эмпирические значения:
| Параметр | Рекомендация для FAQ | Почему |
|---|---|---|
| limit | 3-5 | После dedupe это уникальные FAQ. Больше 5 размывает контекст LLM. |
| score_threshold | 0.4-0.5 | Отсечь шум. Без порога Qdrant groupBy добивает результаты до limit мусором. |
| Reranker top_k | 3 | Финальное число для LLM. Лучше 3 чётких ответа, чем 5 размытых. |
Без score_threshold пользователь, задавший несуществующий вопрос, получит мусорный матч с низким score (например, 0.28) — и LLM ответит уверенно неверно. С порогом 0.5 такие запросы возвращают пусто, и LLM может честно сказать «не нашёл информации».
Для документов слабый матч безвреден (LLM игнорирует), для FAQ — это уверенно неверный ответ.
Полный жизненный цикл
- Подготовьте JSON-датасет в формате выше
- Загрузите файл в S3 (через коннектор)
- Создайте ingestion-пайплайн:
S3 Source → FAQ Chunking → OpenAI Embedding → Vector Store(collection_name=faq_chunks) - Запустите индексацию — данные попадают в коллекцию
faq_chunks - Создайте retrieval-пайплайн:
OpenAI Query Embedding → Vector Search(collection=faq_chunks, dedupe_by=faq_id, return_field=answer) → Reranker - Привяжите retrieval-пайплайн к агенту в настройках агента (поле RAG → Retrieval Pipeline)
- При повторных загрузках того же датасета — обновлённые записи перезапишутся на месте, удалённые исчезнут из индекса (full-sync по
run_id)