refactor: remove global robot_id, require it per-scenario

- config.py: remove robot_id field from SESConfig (no global default)
- session_client.py: robot_id is now REQUIRED in ask(), no fallback
- mfc_dialog.yaml: add explicit robot_id
- example_*.yaml: add commented robot_id placeholder, fix outdated comments
- config.yaml.example: remove robot_id from ses section
- README: full audit — remove archive references, fix deployment docs,
    fix scenario format docs, remove outdated sections
This commit is contained in:
Maksim Totmin 2026-06-19 20:53:41 +07:00
parent 9505742b96
commit bec273d7ac
7 changed files with 58 additions and 100 deletions

134
README.md
View File

@ -7,8 +7,7 @@
Watchdog-демон, который:
- **Проверяет доступность** SES API каждые 30 секунд
- **Имитирует диалоги пользователей** по сценариям, описанным в YAML-файлах
- **Анализирует архив** диалогов на ошибки (unknown, not found, no data)
- **Отправляет метрики в Zabbix** через `zabbix_sender`
- **Отправляет метрики в Zabbix** через zabbix_sender или UserParameter
SES — универсальная сценарная машина, поэтому мониторинг не привязан к конкретному проекту. Один и тот же демон может обслуживать любой сценарий: голосового ассистента, чат-бота, IVR, диалогов МФЦ, медорганизаций и любых других.
@ -18,23 +17,23 @@ SES — универсальная сценарная машина, поэтом
┌─────────────────────────────────────────────────────┐
│ ses-monitor (watchdog-демон) │
│ │
│ ┌──────────────┐ ┌────────────────┐ ┌──────────┐
│ │ LivenessCheck │ │ ScenarioRunner │ │ Archive │
│ │ (30 сек) │ │ (по YAML) │ │ Auditor │
│ │ │ │ │ │ (10 мин) │
│ │ GET /hostname│ │ POST /ses/ask │ │ /archive │
│ │ GET /license │ │ шаги+expect │ │ /list │
│ └──────┬───────┘ └───────┬─────────┘ └────┬─────┘
│ │ │
│ ┌──────▼──────────────────▼──────────────────▼─────┐│
│ │ ZabbixSender (zabbix_sender) ││
│ └─────────────────────────────────────────────────┘│
└────────────────────────────────────────────────────┘
│ TCP 10051
┌───────────▼───────────┐
│ Zabbix Server │
│ (триггеры, алерты) │
└───────────────────────┘
│ ┌──────────────┐ ┌────────────────┐
│ │ LivenessCheck │ │ ScenarioRunner │
│ │ (30 сек) │ │ (по YAML) │
│ │ │ │ │
│ │ GET /hostname│ │ POST /ses/ask │
│ │ GET /license │ │ шаги+expect │
│ └──────┬───────┘ └───────┬─────────┘
│ │ │
│ ┌──────▼──────────────────▼──────┐
│ │ Zabbix (sender / UP)
│ └──────────────┬─────────────────┘
└─────────────────┼────────────────────────────────────┘
│ TCP 10051 / JSON file
┌───────────▼───────────┐
│ Zabbix Server │
│ (триггеры, алерты) │
└───────────────────────┘
```
@ -69,29 +68,35 @@ Zabbix агент по запросу сервера запускает скри
```
/opt/ses-monitor/
├── ses_monitor/ # Python-пакет
│ ├── __init__.py # Версия, метаданные
│ ├── __main__.py # Точка входа: python -m ses_monitor
│ ├── main.py # Главный asyncio watchdog-цикл
│ ├── config.py # Загрузка YAML-конфигурации
│ ├── checker.py # Liveness-проверки (API, лицензия, роботы)
│ ├── session_client.py # HTTP-клиент для SES API
│ ├── scenario_runner.py # Движок выполнения сценариев
│ ├── log_client.py # Чтение логов SES
│ ├── zabbix.py # Интеграция с Zabbix (zabbix_sender)
│ └── utils.py # Утилиты (логирование, JSON-пути, проверки)
├── src/
│ └── ses_monitor/ # Python-пакет (src-layout)
│ ├── __init__.py # Версия, метаданные
│ ├── __main__.py # Точка входа: python -m ses_monitor
│ ├── main.py # Главный asyncio watchdog-цикл
│ ├── config.py # Загрузка YAML-конфигурации
│ ├── checker.py # Liveness-проверки (API, лицензия, роботы)
│ ├── session_client.py # HTTP-клиент для SES API
│ ├── scenario_runner.py # Движок выполнения сценариев
│ ├── log_client.py # Чтение логов SES (задел на будущее)
│ ├── zabbix.py # Интеграция с Zabbix (sender / UserParameter)
│ └── utils.py # Утилиты (логирование, JSON-пути, проверки)
├── config/
│ ├── config.yaml # Основная конфигурация
│ ├── config.yaml.example # Шаблон конфигурации (скопировать в config.yaml)
│ └── scenarios/
│ ├── example_liveness.yaml # Пример: быстрая проверка
│ └── example_dialog.yaml # Пример: многошаговый диалог
│ ├── example_dialog.yaml # Пример: многошаговый диалог
│ └── mfc_dialog.yaml # Production: диалог с МФЦ
├── zabbix/
│ ├── zbx_ses_template.xml # Шаблон Zabbix (импорт в frontend)
│ └── zbx_ses_monitor.conf # UserParameter для Zabbix Agent (резерв)
│ ├── zbx_ses_template_5.0.xml # Шаблон для sender (trapper)
│ ├── zbx_ses_template_5.0_up.xml # Шаблон для UserParameter
│ └── zbx_ses_monitor.conf # UserParameter для Zabbix агента
├── scripts/
│ └── zbx_get.py # Скрипт чтения JSON для UserParameter
├── systemd/
│ └── ses-monitor.service # Systemd unit
│ └── ses-monitor.service # Systemd unit
├── tests/
│ └── test_scenario_runner.py
│ ├── __init__.py
│ └── test_scenario_runner.py # 48 тестов
├── pyproject.toml # Зависимости и метаданные пакета
└── README.md
```
@ -139,7 +144,6 @@ nano config/config.yaml
```yaml
ses:
base_url: "http://10.101.60.31:6190" # URL вашего SES API
robot_id: "" # ID робота из GET /ses/robot/listall
channel: "monitoring" # Имя канала в архиве
request_timeout: 30
@ -206,8 +210,8 @@ interval: 300
# Таймаут HTTP-запроса в секундах
timeout: 30
# ID робота (если не указан — берётся из config.yaml)
robot_id: "ваш-uuid-робота"
# ID робота (обязательно). Узнать: GET /ses/robot/listall
robot_id: "uuid-вашего-робота"
# Канал (если не указан — берётся из config.yaml)
channel: "monitoring"
@ -263,10 +267,10 @@ path: "request.data[0].reply.confidence"
1. **Узнайте robot_id** вашего сценария:
```bash
curl -s http://<ses-host>:6190/ses/robot/list
curl -s http://<ses-host>:6190/ses/robot/listall
```
2. **Пропишите robot_id** в `config/config.yaml`
2. **Пропишите robot_id** в файле сценария (поле `robot_id` в YAML)
3. **Создайте файл сценария**, описав шаги диалога:
```bash
@ -279,9 +283,7 @@ path: "request.data[0].reply.confidence"
curl -X POST "http://<ses-host>:6190/ses/ask/<robot_id>?text=Привет&channel=monitoring"
```
5. **Добавьте item в Zabbix** для вашего сценария (см. комментарий в `zbx_ses_template.xml`)
6. **Запустите демон** и проверьте что метрики идут в Zabbix
5. **Запустите демон** — метрики агрегируются автоматически (шаблон Zabbix универсален)
## Расширение системы
@ -295,48 +297,8 @@ path: "request.data[0].reply.confidence"
### Добавление новой метрики
1. В нужном модуле добавить поле в датакласс-результат
2. В методе `to_zabbix_metrics()` добавить соответствующий ключ
3. В `main.py` вызвать `self._zabbix.add(key, value)`
4. Добавить item в Zabbix шаблон
### Мониторинг SEE, SMC, TTS, QAS, SPR
Заложена возможность расширения для мониторинга внутренних сервисов SES.
**SEE (распознавание намерений/сущностей):**
```python
for entry in entries:
for model in entry.models:
if model.startswith("see:"):
see_model = model.split(":", 1)[1]
# Анализ точности распознавания
```
**SMC (классификация):**
```python
if model.startswith("smc:"):
# Отслеживание дрифта классификации
```
**TTS (синтез речи):**
```python
# Проверка наличия голосовых ответов в реплаях
if any(a.get("voice") for a in entry.raw.get("reply", {}).get("answers", [])):
tts_ok_count += 1
```
**QAS (вопросно-ответная система):**
```python
# Поле qas в archive entry — проверка наполненности
if not entry.raw.get("current", {}).get("qas"):
qas_empty_count += 1
```
**SPR (распознавание речи):**
```python
# Косвенная проверка через тестовые сценарии,
# которые отправляют аудио и проверяют текстовую расшифровку
```
2. В `main.py` вызвать `self._zabbix.add(key, value)`
3. Добавить item в Zabbix шаблон
### Prometheus/Grafana

View File

@ -12,11 +12,6 @@ ses:
# Базовый URL SES API
base_url: "http://YOUR_SES_HOST:6190"
# ID робота (сценария) для проверок.
# Узнать ID: GET /ses/robot/list или /ses/robot/listall
# Каждый сценарий может переопределить robot_id в своём YAML-файле
robot_id: "YOUR_ROBOT_ID"
# Эндпоинт для получения списка роботов:
# /ses/robot/list — только опубликованные
# /ses/robot/listall — все (включая черновики)

View File

@ -16,6 +16,7 @@ description: >
enabled: false # Пример выключен по умолчанию
interval: 600
timeout: 45
# robot_id: "uuid-вашего-робота" # ОБЯЗАТЕЛЬНО: ID робота из /ses/robot/listall
restart_closed: false # Не переоткрывать закрытые сессии
steps:

View File

@ -2,11 +2,11 @@
# Пример сценария: базовая проверка liveness робота
# =============================================================================
# Проверяет что робот отвечает на приветствие и не возвращает ошибок API.
# Подходит для любого сценария SES — достаточно заменить robot_id в config.yaml.
# robot_id указывается в этом же файле (не наследуется из глобального конфига).
#
# ИНСТРУКЦИЯ: скопируйте этот файл, переименуйте и адаптируйте под ваш проект.
# cp example_liveness.yaml my_project_liveness.yaml
# # отредактируйте: name, steps, expect
# # отредактируйте: name, robot_id, steps, expect
name: "example: liveness"
description: >
@ -17,6 +17,7 @@ description: >
enabled: false # Пример выключен по умолчанию
interval: 60
timeout: 15
# robot_id: "uuid-вашего-робота" # ОБЯЗАТЕЛЬНО: ID робота из /ses/robot/listall
steps:
- send: "Здравствуйте"

View File

@ -13,6 +13,7 @@ description: >
enabled: true
interval: 60
timeout: 15
robot_id: "8ae503dc-ee33-4a6c-a6cf-3dcad3900986_prod"
steps:
- send: "Здравствуйте"

View File

@ -39,7 +39,6 @@ class SESConfig:
"""Настройки подключения к SES серверу."""
base_url: str = "http://10.101.60.31:6190"
robot_id: str = "" # ID робота задаётся в config.yaml под конкретный проект
channel: str = "monitoring"
request_timeout: int = 30
robot_list_endpoint: str = "/ses/robot/list" # или "/ses/robot/listall"
@ -190,7 +189,6 @@ def _build_config(data: Dict[str, Any]) -> AppConfig:
ses_data = data.get("ses", {})
ses = SESConfig(
base_url=ses_data.get("base_url", SESConfig.base_url),
robot_id=ses_data.get("robot_id", SESConfig.robot_id),
channel=ses_data.get("channel", SESConfig.channel),
request_timeout=ses_data.get("request_timeout", SESConfig.request_timeout),
robot_list_endpoint=ses_data.get("robot_list_endpoint", SESConfig.robot_list_endpoint),

View File

@ -137,7 +137,6 @@ class SESClient:
def __init__(self, config: SESConfig):
self._base_url = config.base_url.rstrip("/")
self._robot_id = config.robot_id
self._channel = config.channel
self._timeout = config.request_timeout
self._robot_list_endpoint = config.robot_list_endpoint
@ -221,8 +220,8 @@ class SESClient:
self,
text: str,
*,
robot_id: str,
session_id: Optional[str] = None,
robot_id: Optional[str] = None,
channel: Optional[str] = None,
restart_closed: bool = False,
) -> AskResponse:
@ -230,15 +229,16 @@ class SESClient:
Args:
text: Текст сообщения.
robot_id: ID робота (обязательно, не наследуется из конфига).
session_id: UUID сессии. Если None генерируется новый.
robot_id: ID робота. Если None из конфигурации.
channel: Канал. Если None из конфигурации.
restart_closed: Переоткрывать закрытую сессию.
Returns:
AskResponse с ответом робота.
"""
rid = robot_id or self._robot_id
if not robot_id:
raise ValueError("robot_id обязателен для POST /ses/ask")
ch = channel or self._channel
if session_id is None:
@ -251,7 +251,7 @@ class SESClient:
"restartClosed": int(restart_closed),
}
data = await self._post(f"/ses/ask/{rid}", params=params)
data = await self._post(f"/ses/ask/{robot_id}", params=params)
return AskResponse.from_json(data)
# --------------- Архив диалогов ---------------