Конфигурация сервера

🔵 Платформу настраивают два файла: config/server.yaml (ядро, провайдер, логирование, auth, виджет) и config/budgets.yaml (бюджетные группы и привязка шаблонов). Расписания — в config/cron.yaml, слежение за изменениями — в config/monitors.yaml, воркфлоу — в config/workflows/. Секреты — в .env (машинно-локальные) либо зашифрованными в config/secrets.yaml (см. Версионирование конфигов).


3.1 Источник конфига — локальная папка или git-репозиторий

Всё дерево конфига (server.yaml, agents/, web/, workflows/, budgets.yaml, cron.yaml, monitors.yaml) грузится из одного корня. По умолчанию это локальная ./config; вместо неё можно клонировать git-репозиторий, чтобы конфиг версионировался и ревьюировался отдельно от бинарника.

Приоритет: CLI-флаг → переменная окружения → локальный ./config.

CLI-флаг Переменная Значение по умолчанию Что делает
--config-repo <url> AGENT_OS_CONFIG_REPO — клонировать конфиг из git (иначе — локальная папка)
--config-ref <ref> AGENT_OS_CONFIG_REF main ветка / тег / коммит, на который фиксируется конфиг
--config-dir <path> AGENT_OS_CONFIG_DIR ./config локальная папка конфига (игнорируется в git-режиме)
--config-poll <secs> AGENT_OS_CONFIG_POLL off периодически git pull + hot-reload конфига
AGENT_OS_CONFIG_REPO=https://github.com/org/agent-config \
AGENT_OS_CONFIG_REF=v1.2.0 \
./agent-os serve

Репозиторий клонируется в ~/.agent-os/configs/<repo>-<ref> на локальную ветку (--config-ref), а не в detached HEAD, чтобы хост мог коммитить и пушить; SHA коммита пишется в лог при старте и виден на /health в поле config. Поллер конфига делает merge --ff-only/rebase — локальные коммиты (версии конфига) не затираются. Секреты можно хранить в конфиг-репозитории зашифрованными (config/secrets.yaml, см. Версионирование конфигов); .env и env.overrides.yaml остаются вне репозитория.


3.2 config/server.yaml

provider — LLM

provider:
  base_url: "https://api.deepseek.com"   # любой OpenAI-совместимый endpoint
  model: "deepseek-v4-flash"
  api_key: ""                            # опционально; предпочтительно .env
  pricing:                               # оценка стоимости инференса
    prompt_per_million: 0.14
    cached_per_million: 0.014
    completion_per_million: 0.28
  fallbacks: ["strong"]                  # цепочка failover при сбое провайдера
  circuit_breaker:                       # пропускать упавший провайдер
    failure_threshold: 5                 # подряд ошибок до размыкания (0 = выкл.)
    cooldown_secs: 30                    # пауза до пробного запроса
    half_open_max: 1                     # успешных проб для замыкания
Поле По умолчанию Описание
base_url https://api.deepseek.com базовый URL OpenAI-совместимого провайдера
model deepseek-v4-flash имя модели
api_key пусто ключ; пустое значение → берётся из env
pricing.prompt_per_million 0.14 $ за 1M prompt-токенов
pricing.cached_per_million 0.014 $ за 1M кэшированных prompt-токенов
pricing.completion_per_million 0.28 $ за 1M completion-токенов
fallbacks [] имена models: для failover (по порядку)
circuit_breaker.failure_threshold 5 ошибок подряд до размыкания; 0 = выключить
circuit_breaker.cooldown_secs 30 пауза перед пробным запросом (half-open)
circuit_breaker.half_open_max 1 успешных проб подряд для замыкания

Circuit breaker (нативный). После failure_threshold ошибок подряд провайдер помечается «open» и пропускается в цепочке failover — запросы идут на следующий провайдер, а не долбятся в упавший. Через cooldown_secs провайдер пробуется одним запросом (half-open): успех замыкает цепь, ошибка снова размыкает. Ключ брейкера — имя model-config (default, strong, …). Механика — часть ядра (agent_os_core::circuit_breaker). Здесь задаётся глобальный дефолт; конкретный агент может переопределить его хуком kind: circuit_breaker (см. 06-hooks-routing §6.17). Сбрасывается оператором через Kernel::provider_breakers().reset(name).

Порядок выбора ключа: provider.api_key → AGENT_OS_API_KEY → DEEPSEEK_API_KEY. Пусто везде → агенты отвечают симулированными ответами.

models — именованные модели

Дополнительные конфиги моделей: каждый становится отдельным провайдером со своим base_url / api_key / model / pricing. Шаблон агента выбирает нужную полем model: в своём agent.yaml. provider: выше — это дефолт (он же регистрируется как "default").

models:
  cheap:
    base_url: "https://api.deepseek.com"
    model: "deepseek-chat"
  strong:
    base_url: "https://api.openai.com/v1"
    model: "gpt-4o"
    api_key: ""

kernel — планировщик и сессии

kernel:
  scheduler: round_robin
  concurrency_limit: 3
  time_slice_secs: 30
  live_reload: true
  session_ttl_secs: 300
  session_cleanup_interval_secs: 60
  max_output_tokens: 4096
Поле По умолчанию Описание
scheduler round_robin политика планирования: round_robin / priority_preemptive / fair_share / deadline_driven
concurrency_limit 3 максимум одновременных LLM-стримов
time_slice_secs 30 квант преемпции (сек) — только при priority_preemptive; 0 = выкл
live_reload true hot-reload config/agents/** и config/web/**
session_ttl_secs 300 максимум простоя сессии (сек), после которого она убивается
session_cleanup_interval_secs 60 как часто (сек) запускается чистильщик сессий
max_output_tokens 4096 глобальный потолок длины ответа за один inference (главный рычаг: при необходимости поднимает per-turn бюджет процесса; пер-агентный max_output_tokens в agent.yaml перекрывает; 0 → дефолт)

logging — логи и хранилища

logging:
  filter: "agent_os=debug"
  jsonl_dir: "logs"
  duckdb_path: "logs/events.db"
  messages_db_path: "logs/messages.db"
  messages_retention_days: 30
  state_db_path: "logs/state.db"
  temp_dir: "logs/temp"
  access_db_path: "logs/access.db"
  cron_db_path: "logs/cron.db"
  user_db_path: "logs/users.db"
  team_db_path: "logs/teams.db"
  monitor_db_path: "logs/monitors.db"
  workflow_db_path: "logs/workflows.db"
  bench_db_path: "logs/bench.db"
  side_effect_db_path: ""            # пусто = идемпотентность выключена
  outbox_db_path: ""                 # пусто = outbox/саги выключены
  dead_letter_db_path: ""            # пусто = dead-letter только в памяти
  durable_db_path: ""                # пусто = durability (durable-агенты) выключена
  pending_db_path: ""                # пусто = pending (approvals/ввод/await_event) только в памяти
Поле По умолчанию Описание
filter agent_os=debug фильтр уровня логирования (tracing)
jsonl_dir logs папка JSONL-логов (process-*.jsonl, access.jsonl)
duckdb_path logs/events.db событийное хранилище DuckDB (пустая строка = выкл)
messages_db_path logs/messages.db общий message store: транскрипты + FAQ-кэш (пусто = выкл)
messages_retention_days 30 сколько дней хранить транскрипты до очистки
state_db_path logs/state.db scoped state store (session / user / agent); пусто = выкл
temp_dir logs/temp scratch-хранилище сессий для файлов-генераторов; file_tool читает оттуда (пусто = выкл)
access_db_path logs/access.db DuckDB-лог HTTP-запросов (пусто = выкл)
cron_db_path logs/cron.db cron: динамические задания + история запусков (пусто = динамические задания не переживают рестарт)
user_db_path logs/users.db реестр канонических пользователей: канальные id → один user id (пусто = выкл)
team_db_path logs/teams.db реестр команд: членство (многие-ко-многим) и гранты; пусто = команды выключены (см. §45)
monitor_db_path logs/monitors.db курсоры change-мониторов (пусто = курсоры не персистятся)
workflow_db_path logs/workflows.db история запусков воркфлоу (пусто = выкл)
bench_db_path logs/bench.db история бенчмарков и снапшоты (пусто = история не пишется)
side_effect_db_path (пусто) хранилище идемпотентности сайд-эффектов (DuckDB); пусто = дедупликация выключена
outbox_db_path (пусто) durable outbox (DuckDB): at-least-once доставка сайд-эффектов + саги/компенсации; открытые интенты догоняются на boot; пусто = выключено
dead_letter_db_path (пусто) durable dead-letter (DuckDB) для входов, карантинённых poison-turn'ами супервизии; пусто = только в памяти
durable_db_path (пусто) хранилище durable-сессий (DuckDB) для агентов с durable: true; пусто = персистентность процессов выключена
pending_db_path (пусто) хранилище незавершённых запросов (approvals / ввод / await_event); пусто = только в памяти. Нужен для durable-агентов, переживающих рестарт

teams — командный доступ

Включается наличием logging.team_db_path (см. §45 «Команды и доступ»). Секция выбирает, какие ресурсы гейтятся грантом команды.

teams:
  gated_tools: ["gdrive_upload", "notion_write"]   # только по гранту команды
Поле По умолчанию Описание
gated_tools [] инструменты, доступные пользователю в командах только по tool-гранту; пусто = гейт инструментов выключен

Доступ к агентам и модульным роутам конфигурируется не здесь: агент — грантом template (плюс per-agent login), роут — объявлением Access::Team.

sandbox — лимиты для процессов на хосте

Глобальная политика для процессов, которые запускают инструменты, скрипты (shell()/exec()) и браузер. Rootless-лимиты: Unix — setrlimit (CPU/AS/NPROC/NOFILE/FSIZE) + новая сессия/process-group; Windows — Job Object (mem/procs/CPU, kill-on-close). ФС-конфайнмент (read_roots/write_roots) — через Landlock (Linux) и проверку путей; tree-wide память/CPU/pids — cgroup v2. Сеть пока не ограничивается. Пусто/enabled: false = выключено. Per-agent override — agent.yaml → sandbox:.

sandbox:
  enabled: true
  cpu_secs: 30          # лимит CPU-времени
  mem_mb: 512           # адресное пространство / память процесса
  max_procs: 64         # число процессов
  max_fds: 256          # открытых файловых дескрипторов
  max_file_mb: 64       # размер создаваемого файла (Unix RLIMIT_FSIZE)
  read_roots: []        # allowlist чтения (пусто = без ограничений); file_tool, fs_read/fs_list
  write_roots: []       # allowlist записи (пусто = без ограничений); file_tool, fs_write
  cgroup_parent: ""     # cgroup v2 для лимитов (Linux); пусто = cgroups выкл
  cpu_quota_percent: null  # доля одного ядра (cgroup cpu.max), напр. 50 = 0.5 ядра
  allow_net: true       # false = процесс в изолированном netns (Linux, без сети)
  clear_env: true       # очистить окружение
  env_allow: [PATH, LANG, HOME]   # что оставить при clear_env

Бэкенды (Linux): лимиты ФС (read_roots/write_roots) применяются через Landlock (rootless, ядро ≥5.13) в file_tool и скриптовых fs_*; лимиты памяти/CPU/pids — через cgroup v2 (tree-wide, включая потомков) когда задан cgroup_parent; сеть (allow_net: false) — через unshare(CLONE_NEWUSER | CLONE_NEWNET) (нет маршрутов). На Windows лимиты ресурсов даёт Job Object.

Наблюдаемость. Отказы песочницы считаются и логируются; GET /metrics отдаёт agentos_sandbox_violations_total{kind="…"} (например fs_read_denied, fs_write_denied).

cgroup v2 — требование к деплою. cgroup_parent должен быть делегирован сервису (rootless-модель): каталог D без процессов, с включёнными D/cgroup.subtree_control (библиотека включает доступные из +memory +pids +cpu), принадлежащий пользователю сервиса, и сам сервис запущен внутри D/<child>. Пример systemd: Delegate=yes на юните + sandbox.cgroup_parent: /sys/fs/cgroup/<slice>/<unit>.

auth — админ-API и web-логин

auth:
  admin_token_env: ""              # имя env-переменной с токенами (опционально)
  admin_tokens:                    # карта имя → токен (или { token, templates })
    admin: "<token>"
    drom-op:
      token: "<token>"
      templates: [drom-agent]
  users:                           # web-логин: логин → пароль (или { password | password_env, templates })
    admin:
      password_env: AGENT_OS_ADMIN_PASSWORD   # пароль из env, а не из git
    drom-viewer:
      password: "<password>"
      templates: [drom-agent]
  web_user_secret_env: ""          # env с HMAC-секретом для токенов конечных пользователей

web_user_secret_env — имя env-переменной с HMAC-секретом для подписи токенов идентичности конечных пользователей (POST /v1/user-ticket). Если пусто, сервер сам генерирует и хранит секрет в data/web_user_secret — идентичность подписывается HMAC по умолчанию. Подробности — в 08-http-api.

Админ-эндпоинты принимают либо Authorization: Bearer <токен>, либо cookie agentos_session.

Те же источники (auth.users и auth.oidc) обслуживают и вход конечного пользователя в виджете: агент может требовать авторизацию через login.required: true (providers — OIDC, allow_users — логин/пароль из auth.users). См. 04-building-an-agent и 19-widget-and-channels.

auth.oidc — вход через Google / Яндекс / Тинькофф

Дополнительно к паролям (auth.users) админ-UI может логинить через внешние identity-провайдеры (OIDC / OAuth 2.0, Authorization Code + PKCE). Роль даётся грантами по внешней личности — subject (точный provider:sub), email или email_domain (суффикс). Первое совпадение выигрывает; нет совпадения = вход отклонён (fail-closed).

auth:
  oidc:
    base_url: ""                         # пусто = авто-вывод из запроса (Host + X-Forwarded-Proto)
    providers:
      google:
        issuer: "https://accounts.google.com"   # discovery
        client_id: "<client id>"
        client_secret_env: "GOOGLE_OIDC_CLIENT_SECRET"
      yandex:                                   # discovery нет — явные endpoints
        authorization_endpoint: "https://oauth.yandex.ru/authorize"
        token_endpoint: "https://oauth.yandex.ru/token"
        userinfo_endpoint: "https://login.yandex.ru/info?format=json"
        client_id: "<client id>"
        client_secret_env: "YANDEX_OIDC_CLIENT_SECRET"
        scopes: ["login:email", "login:info"]
        userinfo_sub_field: "id"
        userinfo_email_field: "default_email"
      tinkoff:
        issuer: "https://id.tinkoff.ru/auth/realms/tinkoff"
        client_id: "<client id>"
        client_secret_env: "TINKOFF_OIDC_CLIENT_SECRET"
    grants:
      - email_domain: "example.com"
        role: "admin"                        # полный доступ
      - email: "ops@example.com"
        templates: [drom-agent]              # оператор: только эти шаблоны

connectors — доступ к внешним API от имени пользователя

Агент может действовать в сторонних сервисах (Google Drive, Яндекс.Диск и т.п.) от имени конечного пользователя. Коннектор переиспользует OIDC-клиента из auth.oidc.providers и запрашивает собственные scope. Токены хранятся per-user в logging.connector_db_path, зашифрованные AES-256-GCM (ключ из auth.connector_encryption_key_env или авто-секрет data/connector_secret).

connectors:
  gdrive:
    provider: google
    scopes: ["https://www.googleapis.com/auth/drive.file"]
  yandex_disk:
    provider: yandex
    scopes: ["cloud_api:disk.app_folder"]

Инструменты (kind: oauth_connect / kind: connector_request) подключаются в agent.yaml через tool_files — см. 05-tools-reference. Redirect URI коннекторов: {base_url}/v1/connectors/callback.

widget — встраиваемый виджет

widget:
  default_model: "drom-agent"   # агент по умолчанию

Единственная серверная настройка — default_model: агент, к которому виджет обращается, когда модель не задана явно. Полный разбор виджета (встраивание, порядок выбора модели, демо, визуальные блоки) — в §19.

proxy — ограничения HTTP-прокси

proxy:
  allow_hosts: []   # пусто = любой публичный хост

/proxy и /proxy/content всегда блокируют приватные, loopback, link-local и reserved-диапазоны (включая 169.254.169.254) на каждом редиректе. allow_hosts дополнительно ограничивает по хосту (точное совпадение или поддомен).

inbound — входящий webhook

inbound:
  token_env: ""   # env с bearer-токеном для POST /v1/inbound/:template (пусто = открыто, dev)

POST /v1/inbound/:template — внешние системы запускают один ход агента синхронно. См. 08-http-api.

approval — одобрение действий (human-in-the-loop)

approval:
  required_tools: ["refund", "send_email", "delete_*"]  # glob-паттерны тулов, требующих одобрения
  operator_notify:              # доставка операторских запросов в мессенджер/webhook
    - type: telegram
      chat_id: -100123456789
      token_env: TG_OPERATOR_TOKEN
    - type: webhook
      url: https://ops.example.com/approvals
  context_template: |           # необязательный MiniJinja-шаблон контекста
    📝 {{ user_request }}
    {% for m in messages %}{{ m.role }}: {{ m.content }}{% endfor %}

modules — модули и нативные плагины

Платформа модульная: фичи (workflow, cron, каналы, docs, widget, approvals, …) и нативные расширения подключаются как модули — linked-крейты или DLL.

modules:
  enabled: [docs, widget, analytics]  # активировать на буте (+ requires транзитивно)
  auto_install: false                  # не ходить в сеть за артефактами на старте

См. 20-native-plugins и 32-building-a-module.


3.3 config/budgets.yaml

budget_groups:
  - name: my-shared
    refill_per_sec: 50
    max_bucket: 50000
    max_total: 5000000        # null/отсутствует = без лимита
    warn_at: 0.8              # порог on_budget_threshold для группы
    per_user:                 # опционально
      refill_per_sec: 20
      max_bucket: 20000
      max_total: 500000
      warn_at: 0.8            # порог on_budget_threshold для per_user
    per_session:              # опционально
      refill_per_sec: 30
      max_bucket: 10000
      max_total: 200000

agents:
  - template: my-agent        # перекрывает budget_group / budget из agent.yaml
    group: my-shared
    budget:                   # опционально
      refill_per_sec: 10
      max_bucket: 200
      max_total: 1000

Поля бюджета

Поле Описание
refill_per_sec пополнение ведра, токенов/сек
max_bucket ёмкость ведра (burst)
max_total жёсткий лимит за всю жизнь (отсутствует = без лимита)
warn_at доля (0..1) от max_total, при которой срабатывает хук on_budget_threshold

warn_at можно задать на каждом слое (группа / per_user / per_session), и хук вызывается для того слоя, чей порог пересёкся (level = group / user / session).


3.4 Запланированные задания (config/cron.yaml)

Cron запускает шаблон агента по расписанию. Задаются в двух местах, оба hot-reload'ятся:

jobs:
  - name: morning-digest
    template: rss
    schedule: "0 0 9 * * *"      # sec min hour dom month dow (UTC)
    prompt: "Резюмируй сегодняшние новости."
    notify: [{ type: log }]

Каждое задание задаёт ровно одно из schedule / interval_secs / oneshot_at и доставляет результат в список notify-sink'ов (log, webhook, telegram, vk, yandex_messenger, messages_db, agent, email, workflow). История — в logs/cron.db, эндпоинт GET /v1/cron. Полный справочник — 11-cron-jobs.


3.5 Change-мониторы (config/monitors.yaml)

Монитор — это pull-триггер: следит за внешним источником и запускает ход агента только при появлении новых элементов (в отличие от cron, который стреляет по расписанию независимо). Полное описание — Мониторы.

monitors:
  - name: news-watch
    source:
      type: rss
      url: "https://lenta.ru/rss"
    template: rss-agent
    prompt: "Кратко резюмируй эти новые статьи:\n{{items}}"
    notify: [{ type: telegram, chat_id: 123456 }]
    poll_secs: 300

Монитор хранит курсор в logs/monitors.db: первый опрос только «догоняет» (сохраняет курсор, ничего не запускает), дальше запускается только на элементах новее курсора.


3.6 Переменные окружения

Переменная Назначение
DEEPSEEK_API_KEY / AGENT_OS_API_KEY ключ LLM-провайдера
AGENT_OS_BIND адрес привязки (по умолчанию 127.0.0.1:3000)
AGENT_OS_URL публичный origin сервера за реверс-прокси (например https://platform.vectorres.ru). Хост передаёт его служебным агентам как базу для server_url, чтобы ссылки не вели на 127.0.0.1:3000. Имеет приоритет над public_url: в config/server.yaml. Если не задано — выводится из AGENT_OS_BIND
AGENT_OS_TLS_CERT / AGENT_OS_TLS_KEY пути к PEM-сертификату и ключу для HTTPS
AGENT_OS_ADMIN_TOKEN админ-токены: список через запятую, имя=токен. Читается только если имя переменной указано в config/server.yaml → auth.admin_token_env: AGENT_OS_ADMIN_TOKEN (по умолчанию поле пустое, и переменная игнорируется). Альтернатива — карта auth.admin_tokens
AGENT_OS_BENCH_MODE 1 = безлимитные бюджеты (для локального спавна бенчмарка)
AGENT_OS_CONFIG_REPO / _REF / _DIR / _POLL источник конфига (см. §3.1)
AGENT_OS_DISTRIB_DIR путь к папке дистрибутива (по умолчанию distrib)
(на агента) имя из telegram.token_env токен Telegram-бота, названный в agent.yaml
(на агента) имя из email.imap_password_env пароль IMAP для email-канала
(на агента) имя из vk.token_env group access token VK-сообщества, названный в agent.yaml
(на агента) имя из yandex_messenger.token_env OAuth-токен бота Яндекс Мессенджера, названный в agent.yaml

.env подхватывается автоматически при старте (dotenv). Он в .gitignore, шаблон — .env.example.


3.7 Live reload

При kernel.live_reload: true (по умолчанию) сервер следит за config/ и применяет изменения без рестарта:

Если конфиг приходит из git-репозитория, при --config-poll / AGENT_OS_CONFIG_POLL тот же путь применяется после каждого нового коммита; неизменённый SHA пропускается.


3.8 Логирование HTTP-запросов

Каждый HTTP-запрос (метод, путь, статус, длительность, IP клиента, user-agent, X-User-Id, имя админ-токена) пишется в logs/access.jsonl и logs/access.db (оба отключаются пустой строкой в соответствующем поле logging). Значение токена не логируется — только его имя или пометка invalid.


3.9 ONNX-сервис (security.onnx)

Локальный ONNX Runtime для NER / классификации / эмбеддингов. Требует сборки сервера с фичей onnx. Ядро ONNX Runtime не линкуется и не шипится: загружается динамически (load-dynamic) и резолвится/скачивается при первом использовании блоком runtime. Модели грузятся лениво и скачиваются при первом использовании (закреплённая ревизия), если не download: never / offline: true.

security:
  onnx:
    models_dir: data/models
    download: auto            # auto | eager | never
    offline: false
    allow_hosts: []           # пусто = встроенные хосты HF (+ GitHub для runtime)
    runtime:                  # динамически загружаемое ядро ONNX Runtime
      # lib_path: /opt/onnxruntime/lib/libonnxruntime.so  # явный путь (без скачивания)
      version: "1.28.0"       # должно соответствовать ort (rc.13 → 1.28)
      variant: cpu            # cpu | gpu | cuda12 | cuda13 | directml
      # base_url: https://github.com/microsoft/onnxruntime/releases/download/v1.28.0
      # sha256: "..."
    models:
      - id: langid            # используется хуком language_guard (detector: onnx)
        task: text_classification
        # templates: [ngu-agent]        # per-agent селектор (необязательно)
        source:
          hf: { repo: onnx-community/language_detection-ONNX, revision: <commit>, path: onnx/model_quantized.onnx }
        tokenizer: tokenizer.json
        config: config.json
Поле Описание
models_dir каталог артефактов (кэш; data/models/ в .gitignore); там же ort/<version>/<variant>/
download auto (на первом использовании), eager (при старте), never (только локальные)
offline запретить сеть (только вендоренные модели/ORT)
allow_hosts разрешённые хосты загрузки; пусто = встроенные HF-хосты (для дефолтного источника ORT GitHub-хосты добавляются автоматически)
runtime.lib_path явный путь к ядру ORT (пропускает скачивание)
runtime.version / variant релиз ORT и его вариант (cpu/gpu/cuda12/cuda13/directml)
runtime.base_url / sha256 зеркало релиза и контрольная сумма архива
models[].id id для OnnxService и хуков (onnx_model/ner_model)
models[].task ner | text_classification | embeddings
models[].source hf / http (+ sha256) / local
models[].tokenizer / config относительные пути внутри источника (tokenizer.json, vocab.txt, spm.model, …)
models[].templates ограничить модель шаблонами агентов (селектор per-agent)
models[].labels / map переопределение меток / маппинг классов
models[].execution_provider / device_id EP (cpu/cuda/…) и GPU-индекс; требует фичи agent_os_onnx

Админ-эндпоинты: GET /v1/onnx/models, GET /v1/onnx/metrics, GET /v1/onnx/runtime.

Модель langid (мини-BERT, ~25 МБ int8, 200 языков) зарегистрирована по умолчанию и используется хуком language_guard с detector: onnx (06 §6.18). Если сервис или модель недоступны, хук нейтрален.