MAATRIX / Блог / Внутренний чат-бот для сотрудников на своём сервере

Внутренний чат-бот для сотрудников на своём сервере

Внутренний чат-бот для сотрудников на своём сервере

MAATRIX

Завести базу знаний с ИИ-поиском — это половина дела: сотрудники не будут заходить на отдельный сайт, чтобы задать вопрос про регламент отпусков или процесс релиза. Они будут писать в тот чат, где уже сидят целый день, — в Telegram, или пользоваться привычным окном во внутреннем портале. Задача этой статьи — не поднять саму базу знаний (об этом отдельный разбор), а построить бот-интерфейс поверх неё: Telegram-бота и веб-виджет, которые подключаются к AnythingLLM по API, разграничивают доступ по отделам и не превращаются в свалку устаревших ответов через полгода.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Архитектура: зачем нужен отдельный слой поверх AnythingLLM

У AnythingLLM есть встроенный веб-чат и встроенный embed-виджет, и для маленькой команды из десяти человек их обычно достаточно — можно остановиться на этом и не городить лишнего. Но как только компания вырастает до нескольких отделов с разным уровнем доступа, или сотрудники массово сидят в Telegram, а не заходят на внутренние сайты, встроенного интерфейса не хватает по трём причинам.

Во-первых, вход. Открытый веб-чат AnythingLLM либо требует отдельной авторизации (ещё один логин-пароль, который никто не будет держать в голове), либо остаётся полностью открытым — что для базы знаний с чувствительными документами не вариант. Telegram-бот использует уже существующую идентификацию: у сотрудника есть Telegram, и достаточно один раз сверить его user_id со списком в компании.

Во-вторых, маршрутизация. У AnythingLLM один воркспейс — это одна база знаний с одним набором документов. Если бухгалтерии нужен один набор регламентов, а разработчикам — другой, встроенный интерфейс не умеет сам решать, кому что показывать. Это должен делать промежуточный слой — бот, который знает, кто спрашивает, и обращается к нужному воркспейсу.

В-третьих, поведение. Голый API-ответ модели — это сырой текст. Бот-прослойка может обрезать длинные ответы, добавлять кнопки «уточнить» и «это не то, разверни детали», логировать вопросы без ответа для последующей доработки базы — то, что через голый чат AnythingLLM не сделать без правки его исходников.

Схема простая: Telegram / веб-виджет → бот-сервис (Python, слушает вебхуки или API-запросы) → AnythingLLM API (RAG-поиск по документам конкретного воркспейса) → ответ обратно пользователю. Сама база знаний и то, как в неё загружать и индексировать документы, подробно разобраны в статье про внутреннюю базу знаний компании на VPS с ИИ — здесь считаем, что она уже поднята и работает, и сосредоточимся на слое, через который сотрудники до неё дотягиваются.

Бэкенд: AnythingLLM и его API

Если AnythingLLM ещё не установлен, разворачивается он одной Docker-командой, подробности — в статье как установить и настроить AnythingLLM на VPS. Для бота важны три вещи, которые нужно настроить именно ради интеграции.

Первое — API-ключ. В админке AnythingLLM (Settings → API Keys) генерируется ключ, который передаётся в каждом запросе бота в заголовке Authorization: Bearer <ключ>. Ключ один на инстанс, поэтому если нужно разное логирование по отделам — разграничение делается на уровне бота, а не ключа.

Второе — воркспейсы. Каждый отдел или тема — отдельный воркспейс со своим набором документов и своим slug (например, hr, it, sales). Узнать slug воркспейса можно через API:

curl -H "Authorization: Bearer $ANYTHINGLLM_API_KEY" \
  http://127.0.0.1:3001/api/v1/workspaces

Третье — сам эндпоинт чата. Основной запрос, который будет слать бот:

curl -X POST http://127.0.0.1:3001/api/v1/workspace/hr/chat \
  -H "Authorization: Bearer $ANYTHINGLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Как оформить отпуск, если я в командировке?",
    "mode": "chat",
    "sessionId": "user-123456"
  }'

sessionId — обязательно передавайте стабильный идентификатор пользователя (например, его Telegram user_id): AnythingLLM хранит по нему историю диалога, и без этого бот будет каждый раз «забывать», о чём был предыдущий вопрос. Ответ приходит в JSON с полями textResponse (готовый текст) и sources (список фрагментов документов, на основе которых собран ответ) — вторые пригодятся, чтобы бот показывал, откуда взят ответ, а не выдавал его как истину без источника.

Держите AnythingLLM за обратным прокси с TLS, даже если бот стучится к нему по внутреннему адресу localhost — если позже понадобится доступ извне (например, второй сервер с ботом), голый HTTP с API-ключом в заголовке лучше не светить наружу.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть AnythingLLM

Telegram-бот: код и логика

Бот — это тонкая прослойка: принять сообщение, определить пользователя и его воркспейс, отправить запрос в AnythingLLM, вернуть ответ. На Python это укладывается в компактный сервис на aiogram и aiohttp.

import aiohttp
from aiogram import Bot, Dispatcher, F
from aiogram.types import Message

ANYTHINGLLM_URL = "http://127.0.0.1:3001/api/v1"
API_KEY = "ваш_api_ключ"

# статическая карта: telegram_id -> slug воркспейса
# в реальной системе лучше брать из БД или HR-системы
USER_WORKSPACE = {
    111111111: "hr",
    222222222: "it",
    333333333: "sales",
}

bot = Bot(token="TELEGRAM_BOT_TOKEN")
dp = Dispatcher()

@dp.message(F.text)
async def handle_message(message: Message):
    workspace = USER_WORKSPACE.get(message.from_user.id)
    if workspace is None:
        await message.answer("У вас нет доступа к базе знаний. Обратитесь в IT.")
        return

    payload = {
        "message": message.text,
        "mode": "chat",
        "sessionId": str(message.from_user.id),
    }
    headers = {"Authorization": f"Bearer {API_KEY}"}

    async with aiohttp.ClientSession() as session:
        url = f"{ANYTHINGLLM_URL}/workspace/{workspace}/chat"
        async with session.post(url, json=payload, headers=headers, timeout=60) as resp:
            data = await resp.json()

    answer = data.get("textResponse", "Не удалось получить ответ, попробуйте позже.")
    await message.answer(answer[:4000])  # лимит Telegram на длину сообщения

Пара практических нюансов, которые вылезают не сразу. Ответы модели с RAG-поиском по объёмным документам иногда занимают 10–20 секунд — это ориентир, у вас будет зависеть от модели и длины контекста, но в любом случае Telegram нужно предупредить пользователя, что бот «думает» (bot.send_chat_action(chat_id, "typing")), иначе кажется, что бот завис. И второе — оборачивайте запрос к AnythingLLM в try/except с таймаутом: если сервис индексирует новые документы (переиндексация грузит CPU), ответ может задержаться, и бот не должен падать целиком из-за одного медленного запроса.

Про выбор между вебхуком и polling для самого Telegram-бота, если ставите его впервые, — отдельный разбор в статье webhook или polling, что выгоднее; для внутреннего бота на десятки-сотни сотрудников обычно достаточно polling, вебхук имеет смысл при большой нагрузке или при желании держать бота за тем же nginx, что и остальные сервисы.

Веб-виджет для интранета

Часть сотрудников удобнее обслуживать не через Telegram, а через виджет на внутреннем портале — например, если в компании уже есть Confluence-подобная страница или самодельный интранет. У AnythingLLM есть готовый embed-виджет: в админке Settings → Embed Chat Widget создаётся виджет, привязанный к конкретному воркспейсу, и выдаётся сниппет вида:

<script
  data-embed-id="ваш-embed-id"
  data-base-api-url="https://ваш-домен/api/embed"
  src="https://ваш-домен/embed/anythingllm-chat-widget.min.js">
</script>

Минус готового виджета — он завязан на один воркспейс, и разграничение по отделам через него не сделать: у каждого отдела был бы свой скрипт на своей внутренней странице, что нормально, если страницы отдела и так разделены. Если же нужен единый вход с определением отдела по залогиненному пользователю портала, проще собрать минимальный собственный фронтенд — статичную HTML-страницу с полем ввода, которая берёт идентификатор пользователя из SSO-сессии интранета и стучится не напрямую в AnythingLLM, а в тот же бот-сервис (достаточно добавить в него HTTP-эндпоинт рядом с Telegram-хендлером), который уже решает, в какой воркспейс идти. Так логика доступа остаётся в одном месте, а не дублируется между ботом и виджетом.

Разграничение доступа по отделам

Базовый вариант — статическая карта «пользователь → воркспейс», как в примере кода выше. Она работает, но требует ручной правки при каждом найме и увольнении. На практике для компании от 20–30 человек стоит вынести это в БД (SQLite достаточно для старта) и синхронизировать со списком сотрудников из HR-системы или хотя бы из отдельной Google-таблицы, которую HR ведёт сам.

Более гибкая модель — не «один пользователь = один воркспейс», а сопоставление многие-ко-многим: у пользователя может быть доступ к нескольким воркспейсам (например, тимлид видит и IT-базу, и общую корпоративную). Удобный вариант — один воркспейс «общий» с документами для всех, плюс отдельные воркспейсы для отделов, и роутинг по явной команде /hr, /it перед вопросом.

CREATE TABLE access (
    telegram_id INTEGER NOT NULL,
    workspace_slug TEXT NOT NULL,
    PRIMARY KEY (telegram_id, workspace_slug)
);

Отдельный момент — сама AnythingLLM в многопользовательском режиме (Settings → Users) умеет заводить учётные записи с ролями и ограничивать доступ к воркспейсам на уровне инструмента. Это полезный второй рубеж: даже если кто-то обойдёт бота и достучится до AnythingLLM напрямую, без своей учётки или API-ключа он не увидит чужие документы. Не полагайтесь только на логику бота — держите оба слоя.

Поддержание базы в актуальном виде

Бот с доступом к устаревшей базе быстро теряет доверие сотрудников: пара неверных ответов про отпускные дни или процесс согласования — и люди возвращаются к вопросам в общем чате. Три практики снижают этот риск.

Автоматическая переиндексация. Если документы лежат в папке, синхронизируемой из Google Drive или Nextcloud, настройте cron-задачу, которая раз в день (или чаще для быстро меняющихся регламентов) вызывает переиндексацию воркспейса через API:

curl -X POST http://127.0.0.1:3001/api/v1/workspace/hr/update-embeddings \
  -H "Authorization: Bearer $ANYTHINGLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"adds": ["hr/otpusk-2026.pdf"], "deletes": []}'

Логирование вопросов без ответа. Добавьте в бот сохранение в отдельную таблицу или файл всех случаев, когда модель отвечает в духе «в документах нет информации» — это готовый список тем, которые нужно задокументировать, а не гадать, чего не хватает в базе.

if "нет информации" in answer.lower() or not data.get("sources"):
    log_unanswered(message.from_user.id, message.text)

Владелец за каждым воркспейсом. Назначьте на каждый отдел конкретного человека, который раз в месяц просматривает лог неотвеченных вопросов и обновляет документы — без владельца база стагнирует уже через пару месяцев, даже если техническая часть работает идеально. Это организационная мера, но именно она чаще всего решает, будет бот полезен через полгода или превратится в заброшенный инструмент.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть AnythingLLM

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Можно ли обойтись без Telegram-бота и оставить только веб-виджет AnythingLLM?

Можно, если у компании уже есть привычка заходить на внутренний портал. На практике Telegram даёт заметно более высокий процент реального использования — люди пишут туда, не открывая лишнюю вкладку.

Что если сотрудник задаёт вопрос не по теме своего отдела?

Бот увидит только те воркспейсы, к которым у пользователя есть доступ по карте access, и либо ответит «не найдено», либо — если настроен общий воркспейс — поищет там. Прямого доступа к чужим документам через API у него не появится.

Нужна ли отдельная модель для бота, отличная от той, что использует база знаний?

Нет, бот не запускает модель сам — он только вызывает уже настроенный AnythingLLM, который сам решает, какую LLM использовать для генерации ответа (в её настройках).

Как быть с историей переписки и приватностью?

История хранится в AnythingLLM по sessionId на стороне сервера компании, а не у стороннего облачного бота — это и есть основной довод в пользу своего сервера вместо готового SaaS-решения.

Сколько ресурсов нужно серверу под такую связку?

Сам бот лёгкий (несколько сотен мегабайт памяти), но требования зависят в первую очередь от AnythingLLM и модели, которую он использует — ориентиры по памяти есть в статье сколько RAM нужно для AnythingLLM.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.