commit 977dc51d8aa04b395721115404031e51ac70c29c Author: estorozhenko Date: Sun Sep 6 13:50:44 2026 +0000 Initial commit: Hermes skill hermes-auxiliary-local-models diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..a963d5d --- /dev/null +++ b/SKILL.md @@ -0,0 +1,91 @@ +--- +name: hermes-auxiliary-local-models +description: "Use when routing Hermes auxiliary tasks to local Ollama." +version: 1.0.0 +author: Agent +--- + +# Hermes Auxiliary Local Models + +## Когда использовать + +Когда main-модель Hermes облачная (polza.ai, DeepSeek, Claude, ...), а auxiliary-задачи +(vision/анализ картинок, web_extract, title_generation, session_search, compression) +хочется переложить на локальную Ollama-модель: экономия облачных токенов, приватность +(картинки не уходят в облако), скорость. Компаньон скилла `hermes-mixed-model-delegation` +(тот про delegate_task/subagents, этот про auxiliary). + +## Как устроен auxiliary в Hermes (факты из agent/auxiliary_client.py) + +- Раздел конфига: `auxiliary..provider/model/base_url/api_key` в config.yaml. +- `provider: auto` = цепочка: (1) main-провайдер, (2) OpenRouter, (3) Nous Portal, + (4) custom endpoint, (5) Anthropic, (6) прямые провайдеры. Для vision отдельная цепочка. +- Только `compression` имеет жёсткий минимум контекста: `MINIMUM_CONTEXT_LENGTH = 64_000` + (agent/model_metadata.py). Остальные задачи (vision, title, web_extract, session_search) + — без флора, могут работать на малом контексте. +- Для локальной Ollama: `provider: custom` + `base_url: http://localhost:11434/v1` + + `api_key: ollama` (любая непустая строка). Hermes зарезолвит OpenAI-совместимый клиент. +- Vision-поддержку локальной модели Hermes определяет через `query_ollama_supports_vision` + (поле `capabilities` в `/api/show`). + +## Конфигурация (проверенный пример: vision на qwen3-vl:8b) + +```bash +hermes config set auxiliary.vision.provider custom +hermes config set auxiliary.vision.model qwen3-vl:8b +hermes config set auxiliary.vision.base_url http://localhost:11434/v1 +hermes config set auxiliary.vision.api_key ollama +``` + +Остальные auxiliary оставить `auto` (уходят на облачный main-провайдер). Изменения вступают +в силу после `/reset` (новой сессии). + +## Подбор модели под VRAM + +- 16 GB (RTX 5060 Ti, 14 GB free): qwen3-vl:8b ≈ 7.2 GB при ctx 4096/8192. + Плюс bge-m3 (эмбеддинги) ≈ 1.2 GB. Итого ~8.5 GB, остаётся ~7 GB запаса. +- qwen3:8b (текст) ≈ 5.2 GB, но GGUF-контекст 40960 < 64K флора compression — + текстовые aux можно, compression НЕТ (оставить на облаке). +- Всегда проверяй реальный GGUF-контекст: `/api/show` → `model_info.*.context_length` + (имена тегов "-64k"/"-65k" часто лгут). + +## Проверка vision E2E (без UI) + +1. Прямой тест Ollama `/api/chat` с картинкой (base64) + `options: {num_ctx: 8192}`. +2. OpenAI-совместимый путь `/v1/chat/completions` с `image_url: data:image/png;base64,...` — + именно его использует Hermes. +3. Резолв из Python (сигнатура важна: `explicit_base_url`/`explicit_api_key`, НЕ base_url/api_key): +```python +from agent.auxiliary_client import _resolve_task_provider_model, resolve_provider_client +provider, model, base_url, api_key, api_mode = _resolve_task_provider_model(task="vision") +client, resolved = resolve_provider_client(provider=provider, model=model, + explicit_base_url=base_url, explicit_api_key=api_key, is_vision=True, task="vision") +``` + +## Pitfalls + +- **Тестовая картинка должна быть ≥ 32×32 px.** Qwen3-VL image processor в Ollama падает + с panic `height:1 or width:1 must be larger than factor:32` на картинках меньше 32 px — + это выглядит как 500/EOF runner, но это баг размера картинки, а не конфигурации. +- **GGUF-контекст qwen3-vl:8b = 262144** — KV cache при полном контексте ~309 GB, физически + не влезает. Ollama по умолчанию грузит с num_ctx 4096 (нормально); для запросов можно + задавать `options: {num_ctx: 8192}`. Не доверяй GGUF-контексту при планировании VRAM. +- **`hermes chat --provider custom` в CLI может не подхватить ключ** — это не показатель + поломки: auxiliary резолвится из config.yaml напрямую. Проверяй через Python-резолвер + (см. выше), а не через CLI-запуск. +- **Пустой content при vision-ответе** — если картинка слишком мала/простая, content может + быть пустым при `finish_reason: stop` (reasoning ушёл в `reasoning`/`reasoning_content`). + Перепроверь на картинке 256×256. +- Модель держится в VRAM (~7-10 мин keep_alive по умолчанию), потом выгружается. Освободить + принудительно: `docker exec ollama ollama stop qwen3-vl:8b`. +- Ollama в Docker: CLI-команды через `docker exec ollama ollama ...`. + +## Откат + +```bash +hermes config set auxiliary.vision.provider auto +hermes config set auxiliary.vision.model "" +docker exec ollama ollama stop qwen3-vl:8b +``` + +Подробный разбор сессии с транскриптом ошибок: `references/qwen3-vl-ollama-setup.md`. \ No newline at end of file diff --git a/references/auxiliary-task-inventory.md b/references/auxiliary-task-inventory.md new file mode 100644 index 0000000..4d7b2a7 --- /dev/null +++ b/references/auxiliary-task-inventory.md @@ -0,0 +1,73 @@ +# Inventory of Hermes auxiliary tasks + model routing (2026-09-04) + +Все задачи, которые Hermes решает "в фоне" отдельно от main-модели через +`call_llm(task=...)` (agent/auxiliary_client.py) и точки их вызова в коде. +Использовать для решения: какую задачу направить на локальную Ollama, какую оставить на облаке. + +## Полная карта задач + +| Задача | Назначение / где вызывается | Требования/характер | +|---------------------|------------------------------------------------------|----------------------------------------| +| title_generation | Заголовки сессий (/title) | короткий вывод, мгновенно | +| web_extract | Пересказ/извлечение из веб-страниц (web_extract) | средний вывод | +| vision | Анализ изображений, скриншоты браузера | нужна vision-модель, приватность | +| skills_hub | Поиск/описание скиллов в хабе (skills_hub) | короткий вывод | +| profile_describer | Описание пользовательского профиля (profile_describer)| не hot-path, краткий вывод | +| compression | Сжатие длинных диалогов (context_compressor) | **контекст ≥ 64K**, важна надёжность | +| approval | Умное авто-одобрение команд, режим smart (approval.py) | безопасность, одно слово (APPROVE/DENY/ESCALATE) | +| mcp | Обёртка MCP-серверов (tools/mcp_tool.py) | может быть сложный вывод | +| kanban_decomposer | Декомпозиция задач каньбан в JSON (kanban_decompose.py) | длинный JSON (до max 4000 токенов) | +| triage_specifier | Спецификация/уточнение задач каньбан (kanban_specify.py) | рабочие задачи, надёжность | +| curator | Фоновый анализ/жизненный цикл скиллов (curator) | фоновый, но смысловой анализ | + +## Рекомендуемое распределение (проверено, применено в конфиге) + +**На локальную qwen3:8b-nothink (текст) / qwen3-vl:8b (vision):** +title_generation, web_extract, vision, skills_hub, profile_describer. +Экономит облачные токены, картинки не уходят в облако. + +**Оставить на облаке (provider: auto / main):** +- compression — жёсткий минимум `MINIMUM_CONTEXT_LENGTH = 64_000`, локаль (< 40K) не тянет +- approval — безопасность, не направлять на слабую локаль +- mcp — может требовать сложного вывода +- kanban_decomposer — длинный JSON, локаль 8b рискует битой структурой +- triage_specifier — рабочие задачи, надёжность +- curator — фоновый, смысловой анализ + +## КРИТИЧНО: session_search — НЕ auxiliary-задача + +`session_search` НЕ имеет отдельной auxiliary-модели. Проверено резолвером: +``` +_resolve_task_provider_model(task="session_search") # -> provider=auto model=None +``` +Это agent-loop инструмент (наравне с todo, memory, delegate_task), он ВСЕГДА идёт на +main-модель/auto. Настройка `auxiliary.session_search.provider` не имеет эффекта +(её блок в конфиге имеет только extra_body/max_concurrency/timeout). Не трать время +на попытку переключить её на локаль — это ограничение Hermes, а не наша ошибка. + +## Настройка блока в config.yaml (вручную, НЕ hermes config set) + +`hermes config set` не правит dict-блоки aux корректно — правь текстом: +```yaml + : + provider: custom + model: qwen3:8b-nothink + base_url: http://localhost:11434/v1 + api_key: ollama + timeout: 60 +``` +Замена по маркеру ` :` (2 пробела + имя целиком, включая последующий отступ +в 4 пробела + пустую строку до следующей задачи). Порядок полей в существующем блоке +в файле: provider, model, base_url, api_key, timeout, extra_body. + +## Проверка E2E после смены модели + +Реальный контур (не CLI, который может не подхватить ключ): +```python +import sys; sys.path.insert(0, "/opt/hermes/.hermes/hermes-agent") +from agent.auxiliary_client import call_llm +r = call_llm(task="", messages=[{"role":"user","content":"<короткий промпт>"}], max_tokens=80) +# смотри: r.model == ожидаемая модель, r.choices[0].finish_reason == "stop", content не пуст +``` +Признак рабочей локальной модели: `finish_reason: stop` + осмысленный content. +| r.choices[0].message.content | r.model | \ No newline at end of file diff --git a/references/qwen3-vl-ollama-setup.md b/references/qwen3-vl-ollama-setup.md new file mode 100644 index 0000000..b46a131 --- /dev/null +++ b/references/qwen3-vl-ollama-setup.md @@ -0,0 +1,79 @@ +# Qwen3-VL 8B + Ollama: разбор сессии настройки auxiliary.vision + +Дата: 2026-08-30. Хост: RTX 5060 Ti 16 GB VRAM, Ollama в Docker (контейнер `ollama`), +main-модель Hermes — облачная polza.ai (deepseek/deepseek-v4-flash-0731). + +## Итог + +`auxiliary.vision` переведён на локальную `qwen3-vl:8b`: +- model: qwen3-vl:8b (~6.14 GB на диске, ~7.2 GB VRAM при ctx 4096/8192) +- provider: custom, base_url: http://localhost:11434/v1, api_key: ollama +- остальные auxiliary остались на `auto` → облачный main-провайдер + +Проверено E2E: vision-запрос через Hermes-клиент (`resolve_provider_client`) отвечает +"red"/"blue" на тестовых PNG. + +## Ключевые факты о Hermes auxiliary (agent/auxiliary_client.py) + +- Раздел конфига: `auxiliary..provider/model/base_url/api_key` в config.yaml. +- `provider: auto` цепочка (текст): main-провайдер → OpenRouter → Nous Portal → + custom endpoint → Anthropic → прямые провайдеры. Для vision своя цепочка: + main-провайдер (если поддерживает vision) → OpenRouter → Nous → Anthropic → + custom endpoint (локальные Qwen-VL/LLaVA/Pixtral). +- `MINIMUM_CONTEXT_LENGTH = 64_000` (agent/model_metadata.py:405) — флор только для + `compression`. Остальные задачи без флора. +- `resolve_provider_client()` сигнатура: `explicit_base_url` / `explicit_api_key` + (не `base_url`/`api_key` — TypeError). +- Vision-capable локальных моделей определяется через `query_ollama_supports_vision` + (capabilities в `/api/show`, или model_info.*.vision.block_count на старых серверах). + +## Ошибки в процессе (транскрипт) + +1. `HTTP 500` на `/api/chat` с картинкой 1×1 px: + ``` + panic: height:1 or width:1 must be larger than factor:32 + github.com/ollama/ollama/model/models/qwen3vl.(*ImageProcessor).SmartResize + ``` + → Ошибка картинки, не конфигурации. Входной образ должен быть ≥ 32 px. +2. Пустой `content` при `finish_reason: stop` на картинке 64×64 — reasoning ушёл в + `reasoning` поле. На 256×256 ответ нормальный ("red"). +3. `hermes chat -m ... --provider custom` в CLI: "No API key found for provider 'custom'" + — CLI-запуск не отражает auxiliary-резолв; auxiliary берётся из config.yaml напрямую. +4. `curl ... | python3` в terminal — блокируется security scan (pipe to interpreter); + использовать execute_code с urllib вместо пайпов. + +## VRAM-планирование (qwen3-vl:8b, GGUF) + +- GGUF-контекст: `qwen3vl.context_length = 262144`, layers=36, embedding=4096. +- KV cache (f16) ≈ 2 · layers · dim · 2 · ctx · 2 bytes: + - 32K → ~38.7 GB, 64K → ~77 GB, 128K → ~155 GB, 262K → ~309 GB. + → Физически полный контекст не влезает в 16 GB. Ollama грузит с num_ctx 4096 по умолчанию, + для запросов задавать `options: {num_ctx: 8192}`. +- Итоговое распределение: qwen3-vl 7.19 GB + bge-m3 1.21 GB = 8.4 GB занято, ~7.3 GB свободно. +- qwen3:8b и qwen2.5-coder:14b имеют GGUF-контекст 40960/32768 (имена "-64k"/"-65k" лгут) — + ниже 64K флора compression, поэтому compression оставлен на облаке. + +## Полезные команды + +```bash +# Pull в Docker +docker exec ollama ollama pull qwen3-vl:8b + +# Проверка caps/контекста +curl -s http://localhost:11434/api/show -d '{"model":"qwen3-vl:8b"}' | jq '.capabilities, .model_info["qwen3vl.context_length"]' + +# Тест vision напрямую +curl -s http://localhost:11434/api/chat -d '{"model":"qwen3-vl:8b","messages":[{"role":"user","content":"What color?","images":[""]}],"options":{"num_ctx":8192},"stream":false}' + +# OpenAI-совместимый путь (что использует Hermes) +curl -s http://localhost:11434/v1/chat/completions -d '{"model":"qwen3-vl:8b","messages":[{"role":"user","content":[{"type":"text","text":"What color?"},{"type":"image_url","image_url":{"url":"data:image/png;base64,"}}]}],"max_tokens":50}' + +# Выгрузка из VRAM +docker exec ollama ollama stop qwen3-vl:8b +``` + +## Откат конфигурации + +```bash +hermes config set auxiliary.vision.provider auto +hermes config set auxiliary.vision.model "" \ No newline at end of file diff --git a/references/text-aux-qwen3-think-false.md b/references/text-aux-qwen3-think-false.md new file mode 100644 index 0000000..bea739f --- /dev/null +++ b/references/text-aux-qwen3-think-false.md @@ -0,0 +1,100 @@ +# Текстовые aux на локальной Ollama (qwen3:8b) + отключение thinking + +Дата: 2026-08-30. Продолжение `qwen3-vl-ollama-setup.md`: после vision переведены +текстовые auxiliary-задачи (title_generation, web_extract) на локальную qwen3:8b. +Хост: RTX 5060 Ti 16 GB VRAM, Ollama в Docker, main-модель — polza.ai. + +## Итоговая конфигурация + +```yaml +auxiliary: + vision: # qwen3-vl:8b (мультимодальная) + provider: custom + model: qwen3-vl:8b + base_url: http://localhost:11434/v1 + api_key: ollama + title_generation: # qwen3:8b (текстовая) + provider: custom + model: qwen3:8b + base_url: http://localhost:11434/v1 + api_key: ollama + extra_body: + think: false + web_extract: # qwen3:8b + provider: custom + model: qwen3:8b + base_url: http://localhost:11434/v1 + api_key: ollama + extra_body: + think: false + compression: # остаётся в облаке (нужен 64K+ контекст) + provider: auto +``` + +Проверено E2E через `from agent.auxiliary_client import call_llm`: +- title_generation: "Настройка Ollama: локальные модели" — 11.3 сек (на qwen3-vl было 109 сек) +- web_extract: нормальный пересказ — 7.5 сек +- compression резолвится на auto → polza, не тронута + +## Почему qwen3-vl НЕ годится для текстовых aux + +На простой вопрос ("какой сегодня день недели?") qwen3-vl:8b сгенерила 3778 токенов +(eval_ms 54 сек) и отвечала 64 сек. reasoning=false в ответе, но content огромный — +модель "растекается". Для быстрых aux (заголовки, извлечение) непригодна. + +## Ключевая находка: отключение thinking у qwen3 в Ollama + +**ВАЖНО (2026-09-04, Ollama 0.22.1):** `think: false` через OpenAI `/v1/chat/completions` НЕ работает +для qwen3 — модель все равно думает, content пуст, `finish: length` (reasoning съел лимит). +Через нативный `/api/chat` + `think: false` думание выключается корректно (но Hermes ходит через /v1). + +**Решение для Hermes/aux — собрать тег с вырезанным reasoning** (никакие опции не помогают): + +```bash +# 1. Получить modelfile (docker): +docker exec ollama ollama show qwen3:8b # или GET /api/show -> поле "modelfile" +# 2. В TEMPLATE заменить все $.IsThinkSet -> false (ветки thinking навсегда выключены) +# 3. Собрать (переиспользует blob, без скачивания): +docker cp /tmp/qwen3-nothink-Modelfile ollama:/root/ +docker exec ollama ollama create qwen3:8b-nothink -f /root/qwen3-nothink-Modelfile +``` +Ключ: `$.IsThinkSet` управляет думанием (вставляет `/think`/`/no_think` в user msg и `thinking{{ .Thinking }}` +в assistant). При `false` модель просто отвечает. Проверено: `qwen3:8b-nothink` через /v1 отвечает +мгновенно (finish: stop), E2E через `call_llm(task=...)` в Hermes — корректный content +(title_generation, web_extract). Оригинальный `qwen3:8b` не трогаем (откат — 1 строка в config). + +Затем в config.yaml (вручную, т.к. `hermes config set` не правит dict extra_body; порядок полей в +файле: provider, model, base_url, api_key, timeout, extra_body): model для +`auxiliary.title_generation` и `auxiliary.web_extract` := `qwen3:8b-nothink`. + +**ОПРОВЕРГНУТЫЕ способы (не работают — модель все равно генерит reasoning первыми токенами):** +- `enable_thinking: false` в options (`/api/chat`) — таймаут +- `enable_thinking: false` в extra_body (`/v1/chat/completions`) — content пустой +- `/no_think` в начале user-сообщения — reasoning=True, content пустой +- system prompt "don't think" — не помогает +- корневой `think: false` в /v1 — НЕ работает в Ollama 0.22 (работает только native /api/chat) + +Симптом при неотключённом thinking: **content = ''**, `usage.completion_tokens == max_tokens` +(весь бюджет съело reasoning), время ответа большое. Диагностика: смотреть поле +`reasoning`/`reasoning_content` в ответе. + +## Полный список auxiliary-задач (из config.yaml) + +vision, web_extract, compression, skills_hub, approval, mcp, title_generation, +triage_specifier, kanban_decomposer, profile_describer, curator. + +`session_search` — НЕ auxiliary-задача: `hermes config set auxiliary.session_search.provider` +даёт warning "not a recognized config key" (в конфиге у неё только extra_body/max_concurrency/ +timeout). Не настраивать как auxiliary. + +## VRAM (обе модели одновременно) + +qwen3:8b 6.13 GB + qwen3-vl:8b 7.19 GB + bge-m3 1.21 GB ≈ 14.5 GB — влезают в 16 GB, +переключение vision↔текст без выгрузки (keep_alive по умолчанию). При нехватке Ollama +выгружает LRU-модель и перезагружает при следующем вызове (+5-15 сек). + +## Нюанс `hermes config set` + +`hermes config set auxiliary..extra_body` не пройдёт (extra_body — dict, не скаляр) — +править YAML вручную (python+yaml.safe_dump). Остальные ключи (provider/model/base_url/ +api_key) ставятся через `hermes config set` с `--force` если ключ не в schema. \ No newline at end of file