Найдено проверкой всех шести ролей на живой машине владельца. До правок работали три роли из шести. 1. antigravity_adapter.classify_error возвращал ErrorCategory.UNKNOWN — значения с таким именем не существует, есть AUTH_REQUIRED, FATAL, INVALID_REQUEST, QUOTA_EXHAUSTED, RATE_LIMITED, TRANSIENT. Обращение роняло сам классификатор с AttributeError, то есть отказ происходил ровно там, где обрабатывался другой отказ, и маршрутизация обрывалась вместо перехода к резервному профилю. Заменено на FATAL по образцу codex и opencode: неразобранная ошибка не должна давать повторов. 2. Уровень усилия не подставлялся, если у конкретного профиля не выполнен вход agy: карта усилий строится обнаружением ЧЕРЕЗ этот профиль, и при неудаче оставалась пустой. Уровни же — свойство модели, а не аккаунта. Добавлен запасной источник: сохранённый на диске список моделей со склеенными идентификаторами вида gemini-3.7-flash-high, из которых уровни выводятся напрямую и переживают перезапуск. После правок работают все шесть ролей, включая живое переключение 'fast': opengo-1 -> ag-w4. Закрыто тестами, включая защиту от возврата несуществующей категории. Тесты: 373 passed, ruff чисто. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60 lines
3.4 KiB
Python
60 lines
3.4 KiB
Python
"""Путь обработки ошибок не должен падать сам.
|
||
|
||
Два дефекта, найденные проверкой всех шести ролей на живой машине:
|
||
|
||
1. antigravity_adapter.classify_error возвращал ErrorCategory.UNKNOWN —
|
||
значения с таким именем не существует. Обращение к нему роняло сам
|
||
классификатор, то есть отказ случался ровно там, где обрабатывался
|
||
другой отказ, и маршрутизация обрывалась вместо перехода к резерву.
|
||
|
||
2. Уровень усилия не подставлялся, если у конкретного профиля не выполнен
|
||
вход agy: карта усилий строится обнаружением через этот профиль. Уровни
|
||
же — свойство модели, а не аккаунта, и берутся из сохранённого списка.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from antigravity_provider.router.adapters import get_adapter
|
||
from antigravity_provider.router.adapters.base_adapter import ErrorCategory
|
||
|
||
|
||
def test_error_category_has_no_phantom_members():
|
||
"""Классификаторы обязаны ссылаться только на существующие категории."""
|
||
valid = {m for m in dir(ErrorCategory) if not m.startswith("_")}
|
||
assert "UNKNOWN" not in valid, (
|
||
"если UNKNOWN добавили — обновите классификаторы, раньше его не было"
|
||
)
|
||
for name in ("AUTH_REQUIRED", "FATAL", "QUOTA_EXHAUSTED", "RATE_LIMITED", "TRANSIENT"):
|
||
assert name in valid
|
||
|
||
|
||
def test_unclassified_error_does_not_crash_classifier():
|
||
"""Неразобранная ошибка должна классифицироваться, а не ронять обработчик."""
|
||
for provider in ("antigravity", "openai-codex", "opencode-go", "grok", "claude"):
|
||
adapter = get_adapter(provider)
|
||
result = adapter.classify_error(Exception("нечто совершенно неразобранное"))
|
||
assert result.category, f"{provider}: классификатор вернул пустую категорию"
|
||
assert isinstance(result.category, str)
|
||
|
||
|
||
def test_effort_levels_derived_from_cached_model_ids(monkeypatch):
|
||
"""Уровни усилия берутся из склеенных идентификаторов, без обращения к профилю."""
|
||
import antigravity_provider.agy_subprocess as agy
|
||
from antigravity_provider.router import model_discovery
|
||
|
||
monkeypatch.setattr(agy, "discover_models", lambda profile_id=None: {})
|
||
monkeypatch.setattr(agy, "_AGY_EFFORT_MAP", {}, raising=False)
|
||
|
||
class _Svc:
|
||
@staticmethod
|
||
def get():
|
||
return _Svc()
|
||
|
||
def get_models(self, provider):
|
||
return ["gemini-3.7-flash-low", "gemini-3.7-flash-high", "claude-sonnet-4-6"]
|
||
|
||
monkeypatch.setattr(model_discovery, "ModelDiscoveryService", _Svc)
|
||
|
||
assert agy._model_supported_efforts("gemini-3.7-flash") == {"low", "high"}
|
||
# У модели без суффиксов уровней быть не должно — подставлять нечего.
|
||
assert agy._model_supported_efforts("claude-sonnet-4-6") == set()
|