From bec273d7ac4b15d557d296b1c88c2de8ce121d7a Mon Sep 17 00:00:00 2001 From: Maksim Totmin Date: Fri, 19 Jun 2026 20:53:41 +0700 Subject: [PATCH] refactor: remove global robot_id, require it per-scenario MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- README.md | 134 +++++++++---------------- config/config.yaml.example | 5 - config/scenarios/example_dialog.yaml | 1 + config/scenarios/example_liveness.yaml | 5 +- config/scenarios/mfc_dialog.yaml | 1 + src/ses_monitor/config.py | 2 - src/ses_monitor/session_client.py | 10 +- 7 files changed, 58 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 3d76208..d539298 100644 --- a/README.md +++ b/README.md @@ -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://:6190/ses/robot/list + curl -s http://: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://:6190/ses/ask/?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 diff --git a/config/config.yaml.example b/config/config.yaml.example index fd5ffe9..8e29ccb 100644 --- a/config/config.yaml.example +++ b/config/config.yaml.example @@ -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 — все (включая черновики) diff --git a/config/scenarios/example_dialog.yaml b/config/scenarios/example_dialog.yaml index fa3b592..2627bb6 100644 --- a/config/scenarios/example_dialog.yaml +++ b/config/scenarios/example_dialog.yaml @@ -16,6 +16,7 @@ description: > enabled: false # Пример выключен по умолчанию interval: 600 timeout: 45 +# robot_id: "uuid-вашего-робота" # ОБЯЗАТЕЛЬНО: ID робота из /ses/robot/listall restart_closed: false # Не переоткрывать закрытые сессии steps: diff --git a/config/scenarios/example_liveness.yaml b/config/scenarios/example_liveness.yaml index 2c399e6..c87cf41 100644 --- a/config/scenarios/example_liveness.yaml +++ b/config/scenarios/example_liveness.yaml @@ -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: "Здравствуйте" diff --git a/config/scenarios/mfc_dialog.yaml b/config/scenarios/mfc_dialog.yaml index c3059ee..0744784 100644 --- a/config/scenarios/mfc_dialog.yaml +++ b/config/scenarios/mfc_dialog.yaml @@ -13,6 +13,7 @@ description: > enabled: true interval: 60 timeout: 15 +robot_id: "8ae503dc-ee33-4a6c-a6cf-3dcad3900986_prod" steps: - send: "Здравствуйте" diff --git a/src/ses_monitor/config.py b/src/ses_monitor/config.py index e924e54..245f1a6 100644 --- a/src/ses_monitor/config.py +++ b/src/ses_monitor/config.py @@ -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), diff --git a/src/ses_monitor/session_client.py b/src/ses_monitor/session_client.py index 5f80378..98dcfbc 100644 --- a/src/ses_monitor/session_client.py +++ b/src/ses_monitor/session_client.py @@ -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) # --------------- Архив диалогов ---------------