---
name: avtero_reliability_fallback
description: avtero — безотказность подбора: бюджет LLM + детерминированный каталог-fallback при отказе всего чейна (никогда 502/таймаут, если деталь в каталоге)
metadata:
  node_type: memory
  type: project
  originSessionId: 4eea915e-3b47-4cd5-8a0e-bfa29b78a87c
---

**2026-06-30: подбор больше не 502-ит и не виснет, когда LLM-чейн падает.** Симптом юзера на сайте — «Подбор слишком долго не отвечает» (фронт-таймаут `page.tsx:714` = **75000мс**). Корень: весь free-reseller LLM-чейн деградирует (zyloo/bynara ReadTimeout под нагрузкой, выгорают free-квоты; r9/mistral-роуты на OmniRoute :20128 отдавали 404) → 291с → 502.

**Два механизма в `orchestrator.py` `run()` (бэкапы `*.bak-fallback-*`/`*.bak-budget-*`):**
1. **Бюджет** `AVTERO_LLM_BUDGET_SEC` (дефолт 60, env-tunable, 0=выкл): `asyncio.wait_for` вокруг `_llm_with_tools`. Превышен → TimeoutError → fallback. 60с < 75с фронта → fallback успевает.
2. **Каталог-fallback** `_build_catalog_fallback(vin, work_text, vehicle)`: на `asyncio.TimeoutError` ИЛИ `RuntimeError("All models in fallback chain failed")` собирает `PartListResponse` напрямую из `work_groups.expand_work_group` + autodoc/emex `get_oem_by_vin` + `search_cross` (аналоги). Ранний `return OrchestratorResult(model="catalog_fallback", confidence="low")` — МИНУЯ LLM-санитайзинг boundary (vehicle авторитетен, OEM каталог-прямые). Честный disclaimer в general_notes+warnings. None если каталог пуст → отдаём настоящий отказ. Проверено: форс дохлого чейна → возврат 1.0с, 10 OEM, НЕ 502; прод E2E того же запроса → HTTP 200 за 60.8с (бюджет сработал, fallback отдал каталог).

**Чейн почищен** (плист `PROVIDER_CHAT_FALLBACK`, бэкап `*.bak-chain-*`): убраны мёртвые `gateway:r9/gpt-5.5-pro20x` + `gateway:mistral/mistral-medium-latest` (404).

**2026-06-30 — NVIDIA свои-ключ якоря НАЙДЕНЫ (бэкап плиста `*.bak-nvanchor-*`).** Прогнал кандидатов NVIDIA NIM единственной головой на РЕАЛЬНОЙ задаче подбора (harness `/tmp/nv_probe.py`): **`nvidia:mistralai/mistral-large-3-675b-instruct-2512` ✅ (LLM_OK, 37с)** и **`nvidia:moonshotai/kimi-k2.6` ✅ (56с)** — выдают валидный финальный JSON через полный tool-loop на своём `NVIDIA_API_KEY` (надёжнее free-reseller). Не годятся: qwen3.5-397b/deepseek-v4-pro (ReadTimeout >60с), nemotron-super-49b (tool_calls без JSON). Новый чейн 6 голов: `zyloo/claude-opus-4-7, bynara/mimo, nvidia:mistral-large-3-675b, nvidia:kimi-k2.6, cerebras/zai-glm-4.7, cerebras/gpt-oss-120b` — две свои-ключ головы как якоря перед cerebras. **Тот же чейн продублирован в `local.avtero.llmmon.plist`** (монитор читает свой plist; бэкап `*.bak-nvanchor-*`) — иначе мониторил старые головы. Монитор: 6/6 голов up. Теперь безотказность держится не только на каталог-fallback, а на реальных LLM-головах со своим ключом. **integrate.api.nvidia.com доступен напрямую без прокси** (curl http=200).

**SSE keepalive (2026-06-30) — критично для сайта.** Сайт идёт через `/api/ask_async`+EventSource; клиентский сторож 75с (`page.tsx` watchdog) показывал «слишком долго не отвечает» при тишине событий, хотя бэкенд жив (пост-LLM фитмент/обогащение шло ~41с без событий). Фикс: `jobs.py iter_events` keepalive 60→15с (`ping`), клиент сбрасывает сторож на `ping` (`pingTick`). Проверено: макс. окно тишины 15с при прогоне 61с. Теперь таймаут-ошибка на живом бэкенде невозможна. **agy/ (Antigravity) как голову подключить НЕ удалось** — провайдер в OmniRoute не авторизован (нужен Google OAuth/Code-Assist projectId в OmniRoute UI, не из avtero). Безотказность держится на каталог-fallback + keepalive, не на agy. Админ-доступ: `_is_pk_admin` теперь признаёт и веб-аккаунт, слинкованный с админским TG (не только `sess.tg_id`); кнопка «Админка» в кабинете при is_admin.

**g0i-провайдер + per-model монитор (2026-06-30).** g0i.ai (OpenAI-совместимый, base `https://api.g0i.ai/v1`) добавлен в `llm.py _build_providers` (env `G0I_API_KEY`/`G0I_BASE_URL`); ключ юзера в плисте. 42 модели (claude-opus-4-8/4-7, gpt-5.5, gemini-3.1-pro…), НО текущий ключ trial истёк (403) → в чейн НЕ добавлен (включить: рабочий ключ + `g0i:claude-opus-4-7` в `PROVIDER_CHAT_FALLBACK`). Монитор `scripts/llm_head_monitor.py` + launchd `local.avtero.llmmon` (10 мин): пробит каждую голову чейна + watchlist `LLM_MONITOR_EXTRA`; дебаунс 2 подряд-фейла (free-пулы флапают); TG-пуш админам на переход up↔down; state `~/.avtero/llm_head_state.json`. agy/(Antigravity) по-прежнему не авторизован (OmniRoute UI OAuth). OmniRoute-ротация g0i — только через UI.

**2026-06-30 — чейн переупорядочен + never-502 + смягчён fallback + стрелка-rAF.** Бенч голов (`/tmp/head_bench.py`, реальная задача): `nvidia:mistral-large-3` LLM_OK 26.7с (свой ключ, быстро), `cerebras/zai-glm-4.7` TimeoutError 160с (мёртв → выкинут), gpt-oss-120b→fallback. Новый чейн (плисты avtero+llmmon, бэкап `*.bak-reorder-*`): ЛИДЕР `nvidia:mistralai/mistral-large-3-675b-instruct-2512` → `gateway:kiro/claude-opus-4.8` → `gateway:zyloo/claude-opus-4-7` → `nvidia:moonshotai/kimi-k2.6` → `gateway:bynara/mimo-v2.5-pro-free` → `gateway:cerebras/gpt-oss-120b`. Бюджет 60→55с. **Never-502** (`orchestrator.py` бэкап `*.bak-graceful-*`): `if fb is None` теперь не raise, а graceful PartListResponse (model="unresolved", honest «уточните VIN/марку», 200). **Смягчён текст** каталог-fallback (`*.bak-softfb-*`): «Предварительный подбор по заводскому каталогу — OEM точные, аналоги уточняются» (3 строки). **Стрелка спидометра** (`page.tsx` `*.bak-speedo2-*`): SMIL animateTransform не крутил → переписан на requestAnimationFrame (needleRef/fillRef setAttribute каждый кадр); пересобран+next start рестарт, rAF в бандле. **⚠️ Честное ограничение:** LLM-латентность НЕДЕТЕРМИНИРОВАНА (тот же запрос 16.7с LLM ↔ 55с fallback; free/shared/NIM гуляет 26→90с). Простые→LLM 12-38с обычно; тяжёлые catalog-gap (задние колодки/салонный фильтр) зацикливают тул-петлю→fallback. Always-fast-AI = только платная выделенная голова. Бюджет 90с делает ХУЖЕ (116с ожидания, всё равно fallback).

**2026-07-01 — СТРОГИЙ режим по названной детали (юзер: «строго то, что просили»).** Баг «сказала диски перёд+зад — нашёл суппорт, дисков нет»: каталог-fallback брал детали из `work_groups.expand_work_group` (keyword→БАНДЛ группы: «диски»→brake_full=диск+колодки+суппорт+шланг), резолвил `get_oem_by_vin(cat_key)` БЕЗ оси и оставлял всё срезолвленное → если диск не нашёлся, а суппорт(optional) да → суппорт вместо дисков; перёд/зад терялись. Фикс `orchestrator.py` (бэкап `*.bak-strict-*`): `_requested_parts(work_text)` детектит НАЗВАННЫЕ детали (таблица `_LEAF_TRIGGERS` ~30 кат + сленг/морфология, bare «диск(и)/колодки» с исключениями колёсные/литые/сцепления/ручник) + ось (`emex._axle_flags`), возвращает axle-split список или None для job-фраз (→ прежний бандл). `_resolve_named_part` — **axle-aware `resolve_by_name`** (autodoc→emex→cat-level→local trees). `_strict_catalog_report` отдаёт РОВНО названное; ненайденное → честно в general_notes, БЕЗ подмены. `_pre_resolve_strict` — тот же строгий снипет+инструкция в рабочий LLM-путь. **Слой:** LLM ловит сленг когда жив; таблица держит строгость в фолбэке (где баг и был). Проверено живьём: Kia Cerato «диски перёд+зад» → РОВНО Диск(перёд)517121M000 + Диск(зад)584112K300, по 6 аналогов, без суппорта/колодок. Юзерская фраза БЕЗ «тормозные» покрыта.

**2026-07-01 (4) — ЦЕЛЬ СМЕНИЛАСЬ: фолбэк без ИИ = аварийный, не норма (юзер: «без ИИ отчёт не выдавать, цепочку сделать безотказной; может, лишние обращения к ИИ ломают цепочку?»).** Диагностика (инструментировал /api/ask, бюджет врем. 200с): успешный ИИ-прогон 6-позиц. запроса = **103с, 3 раунда, 11 tool-calls** (search_cross ×5, search_reviews ×4, profile, web) → при бюджете 55с падал в фолбэк. КОРЕНЬ: `_enrich_alternatives_from_tecdoc` (пост-LLM) + фитмент-гейт + backfill УЖЕ добивают аналоги из каждого primary_oem детерминированно — значит **search_cross/search_reviews, что крутит LLM, дублируют это = чистая трата раундов** (у free-моделей ~20-30с/раунд). Фиксы (`orchestrator.py` бэкап `*.bak-prompt-*`): (1) правило 18 промпта усилено — «аналоги и review_summary добавляются движком ПОСЛЕ тебя; НЕ вызывай search_cross (оставляй alternatives=[]), НЕ вызывай search_reviews; OEM бери из PRE-RESOLVED, не перезапрашивай search_oem; финализируй сразу»; (2) **бюджет 55→120с** (SSE-keepalive [[avtero_reliability_fallback]] уже защищает фронт 75с пингами каждые 15с, старый лимит 55 был искусственно тесен). **Проверено живьём:** 6-позиц. → nvidia mistral-large-3, **67с, 1 раунд, 5 вызовов (0 cross, 0 reviews), 65 аналогов**; 8-позиц. #570 → fmd, **95с, 1 раунд, 1 вызов**, 11 деталей с аналогами — ОБА через ИИ, не фолбэк. Раньше «бюджет 90 делал хуже» — та посылка УСТАРЕЛА: причина была не в нехватке времени, а в лишних раундах; убрав их, ИИ сходится за 1 раунд. Всё backend → применяется и на сайте, и в боте (общий API). Фолбэк остаётся как настоящая авария (весь чейн лёг).

**2026-07-01 (3) — «⚠ проверьте» на неуверенных совпадениях (юзер: «если не уверен — уточняй, пользователь подтвердит»).** Дизайн (утв. юзером): отчёт целиком + пометка на сомнительных, БЕЗ блокирующего раунда, оба канала. Мера уверенности `_confident_name_match(req_name, detailname)` в `orchestrator.py` (бэкап `*.bak-confirm-*`): берёт ГОЛОВНОЕ существительное запроса и требует его stem в `detailname` найденного узла; категорийный (brand-native get_oem_by_vin/vehicle_oe) = уверенно. Схема `Part` +`needs_check: bool`+`check_note` (schemas.py бэкап `*.bak-confirm-*`). Рендер: бот `rendering.py _render_one_part` → строка «⚠️ …— подтвердите»; сайт `page.tsx` PartCard → амбер-бейдж (бэкап `*.bak-confirm`). **Флаги оказались ТОЧНЫМИ** (Kia #570): «Балка задней подвески»→нашлась ПРУЖИНА (553301M011), «Опора амортизатора»→АМОРТИЗАТОР задний, «подшипник компрессора конд.»→КАТУШКА индуктивности (муфта) — все ⚠ верно; диск/амортизатор — чисто. ⚠️ ГЛУБЖЕ: это вскрыло, что `resolve_by_name` МИС-ПИКАЕТ сложные имена (балка→пружина, опора→амортизатор, подшипник→катушка). **ИСПРАВЛЕНО 2026-07-01 (гейт головного слова):** в `select_by_name_axle_aware` (emex_catalog.py, бэкап `*.bak-headgate-*`, переиспользуется и autodoc'ом) головное существительное запроса (префикс 4 симв, синонимы колодки→накладки/трос→кабель) обязано быть в detailname; отличительное отсутствует у всех → None (не сосед), родовое (фильтр/диск/ремень…) → скоринг решает. В autodoc `resolve_by_name_full` loose-fallback (бэкап `*.bak-loose-*`) longest-word→головное(первое). Проверено: опора→546101M000 (не ABS-shock), подшипник→не найдено (не катушка), балка→КРОНШТЕЙН БАЛКИ; диск/фильтры/колодки/трос/свеча/лобовое не регрессировали; 74 теста резолвера passed. NB: пометки ставятся в детерминированном строгом отчёте; главный LLM-путь собирает Part иначе (там пока без флага).

**2026-07-01 (2) — строгий режим переведён на ПО-ПОЗИЦИОННЫЙ разбор (юзер: «не все запчасти показал, каталоги должны всё покрывать»).** Симптом (запрос #570, Kia Cerato, 8 позиций): вернулось 5, из них лишний «Амортизатор (зад)» (просили только перёд), а трос ручника/задняя балка/опора аморт/подшипник кондиционера МОЛЧА выпали (даже не в «не найдено»). Корни: (1) хардкод-таблица `_LEAF_TRIGGERS` не покрывала эти категории; (2) ось (перёд/зад) считалась по ВСЕМУ запросу, а не по детали → к «амортизаторы передние» прилипал задний. Проверено: каталог (resolve_by_name) ВСЁ покрывает — трос стояночного 597601M300, балка 553301M011, опора аморт, подшипник компрессора кондиционера 976414D100. Фикс `orchestrator.py` (бэкап `*.bak-multi-*`): `_requested_parts` теперь **режет запрос по позициям** (`re.split [,\n;]`), ось/сторону читает из КАЖДОЙ позиции отдельно, категорию ищет пер-айтем; **позиция без категории → резолвится по сырому имени** (resolve_by_name), а не выпадает; что реально не нашлось → честно в general_notes «Не найдено…», без подмены. Добавлены категории handbrake_cable(+формы «троса/ручника»), rear_axle_beam, strut_mount (перёд `strut_mount` раньше `shock_absorber`, exclude «опор»). Free-name синонимы `_FREE_NAME_SYN` (подшипник кондиц→подшипник компрессора кондиционера). `_resolve_named_part` для cat=None резолвит только по имени. Job-фразы (нет ни одной категории) → None → прежний бандл. **Проверено живьём** (/api/ask, #570): ВСЕ 8 позиций, точные OEM, только передний амортизатор, новая нота. Юнит-тест _requested_parts на фразе #570 + краевых. NB: главный LLM-путь и так разбирает мультизапрос сам; это чинит именно детерминированный фолбэк (куда #570 упал по 55с-бюджету).

**2026-07-01 — стрелка спидометра: реальная причина найдена (прошлый rAF-фикс НЕ помог).** Шкала анимировалась, стрелка нет — хотя один rAF-цикл: **SVG-атрибут `transform` перебивается CSS-свойством `transform`** (`setAttribute("transform")` не виден), а `stroke-dashoffset` — нет. Фикс: стрелка через пересчёт `x2/y2` (`polar(ang,r-6)`) каждый кадр, `transform`-атрибут убран. `page.tsx` бэкап `*.bak-probe`. **Проверено РЕАЛЬНО** (Playwright на мини, временный `/probe-speedo` рендерил тот же компонент бандла): x2=43 значения 30→89.9, computed transform=none. probe удалён. Мораль: анимировать SVG движением геометрии, НЕ `transform`-атрибутом. Также убрана строка «Источники:» из отчёта (page.tsx).

**2026-07-05 — codex → PRIMARY на период обучения + AI-less отчёт ПОЛНОСТЬЮ запрещён (юзер: «поставить codex полноценно, если справляется; cerebras обязан подхватить; отчёт без AI формироваться не должен»).** Квота codex починена (mini: 9 codex-connections, 4 воркспейса, без 402 — юзер добавил учётки с РАЗНЫХ воркспейсов; квота codex у ChatGPT **per-workspace**, не per-email-сиденье). Боевой чейн стал `codex:codex/gpt-5.4-low → cerebras/gpt-oss-120b → glm-4.5-flash → groq → cerebras-glm` (codex ПЕРВЫМ). Замер: codex-primary валидный отчёт **67-123с** (медленно; cerebras ~34с). **Механика эстафеты:** общий `asyncio.wait_for(AVTERO_LLM_BUDGET_SEC)` оборачивает всю цепочку → codex-primary мог съесть весь бюджет и cerebras не получал ход. Развёл: **`CODEX_TIMEOUT_SEC=90` (per-call) < `AVTERO_LLM_BUDGET_SEC=260` (общий)** — зависший codex падает по своему таймауту, `_llm_with_tools` `continue`-ит к cerebras (проверено: codex bad-model → cerebras depth=1 conf=high валидно). **Запрет AI-less:** новый флаг **`ALLOW_CATALOG_FALLBACK` (дефолт `0`)** в `run()` (`orchestrator.py.bak-nocatfb-*`) — при исчерпании цепочки/таймауте `_build_catalog_fallback` НЕ строится (раньше отдавал `model=catalog_fallback` без AI); вместо этого graceful `model=unresolved` «попробуйте снова». `=1` возвращает старое поведение (обратимо). Проверено subprocess (FAILOVER/SUPPRESS_OFF/FLAG_ON) + suite 1058 + живой HTTP (БД req #706 `codex depth=0 67с`). **⚠️ ВРЕМЕННО (обучение):** откат после — чейн вернуть cerebras-first + при желании `ALLOW_CATALOG_FALLBACK=1`, `AVTERO_LLM_BUDGET_SEC=120`. Обновляет предыдущую заметку «фолбэк = аварийный»: теперь даже аварийный каталожный отчёт не выдаётся (только graceful-retry).

**2026-07-06 — codex завис на OmniRoute → вылечен рестартом; + codex loop-budget (v0.8.6).** Замер codex-primary вскрыл хвост 397с; лог показал корень: **codex :20129 висел (прямой `/v1/chat/completions` не отвечал 110с, http=000)**, cerebras :20128 = 0.47с. Каждый avtero-запрос платил 90-190с таймаут-налог (codex в VIN-декоде/gap-oem/main-loop, каждый бьётся о `CODEX_TIMEOUT_SEC=90`). Диагноз (sudo): воркер жив, но логи стоят с Jul5 10:24; codex `provider_connections`=18 (8 expired-401, 4 rate-limit-502, 6 active). **Фикс: рестарт OmniRoute** (`sudo launchctl kickstart -k system/com.cebers.omniroute`) → codex 200 за 1.7с. Полный разбор [[omniroute_custom_provider]] + Obsidian omniroute/Changelog. **На время починки** avtero временно ставил cerebras-primary; потом **вернул codex-primary** (намерение юзера). **v0.8.6 (`orchestrator.py.bak-codexbudget-*`):** `CODEX_LOOP_BUDGET_SEC` (env, дефолт 0=выкл, стоит 120) оборачивает `_run_tool_loop` первого провайдера (codex) в `asyncio.wait_for` — codex-грайнд многих раундов (каждый <90с, суммарно 250-325с) бэйлится на cerebras быстро. Замер после починки (боевой /api/ask): Volvo фильтр → codex depth=0 **96с**; Kia колодки → codex не справился → бэйл ~120с → cerebras **163с** (вместо ~400с). **Итог: codex-primary даже здоровый = 96-163с (медленный для агентного tool-loop); loop-budget ограничивает провалы.** **⚠️ ХВОСТ ЮЗЕРУ:** 8 codex-токенов истекли (401 «token invalidated») — нужен **интерактивный re-auth** в OmniRoute UI, иначе под нагрузкой codex снова 502/насыщение; до re-auth работает на 6 active. Быстрый откат на скорость: `PROVIDER_CHAT_FALLBACK` → `gateway:cerebras/gpt-oss-120b` первым + bootout/bootstrap (~1-40с). **Урок:** codex-роутинг OmniRoute может подвиснуть без падения процесса (models/dashboard живы) — признак = логи перестали расти при живом pid; мониторить прямым codex-пробом.

**2026-07-06 — grok-cli (`gc/`) добавлен 3-й головой (юзер завёл провайдера в OmniRoute).** `gc/` = провайдер **grok-cli** на OmniRoute (1 active oauth-подключение), модели `gc/grok-composer-2.5-fast` (быстрее) и `gc/grok-build`. Проверено реальным прогоном через `orchestrator.run` (форс-цепочка, subprocess): **gc работает головой avtero** — гоняет tool-loop, отдаёт валидный `PartListResponse`, **genuine-OEM совпадает с cerebras один-в-один** (натяжитель `30638277`, диск зад `8624926`), `conf=high`, гейты честности живут. Латентность **~43-53с** (уровень cerebras 52-64с; «fast» в имени НЕ даёт сквозного выигрыша — время съедает tool-loop+pre-resolve, сырой отклик модели 1-2с). Иногда падает → cerebras принимает. 1 подключение → под нагрузкой упрётся в лимит. **Боевая цепочка (ФИНАЛ 2026-07-06, юзер «cerebras вперёд, codex+grok на фолбэк»):** `cerebras/gpt-oss-120b → zai/glm-4.5-flash → groq/llama → gc/grok-composer-2.5-fast → codex/gpt-5.4-low → cerebras/zai-glm-4.7` — cerebras единственный primary, gc+codex глубокий fallback. Проверено: req 727 cerebras depth=0 **53с** (было codex-primary 96-201с). codex-primary learning свёрнут по latency-цене; CODEX_LOOP_BUDGET_SEC=120 привязан к провайдеру → защитит codex и в fallback. Бэкапы плиста `*.bak-cerebrasprimary-*`, `*.bak-grokfallback-*`, `*.bak-addgc-*`. ГРАБЛИ теста: `OrchestratorResult.response.work_units[].parts` (parts под `.response`, не напрямую — иначе ложный «parts=0»); 5-мин кэш run() бьёт по (vin,work_text,region) — для сравнения голов нужен кэш-бастер/разные запросы.

Когда free-пулы живы — вернётся полный AI-отчёт в пределах бюджета; когда деградированы — каталог-fallback. См. [[avtero_full_coverage_program]] [[avtero_llm_chain_incident]] [[avtero_api_hardening]] [[avtero_fastpath_singlepass]] [[avtero_live_harvest_selffill]].
