Для тех, кто создает 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 сервера обработчик вебхука:
- Получает
post_id - Загружает последний контент через WP REST API
- Парсит HTML → извлекает чистый текст
- Разделяет на части по заголовкам h2/h3 (~800 символов каждая)
- Векторизирует каждую часть используя
text-embedding-3-large - Обновляет записи в 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.

