Initial commit: sd_dispatcher code

This commit is contained in:
Denis Krivchenko
2026-07-04 00:14:13 +07:00
commit 78f0c5d1b1
3 changed files with 1262 additions and 0 deletions
+303
View File
@@ -0,0 +1,303 @@
import os
import sys
from dotenv import load_dotenv
# Автоматическое определение корневой директории проекта
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Загружаем приватные переменные окружения из вашего .env
load_dotenv('/opt/GigaAM/config/.env')
# Карта соответствия: Переменная диспетчера -> Ключ из вашего .env
# Объявлена в самом начале, чтобы скрипт знал имена карточек до обращения к API
CREDENTIALS_MAP = {
"FTP_PASS": os.getenv("PW_ID_FTP", "").strip(' "\'\r\n'), # Мягкий поиск по ключу
"API_KEY": os.getenv("PW_ID_SD", "").strip(' "\'\r\n'), # Ищет карточку по значению из .env
"AD_PASSWORD": os.getenv("PW_ID_AD", "").strip(' "\'\r\n'), # Ищет карточку по значению из .env
}
# Единственный токен, который может быть зашит в локальном .env, а не в Пассворке
EMBEDDING_API_TOKEN = os.getenv("EMBEDDING_API_TOKEN", "").strip(' "\'\r\n')
# =====================================================================
# ИНТЕГРАЦИЯ С PASSWORK (Динамическая загрузка паролей)
# =====================================================================
PASSWORK_DIR = "/opt/passwork"
if PASSWORK_DIR not in sys.path:
sys.path.insert(0, PASSWORK_DIR)
try:
from passwork import get_passwork_secrets
print("LOG: 🔐 Обращение к модулю Passwork и выгрузка сейфа для GigaAM...")
# Формируем и пробрасываем список обязательных карточек для автозащиты от коллизий
required_to_wait = [name for name in CREDENTIALS_MAP.values() if name]
passwork_pool = get_passwork_secrets(required_cards=required_to_wait)
except Exception as e:
print(f"❌ КРИТИЧЕСКАЯ ОШИБКА: Не удалось подключить модуль Passwork: {e}")
sys.exit(1)
print("LOG: 🔐 Распределение секретов и логинов по модулям диспетчера...")
for var_name, card_name in CREDENTIALS_MAP.items():
if not card_name:
print(f"⚠️ Внимание: Название карточки для {var_name} не задано в .env. Используется локальное значение.")
continue
clean_card_name = card_name.strip(' "\'\r\n')
card_data = passwork_pool.get(clean_card_name)
if card_data is None:
print(f"❌ КРИТИЧЕСКАЯ ОШИБКА: Карточка с именем '{clean_card_name}' не найдена в сейфе Passwork!")
sys.exit(1)
# Динамически заполняем пароль/токен в глобальную область видимости config.py
globals()[var_name] = card_data["password"].strip(' "\'\r\n')
# Динамически вытаскиваем сопутствующие логины из карточек
if var_name == "FTP_PASS":
globals()["FTP_USER"] = card_data.get("login", "").strip(' "\'\r\n')
elif var_name == "AD_PASSWORD":
globals()["AD_USER"] = card_data.get("login", "").strip(' "\'\r\n')
print(f"{var_name} и логин успешно синхронизированы из карточки '{clean_card_name}'")
# Интеграция с АТС (Выгрузка записей звонков по FTP)
FTP_HOST = "10.0.0.7"
FTP_DIR = "/zabbix"
FTP_ARCHIVE = "/zabbix/archive"
# =====================================================================
# 1. СИСАДМИНСКАЯ ИНФРАСТРУКТУРА, ФЛАСК И ЛОГИРОВАНИЕ
# =====================================================================
# Порт, на котором Flask-вебхук слушает входящие JSON-пакеты от ManageEngine
WEBHOOK_PORT = 5000
# Путь к файлу сквозного отладочного лога диспетчера
WEBHOOK_LOG_FILE = "/opt/GigaAM/dispatcher_log.txt"
# Включение профилирования (замеры времени выполнения этапов обработки заявки)
ENABLE_PROFILING = True
# Ограничение длины текста исторических документов при выводе в файл лога
LOG_EXAMPLE_MAX_CHARS = 350
# Ограничение длины строки текста заявки при выводе в интерактивную CLI-консоль
CLI_LOG_MAX_CHARS = 80
# =====================================================================
# 2. КОНВЕЙЕР РАСПОЗНАВАНИЯ РЕЧИ И ПЕРВИЧНОЙ ОБРАБОТКИ (ASR PIPELINE)
# =====================================================================
# Флаг включения суммаризации текста звонка (сейчас отключен для экономии CPU)
ENABLE_TEXT_SUMMARIZATION = False
# Рабочие директории для хранения входящих аудиозаписей и результатов транскрибации
AUDIO_DIR = "/opt/GigaAM/audio"
JSON_DIR = "/opt/GigaAM/json"
# Длина одного чанка (куска аудио) при нарезке для модели распознавания (в секундах)
CHUNK_SECONDS = 25
# Пути к словарям терминов и золотых ИТ-токенов для исправления ошибок транскрипции
DICT_PATH = "/opt/dict/dict.json"
GOLDEN_DICT_PATH = "/opt/dict/GOLDEN_TERMS.json"
# Переключатели модулей постобработки и исправления текста после ASR
ENABLE_REGEX_CORRECTION = True # Исправление по регулярным выражениям
ENABLE_FUZZY_CORRECTION = True # Нечёткое исправление по Левенштейну/словарям
# =====================================================================
# 3. ИНТЕГРАЦИЯ С MANAGEENGINE SERVICE DESK API
# =====================================================================
# Корпоративный URL и API-эндпоинт для работы с заявками по протоколу v3
SERVER_URL = "https://servicedesk.sibcem.ru:8080"
ENDPOINT = f"{SERVER_URL}/api/v3/requests"
# Системный email-адрес учетной записи ИИ-сортировщика в Service Desk
DEFAULT_REQUESTER = "ai@sibcem.ru"
# =====================================================================
# 4. ИНТЕГРАЦИЯ С ACTIVE DIRECTORY (LDAP)
# =====================================================================
# Контроллер домена и базовая точка поиска пользователей (Base DN)
AD_SERVER = "ldap://172.16.20.20"
AD_BASE = "OU=-Пользователи,DC=sibcem,DC=ru"
# Сетевые тайм-ауты (в секундах) для защиты фоновых потоков от зависания на LDAP-запросах
AD_CONNECT_TIMEOUT = 5 # Время на установку tcp-соединения
AD_RECEIVE_TIMEOUT = 5 # Время ожидания ответа на поисковый запрос
# =====================================================================
# 5. ПОЧТОВЫЕ УВЕДОМЛЕНИЯ И АЛЕРТЫ (SMTP)
# =====================================================================
# Корпоративный почтовый релей и порт (используется стандартный 25 без SSL)
SMTP_SERVER = "srvkem-mail.sibcem.ru"
SMTP_PORT = 25
# Отправитель по умолчанию для ИИ-отчетов
DEFAULT_EMAIL_FROM = "ai@sibcem.ru"
# Списки рассылки и исключений для отправки логов принятия решений
ALERTS_DISABLED_USERS = ["ds.krivochenko@sibcem.ru"]
ALERTS_ALL_TICKETS = ["ds.krivochenko@sibcem.ru"]
# =====================================================================
# 6. ВЕКТОРНЫЙ ПОИСК И ЭМБЕДДИНГИ (QDRANT API)
# =====================================================================
# Адрес, порт и целевая коллекция локального сервера Qdrant
QDRANT_HOST = "localhost"
QDRANT_PORT = 6333
QDRANT_COLLECTION = "tickets"
# Осредненный лимит выборки документов из Qdrant под дальнейший реранкинг (оптимально для CPU)
QDRANT_SEARCH_LIMIT = 20
# Локальная служба генерации векторов (Embedding API)
EMBEDDING_API_URL = "http://127.0.0.1:8010/embed"
EMBEDDING_MODEL_NAME = "multilingual-e5-base"
# =====================================================================
# 7. ПЕРЕРАНЖИРОВАНИЕ ПРЕЦЕДЕНТОВ (CROSS-ENCODER RERANKER)
# =====================================================================
# Адрес выделенной службы реранкера (работает на порту 8081)
RERANKER_URL = "http://127.0.0.1:8081/rerank"
RERANKER_MODEL_NAME = "bge-reranker-v2-m3-Q8_0.gguf"
# Предохранитель от OOM: максимальное количество символов исторического билета, скармливаемое реранкеру
RERANKER_MAX_DOC_CHARS = 1200
# Жесткий тайм-аут сокета (в секундах) на ожидание пачки документов от реранкера
RERANKER_TIMEOUT = 60
# Коэффициент смешивания скоров (Score Blending):
# 1.0 — абсолютная власть реранкера, 0.0 — чистый Qdrant (база векторов)
RERANKER_WEIGHT = 0.7
# =====================================================================
# 8. ЛОКАЛЬНЫЙ ИНТЕЛЛЕКТ И СЕРВЕРЫ LLM / STT
# =====================================================================
# Переключатель вызова большой языковой модели
ENABLE_OLLAMA = True
# ГЛОБАЛЬНЫЙ ДИСПЕТЧЕР И АНАЛИЗАТОР: Классификация тикетов (Тяжелый квант Q8)
OLLAMA_URL = "http://127.0.0.1:8080/v1/chat/completions"
OLLAMA_MODEL = "gemma-4-12b-it-Q8_0"
# РАСПОЗНАВАНИЕ ЗВОНКОВ: Локальный роутер моделей llama.service (Порт 8080)
OLLAMA_SUBJECT_URL = "http://127.0.0.1:8080/v1/chat/completions"
OLLAMA_SUBJECT_MODEL = "gemma-4-E4B-it-Q4_K_M"
# МИКРОСЕРВИС ТРАНСКРИБАЦИИ: Точка подключения к демону GigaAM (Порт 5001)
STT_SERVICE_URL = "http://127.0.0.1:5001/transcribe"
# Размер контекстного окна для слота (динамическое выделение памяти из пула 16384)
OLLAMA_NUM_CTX = 12288
# Параметры инференса текстовой модели
OLLAMA_TEMPERATURE = 0.0 # Строгая детерминированность вывода
OLLAMA_MAX_TOKENS = 65 # Ограничение длины ответа (модели нужно выдать только имя группы)
OLLAMA_TIMEOUT = 1200 # Предельный тайм-аут генерации токенов (в секундах) под CPU-нагрузкой
# =====================================================================
# 9. МАТЕМАТИЧЕСКИЕ ФИЛЬТРЫ, ТРИГГЕРЫ МУСОРА И ПОРОГОВЫЕ ОГРАНИЧЕНИЯ
# =====================================================================
# Осредненный порог относительной уверенности Qdrant. Ниже него — принудительный вызов Qwen 2.5
CONFIDENCE_THRESHOLD = 0.85
# КРИТИЧЕСКИЙ ПРЕДОХРАНИТЕЛЬ: если абсолютный скор лучшего совпадения реранкера ниже этого веса,
# сквозной байпас ИИ запрещается, и билет отправляется на перепроверку в Ollama
FORCE_OLLAMA_SCORE_THRESHOLD = 0.75
# Сколько голосов (совпадений) максимум может забрать одна ИТ-группа в векторном пуле
QDRANT_MAX_VOTES_PER_GROUP = 10
# Абсолютный скор, выше которого заявка считается стопроцентным историческим дублем
EXACT_MATCH_SCORE_THRESHOLD = 0.9
# Фильтры детектирования аномального распределения (маркеры мусорных/непонятных заявок)
GARBAGE_CONFIDENCE_THRESHOLD = 0.01 # Предельно низкая уверенность
GARBAGE_UNIQUE_GROUPS_LIMIT = 4 # Слишком много разных групп в топ-выборке
GARBAGE_VARIANCE_THRESHOLD = 0.02 # Слишком плотный шаг весов (нет явного лидера)
GARBAGE_MIN_GROUPS_FOR_VARIANCE = 2 # Минимальное число конкурентов для оценки дисперсии
GARBAGE_BYPASS_DOMINANCE = 0.90 # Порог доминации, снимающий метку мусора
# Фильтры условий обхода вызова Ollama (Bypass)
OLLAMA_BYPASS_DOMINANCE = 0.95 # Если одна группа заняла 80% топа выборки
OLLAMA_BYPASS_MIN_CONFIDENCE = 0.10 # Минимальная базовая уверенность для байпаса
OLLAMA_ACCEPTANCE_THRESHOLD = 0.65 # Минимальная уверенность логпробов Ollama для принятия ответа
DOMINANCE_TOP_K = 10 # Глубина среза топ-результатов для расчёта коэффициента доминации
LOG_TOP_GROUPS_LIMIT = 5 # Количество позиций истории, выводимых в логи структуры decision_log
# =====================================================================
# 10. СПРАВОЧНИКИ МАРШРУТИЗАЦИИ, КАТЕГОРИИ И БИЗНЕС-ЛОГИКА АВТОМАТИЗАЦИИ
# =====================================================================
# White-list валидных ИТ-групп в системе Service Desk Plus
VALID_GROUPS = [
"CRM", "АХО", "Техподдержка", "1С", "Axapta (Microsoft Dynamics AX)",
"АСУТП", "Босс-Кадровик", "ОПС, видеонаблюдение, СКУД, СМТ",
"Программисты КТЦ", "Системное администрирование", "Телеком и сообщения",
"Битрикс", "СЭД Directum", "Администрирование систем BI", "СБИС", "Служба информационной безопасности", "НСИ"
]
# Группы, для которых открыто прямое автоматическое исполнение Put-запросов назначения через API
ALLOWED_ASSIGNMENT_GROUPS = [
"Техподдержка КЦ", "Техподдержка ХК", "Техподдержка Тимлюй",
"Техподдержка Ангарск", "Техподдержка Искитим", "Техподдержка Топки",
"Техподдержка КТЦ", "Техподдержка Волна", "Техподдержка Москва","Телеком и сообщения"
]
# Матрица ответственных менеджеров групп для отправки алертов о сбоях/назначениях билетов
GROUP_MANAGERS = {
"Техподдержка": ["e.voevodin@sibcem.ru"]
}
# Региональный справочник привязки городов к конкретным Кустовым Центрам (КЦ) и ХК
CITY_SUPPORT = {
"Ангарск": "Техподдержка Ангарск",
"Слюдянка": "Техподдержка Ангарск",
"Искитим": "Техподдержка Искитим",
"Новосибирск": "Техподдержка КТЦ",
"Топки": "Техподдержка Топки",
"Кемерово": "Техподдержка ХК",
"Москва": "Техподдержка Москва",
"Тимлюй": "Техподдержка ХК",
"Каменск": "Техподдержка ХК",
"Улан-Удэ": "Техподдержка ХК",
"Татарский Ключ": "Техподдержка ХК",
"Спас-Клепики": "Техподдержка ХК",
"Красноярск": "Техподдержка КЦ"
}
# Настройки матрицы автоматического создания уровней согласования руководителями
ENABLE_AUTO_APPROVAL = True
APPROVAL_CITIES = ["Новосибирск"]
APPROVAL_GROUPS = ["1С", "1с", "Программисты КТЦ"]
APPROVAL_CATEGORIES = {
"ИТ": ["ПО КТЦ", "Проблемы с Договорной Работой"],
"1С": ["*"],
"1с": ["*"]
}
# План «Б» маршрутизации: жесткое определение локации по телефонному префиксу внутренней АТС
PREFIX_ROUTING = {
"700": {"city": "Красноярск", "company": "ООО Комбинат Волна"},
"702": {"city": "Кемерово"},
"703": {"city": "Новосибирск"},
"704": {"city": "Красноярск"},
"705": {"city": "Каменск"},
"701": {"city": "Ангарск"},
"706": {"city": "Ангарск"},
"707": {"city": "Ангарск"},
"709": {"city": "Москва"},
"724": {"city": "Искитим"},
"71": {"city": "Топки"}
}