Назад в блог

WordPress как бэкенд для RAG: Как мы это сделали и почему это работает

Sergey Nesmachny
Sergey Nesmachny
02.05.2026
6 мин чтения
Поделиться:

Для тех, кто создает AI-ассистентов и ищет удобное хранилище знаний

Начальная точка: проблема хранения

Когда вы хотите добавить реальные знания о продукте в ваш AI-ассистент, первый вопрос, который возникает: где хранить документы?

Очевидные варианты: Notion, Google Docs, Confluence, папка с PDF-файлами, собственная CMS. Каждый предлагает что-то, но у каждого есть нюансы:

  • Notion — отличный для редактирования, но API платный и ненадежный
  • Google Docs — знаком, но нет нормальной структуры или версионирования
  • Confluence — корпоративного уровня, избыток для небольшой команды
  • Папка с файлами — просто, но нет интерфейса редактирования, нет контроля доступа, нет вебхуков

Я пришел к другому решению: у меня уже был WordPress. И я решил не множить системы.

WordPress как CMS базы знаний

WordPress — это не просто блог. Это полнофункциональная система управления контентом с:

  • Редактором (Gutenberg или Classic) — любой менеджер может писать и редактировать статьи без технических знаний
  • Контролем доступа — кто может редактировать, кто только может читать
  • REST API из коробкиGET /wp-json/wp/v2/posts возвращает JSON с контентом, мета-полями и датами обновления
  • Хуками — вы можете присоединить действие к сохранению записи (save_post)
  • Статусами публикации — опубликовано, приватно, черновик

Последний пункт оказался ключевым.

Два типа контента в одной CMS

В нашей реализации база знаний построена из двух источников в одном WordPress:

Публичные страницы — это реальные статьи на веб-сайте: описания продуктов, учебные материалы, часто задаваемые вопросы. Они одновременно служат SEO-контентом для людей и источником знаний для AI-ассистента.

 

Приватные страницы (status: private) — внутренние документы, невидимые для посетителей сайта, но доступные через REST API с аутентификацией. Это включает: скрипты продаж, руководства по обработке возражений, системные подсказки для AI и технические спецификации.

Это разделение решает важную проблему: один и тот же WordPress одновременно является публичным веб-сайтом и приватной базой знаний.

WordPress
├── Публичные посты/страницы → веб-сайт + база знаний AI
└── Приватные страницы      → только база знаний AI

Кроме того, поле ”Исключить из AI” (пользовательский флажок _wifly_no_ai) позволяет отметить конкретную запись так, чтобы она не индексировалась. Это полезно для служебных страниц, черновиков и рекламного контента, который не должен влиять на ответы ассистента.

Архитектура синхронизации

Самая интересная часть: как контент из WordPress попадает в базу векторов в реальном времени.

Плагин WordPress как триггер

Мы написали небольшой плагин WordPress (~80 строк PHP), который подключается к save_post:

add_action('save_post', function($post_id) {
    if (wp_is_post_revision($post_id)) return;
    if (get_post_meta($post_id, '_wifly_no_ai', true)) return;

    // Debounce: не более одного раза каждые 2 минуты
    $last = get_transient('wifly_kb_sync_' . $post_id);
    if ($last) return;
    set_transient('wifly_kb_sync_' . $post_id, 1, 120);

    // Вебхук на сервер AI-ассистента
    wp_remote_post(KB_SYNC_URL, [
        'body'     => json_encode(['post_id' => $post_id]),
        'headers'  => ['Authorization' => 'Bearer ' . KB_SYNC_SECRET],
        'timeout'  => 5,
        'blocking' => false, // не ждать ответа
    ]);
});

Ключевые детали:

  • blocking: false — WordPress не ждет ответа сервера; страница сохраняется мгновенно
  • Debounce через set_transient — предотвращает повторные вызовы во время автосохранения
  • Секретный токен — вебхук защищен авторизацией Bearer

Сервер получает вебхук и обновляет индекс

На стороне Node.js/Express сервера обработчик вебхука:

  1. Получает post_id
  2. Загружает последний контент через WP REST API
  3. Парсит HTML → извлекает чистый текст
  4. Разделяет на части по заголовкам h2/h3 (~800 символов каждая)
  5. Векторизирует каждую часть используя text-embedding-3-large
  6. Обновляет записи в Qdrant (upsert по post_id + chunk_index)
app.post('/api/kb/sync', verifySecret, async (req, res) => {
  res.json({ ok: true }); // Ответить сразу же

  const { post_id } = req.body;
  const post = await fetchFromWordPress(post_id);
  const chunks = splitByHeadings(post.content, 800);

  for (const [i, chunk] of chunks.entries()) {
    const vector = await openai.embeddings.create({
      model: 'text-embedding-3-large',
      input: chunk.text,
    });
    await qdrant.upsert('wifly_kb', {
      points: [{
        id: `${post_id}_${i}`,
        vector: vector.data[0].embedding,
        payload: { text: chunk.text, heading: chunk.heading, post_id, url: post.link }
      }]
    });
  }
});

Результат: редактор сохраняет статью — в течение 5–10 секунд AI-ассистент уже знает обновленный контент.

Векторизация и поиск

Модель встраивания

Мы используем text-embedding-3-large от OpenAI (3072 измерения). Он дороже, чем text-embedding-3-small, но точность поиска заметно выше — особенно для предметной терминологии и технических терминов.

Гибридный поиск: плотный + ключевой → RRF

Чистый векторный поиск хорошо справляется с поиском семантически похожего контента, но борется с точными названиями — коды продуктов, названия продуктов, аббревиатуры. Если пользователь спрашивает о ”flyAir” или конкретном плане цен, вектор может пропустить.

Решение — гибридный поиск с Reciprocal Rank Fusion (RRF):

Запрос пользователя
     │
     ├──→ Плотный поиск (Qdrant ANN) → ТОП-14 кандидатов
     │
     └──→ Поиск по ключевым словам (фильтр полнотекстового поиска) → ТОП-10 кандидатов
                      │
                      ▼
          Слияние RRF (k=60)
                      │
                      ▼
          ТОП-7 финальных чанков → в контекст LLM

Формула RRF для каждого документа:

score(d) = Σ 1 / (k + rank_i(d))

Документы, которые занимают высокое место в обоих списках, получают наивысший финальный балл. Это простой и очень эффективный метод без необходимости подстраивать веса.

Полнотекстовый индекс в Qdrant создается при запуске сервера (идемпотентно):

await qdrant.createPayloadIndex('wifly_kb', {
  field_name: 'text',
  field_schema: 'text',
});

Что хранится в Qdrant

Каждый чанк — это точка в векторном пространстве с полезной нагрузкой:

{
  "id": "1847_2",
  "vector": [0.023, -0.041, ...],  // 3072 чисел с плавающей точкой
  "payload": {
    "text": "flyAir — это устройство для пассивного сбора MAC-адресов...",
    "heading": "Как работает MAC Radar",
    "post_id": 1847,
    "post_type": "page",
    "url": "https://wifly.ru/flyair",
    "updated_at": "2026-04-27T11:32:00Z"
  }
}

Разбиение по заголовкам имеет значение: оно сохраняет семантическую целостность каждого фрагмента. Механическое разбиение на 800 символов часто обрезает контекст в неправильном месте.

Что работает хорошо

Беспрепятственный редакционный рабочий процесс. Менеджеры продолжают работать в знакомом WordPress. Они не знают, что их статьи становятся частью системы AI — это просто работает.

Живые данные. Классический RAG с ручной загрузкой файлов быстро становится устаревшим. Синхронизация через webhook держит индекс в актуальном состоянии автоматически.

Разделение публичного и приватного контента. Один WordPress — два слоя: публичный веб-сайт и приватная база знаний. Нет необходимости поддерживать две отдельные системы.

Полнотекстовый + векторный поиск. Гибридный подход закрывает слепые пятна чистого векторного поиска — особенно для названий продуктов и технических терминов.

Подводные камни

save_post срабатывает много раз. При автосохранении, публикации и обновлении мета-полей — хук может срабатывать 3–5 раз для одного действия. Debounce через set_transient обязателен.

Приватные страницы требуют аутентификации REST API. Простой GET /wp-json/wp/v2/pages их не вернет. Вам нужен пароль приложения и заголовок Authorization: Basic ....

HTML в API контента. WordPress возвращает HTML, а не чистый текст. Вам нужен парсер — мы используем cheerio на стороне Node.js. Важно удалять шорткоды, скрипты и блоки объявлений.

Затраты на встраивание. text-embedding-3-large стоит $0,13 за 1M токенов. При 800 символах на чанк и 1000 статьях это тривиально для начальной загрузки — но имейте это в виду для частых обновлений больших баз знаний.

Финальная архитектура

WordPress (cms.wifly.ru)
├── Публичные страницы  → SEO + KB
├── Приватные страницы → KB только
└── Плагин: save_post → webhook (неблокирующий)
          │
          ▼
  Node.js API Server
  ├── Получить контент WP (REST API)
  ├── Парсить HTML → чистый текст
  ├── Разбить по заголовкам (~800 символов)
  ├── Встроить (text-embedding-3-large)
  └── Upsert → Qdrant
               │
               ▼
     Гибридный поиск (плотный + ключевые слова → RRF)
               │
               ▼
     GPT-4o с контекстом KB → ответ пользователю

Заключение

Если у вас уже есть WordPress — не спешите искать другое решение для хранения RAG. Он дает вам все необходимое: редактор, контроль доступа, REST API и хуки для синхронизации. Добавьте векторизацию через webhook — и ваша существующая CMS становится живым, самообновляющимся источником знаний для вашего AI-ассистента.

Полный стек, который мы используем: WordPress → Node.js/Express → Qdrant → OpenAI Realtime API — и это работает в production.


Заинтересованы? Следующая статья расскажет, как построить голосового помощника на основе этой же базы знаний, используя OpenAI Realtime API.

Sergey Nesmachny

Автор

Sergey Nesmachny

Поделиться:

Другие статьи

Продолжайте читать блог

Как я настроил бесплатную профессиональную почту для своего стартапа с помощью Cloudflare (и вы тоже можете)

Каждый стартап нуждается в профессиональном адресе электронной почты. Никто не воспринимает support@gmail.com серьезно. Но платить $6–12 за пользователя в месяц за Google Workspace или Microsoft 365 только для того, чтобы…

17.03.2026
Почему мы создаём европейскую альтернативу Google Analytics?

Почему мы создаём европейскую альтернативу Google Analytics?

Коротко говоря: Google Analytics теряет 30-40% ваших данных из-за баннеров согласия. Суды в Австрии, Франции, Италии и Дании постановили, что это несовместимо с GDPR. Моя команда и я создали EuroMetrics…

26.02.2026
AudioEasyTalk: как я создал плагин для WordPress, чтобы превращать статьи в аудио с помощью ElevenLabs

EasyTalk: как я создал плагин для WordPress, чтобы превращать статьи в аудио с помощью ElevenLabs

Кратко: Я хотел, чтобы мои блог-посты можно было не только читать, но и слушать. Я создал плагин для WordPress, который преобразует посты в естественно звучащее аудио с использованием API ElevenLabs.…

10.02.2026