Перейти к основному содержимому

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_idstringдаСтабильный уникальный идентификатор записи. При повторной заливке датасета записи с тем же faq_id обновляются на месте, новые — добавляются, отсутствующие в новой загрузке — удаляются (full-sync).
question_variantsstring[]да, ≥1 непустойВарианты формулировки вопроса. Каждый вариант становится отдельной точкой в Qdrant, эмбеддится отдельно.
answerstringдаКанонический ответ. Не индексируется, хранится в payload. Возвращается LLM как результат поиска.
categorystringнетКатегория для фильтрации на стороне поиска. Если задана — позволяет ограничить scope retrieval'а на конкретные темы.
metadataobjectнетПроизвольные дополнительные поля (например, priority, language). Копируются в payload Qdrant как есть.
warning
Важно про faq_id

ID должен быть стабильным — это ключ для обновлений и дедупликации. Не используйте автогенерируемые UUID при каждой выгрузке: повторная заливка создаст дубликаты вместо обновления существующих записей. Используйте человекочитаемые ID вроде payout-delay-001, promo-no-credit-001.

Несколько вариантов вопроса = лучший recall

Чем больше формулировок одного и того же вопроса вы перечислите в question_variants, тем точнее система найдёт релевантный ответ на нестандартный запрос пользователя. Дубликаты на выдаче исключаются автоматически — см. раздел про дедупликацию ниже.


Индексация FAQ — нода FAQ_CHUNKING

Для индексации FAQ-данных используется специальная нода FAQ Chunking (faq_chunking). Она парсит JSON-массив, валидирует каждую запись и режет на чанки по вариантам вопроса.

Структура пайплайна индексации FAQ

Start → S3 Source → FAQ Chunking → OpenAI Embedding → Vector Store
Обязательно OpenAI Embedding

FAQ-коллекция в Qdrant создана с размерностью 1536 (соответствует модели OpenAI text-embedding-3-small). Локальная модель BAAI/bge-small-en-v1.5 (384) не подходит — Qdrant отвергнет upsert с ошибкой dimension mismatch. Также OpenAI лучше работает с многоязычным контентом (русский, английский и т.д.).

Document Parser НЕ нужен

В отличие от обычного пайплайна, между S3 Source и FAQ Chunking нет ноды Document Parser. FAQ Chunking сам парсит JSON напрямую из байтов. Если поставить Document Parser в этот пайплайн — валидатор отклонит граф с ошибкой document_parser (outputs text) → faq_chunking (expects file_bytes).

Что делает нода FAQ Chunking

Для каждой записи в JSON-массиве:

  1. Валидирует обязательные поля (faq_id, question_variants, answer)
  2. Создаёт по одной точке Qdrant на каждый question_variant:
    • text точки — сам вариант вопроса (то, что эмбеддится)
    • В payload точки кладутся faq_id, answer, category, question_variant и любые поля из metadata
  3. Генерирует детерминированный point ID на основе (faq_id, variant_index) — повторная заливка тех же записей перезапишет точки на месте, без дубликатов
  4. Помечает все точки одним run_id (UUID прогона) — после успешного апсерта все точки с тем же source_id но другим run_id удаляются (full-sync)

Параметров у ноды нет — поведение полностью определяется содержимым входного JSON.

Коллекция Qdrant

FAQ-данные хранятся в отдельной коллекции faq_chunks (изолированной от knowledge_base, где живут обычные документы). Это позволяет использовать другие модели эмбеддингов и payload-индексы для FAQ без влияния на остальную базу знаний.

В ноде Vector Store при настройке FAQ-пайплайна обязательно укажите:

ПараметрЗначениеНазначение
collection_namefaq_chunksЗаписывать в FAQ-коллекцию, а не в общую knowledge_base

Для retrieval из FAQ-базы используется обычный пайплайн поиска, но на ноде Vector Search заполняются специальные FAQ-поля:

Start → OpenAI Query Embedding → Vector Search → Reranker
ПараметрЗначение для FAQНазначение
collection_namefaq_chunksИскать в FAQ-коллекции, а не в основной
dedupe_byfaq_idServer-side группировка результатов по faq_id — N вариантов одной записи схлопываются в один результат
return_fieldanswerНа выходе подменить 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Почему
limit3-5После dedupe это уникальные FAQ. Больше 5 размывает контекст LLM.
score_threshold0.4-0.5Отсечь шум. Без порога Qdrant groupBy добивает результаты до limit мусором.
Reranker top_k3Финальное число для LLM. Лучше 3 чётких ответа, чем 5 размытых.
Score threshold критичен для FAQ

Без score_threshold пользователь, задавший несуществующий вопрос, получит мусорный матч с низким score (например, 0.28) — и LLM ответит уверенно неверно. С порогом 0.5 такие запросы возвращают пусто, и LLM может честно сказать «не нашёл информации».

Для документов слабый матч безвреден (LLM игнорирует), для FAQ — это уверенно неверный ответ.


Полный жизненный цикл

  1. Подготовьте JSON-датасет в формате выше
  2. Загрузите файл в S3 (через коннектор)
  3. Создайте ingestion-пайплайн: S3 Source → FAQ Chunking → OpenAI Embedding → Vector Store(collection_name=faq_chunks)
  4. Запустите индексацию — данные попадают в коллекцию faq_chunks
  5. Создайте retrieval-пайплайн: OpenAI Query Embedding → Vector Search(collection=faq_chunks, dedupe_by=faq_id, return_field=answer) → Reranker
  6. Привяжите retrieval-пайплайн к агенту в настройках агента (поле RAG → Retrieval Pipeline)
  7. При повторных загрузках того же датасета — обновлённые записи перезапишутся на месте, удалённые исчезнут из индекса (full-sync по run_id)

См. также

  • Пайплайны — общая документация по узлам ingestion и retrieval
  • Поиск — общие принципы работы поиска по базе знаний