Тринадцать файлов существовали только на диске ПК владельца, в рабочей копии, отставшей от origin/main на 122 коммита. Их реализация и тесты давно влиты: tests/test_a42_provider_connect.py, test_a49_subagents_skills_memory.py, test_a51_hub_controls_hermes.py, test_a52_local_models_supervisor_dual.py, test_a55_account_connection.py, test_a56_context_compression.py и другие. Постановок, объясняющих, что эти тесты обязаны доказывать, в репозитории не было. Правило записано в agents/AGENTS.md: задание живёт в репозитории, а не в переписке и не в личных папках на диске. A48, A50 и A54 не переносятся: они уже есть на origin под другими именами, содержимое совпадает с точностью до перевода строки в конце файла. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
166 lines
14 KiB
Markdown
166 lines
14 KiB
Markdown
# Задание A42: подключение провайдеров — OpenRouter, NVIDIA, Ollama, квота Codex
|
||
|
||
## Дата поступления
|
||
2026-08-31
|
||
|
||
## База
|
||
|
||
`origin/main` (`ff303b5`) — туда уже слиты правки ревьюера по вебу и A41 (чистая первая установка).
|
||
|
||
```
|
||
git fetch origin --prune
|
||
git checkout -b antigravity/a42-provider-connect origin/main
|
||
```
|
||
|
||
В `main` напрямую не пушить.
|
||
|
||
## Порядок исполнения
|
||
|
||
Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-5** написан для аудитора.
|
||
|
||
Это задание **по коду**. Вёрстка и холст — отдельное задание A43, туда не залезать.
|
||
|
||
---
|
||
|
||
## Задача
|
||
|
||
Владелец сообщает: «опенроутер не подключается, нвидиа не подключаются, кодекс выдаёт ошибку по квоте, хотя квоты полные, оллама не выдаёт облачные модели».
|
||
|
||
Причины найдены ревьюером и проверены по коду. Заново их выяснять не нужно — нужно чинить.
|
||
|
||
## Что проверено ревьюером
|
||
|
||
### OpenRouter и NVIDIA реализованы наполовину
|
||
|
||
```
|
||
adapters/__init__.py OpenRouterAdapter и NvidiaAdapter в реестре есть
|
||
web/static/index.html:163 пункты в списке провайдеров есть
|
||
app.js:2482 шаг мастера с полем API-ключа и Base URL есть
|
||
|
||
action_handler.py:574 add_account сохраняет ТОЛЬКО для
|
||
(local, local-llm, llama.cpp, ollama, vllm)
|
||
и только при непустом base_url
|
||
action_handler.py:588 для всех остальных возвращается ok=True с текстом
|
||
«Навигация» — и не сохраняется ничего
|
||
```
|
||
|
||
То есть мастер докладывает об успехе и **не сохраняет ничего**. Аккаунт не появляется, потому что его никто не создал.
|
||
|
||
Дальше по цепочке пусто тоже:
|
||
|
||
```
|
||
auto_assigner.py:129 слоты объявлены для ollama; openrouter и nvidia отсутствуют
|
||
auto_assigner.py:219 роли по умолчанию — то же самое
|
||
router_config.py в конфигурации по умолчанию нет ни одного из трёх
|
||
model_discovery_service.py:216 _probe_provider не имеет ветки ни для
|
||
openrouter, ни для nvidia, и возвращает None
|
||
```
|
||
|
||
### Ollama обнаруживает не то
|
||
|
||
`model_discovery_service.py:325` заводит `ollama` в одну ветку с `local`, `llama.cpp`, `vllm`. Эта ветка:
|
||
|
||
1. перебирает **зашитые** идентификаторы `local-1` и `local-2` — профиль `ollama-1` не смотрит вообще;
|
||
2. читает учётные данные провайдера `local`, а не `ollama`;
|
||
3. по умолчанию идёт на `http://127.0.0.1:8081/v1` — это порт llama.cpp, а не Ollama (11434);
|
||
4. дёргает `/v1/models` и ничего не знает про облачные модели Ollama.
|
||
|
||
Скриншот владельца: «Список моделей ещё не получен от провайдера ollama» при подключённом `ollama-1`.
|
||
|
||
Та же болезнь рядом: ветка Codex перебирает зашитые `codex-orch`, `codex-worker-1`, `codex-worker-2`. После A26 идентификаторы выдаются автоматически (`codex-4`, `codex-5`), и такой профиль обнаружение пропустит.
|
||
|
||
### «Квота исчерпана» при полной квоте
|
||
|
||
На скриншоте у Codex значок «Квота исчерпана», а Session и Weekly показывают `Н/Д`. То есть **вердикт об исчерпании выносится там, где квота не измерена вовсе**.
|
||
|
||
```
|
||
unified_health.py:467 ветка: max_cd > 0 либо overall_state == QUOTA_EXHAUSTED
|
||
→ health_state = STATUS_QUOTA_EXHAUSTED
|
||
unified_health.py:470 ветка RATE_LIMITED идёт НИЖЕ
|
||
```
|
||
|
||
`max_cd` берётся из `frec.reset_at > now` — это **окно отката после ошибки**, а не остаток квоты. Отсюда два разных дефекта:
|
||
|
||
1. Любой откат показывается как исчерпание квоты, хотя квота может быть полной.
|
||
2. Ветка `RATE_LIMITED` практически мертва: при активном лимите запросов `reset_at` всегда в будущем, поэтому строка 467 срабатывает раньше и лимит запросов выдаёт себя за исчерпанную квоту.
|
||
|
||
И третье, в `health_tracker.py:483`: при пустом имени модели или значении `default` исчерпанным помечается **весь аккаунт** (`record.overall_state`). Hermes имя модели передаёт не всегда.
|
||
|
||
Классификатор в `codex_adapter.py:132` ловит подстроку `quota` в любом месте текста ошибки — проверить, не попадают ли туда сообщения, к квоте не относящиеся.
|
||
|
||
---
|
||
|
||
## P0-1. OpenRouter и NVIDIA подключаются по-настоящему
|
||
|
||
1. **`add_account` сохраняет профиль** для `openrouter`, `nvidia`, `nvidia-nim`: создаёт определение профиля, пишет учётные данные (ключ и адрес), назначает роль — по образцу существующей локальной ветки.
|
||
2. **Слоты и роли по умолчанию** для обоих провайдеров в `AutoAssigner`, как сделано для `ollama`.
|
||
3. **Адреса по умолчанию**: `https://openrouter.ai/api/v1` и `https://integrate.api.nvidia.com/v1`; владелец может переопределить в мастере.
|
||
4. **Ветка с мнимым успехом не должна остаться ловушкой.** Провайдер, для которого сохранение не реализовано, обязан получать честный отказ с причиной, а не `ok: True`. Это главное требование пункта: молчаливый успех стоил владельцу нескольких попыток подключения.
|
||
|
||
## P0-2. Обнаружение моделей для трёх провайдеров
|
||
|
||
1. **OpenRouter**: запрос списка моделей по адресу профиля с его ключом.
|
||
2. **NVIDIA**: то же самое.
|
||
3. **Ollama — отдельная ветка**, не общая с llama.cpp:
|
||
- адрес берётся из **самого профиля**, а не из зашитых `local-1`/`local-2`;
|
||
- учётные данные читаются для провайдера `ollama`;
|
||
- по умолчанию `http://127.0.0.1:11434`;
|
||
- локальные модели — через нативный `/api/tags`;
|
||
- **облачные модели Ollama** — отдельный источник, требующий ключа. Выяснить по действующей документации Ollama способ и адрес; **не выдумывать эндпоинт**. Если способ не подтверждён — так и написать в отчёте, а в интерфейсе показать `Н/Д` с причиной.
|
||
4. **Зашитые идентификаторы профилей убрать везде**, включая ветку Codex: перебирать профили провайдера из конфигурации. После A26 идентификаторы выдаются автоматически, и любой зашитый список рано или поздно промахнётся.
|
||
5. **Ошибка обнаружения показывается с текстом ответа сервера.** Сейчас `_probe_provider` возвращает `None` и когда ветки нет, и когда сервер отказал — владелец не может отличить одно от другого.
|
||
|
||
## P0-3. Квота говорит только то, что измерено
|
||
|
||
1. **Откат после ошибки — это не исчерпание квоты.** Разделить состояния: исчерпание объявлять по измеренному остатку, откат показывать как откат с причиной и временем окончания.
|
||
2. **Порядок веток исправить**: лимит запросов не должен выдавать себя за исчерпанную квоту.
|
||
3. **Ошибка без имени модели не помечает весь аккаунт.** Помечать конкретное семейство; общий вердикт — только при подтверждении.
|
||
4. **Ярлык называет источник.** «Квота исчерпана» — когда есть измерение. Иначе «Откат до HH:MM после ошибки: текст».
|
||
5. **Классификатор Codex** проверить на ложные срабатывания подстроки `quota`.
|
||
|
||
## P0-4. Проверка исполнением
|
||
|
||
Заглушек недостаточно, но и ключей владельца у исполнителя нет. Поэтому:
|
||
|
||
1. **Сохранение профиля** проверить с заведомо неверным ключом: профиль обязан создаться, а проверка подключения — вернуть внятную ошибку авторизации, а не тишину.
|
||
2. **Ollama** проверить на живом сервере: локальные модели через `/api/tags` обязаны появиться в списке.
|
||
3. **Квота**: смоделировать откат после ошибки и убедиться, что интерфейс не пишет «квота исчерпана» при неизмеренной квоте.
|
||
4. **Отказ вместо мнимого успеха** проверить отдельно.
|
||
|
||
## P0-5. Аудит вторым проходом
|
||
|
||
1. **Искать оставшиеся зашитые идентификаторы профилей** по всему коду — это повторяющийся класс дефекта.
|
||
2. **Проверить, что мнимых успехов не осталось**: действие, ничего не сохранившее, не возвращает `ok: True`.
|
||
3. **Эндпоинт облачных моделей Ollama** сверить с документацией. Выдуманный адрес — дефект того же рода, что выдуманные метрики в A38.
|
||
4. **Побочные изменения** объяснить.
|
||
5. **Пропущенный пункт назвать пропущенным.**
|
||
|
||
---
|
||
|
||
## Ограничения
|
||
|
||
- Ключи владельца не запрашивать и в репозиторий не класть.
|
||
- Каталог `~/.hermes/agy_profiles/` не трогать.
|
||
- Вёрстку и холст не менять — это A43.
|
||
- Версию `0.1.1` не поднимать.
|
||
- Правило честности без исключений: неизмеренное показывать как `Н/Д` с причиной.
|
||
|
||
## Критерии приёмки
|
||
|
||
1. Ветка в `origin`, `git status` чист.
|
||
2. OpenRouter и NVIDIA подключаются: профиль создаётся, учётные данные сохраняются, аккаунт виден в списке; проверено.
|
||
3. Действие, ничего не сохранившее, возвращает отказ с причиной; проверено.
|
||
4. Обнаружение моделей работает для openrouter, nvidia и ollama; для Ollama проверено на живом сервере.
|
||
5. Зашитых идентификаторов профилей в обнаружении не осталось.
|
||
6. Ошибка обнаружения доходит до интерфейса с текстом.
|
||
7. Откат после ошибки не показывается как исчерпание квоты; лимит запросов показывается как лимит запросов.
|
||
8. Ошибка без имени модели не помечает весь аккаунт.
|
||
9. `ruff check .` чисто; релизный гейт не ухудшен.
|
||
10. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. На `origin/main` сейчас **491 passed**.
|
||
|
||
## Главное
|
||
|
||
Два провайдера нельзя подключить вовсе, и мастер при этом рапортует об успехе — владелец несколько раз повторял заведомо безрезультатное действие. Третий подключается, но опрашивается по чужому адресу и чужому имени профиля. А Codex объявляется исчерпанным по квоте в тот момент, когда квота не измерена ни разу. Общее у всех четырёх — интерфейс утверждает то, чего не проверял.
|
||
|
||
## Порядок сдачи
|
||
Передать точный `FINAL_COMMIT_SHA`.
|