hermes-hub/agents/inbox/2026-08-31-A42-provider-connect.md
Hermes Team 8b67f0dadb docs(agents): вернуть в репозиторий постановки A42-A56 и отчёт A30
Тринадцать файлов существовали только на диске ПК владельца, в рабочей
копии, отставшей от 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>
2026-09-03 18:16:35 +07:00

166 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Задание 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`.