WeCom (корпоративный WeChat)
Команды установки Python-зависимостей на этой странице рассчитаны на чекаут исходников, подготовленный через PM. После изменения зависимостей заново активируйте чекаут и перезапустите Hermes.
Подключите Hermes к WeCom (企业微信) — корпоративному мессенджеру от Tencent. Адаптер работает через WebSocket-шлюз AI Bot, поэтому связь двусторонняя и идёт в реальном времени: ни публичный эндпоинт, ни вебхук не нужны.
См. также: WeCom Callback — настройка входящего вебхука.
Предварительные требования
- Организационный аккаунт WeCom
- AI Bot, созданный в админ-консоли WeCom
- Bot ID и Secret со страницы учётных данных бота
- Python-пакеты:
aiohttpиhttpx
Настройка
Шаг 1. Создайте AI Bot
Рекомендуемый способ: создание по QR-коду (одна команда)
hermes gateway setup
Выберите WeCom и отсканируйте QR-код мобильным приложением WeCom. Hermes сам создаст бот-приложение с нужными правами и сохранит учётные данные.
Мастер настройки выполнит следующее:
- Покажет QR-код прямо в терминале
- Подождёт, пока вы отсканируете его приложением WeCom
- Автоматически получит Bot ID и Secret
- Проведёт вас через настройку контроля доступа
Альтернатива: ручная настройка
Если создание по QR-коду недоступно, мастер переключается на ручной ввод:
- Войдите в админ-консоль WeCom
- Перейдите в Applications → Create Application → AI Bot
- Задайте имя и описание бота
- Скопируйте Bot ID и Secret со страницы учётных данных
- Запустите
hermes gateway setup, выберите WeCom и введите учётные данные по запросу
Храните Bot Secret в секрете. Любой, у кого он есть, сможет выдать себя за вашего бота.
Шаг 2. Настройте Hermes
Вариант A: интерактивная настройка (рекомендуется)
hermes gateway setup
Выберите WeCom и следуйте подсказкам. Мастер проведёт вас через:
- Учётные данные бота (по QR-коду или вручную)
- Параметры контроля доступа (список разрешений, режим сопряжения или открытый доступ)
- Домашний канал для уведомлений
Вариант B: ручная конфигурация
Добавьте в ~/.hermes/.env следующее:
WECOM_BOT_ID=your-bot-id
WECOM_SECRET=your-secret
# Опционально: ограничить доступ
WECOM_ALLOWED_USERS=user_id_1,user_id_2
# Опционально: домашний канал для cron/уведомлений
WECOM_HOME_CHANNEL=chat_id
Шаг 3. Запустите шлюз
hermes gateway
Возможности
- Транспорт WebSocket — постоянное соединение, публичный эндпоинт не нужен
- Личные и групповые сообщения — настраиваемые политики доступа
- Списки разрешённых отправителей для каждой группы — точный контроль над тем, кто общается с ботом в конкретной группе
- Поддержка медиа — загрузка и скачивание изображений, файлов, голоса и видео
- Медиа с AES-шифрованием — входящие вложения расшифровываются автоматически
- Контекст цитирования — сохраняется привязка ответа к ветке
- Рендеринг Markdown — форматированные ответы
- Корреляция ответов — ответ привязывается к контексту входящего сообщения
- Автопереподключение — экспоненциальная выдержка при обрывах связи
Адаптер WeCom стримит ответы нативно через протокол WeCom msgtype: "stream":
клиент показывает пузырь «печатает» сразу после начала хода, а ответ рендерится
по токенам в одном и том же пузыре по мере генерации. Прогресс вызова инструментов
сворачивается в тот же пузырь. Нативный стриминг подчиняется глобальному
переключателю стриминга, который по умолчанию выключен: включите его через
streaming.enabled: true в config.yaml. Платформенная настройка WeCom
display.platforms.wecom.streaming (по умолчанию true) действует, только пока
глобальный переключатель включён; поставьте false, чтобы в WeCom сохранить доставку одним сообщением.
Параметры конфигурации
Задаются в config.yaml в секции platforms.wecom.extra:
| Ключ | По умолчанию | Описание |
|---|---|---|
bot_id | — | ID бота WeCom AI Bot (обязательно) |
secret | — | Secret бота WeCom AI Bot (обязательно) |
websocket_url | wss://openws.work.weixin.qq.com | URL WebSocket-шлюза |
dm_policy | open | Доступ к личным сообщениям: open, allowlist, disabled, pairing |
group_policy | open | Доступ к группам: open, allowlist, disabled |
allow_from | [] | ID пользователей, которым разрешены личные сообщения (при dm_policy=allowlist) |
group_allow_from | [] | ID разрешённых групп (при group_policy=allowlist) |
groups | {} | Настройки по каждой группе (см. ниже) |
stream_keepalive_enabled | false | Отправлять периодические keepalive-кадры, чтобы продлевать ~6-минутное окно reply-стрима WeCom на длинных ходах |
stream_keepalive_interval_seconds | 120 | Периодичность keepalive-кадров, когда включено |
stream_safe_duration_seconds | 330 | Возраст стрима, после которого финализация предпочитает надёжную проактивную отправку |
Политики доступа
Политика личных сообщений
Определяет, кто может писать боту в личку:
| Значение | Поведение |
|---|---|
open | Писать боту может кто угодно (по умолчанию) |
allowlist | Писать могут только ID пользователей из allow_from |
disabled | Все личные сообщения игнорируются |
pairing | Режим сопряжения (для первичной настройки) |
WECOM_DM_POLICY=allowlist
Политика групп
Определяет, в каких группах бот отвечает:
| Значение | Поведение |
|---|---|
open | Бот отвечает во всех группах (по умолчанию) |
allowlist | Бот отвечает только в группах, чьи ID перечислены в group_allow_from |
disabled | Все групповые сообщения игнорируются |
WECOM_GROUP_POLICY=allowlist
Списки разрешённых отправителей для каждой группы
Чтобы управлять доступом более тонко, можно ограничить круг пользователей, которым разрешено общаться с ботом внутри конкретных групп. Настраивается в config.yaml:
platforms:
wecom:
enabled: true
extra:
bot_id: "your-bot-id"
secret: "your-secret"
group_policy: "allowlist"
group_allow_from:
- "group_id_1"
- "group_id_2"
groups:
group_id_1:
allow_from:
- "user_alice"
- "user_bob"
group_id_2:
allow_from:
- "user_charlie"
"*":
allow_from:
- "user_admin"
Как это работает:
- Параметры
group_policyиgroup_allow_fromрешают, разрешена ли группа в принципе. - Если группа прошла проверку верхнего уровня, список
groups.<group_id>.allow_from(если он задан) дополнительно ограничивает, кто из участников этой группы может общаться с ботом. - Запись с шаблоном
"*"задаёт значение по умолчанию для групп, не перечисленных явно. - В списке разрешений работает шаблон
*— он пропускает всех пользователей; регистр в записях не учитывается. - Записи можно при желании указывать с префиксом
wecom:user:илиwecom:group:— префикс отбрасывается автоматически.
Если для группы не задан allow_from, разрешены все её участники (при условии, что сама группа прошла проверку политики верхнего уровня).
Поддержка медиа
Входящие (приём)
Адаптер принимает медиавложения от пользователей и кеширует их локально, чтобы агент мог их обработать:
| Тип | Как обрабатывается |
|---|---|
| Изображения | Скачиваются и кешируются локально. Поддерживаются как ссылки на изображения, так и base64. |
| Файлы | Скачиваются и кешируются. Имя файла берётся из исходного сообщения. |
| Голос | Если доступна расшифровка голосового сообщения, её текст извлекается. |
| Смешанные сообщения | Сообщения WeCom смешанного типа (текст + изображения) разбираются, все составляющие извлекаются. |
Цитируемые сообщения: медиа из цитируемых сообщений (на которые отвечают) тоже извлекается, чтобы у агента был контекст того, на что отвечает пользователь.
Расшифровка медиа с AES-шифрованием
Часть входящих медиавложений WeCom шифрует алгоритмом AES-256-CBC. Адаптер делает это сам:
- Когда у входящего медиа есть поле
aeskey, адаптер скачивает зашифрованные байты и расшифровывает их по AES-256-CBC с дополнением PKCS#7. - Ключ AES — это значение поля
aeskey, раскодированное из base64 (должно быть ровно 32 байта). - IV берётся из первых 16 байт ключа.
- Понадобится Python-пакет
cryptography(hermes pm repair).
Настраивать ничего не нужно: расшифровка происходит прозрачно при получении зашифрованного медиа.
Исходящие (отправка)
| Метод | Что отправляет | Лимит размера |
|---|---|---|
send | Текстовые сообщения в Markdown | 4000 символов на сообщение; более длинный текст разбивается на несколько сообщений |
send_image / send_image_file | Нативные сообщения с изображением | 10 МБ |
send_document | Файловые вложения | 20 МБ |
send_voice | Голосовые сообщения (для нативного голоса только формат AMR) | 2 МБ |
send_video | Видеосообщения | 10 МБ |
Загрузка по частям: файлы загружаются кусками по 512 КБ через трёхэтапный протокол (init → chunks → finish). Адаптер делает это сам.
Автоматическое понижение: если медиа превышает лимит размера для своего нативного типа, но укладывается в абсолютный лимит файла в 20 МБ, оно автоматически отправляется как обычное файловое вложение:
- Изображения > 10 МБ → отправляются файлом
- Видео > 10 МБ → отправляются файлом
- Голос > 2 МБ → отправляется файлом
- Аудио не в AMR → отправляется файлом (для нативного голоса WeCom поддерживает только AMR)
Файлы сверх абсолютного лимита в 20 МБ отклоняются, а в чат отправляется информационное сообщение.
Ответы в режиме reply
Получив сообщение через WeCom callback, адаптер запоминает ID входящего запроса. Если ответ отправляется, пока контекст запроса ещё активен, адаптер использует режим reply WeCom (aibot_respond_msg) и привязывает ответ напрямую к входящему сообщению. В клиенте WeCom разговор от этого выглядит естественнее.
Когда активен нативный reply-стрим, ответ передаётся постепенно через кадры reply-режима msgtype: "stream". Если контекст входящего запроса истёк, недоступен или кадр стрима не прошёл, адаптер переключается на проактивную отправку через aibot_send_msg.
Режим reply работает и с медиа: загруженный файл можно отправить ответом на исходное сообщение.
Соединение и переподключение
Адаптер держит постоянное WebSocket-соединение со шлюзом WeCom по адресу wss://openws.work.weixin.qq.com.
Жизненный цикл соединения
- Подключение: открывается WebSocket-соединение и отправляется кадр аутентификации
aibot_subscribeс bot_id и secret. - Heartbeat: каждые 30 секунд уходят ping-кадры на уровне приложения — они держат соединение живым.
- Прослушивание: адаптер непрерывно читает входящие кадры и вызывает колбэки сообщений.
Поведение при переподключении
При потере связи адаптер переподключается с экспоненциальной выдержкой:
| Попытка | Задержка |
|---|---|
| 1-я попытка | 2 секунды |
| 2-я попытка | 5 секунд |
| 3-я попытка | 10 секунд |
| 4-я попытка | 30 секунд |
| 5-я и далее | 60 секунд |
После каждого успешного переподключения счётчик выдержки сбрасывается в ноль. При обрыве все ожидающие futures запросов завершаются с ошибкой, чтобы вызывающий код не завис навсегда.
Дедупликация
Входящие сообщения дедуплицируются по ID сообщений с окном в 5 минут и кешем не более чем на 1000 записей. Так одно и то же сообщение не обрабатывается дважды во время переподключения или сетевых сбоев.
Все переменные окружения
| Переменная | Обязательна | По умолчанию | Описание |
|---|---|---|---|
WECOM_BOT_ID | ✅ | — | ID бота WeCom AI Bot |
WECOM_SECRET | ✅ | — | Secret бота WeCom AI Bot |
WECOM_ALLOWED_USERS | — | (пусто) | ID пользователей через запятую для списка разрешений на уровне шлюза |
WECOM_HOME_CHANNEL | — | — | ID чата для вывода cron/уведомлений |
WECOM_WEBSOCKET_URL | — | wss://openws.work.weixin.qq.com | URL WebSocket-шлюза |
WECOM_DM_POLICY | — | open | Политика доступа к личным сообщениям |
WECOM_GROUP_POLICY | — | open | Политика доступа к группам |
Решение проблем
| Проблема | Что делать |
|---|---|
WECOM_BOT_ID and WECOM_SECRET are required | Задайте обе переменные окружения или настройте их в мастере |
WeCom startup failed: aiohttp not installed | Установите aiohttp: python -c "import pm; pm.sync_venv(['messaging'], explicit=True)" |
WeCom startup failed: httpx not installed | Установите httpx: hermes pm repair |
invalid secret (errcode=40013) | Проверьте, что secret совпадает с учётными данными бота |
Timed out waiting for subscribe acknowledgement | Проверьте сетевой доступ к openws.work.weixin.qq.com |
| Бот не отвечает в группах | Проверьте параметр group_policy и убедитесь, что ID группы есть в group_allow_from |
| Бот игнорирует некоторых пользователей в группе | Проверьте списки allow_from по группам в секции groups |
| Не удаётся расшифровать медиа | Установите cryptography: hermes pm repair |
cryptography is required for WeCom media decryption | Входящее медиа зашифровано AES. Установите: hermes pm repair |
| Голосовые сообщения уходят файлами | Для нативного голоса WeCom поддерживает только формат AMR. Остальные форматы автоматически понижаются до файла. |
Ошибка File too large | У WeCom абсолютный лимит на любую загрузку файла — 20 МБ. Сожмите или разбейте файл. |
| Изображения уходят файлами | Изображения > 10 МБ выходят за нативный лимит и автоматически понижаются до файловых вложений. |
Timeout sending message to WeCom | Возможно, WebSocket отвалился. Поищите в логах сообщения о переподключении. |
WeCom websocket closed during authentication | Проблема с сетью или неверные учётные данные. Проверьте bot_id и secret. |