hermes-hub/agents/inbox/2026-08-24-A25-local-llm-provider.md
Hermes Team bad24ff6aa docs(task): A25 — разведка выполнена, задание построено на фактах с сервера
Владелец дал доступ по ключу. На 192.168.1.81 работают два сервера
llama.cpp, оба OpenAI-совместимые, проверено запросами:

  8081  Qwen3.8-27B-Q4_K_M   reasoning on, контекст 65536, -ngl 99
  8082  Qwen3-4B-Instruct    reasoning off, «compressor»

GET /v1/models и POST /v1/chat/completions отвечают 200 на обоих.
Видеокарта Tesla V100-32GB, занято 28.7 из 32.7 ГБ.

Три следствия внесены в задание как определяющие реализацию:
- оба слушают только 127.0.0.1, снаружи недоступны; варианты решения
  предложены владельцу, выбор за ним;
- у обоих --parallel 1, то есть один запрос за раз: лизы Hub обязаны
  ограничивать локального провайдера, иначе роли заблокируют друг друга;
- памяти видеокарты на третью модель нет, слотов ровно два.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 10:46:04 +07:00

14 KiB
Raw Permalink Blame History

Задание A25: локальная модель как провайдер Hub

Дата поступления

2026-08-24

База

Проверочный HEAD на момент выдачи: f757639.

Ветка

antigravity/local-llm-provider

Порядок исполнения

Два прохода: Flash реализует, Pro проводит аудит. Идёт параллельно с A24 (перестройка навигации) — границы по файлам ниже.


Порядок работы с git

cd <каталог репозитория>; git fetch origin --prune; git status
git checkout main; git pull --ff-only origin main
git checkout -b antigravity/local-llm-provider
git commit -m "..."          <- сначала коммит
git push -u origin antigravity/local-llm-provider

В main напрямую не пушить. В конце — push и проверка git log --oneline -1, git status чистый.


Задача

У владельца есть сервер 192.168.1.81 с локальной LLM. Там же стоят Hermes, Docker, git. Он хочет использовать локальную модель как субагента — то есть как обычного провайдера Hub, наравне с Antigravity и Codex.

Ценность очевидна: локальная модель не имеет квоты и не стоит денег. Для ролей вроде fast и research это идеальный резерв, который никогда не исчерпается. Сейчас у владельца реально работает один провайдер из пяти, и запаса нет.

Что уже есть в проекте

Не начинайте с нуля, половина сделана:

  • RouterProfileConfig.custom_base_url — поле для произвольного адреса уже существует (router_config.py:22), сохраняется и загружается;
  • deepseek_adapter.py — готовый образец OpenAI-совместимого адаптера: берёт profile.custom_base_url, зовёт {base_url}/chat/completions. Локальные серверы (Ollama, vLLM, LM Studio, llama.cpp) выставляют тот же интерфейс;
  • у владельца в Hermes уже настроен профиль deepseek через provider: custom, endpoint: https://limitdeckai.ru/v1 — то есть путь проверен на практике.

P0-1. Разведка выполнена — вот что на сервере

Владелец дал доступ по ключу, ревьюер зашёл и всё выяснил. Заново не выясняйте.

На 192.168.1.81 работают два сервера llama.cpp, оба OpenAI-совместимые:

Порт Модель Настройки
8081 Qwen3.8-27B-Q4_K_M.gguf -ngl 99, контекст 65536, flash-attn, KV-кэш q8_0, reasoning on (бюджет 4096, формат deepseek), temp 0.2, top-p 0.9
8082 Qwen3-4B-Instruct-2507-Q4_K_M.gguf то же, но reasoning off; в имени каталога — «compressor»

Проверено запросами:

GET  /v1/models          -> список моделей, формат llama.cpp
POST /v1/chat/completions -> 200   (на обоих портах)

Видеокарта: Tesla V100-PCIE-32GB, занято 28.7 ГБ из 32.7.

Три следствия, которые определяют реализацию

  1. Оба сервера слушают только 127.0.0.1. Снаружи, в том числе с машины владельца под Windows, они недоступны. Пока это не решено, провайдер работать не будет. Решение выбирает владелец, вариант в отчёт:

    • перезапустить llama.cpp с --host 0.0.0.0 — модель станет доступна всем в домашней сети;
    • держать Hub на самом сервере — там уже стоят Hermes и Hub, и 127.0.0.1 доступен напрямую;
    • проброс по SSH — не годится для службы, туннель придётся держать поднятым.
  2. У обоих серверов --parallel 1. Это значит один запрос за раз. Второй встанет в очередь и будет ждать. Для маршрутизации это принципиально: лизы Hub обязаны ограничивать локального провайдера одним одновременным вызовом, иначе роли начнут блокировать друг друга и таймауты пойдут лавиной.

  3. Память видеокарты почти исчерпана — 28.7 из 32.7 ГБ. Третью модель не поднять, и это не проблема Hub, а факт, который надо учитывать: локальный провайдер даёт ровно две модели.

Что это значит для ролей

Набор напрашивается сам:

  • 27B с reasoning — тяжёлые роли: reviewer, coder-secondary. Модель рассуждающая, с большим контекстом;
  • 4Bfast: быстрые вспомогательные вызовы, ради чего она и поднята.

Предложить владельцу, не менять цепочки молча.

P0-2. Провайдер локальных моделей

Добавить провайдера — предлагается ключ local — по образцу deepseek_adapter.

  1. Адаптер: OpenAI-совместимый POST {base_url}/chat/completions. Ключ необязателен: локальные серверы обычно его не требуют. Если ключа нет — не выдумывать заголовок авторизации.
  2. Обнаружение моделей: GET {base_url}/models. Это штатный способ, и он избавляет от той боли, что была с agy models. Список кладётся в тот же ModelDiscoveryService.
  3. health_check: запрос к {base_url}/models с коротким таймаутом. Локальная сеть быстрая, но сервер может быть выключен — отказ должен быть быстрым и внятным, а не висеть.
  4. Профили и слоты: добавить local-1 и local-2 во встроенные умолчания — ровно по числу поднятых моделей; третью на этой видеокарте не запустить, чтобы миграция из A9 довела их до существующих конфигураций. Проверить, что find_free_slot("local") возвращает существующий профиль.

P0-3. Квоты: у локальной модели их нет, и это надо сказать прямо

Самое важное для честности продукта.

У локальной модели нет квоты — не «квота неизвестна», не ноль, а её не существует как понятия. Показывать Н рядом с процентами других провайдеров будет читаться как «данные не пришли».

Требуется отдельное состояние: «Без ограничений» или равнозначное, с пояснением, что это локальная модель. is_loading при этом false, unavailable_reason — не про ошибку, а про природу провайдера.

Границы всё же есть, и их стоит показать, если сервер их отдаёт: длина контекста, число одновременных запросов. Не отдаёт — не выдумывать.

P0-4. Мастер подключения

Добавить локального провайдера в мастер. Поля: адрес сервера (у владельца это http://192.168.1.81:8081/v1 и :8082/v1), необязательный ключ, кнопка проверки.

Проверка должна быть настоящей: запрос к /models, показ найденных моделей. Не «сохранено», а «сервер ответил, доступно N моделей» с их перечислением.

При недоступности — конкретная причина: не отвечает, отвечает не тем, требует ключа. Не «ошибка подключения» без пояснения — этот урок уже оплачен кодом 12 и «terminated unexpectedly».

P0-5. Роль по умолчанию

Предложить владельцу, куда поставить обе модели, и обосновать. Разумно исходить из их назначения: 27B с включённым reasoning — последним резервом у reviewer и coder-secondary, 4B — у fast. Обе бесплатны и не исчерпываются, значит годятся как последняя линия, когда платные квоты кончились.

Помнить про --parallel 1: ставить одну и ту же локальную модель резервом сразу нескольким ролям опасно — при одновременной нагрузке они выстроятся в очередь друг за другом.

Не менять цепочки владельца молча. Предложить в отчёте, решение за ним.

P0-6. Аудит вторым проходом

Для проверяющего:

  1. Ни одного выдуманного значения. В прошлом раунде в веб-мастере нашлись захардкоженные коды устройства GRK-7842 и CDX-9104, а тест требовал наличия неверного адреса, то есть защищал выдумку. Здесь особый риск: модель локальная, соблазн «подставить разумное» велик.
  2. Запустить то, что написано. Адаптер должен быть проверен реальным вызовом к серверу владельца, а не только тестом с заглушкой.
  3. Отказ при выключенном сервере. Проверить, что Hub не виснет и не роняет маршрутизацию, если 192.168.1.81 недоступен. Это домашний сервер, он будет выключаться.
  4. Побочные изменения в файлах, которых задание не касалось, — объяснить каждое.
  5. Пропущенный пункт назвать пропущенным.

Ограничения

  • Параллельно идёт A24 (перестройка навигации веба). Его зона: router/web/static/**. Туда не заходить, кроме добавления локального провайдера в мастер — эту правку согласовать в отчёте.
  • Ваши файлы: adapters/**, router_config.py, auto_assigner.py, quota_collector.py, model_discovery*, router/ui/add_account_wizard.py, соответствующие тесты.
  • Адрес локального сервера — настройка, а не константа. Ни 192.168.1.81, ни порт в коде не зашивать.
  • Отсутствие квоты показывать как отдельное состояние, а не как отсутствие данных.
  • Тег v0.1.1 не создавать.

Критерии приёмки

  1. Ветка в origin, git status чист.
  2. В отчёте приведён вывод разведки с сервера владельца.
  3. Провайдер local работает: реальный вызов к серверу возвращает ответ модели — приложить.
  4. Обнаружение моделей через /models наполняет кэш; список виден в выборе модели.
  5. find_free_slot("local") возвращает существующий профиль; миграция добавляет слоты в существующую конфигурацию.
  6. Отсутствие квоты показано отдельным состоянием, отличимым от «данные не пришли».
  7. При выключенном сервере Hub не виснет, маршрутизация уходит к следующему профилю; проверено.
  8. Мастер показывает настоящий список моделей при проверке подключения; при отказе — конкретную причину.
  9. Адрес сервера нигде не зашит.
  10. ruff check . чисто; релизный гейт не ухудшен.
  11. Отчёт: START_HEAD, FINAL_HEAD, origin/main, git status, X passed / Y skipped / Z failed. На main сейчас 375 passed, 2 skipped.

Главное

У владельца реально работает один провайдер из пяти, и запаса нет: если у Antigravity кончатся квоты, переключаться некуда. Локальная модель этот запас даёт — она бесплатна и не исчерпывается. Ради этого задание и делается.

Порядок сдачи

Передать точный FINAL_COMMIT_SHA. Сдано только после появления коммита в origin.