# llm-proxy Прозрачная прослойка/роутер OpenAI API. Принимает `/v1/chat/completions` и `/v1/models`, маршрутизирует между объявленными в YAML апстримами по внутреннему учёту конкурентности и мержит `patch`-слои (`provider` → `upstream` → `model`) в тело запроса. Подробная спецификация конфига — в `CONFIG.md`. ## Конфиг Конфигурация — в YAML-файле (по умолчанию `config.yaml` в каталоге проекта, CWD; переопределяется env `CONFIG_PATH`). Блоки: `server` (интерфейс биндинга `host` + `port`, дефолты `0.0.0.0` / `8100`), `providers`, `upstreams`, `models`. Старый механизм env-переменных (`UPSTREAM_URL`, `ROUTER_API_KEY`, `EXCLUDED_PROVIDERS`, `THINKING_MODELS`, `PORT`) удалён — его поведение теперь в декларативном `patch` и блоке `upstreams`, а порт/интерфейс — в блоке `server`. Лимит конкурентности `max_concurrency` можно задать и на провайдере (лимит по умолчанию для его апстримов), и на апстриме (перекрывает провайдерский); без обоих — безлимит. Обработку think-тегов включает опциональный флажок `think_tags` у провайдера или апстрима (`off` по умолчанию, `split` — рассуждения из `think`-тегов уходят в `reasoning_content`, `strip` — выбрасываются); работает и в стриме, и в non-stream. Повторяющиеся сбои апстрима увязываются экспоненциальным backoff: поле `backoff` (ISO-8601-потолок, напр. `P1M`) задаётся на провайдере (общий счётчик на его модели) и/или на апстриме (приоритет). Первая ошибка — отдых 1s, далее 2s, 4s, … до потолка; успех сбрасывает. Все апстримы модели в откате — `503` с `Retry-After`. Для `stream=true` запросов действует watchdog на первый байт ответа: `first_byte_timeout` (ISO-8601, дефолт `PT30S`, `PT0S` отключает) — если апстрим не прислал ни одного байта за окно, слот освобождается, ошибка идёт в backoff и роутер фейловерит на следующего свободного апстрима. Приоритет: `upstreams[].first_byte_timeout` → `providers[].first_byte_timeout` → `PT30S`. Пока апстрим «отдыхает» в backoff-откате, его можно фоново пинговать: `probe_interval` (ISO-8601, дефолт `PT1S`, `PT0S` отключает) — HealthProber шлёт апстриму минимальный запрос (`2+2=?`, `max_tokens=4`) и, как только тот ответил, снимает его с отката немедленно, не дожидаясь истечения backoff-интервала. Пинг не занимает слот конкурентности и не засчитывается в backoff. Приоритет: `upstreams[].probe_interval` → `providers[].probe_interval` → `PT1S`. Прокси отдаёт Prometheus-метрики по `GET /metrics` (запросы по `model/upstream/provider/result`, занятые слоты, счётчик и окно backoff-отката) — подключите скрейп в Prometheus и стройте дашборды/алерты в Grafana. Список метрик — в CONFIG.md, раздел «Prometheus-метрики». | Переменная | Default | Описание | |---|---|---| | `CONFIG_PATH` | `config.yaml` (CWD) | Путь к YAML-конфигу | | Ключ конфига | Default | Описание | |---|---|---| | `server.host` | `0.0.0.0` | Интерфейс/адрес биндинга | | `server.port` | `8100` | Порт сервера | Таймаут на запрос к нейронке (весь ответ, включая стриминг) — константа `UPSTREAM_REQUEST_TIMEOUT_MS` = 5 минут; без неё дефолт CIO-движка Ktor — 15 секунд, и длинные генерации обрываются. Значение логируется при старте. ## Сборка и деплой - CI (Gitea Actions, на release): `./gradlew fatJar` → образ `images.binom.pw/llm-proxy:` (zot). - Запуск на 76.179 (llm-router): `podman-compose up -d` из `podman-compose.yaml` (образ тянется из `images.binom.pw`, сеть `bifrost_default` — туда же Bifrost ходит по имени `llm-proxy`). - Bifrost: в `config_providers` провайдера `Routerai` `network_config_json.base_url` = `http://llm-proxy:8100` (после смены — рестарт bifrost_gateway).