hermes-hub/agents/inbox/2026-08-31-A39-local-request-options.md
Hermes Team f8b6783641 docs(agents): задание A39 — параметры запроса для локальных профилей
Локальная модель не закрывает задачи: Hermes сообщает о таймауте 180 секунд
после четырёх вызовов и переключается на другого провайдера.

Причина измерена, а не предположена. Служба запущена с --reasoning on и
--reasoning-budget 4096, а модель выдаёт 13,4 токена в секунду. На простой
задаче рефакторинга с лимитом 1500 токенов получено:

    сгенерировано  1500 токенов за 111,6 с
    рассуждений    5483 символа
    ответа         0 символов

Весь лимит уходит на размышления, до ответа модель не доходит. За 180 секунд
она успевает около 2400 токенов — и это тоже одни рассуждения.

Отключение рассуждений на уровне запроса проверено и работает:

    reasoning_effort: none                        ответ за 19,5 с
    chat_template_kwargs: enable_thinking=false   ответ за 11,4 с

Одиннадцать секунд вместо ста одиннадцати. Серверный флаг --reasoning off
решил бы это грубо, лишив рассуждений насовсем, поэтому владелец выбрал
гибкий путь: параметры задаёт хаб, по профилю.

Задание требует не зашивать ни enable_thinking, ни reasoning_effort: набор
ключей зависит от версии llama.cpp, и это данные конфигурации, а не
константы кода. Отдельным критерием — проверка живым запросом к серверу
владельца с замером времени до и после.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 23:04:18 +07:00

142 lines
9.8 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.

# Задание A39: параметры запроса для локальных профилей
## Дата поступления
2026-08-31
## База
Ветка ревьюера `review/a35-a37-verified` (`88d579a`) — там уже слиты A35, A36, A37 и правки ревьюера.
```
git fetch origin --prune
git checkout -b antigravity/a39-local-request-options origin/review/a35-a37-verified
```
В `main` напрямую не пушить.
## Порядок исполнения
Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-5** написан для аудитора.
Задание небольшое и независимое: зона — только локальный адаптер и конфигурация профиля.
---
## Задача
Локальная модель не закрывает задачи: Hermes сообщает «Локальный 27B не закрыл задачу: таймаут 180s, 4 вызова» и переключается на другого провайдера.
Причина найдена и измерена, гадать не нужно.
## Что измерено ревьюером — заново не выяснять
Сервер владельца, `127.0.0.1:8081`, Qwen3.8-27B на Tesla V100. Служба запущена с `--reasoning on --reasoning-budget 4096`.
**Задача рефакторинга простой функции, лимит 1500 токенов:**
```
сгенерировано: 1500 токенов за 111,6 с (13,4 ток/с)
рассуждений: 5483 символа
самого ответа: 0 символов
```
Модель израсходовала весь лимит на размышления и **до ответа не дошла**. За отведённые Hermes 180 секунд она успевает около 2400 токенов — и это по-прежнему одни рассуждения. Отсюда таймаут и четыре безрезультатных вызова.
**Отключение рассуждений на уровне запроса — проверено, работает:**
```
reasoning_effort: "none" 540 симв. рассуждений, ответ 302, 19,5 с
chat_template_kwargs: {"enable_thinking": false} 0 рассуждений, ответ 449, 11,4 с
```
**11 секунд вместо 111**, и ответ появляется. Проблема не в скорости модели, а в том, что она не доходит до ответа.
Серверный флаг `--reasoning off` решил бы это грубо, но лишил бы рассуждений насовсем. Владелец выбрал гибкий путь: параметры задаёт хаб, по профилю.
## Что уже есть
```
adapters/local_adapter.py:164 payload собирается здесь; сейчас проходят
tools, tool_choice, response_format,
max_tokens, stream, stop
router_config.py:13 RouterProfileConfig — места под произвольные
параметры запроса нет
```
## P0-1. Параметры запроса в профиле
Добавить в `RouterProfileConfig` поле для произвольных параметров, которые адаптер подмешивает в тело запроса. Например `request_options: dict`.
Требования:
1. **Ничего не зашивать.** `enable_thinking`, `reasoning_effort` и прочее — это данные в конфигурации владельца, а не константы в коде. Завтра у llama.cpp появится другой ключ, и правка кода не должна понадобиться.
2. **Сохраняется и переживает перезапуск**, как остальные поля профиля.
3. **Вложенные структуры поддерживаются**: `chat_template_kwargs` — это словарь внутри словаря.
4. **Явное поле запроса не перезаписывается молча.** Если Hermes прислал `max_tokens`, параметры профиля его не затирают; при конфликте выигрывает запрос, а факт расхождения пишется в журнал.
## P0-2. Локальный адаптер их отправляет
`local_adapter.invoke` подмешивает `request_options` в тело перед отправкой.
Осторожно с двумя вещами:
- **Неизвестный параметр не должен ронять вызов.** Сервер вернёт ошибку — её надо показать как ошибку провайдера с текстом, а не как отказ маршрутизатора. Механизм разбора ошибок уже есть в `base_adapter.extract_api_error_message`.
- **Другие провайдеры не затрагиваются.** Поле относится к локальным профилям; попадание `chat_template_kwargs` в запрос к Antigravity или Codex — дефект.
## P0-3. Настройка из интерфейса
Владелец должен задавать это без правки YAML вручную.
В карточке локального аккаунта — поле параметров запроса. Достаточно текстового поля с JSON и проверкой разбора: набор ключей зависит от версии llama.cpp, и выпадающий список из зашитых вариантов быстро устареет.
**Показать, что именно уйдёт на сервер.** И честно сказать, если параметр отклонён сервером, — с его текстом ошибки.
## P0-4. Умолчание для профилей владельца
Предложить в отчёте, но **не применять молча**: для `local-1` (27B, порт 8081) поставить
```json
{"chat_template_kwargs": {"enable_thinking": false}}
```
Обосновать измерением выше. Решение принимает владелец.
Для `local-2` (4B, порт 8082) этого не нужно: она запущена с `--reasoning off`.
## P0-5. Аудит вторым проходом
1. **Проверить на живом сервере владельца**, а не только заглушкой: запрос с `enable_thinking: false` должен вернуть ответ за десяток секунд вместо ста.
2. **Утечка в других провайдеров** — проверить целенаправленно, что параметр уходит только в локальные.
3. **Неизвестный ключ** не роняет вызов и показывается как ошибка провайдера.
4. **Зашитые значения** — искать отдельно; в коде не должно быть ни `enable_thinking`, ни `reasoning_effort`.
5. **Побочные изменения** объяснить.
6. **Пропущенный пункт назвать пропущенным.**
---
## Ограничения
- Зона: `adapters/local_adapter.py`, `router_config.py`, карточка аккаунта в вебе, тесты.
- Серверные юниты `qwen-coder` и `qwen-compressor` не трогать: это машина владельца, он меняет их сам.
- Правило честности без исключений.
- Тег `v0.1.1` не создавать.
## Критерии приёмки
1. Ветка в `origin` от `review/a35-a37-verified`, `git status` чист.
2. `request_options` есть в профиле, сохраняется, переживает перезапуск, поддерживает вложенные структуры.
3. Локальный адаптер отправляет их; **проверено живым запросом к серверу владельца** с приложенным замером времени до и после.
4. Параметр не попадает в запросы к другим провайдерам; проверено.
5. Неизвестный ключ даёт ошибку провайдера с текстом, а не отказ маршрутизатора.
6. Поле настраивается из интерфейса, показывает отправляемое тело.
7. Явные поля запроса Hermes не затираются.
8. Ни `enable_thinking`, ни `reasoning_effort` не зашиты в коде.
9. `ruff check .` чисто; релизный гейт не ухудшен.
10. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. На ветке ревьюера сейчас **508 passed, 2 skipped**.
## Главное
Локальная модель сейчас бесполезна: она не доходит до ответа за отведённое время, и Hermes от неё отказывается. Одна строка параметров превращает 111 секунд без результата в 11 секунд с ответом. Нужно, чтобы владелец задавал эту строку из хаба, а не пересобирал службу.
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`.