first commit

This commit is contained in:
Maksim Totmin 2026-06-23 10:41:45 +07:00
commit 9b78a5a1bb
6 changed files with 2884 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__/
*.pyc

60
README.md Normal file
View File

@ -0,0 +1,60 @@
# SEE/SMC Autotests
Набор тестов для проверки моделей **SEE** (Semantic Entity Extraction) и **SMC** (Short Message Classification) после обновления.
## Файлы
| Файл | Назначение |
|------|-----------|
| `test_see_models.py` | Тест всех моделей SEE (entity + classifier) |
| `test_smc_models.py` | Тест всех моделей SMC (classifier) |
| `see_asserts.json` | Ожидаемые проверки для SEE (опционально) |
| `smc_asserts.json` | Ожидаемые проверки для SMC (опционально) |
## Использование
### Без проверок (только непустой ответ)
```bash
python3 test_see_models.py
python3 test_smc_models.py
```
### С проверками по asserts
```bash
python3 test_see_models.py --asserts see_asserts.json
python3 test_smc_models.py --asserts smc_asserts.json
```
### Другой сервер
```bash
python3 test_see_models.py --host 10.101.60.27 --port 6184
python3 test_smc_models.py --host 10.101.60.27 --port 6181
```
## Формат asserts
```json
{
"asserts": [
{
"model": "fio",
"text": "Иванов Иван Иванович",
"checks": {
"min_found": 1,
"text_contains": "иван",
"confidence_min": 0.3
}
}
]
}
```
- `model` — имя модели (подстрока для поиска)
- `text` — текст для запроса (опционально)
- `min_found` — минимальное количество найденных сущностей/классов
- `text_contains` — подстрока в тексте/классе первой сущности
- `calculated_contains` — подстрока в `calculated` (только entity)
- `confidence_min` — минимальный confidence

2009
see_asserts.json Normal file

File diff suppressed because it is too large Load Diff

237
smc_asserts.json Normal file
View File

@ -0,0 +1,237 @@
{
"_format": "Массив asserts для SMC. Все модели типа classifier.",
"asserts": [
{
"model": "МФЦ НСК~bf505da3280184027363ec7349c0e6fc",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ НСК~ffb403fff9f5802d53aac8a5c844af26",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "филиалы 54_МФЦ-backup",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "mfc_test_old-backup",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "тест мфц нск-new",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ НСК-backup~4f2d37c9a020797ba3151b7814303264",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ НСК-backup",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "mfc_test_old",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "ekc_smc",
"text": "записаться в екц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "ekc_smc-backup",
"text": "записаться в екц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "minsoc_20_july",
"text": "какие выплаты от минсоца",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ НСК-backup~081406f0904c707cf068a6fbb8f249bb",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "филиалы 54_МФЦ",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ НСК-backup~ffb403fff9f5802d53aac8a5c844af26",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "ЕКЦ НСК~ffb403fff9f5802d53aac8a5c844af26",
"text": "записаться в екц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "offices_mfc",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "minsoc_20_july-backup",
"text": "какие выплаты от минсоца",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "Техническая поддержка",
"text": "не работает компьютер",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "Тест~ffb403fff9f5802d53aac8a5c844af26",
"text": "тестовое сообщение",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "филиалы 54_МФЦ~bf505da3280184027363ec7349c0e6fc",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ НСК",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ НСК~4f2d37c9a020797ba3151b7814303264",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ НСК~081406f0904c707cf068a6fbb8f249bb",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "филиалы 54_МФЦ~4f2d37c9a020797ba3151b7814303264",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "ЕКЦ НСК-backup~ffb403fff9f5802d53aac8a5c844af26",
"text": "записаться в екц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ2-new",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "Тест-backup~ffb403fff9f5802d53aac8a5c844af26",
"text": "тестовое сообщение",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "МФЦ1",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
},
{
"model": "филиалы 54_МФЦ-backup~bf505da3280184027363ec7349c0e6fc",
"text": "хочу записаться в мфц",
"checks": {
"confidence_min": 0.1,
"min_found": 1
}
}
]
}

374
test_see_models.py Normal file
View File

@ -0,0 +1,374 @@
#!/usr/bin/env python3
"""SEE model autotest — проверяет все загруженные модели после обновления.
Использование:
python3 test_see_models.py
python3 test_see_models.py --host 10.101.60.26
python3 test_see_models.py --host 10.101.60.25 --port 6184
python3 test_see_models.py --asserts see_asserts.json
Файл ассертов (JSON):
{
"asserts": [
{
"model": "fio", // подстрока для поиска модели
"type": "entity|classifier",
"text": "Иванов Иван Иванович", // текст для запроса
"checks": {
// для entity:
"min_found": 1, // мин. количество найденных сущностей
"text_contains": "иванов", // подстрока в text первой сущности
"calculated_contains": "Ф:", // подстрока в calculated
"confidence_min": 0.5, // мин. confidence
// для classifier:
"min_classes": 1, // мин. количество классов
"top_class_contains": "Yes", // подстрока в top классе
"confidence_min": 0.5 // мин. confidence топ-класса
}
}
]
}
Возвращает exit code 0 если все модели прошли, иначе 1.
"""
import argparse
import json
import os
import sys
import unittest
from urllib.parse import quote
try:
import urllib.request as urlreq
import urllib.error as urlerr
except ImportError:
urlreq = None
DEFAULT_HOST = "10.101.60.25"
DEFAULT_PORT = 6184
BASE_PATH = "/see"
PRETRAINED_TESTS = {
"fio": {"text": "Иванов Иван Иванович", "hint": "ФИО"},
"phone": {"text": "8 913 456 78 90", "hint": "телефон"},
"number": {"text": "пятьсот двадцать три", "hint": "число"},
"date": {"text": "на завтра", "hint": "дата"},
"uuid": {"text": "550e8400-e29b-41d4-a716-446655440000", "hint": "UUID"},
"time": {"text": "в пять часов вечера", "hint": "время"},
}
HANDLER_TESTS = {
"fio2": {"text": "Иванов Иван Иванович", "type": "entity"},
"phone2": {"text": "8 913 456 78 90", "type": "entity"},
"record_date": {"text": "на завтра", "type": "entity"},
"record_time": {"text": "в пять часов вечера", "type": "entity"},
"yesno": {"text": "да", "type": "classifier"},
"yesno_obzv": {"text": "да", "type": "classifier"},
"weekday": {"text": "в пятницу", "type": "classifier"},
"end_repeat": {"text": "все, до свидания", "type": "classifier"},
"operatorcheck": {"text": "соедините с оператором", "type": "classifier"},
"see_grade": {"text": "пять", "type": "classifier"},
"other_payment": {"text": "мобилизационные выплаты", "type": "classifier"},
"mobilizac": {"text": "мобилизационные", "type": "classifier"},
"matcap_direct": {"text": "на улучшение жилищных условий", "type": "classifier"},
"new_minsoc_subsidii": {"text": "субсидия на жкх", "type": "classifier"},
"new_trud": {"text": "потерял работу", "type": "classifier"},
"rating_med": {"text": "отлично", "type": "classifier"},
"zags": {"text": "загс центрального района", "type": "classifier"},
"zags_handler": {"text": "загс на нижегородской", "type": "classifier"},
"filial": {"text": "в мошковский филиал", "type": "classifier"},
"offices_mfc_see": {"text": "родники", "type": "classifier"},
"service_mfc_see": {"text": "хочу получить водительское удостоверение", "type": "classifier"},
"nsk_fillials": {"text": "в филиал на родниках", "type": "classifier"},
}
CUSTOM_MODEL_MATCHES = [
("record_date", "на завтра"),
("record_time", "в пять часов вечера"),
("fio", "Иванов Иван Иванович"),
("phone", "8 913 456 78 90"),
("date", "на завтра"),
("time", "в пять часов вечера"),
("uuid", "550e8400-e29b-41d4-a716-446655440000"),
("number", "пятьсот двадцать три"),
("filial", "в филиал на родниках"),
("zags", "загс центрального района"),
("yesno", "да"),
("weekday", "в пятницу"),
("grade", "пять"),
("obzv", "да"),
("operator", "соедините с оператором"),
("repeat", "все, до свидания"),
("matcap", "на улучшение жилищных условий"),
("mobilizac", "мобилизационные"),
("trud", "потерял работу"),
("subsidii", "субсидия на жкх"),
("rating", "отлично"),
("payment", "мобилизационные выплаты"),
("service_mfc", "хочу получить паспорт"),
("offices_mfc", "родники"),
("паспорт", "хочу получить загранпаспорт"),
("ЕПГУ", "проверить статус заявления на госуслугах"),
("лаврентьев", "как получить лаврентьевскую карту"),
("возраст", "мне 25 лет"),
("загран", "хочу сделать загранпаспорт"),
("вернуться", "вернуться в главное меню"),
("не знаю код", "не знаю код подразделения"),
("оценк", "оцениваю обслуживание на пять"),
("несогласи", "я не согласен с этим"),
("начало диалог", "здравствуйте"),
("пособие", "какое пособие по рождению"),
("УК", "проблема с управляющей компанией"),
]
FALLBACK_TEXT = "Здравствуйте, хочу записаться на приём в МФЦ для получения паспорта, запись на завтра"
def load_asserts(path):
if not path:
return []
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
return data.get("asserts", [])
class SeeAPI:
def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT):
self.base = f"http://{host}:{port}{BASE_PATH}"
def get_models(self):
return json.loads(self._request(f"{self.base}/models"))
def test_entity(self, model_id, text, similarity=70):
encoded = quote(model_id, safe="")
url = f"{self.base}/entities/{encoded}?text={quote(text)}&similarity={similarity}"
return json.loads(self._request(url))
def _request(self, url, timeout=15):
try:
with urlreq.urlopen(url, timeout=timeout) as r:
return r.read().decode("utf-8")
except urlerr.HTTPError as e:
raise RuntimeError(f"HTTP {e.code}: {e.read().decode('utf-8')[:200]}")
except urlerr.URLError as e:
raise RuntimeError(f"Connection failed: {e.reason}")
class TestSeeModels(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.api = SeeAPI(cls.args.host, cls.args.port)
cls.asserts = load_asserts(cls.args.asserts)
data = cls.api.get_models()
TestSeeModels.models = data
total = len(data["models"]) + len(data["handlers"]) + len(data["pretrained"])
print(f"\n models: {len(data['models'])}, "
f"handlers: {len(data['handlers'])}, "
f"pretrained: {len(data['pretrained'])}")
if cls.asserts:
print(f" asserts loaded: {len(cls.asserts)}")
if total == 0:
raise unittest.SkipTest("Нет ни одной модели")
def _find_assert(self, model_id):
for a in self.asserts:
if a["model"] in model_id:
return a
return None
def _check_assert(self, model_id, result):
a = self._find_assert(model_id)
if not a:
return
checks = a.get("checks", {})
has_classes = isinstance(result, dict) and "classes" in result
if has_classes:
classes = result.get("classes", [])
if "min_found" in checks:
self.assertGreaterEqual(len(classes), checks["min_found"],
f"{model_id}: expected >= {checks['min_found']} items, got {len(classes)}")
if classes and "text_contains" in checks:
self.assertIn(checks["text_contains"], classes[0].get("class", ""),
f"{model_id}: missing '{checks['text_contains']}'")
if classes and "confidence_min" in checks:
self.assertGreaterEqual(classes[0].get("confidence", 0), checks["confidence_min"],
f"{model_id}: confidence < {checks['confidence_min']}")
else:
entities = []
for v in result.values():
if isinstance(v, list):
entities.extend(v)
count = len(entities)
if "min_found" in checks:
self.assertGreaterEqual(count, checks["min_found"],
f"{model_id}: expected >= {checks['min_found']} entities, got {count}")
if count > 0:
first = entities[0]
if "text_contains" in checks:
self.assertIn(checks["text_contains"], first.get("text", ""),
f"{model_id}: text missing '{checks['text_contains']}'")
if "calculated_contains" in checks:
self.assertIn(checks["calculated_contains"], first.get("calculated", ""),
f"{model_id}: calculated missing '{checks['calculated_contains']}'")
if "confidence_min" in checks:
self.assertGreaterEqual(first.get("confidence", 0), checks["confidence_min"],
f"{model_id}: confidence < {checks['confidence_min']}")
def _text_for_model(self, model_id):
a = self._find_assert(model_id)
if a and "text" in a:
return a["text"]
for substr, text in CUSTOM_MODEL_MATCHES:
if substr in model_id:
return text
return FALLBACK_TEXT
def _is_empty(self, result):
if not result or result == {}:
return True
return all(
isinstance(v, list) and len(v) == 0 for v in result.values()
)
def test_01_list_models(self):
"""Получение списка моделей"""
data = self.models
self.assertIn("models", data)
self.assertIn("handlers", data)
self.assertIn("pretrained", data)
def _fmt_result(self, model_id, result):
print(f" >> {model_id}")
if isinstance(result, dict) and "classes" in result:
for c in result["classes"][:5]:
print(f" class={c.get('class','?')} conf={c.get('confidence',0):.3f}")
if len(result["classes"]) > 5:
print(f" ... ({len(result['classes'])} classes)")
else:
for key, entities in result.items():
if isinstance(entities, list):
for e in entities[:3]:
text = e.get("text", "?")
calc = e.get("calculated", "?")
conf = e.get("confidence", 0)
print(f" {key}: text='{text}' calc='{calc}' conf={conf:.3f}")
if len(entities) > 3:
print(f" ... ({len(entities)} entities)")
def test_02_pretrained_models(self):
"""Проверка pretrained-моделей"""
errors = []
for model_id, td in PRETRAINED_TESTS.items():
if model_id not in self.models.get("pretrained", []):
errors.append(f" {model_id}: нет в списке")
continue
try:
result = self.api.test_entity(model_id, td["text"])
if self._is_empty(result):
errors.append(f" {model_id}: пустой результат")
else:
self._check_assert(model_id, result)
self._fmt_result(model_id, result)
except AssertionError:
raise
except Exception as e:
errors.append(f" {model_id}: {e}")
if errors:
self.fail("Ошибки pretrained:\n" + "\n".join(errors))
def test_03_handler_models(self):
"""Проверка handler-моделей"""
errors = []
handlers = self.models.get("handlers", [])
if not handlers:
self.skipTest("Нет handler моделей")
for model_id in handlers:
text = FALLBACK_TEXT
for key, td in HANDLER_TESTS.items():
if key in model_id:
text = td["text"]
break
a = self._find_assert(model_id)
if a and "text" in a:
text = a["text"]
try:
result = self.api.test_entity(model_id, text)
if self._is_empty(result):
print(f" [!] {model_id}: пустой результат")
else:
self._check_assert(model_id, result)
self._fmt_result(model_id, result)
except AssertionError:
raise
except Exception as e:
errors.append(f" {model_id}: {e}")
if errors:
self.fail("Ошибки handler:\n" + "\n".join(errors))
def test_04_custom_models(self):
"""Проверка кастомных моделей"""
errors = []
models = self.models.get("models", [])
if not models:
self.skipTest("Нет кастомных моделей")
for model_id in models:
text = self._text_for_model(model_id)
try:
result = self.api.test_entity(model_id, text)
if self._is_empty(result):
print(f" [!] {model_id}")
else:
self._check_assert(model_id, result)
self._fmt_result(model_id, result)
except AssertionError:
raise
except Exception as e:
errors.append(f" {model_id}: {e}")
if errors:
self.fail("Ошибки кастомных моделей:\n" + "\n".join(errors))
def test_05_handler_pretrained_coverage(self):
"""Проверка handler моделей phone/uuid/number/fio"""
errors = []
handlers = self.models.get("handlers", [])
for htype, td in [("phone", PRETRAINED_TESTS["phone"]),
("uuid", PRETRAINED_TESTS["uuid"]),
("number", PRETRAINED_TESTS["number"]),
("fio", PRETRAINED_TESTS["fio"])]:
matching = [h for h in handlers if htype in h.lower()]
if not matching:
continue
for mid in matching[:2]:
try:
result = self.api.test_entity(mid, td["text"])
if self._is_empty(result):
print(f" [!] {mid} ({td['hint']})")
else:
self._check_assert(mid, result)
self._fmt_result(mid, result)
except AssertionError:
raise
except Exception as e:
errors.append(f" {mid}: {e}")
if errors:
self.fail("Ошибки handler coverage:\n" + "\n".join(errors))
def main():
parser = argparse.ArgumentParser(description="SEE model autotest")
parser.add_argument("--host", default=DEFAULT_HOST, help=f"Хост (default: {DEFAULT_HOST})")
parser.add_argument("--port", type=int, default=DEFAULT_PORT, help=f"Порт (default: {DEFAULT_PORT})")
parser.add_argument("--asserts", default=None, help="JSON файл с ассертами")
args = parser.parse_args()
TestSeeModels.args = args
suite = unittest.TestLoader().loadTestsFromTestCase(TestSeeModels)
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
sys.exit(0 if result.wasSuccessful() else 1)
if __name__ == "__main__":
main()

202
test_smc_models.py Normal file
View File

@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""SMC model autotest — проверяет все загруженные модели классификации после обновления.
Использование:
python3 test_smc_models.py
python3 test_smc_models.py --host 10.101.60.27
python3 test_smc_models.py --host 10.101.60.27 --port 6181
python3 test_smc_models.py --asserts smc_asserts.json
Файл ассертов (JSON):
{
"asserts": [
{
"model": "МФЦ НСК", // подстрока для поиска модели
"type": "classifier",
"text": "записаться на приём", // опционально, свой текст
"checks": {
"min_classes": 1, // мин. количество классов
"top_class_contains": "МФЦ", // подстрока в top классе
"confidence_min": 0.5 // мин. confidence топ-класса
}
}
]
}
Возвращает exit code 0 если все модели прошли, иначе 1.
"""
import argparse
import json
import sys
import unittest
from urllib.parse import quote
try:
import urllib.request as urlreq
import urllib.error as urlerr
except ImportError:
urlreq = None
DEFAULT_HOST = "10.101.60.27"
DEFAULT_PORT = 6181
BASE_PATH = "/smc"
DEFAULT_TEXT = "Здравствуйте, хочу записаться на приём в МФЦ для получения паспорта"
def load_asserts(path):
if not path:
return []
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
return data.get("asserts", [])
class SmcAPI:
def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT):
self.base = f"http://{host}:{port}{BASE_PATH}"
def get_models(self):
return json.loads(self._request(f"{self.base}/models"))
def classify(self, model_id, text):
encoded = quote(model_id, safe="")
url = f"{self.base}/classify/{encoded}?text={quote(text)}"
return json.loads(self._request(url))
def _request(self, url, timeout=30):
try:
with urlreq.urlopen(url, timeout=timeout) as r:
return r.read().decode("utf-8")
except urlerr.HTTPError as e:
raise RuntimeError(f"HTTP {e.code}: {e.read().decode('utf-8')[:200]}")
except urlerr.URLError as e:
raise RuntimeError(f"Connection failed: {e.reason}")
class TestSmcModels(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.api = SmcAPI(cls.args.host, cls.args.port)
cls.asserts = load_asserts(cls.args.asserts)
data = cls.api.get_models()
TestSmcModels.models = data
models = data.get("models", [])
handlers = data.get("handlers", [])
print(f"\n models: {len(models)}, handlers: {len(handlers)}")
if cls.asserts:
print(f" asserts loaded: {len(cls.asserts)}")
if len(models) + len(handlers) == 0:
raise unittest.SkipTest("Нет ни одной модели")
def _find_assert(self, model_id):
for a in self.asserts:
if a["model"] in model_id:
return a
return None
def _check_assert(self, model_id, result):
a = self._find_assert(model_id)
if not a:
return
checks = a.get("checks", {})
classes = result.get("classes", [])
if "min_found" in checks:
self.assertGreaterEqual(len(classes), checks["min_found"],
f"{model_id}: expected >= {checks['min_found']} classes, got {len(classes)}")
if classes and "confidence_min" in checks:
self.assertGreaterEqual(classes[0].get("confidence", 0), checks["confidence_min"],
f"{model_id}: top confidence < {checks['confidence_min']}")
def test_01_list_models(self):
"""Получение списка моделей"""
data = self.models
self.assertIn("models", data)
self.assertIn("handlers", data)
print(f"\n models: {len(data['models'])}")
print(f" handlers: {len(data['handlers'])}")
def test_02_models(self):
"""Классификация через модели"""
errors = []
models = self.models.get("models", [])
for mid in models:
if not mid:
continue
text = DEFAULT_TEXT
a = self._find_assert(mid)
if a and "text" in a:
text = a["text"]
try:
result = self.api.classify(mid, text)
classes = result.get("classes", [])
if not classes:
print(f" [!] {mid}: пустой результат")
else:
self._check_assert(mid, result)
print(f" >> {mid}")
for c in classes[:5]:
cl = c.get("class", "?")
conf = c.get("confidence", 0)
print(f" class='{cl}' conf={conf:.3f}")
if len(classes) > 5:
print(f" ... ({len(classes)} classes)")
except AssertionError:
raise
except Exception as e:
errors.append(f" {mid}: {e}")
if errors:
self.fail("Ошибки моделей:\n" + "\n".join(errors))
def test_03_handlers(self):
"""Классификация через handler модели"""
errors = []
handlers = self.models.get("handlers", [])
if not handlers:
self.skipTest("Нет handler моделей")
for mid in handlers:
if not mid:
continue
text = DEFAULT_TEXT
a = self._find_assert(mid)
if a and "text" in a:
text = a["text"]
try:
result = self.api.classify(mid, text)
classes = result.get("classes", [])
if not classes:
print(f" [!] {mid}: пустой результат")
else:
self._check_assert(mid, result)
print(f" >> {mid}")
for c in classes[:5]:
cl = c.get("class", "?")
conf = c.get("confidence", 0)
print(f" class='{cl}' conf={conf:.3f}")
if len(classes) > 5:
print(f" ... ({len(classes)} classes)")
except AssertionError:
raise
except Exception as e:
errors.append(f" {mid}: {e}")
if errors:
self.fail("Ошибки internal:\n" + "\n".join(errors))
def main():
parser = argparse.ArgumentParser(description="SMC model autotest")
parser.add_argument("--host", default=DEFAULT_HOST, help=f"Хост (default: {DEFAULT_HOST})")
parser.add_argument("--port", type=int, default=DEFAULT_PORT, help=f"Порт (default: {DEFAULT_PORT})")
parser.add_argument("--asserts", default=None, help="JSON файл с ассертами")
args = parser.parse_args()
TestSmcModels.args = args
suite = unittest.TestLoader().loadTestsFromTestCase(TestSmcModels)
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
sys.exit(0 if result.wasSuccessful() else 1)
if __name__ == "__main__":
main()