Usage, Quota & Spend Tracking (Русский)
Каждый запрос, проходящий через OmniRoute, создаёт запись об использовании, содержащую:
- Идентификационные данные: какой API-ключ, поставщик, модель и комбинация использовались
- Токены: токены промпта, токены завершения, кэшированные токены и общее количество
- Стоимость: сумма в USD, рассчитанная на основе данных о тарифах
- Время: задержка, временные метки начала и завершения
- Статус: успешно, ошибка, ограничение частоты запросов и т. д.
Эти записи агрегируются в аналитику, сохраняются в виде снимков квот и используются для обеспечения соблюдения ограничений бюджета для каждого ключа.
Запрос ──▶ chatCore ──▶ usage.record() ──▶ SQLite │ ┌───────┼───────┐ ▼ ▼ ▼ аналитика квота биллинг (панель) (контроль) (экспорт)Какие данные записываются
Заголовок раздела «Какие данные записываются»Сервис usage.ts регистрирует событие использования для каждого запроса:
| Поле | Тип | Источник |
|---|---|---|
id |
string | UUID, создаваемый при записи |
apiKeyId |
string | API-ключ, инициировавший запрос |
provider |
string | Идентификатор поставщика (openai, anthropic и т. д.) |
model |
string | Идентификатор модели (gpt-5, claude-opus-4-6 и т. д.) |
comboId |
string? | Идентификатор комбинации, если запрос направлен через неё |
promptTokens |
number | Из ответа вышестоящего сервиса |
completionTokens |
number | Из ответа вышестоящего сервиса |
cachedTokens |
number | Токены, полученные из кэша (кэширование промптов Anthropic и т. д.) |
totalTokens |
number | промпт + завершение |
costUsd |
number | Рассчитывается на основе данных о тарифах |
latencyMs |
number | Полная длительность обработки запроса |
status |
enum | success, error, rate_limited, timeout, cancelled |
errorClass |
string? | Класс ошибки, если статус != success |
timestamp |
string | ISO 8601 UTC |
metadata |
object | Пользовательские данные, добавленные плагином |
Откуда берутся токены
Заголовок раздела «Откуда берутся токены»Данные о токенах извлекаются из ответа вышестоящего поставщика в обработчике ответа:
// Из open-sse/handlers/chatCore.tsconst response = await providerExecutor.execute(provider, request);const usage = response.usage || { prompt_tokens: 0, completion_tokens: 0, cached_tokens: 0,};Для поставщиков, которые не возвращают данные об использовании (например, некоторых поставщиков с веб-аутентификацией через cookie), OmniRoute оценивает количество токенов с помощью эвристики ~4 символа на токен (см. open-sse/services/autoCombo/pipelineRouter.ts).
Кэшированные токены
Заголовок раздела «Кэшированные токены»OmniRoute отслеживает cached_tokens отдельно от prompt_tokens, поскольку:
- При кэшировании промптов Anthropic кэшированные токены тарифицируются по сниженной ставке (10% от обычной)
- Некоторые поставщики возвращают
cache_read_input_tokens, стоимость которых должна рассчитываться иначе - Аналитика может показывать долю попаданий в кэш =
cached_tokens / prompt_tokens
Расчёт стоимости
Заголовок раздела «Расчёт стоимости»Стоимость рассчитывается на основе данных о ценах, синхронизированных с LiteLLM (src/lib/pricingSync.ts):
| Модель | Вход, $/1M | Выход, $/1M | Кэш, $/1M |
|---|---|---|---|
| gpt-5 | $2.50 | $10.00 | — |
| claude-opus-4-6 | $15.00 | $75.00 | $1.50 |
| claude-sonnet-4-5 | $3.00 | $15.00 | $0.30 |
| gemini-2.5-pro | $1.25 | $10.00 | — |
Формула расчёта стоимости (src/lib/usage/costCalculator.ts):
cost = (prompt_tokens - cached_tokens) * input_price + cached_tokens * cached_price + completion_tokens * output_price;Зачем вычитать кэшированные токены из токенов промпта? Кэшированная часть тарифицируется отдельно; применение цены входных токенов ко всему промпту привело бы к завышенному расчёту.
Синхронизация цен
Заголовок раздела «Синхронизация цен»Данные о ценах автоматически синхронизируются с LiteLLM через эндпоинт /api/pricing/sync (запускается встроенной задачей cron, а не пользовательской переменной окружения):
# Запуск вручнуюcurl -X POST http://localhost:20128/api/pricing/syncДля моделей, по которым отсутствуют данные о ценах, OmniRoute выполняет оценочный расчёт стоимости, используя внутренние средние тарифы (полученные из данных о ценах LiteLLM).
Агрегация по диапазону дат
Заголовок раздела «Агрегация по диапазону дат»Модуль usageAnalytics.ts вычисляет виджеты панели мониторинга на основе необработанных данных об использовании. Он поддерживает 7 временных диапазонов:
| Диапазон | Период | Сценарий использования |
|---|---|---|
1d |
Последние 24 часа | Выявление почасовых скачков стоимости |
7d |
Последние 7 дней | Еженедельный анализ |
30d |
Последние 30 дней | Ежемесячный биллинг |
90d |
Последние 90 дней | Ежеквартальный анализ |
ytd |
С 1 января текущего года | Отслеживание годового бюджета |
all |
За всё время | Статистика за весь период |
custom |
Заданные пользователем даты начала и окончания | Аудиты, разовые запросы |
Вычисляемые виджеты панели мониторинга
Заголовок раздела «Вычисляемые виджеты панели мониторинга»Для любого диапазона дат аналитический слой вычисляет:
| Виджет | Описание |
|---|---|
| Карточки сводки | Общее количество запросов, общая стоимость, общее количество токенов, доля успешных запросов |
| График динамики по дням | Стоимость и токены за день с разбивкой по моделям |
| Тепловая карта активности | Сетка «час дня × день недели», цвет = количество запросов |
| Разбивка по моделям | Круговая диаграмма стоимости по моделям |
| Разбивка по провайдерам | Столбчатая диаграмма запросов по провайдерам |
| Лучшие API-ключи | Таблица 10 ключей с наибольшей стоимостью |
| Анализ ошибок | Доля ошибок во времени, наиболее частые классы ошибок |
Программный доступ
Заголовок раздела «Программный доступ»import { computeAnalytics } from "@/lib/usageAnalytics";
const analytics = await computeAnalytics( history, // записи истории использования "7d", // временной диапазон: "1d" | "7d" | "30d" | "90d" | "ytd" | "all" | "custom" connectionMap, // карта подключений провайдеров (connectionId → имя учётной записи) { startDate: "2025-01-01", // необязательно: для диапазона "custom" endDate: "2025-06-01", // необязательно: для диапазона "custom" });
console.log(analytics.summary.totalCost); // 12.34 (цента)console.log(analytics.byModel[0]); // { model, cost, requests, promptTokens, completionTokens }
---
## Контроль квот
Квота для каждого API-ключа контролируется в двух местах:
1. **Мягкий лимит** (`quotaWarnAt`): предупреждение на панели мониторинга, когда использование превышает пороговое значение2. **Жёсткий лимит** (`quotaLimit`): запрос отклоняется с HTTP 429 при превышении лимита
### Конфигурация
```ts// Для каждого API-ключаawait updateApiKey(keyId, { quotaWarnAt: 5_00, // $5.00 — показать предупреждение quotaLimit: 10_00, // $10.00 — жёсткая остановка quotaWindow: "month", // "day" | "week" | "month" | "all"});Процесс контроля
Заголовок раздела «Процесс контроля»Запрос ──▶ quotaCheck() │ ├── В пределах лимита? ──▶ разрешить │ └── Лимит превышен? ──▶ 429 Too Many Requests с заголовком Retry-AfterСнимки квот
Заголовок раздела «Снимки квот»Таблица quotaSnapshots хранит историческое состояние квот для анализа тенденций:
| Поле | Описание |
| ———– | ———————————— | —— | —–– |
| apiKeyId | Отслеживаемый ключ |
| window | “day” | “week” | “month” |
| used | Расходы в этом окне (в центах) |
| limit | Лимит (в центах) |
| resetAt | Время сброса окна |
| createdAt | Время создания снимка |
Снимки создаются при каждом запросе, стоимость которого > 0, и используются для следующих целей:
- Отображение индикатора использования квоты на панели мониторинга
- Отображение графиков динамики квоты за 30 дней
- Отправка оповещений, когда использование приближается к лимиту
REST API
Заголовок раздела «REST API»Получение списка записей об использовании
Заголовок раздела «Получение списка записей об использовании»GET /api/usage?range=7d&limit=100GET /api/usage?apiKeyId=key-123&range=30dGET /api/usage?provider=openai&range=1dОтвет:
{ "records": [ { "id": "uuid", "apiKeyId": "key-123", "provider": "openai", "model": "gpt-5", "promptTokens": 1234, "completionTokens": 567, "totalTokens": 1801, "costUsd": 0.005, "latencyMs": 1234, "status": "success", "timestamp": "2026-06-08T12:00:00Z" } ], "total": 1234, "nextCursor": "..."}Получение сводной аналитики
Заголовок раздела «Получение сводной аналитики»GET /api/usage/analytics?range=7d&groupBy=modelОтвет:
{ "summary": { "totalCost": 12.34, "totalRequests": 5678, "totalTokens": 12345678, "successRate": 0.987, "avgLatencyMs": 1234 }, "models": [ { "model": "gpt-5", "cost": 8.5, "requests": 1234, "tokens": 4567890 }, { "model": "claude-opus-4-6", "cost": 3.84, "requests": 234, "tokens": 234567 } ], "daily": [ { "date": "2026-06-01", "cost": 1.5, "requests": 800 }, { "date": "2026-06-02", "cost": 2.0, "requests": 1000 } ]}Запрос аналитики использования
Заголовок раздела «Запрос аналитики использования»Данные об использовании доступны через панель мониторинга или инструменты MCP, а не через прямые конечные точки экспорта REST. Доступная аналитика:
/api/usage/analytics— агрегированные метрики использования (группировка по модели, провайдеру, ключу)/api/usage/quota— текущее состояние квоты для каждого API-ключа/api/usage/history— журналы истории запросов
Инструменты MCP
Заголовок раздела «Инструменты MCP»Два инструмента MCP предоставляют агентам доступ к данным об использовании (см. open-sse/mcp-server/tools/):
| Инструмент | Описание |
|---|---|
omniroute_cost_report |
Создаёт отчёт о расходах по ключам за указанный период |
omniroute_check_quota |
Возвращает текущее состояние квоты для API-ключа |
Пример вызова агентом:
{ "tool": "omniroute_cost_report", "args": { "period": "week" }}Хранение и очистка
Заголовок раздела «Хранение и очистка»Объём данных об использовании увеличивается примерно на 1–10 КБ с каждым запросом. При больших масштабах это может стать существенным.
Настройки хранения
Заголовок раздела «Настройки хранения»Срок хранения истории использования настраивается в разделе Database Settings пользовательского интерфейса или через /api/settings/database.
По умолчанию история использования хранится 90 дней.
Очистка
Заголовок раздела «Очистка»Старые записи очищаются модулем src/lib/db/cleanup.ts:
- Очистка запускается фоновым процессом cron
- Удаляются записи из
usage_history, возраст которых превышает срок хранения, заданный настройкойusageHistory
Оценка объёма хранилища
Заголовок раздела «Оценка объёма хранилища»| Частота запросов | Хранение за 30 дней | Хранение за 90 дней |
|---|---|---|
| 100 запросов/день | ~3 МБ | ~9 МБ |
| 1 000 запросов/день | ~30 МБ | ~90 МБ |
| 10 000 запросов/день | ~300 МБ | ~900 МБ |
| 100 000 запросов/день | ~3 ГБ | ~9 ГБ |
При очень высокой нагрузке рекомендуется:
- Сократить срок хранения через Database Settings
- Использовать
aggregated_metricsвместо необработанных записей (только для аналитики)
Советы по оптимизации затрат
Заголовок раздела «Советы по оптимизации затрат»1. Используйте подходящую модель
Заголовок раздела «1. Используйте подходящую модель»# Быстрый ответ — используйте дешёвую и быструю модельcurl -d '{"model":"auto/fast","messages":[...]}'
# Сложная задача — используйте качественную модельcurl -d '{"model":"auto/smart","messages":[...]}'2. Включите кэширование
Заголовок раздела «2. Включите кэширование»Кэширование промптов Anthropic позволяет сэкономить 90% при повторном использовании контекста:
// Кэширование выполняется автоматически — просто используйте тот же объёмный системный промптconst response = await openai.chat({ model: "claude-sonnet-4-5", system: longSystemPrompt, // Будет кэшировано автоматически messages: [{ role: "user", content: "..." }],});3. Используйте сжатие
Заголовок раздела «3. Используйте сжатие»Сжатие RTK + Caveman позволяет сэкономить 15–95% в сеансах с интенсивным использованием инструментов:
const config = { compression: { engine: "rtk", intensity: "aggressive", },};4. Устанавливайте квоты для каждого ключа
Заголовок раздела «4. Устанавливайте квоты для каждого ключа»Всегда задавайте quotaLimit, чтобы избежать неконтролируемых расходов:
await updateApiKey(keyId, { quotaLimit: 10_00 }); // Ограничение — $10 в месяц5. Проверяйте крупнейших потребителей
Заголовок раздела «5. Проверяйте крупнейших потребителей»Используйте панель мониторинга или /api/usage/analytics, чтобы сгруппировать данные по API-ключу и отсортировать их по затратам:
GET /api/usage/analytics?groupBy=apiKeyУстранение неполадок
Заголовок раздела «Устранение неполадок»«Затраты выше ожидаемых»
Заголовок раздела ««Затраты выше ожидаемых»»- Проверьте
/api/usage/analytics?groupBy=model— найдите дорогостоящую модель - Проверьте
/api/usage/analytics?groupBy=apiKey— найдите крупнейшего потребителя - Убедитесь, что данные о ценах актуальны:
POST /api/pricing/sync
«Записи отсутствуют»
Заголовок раздела ««Записи отсутствуют»»- Проверьте настройки хранения БД в разделе Dashboard → Database → Cleanup — старые записи удаляются периодической задачей очистки (
src/lib/db/cleanup.ts) - Проверьте наличие ошибок в
src/lib/db/usage*.ts— сбои записи в БД регистрируются в журнале, но не выводятся пользователю - Убедитесь, что запрос действительно достиг
chatCore— проверьте комбинированную маршрутизацию
«Квота не применяется»
Заголовок раздела ««Квота не применяется»»- Проверьте настройку
quotaLimitключа - Убедитесь, что
quotaWindowнастроено правильно - Проверьте наличие записей
quotaSnapshots— они должны создаваться при каждом запросе
См. также
Заголовок раздела «См. также»- DATABASE_GUIDE.md — схема таблиц использования
- ENVIRONMENT.md — переменные окружения для синхронизации цен
- AUTO-COMBO.md — как
auto/fastиauto/cheapснижают затраты - API_REFERENCE.md — полное справочное руководство по
/api/usage/* - Исходный код:
open-sse/services/usage.ts,src/lib/usageAnalytics.ts,src/lib/db/usage*.ts
HagiCode
HagiCode — агентная среда разработки со структурированными процессами, параллельным выполнением несколькими агентами и интерфейсами Hero Dungeon.
Превращайте идеи в полезное ПО с более умным, быстрым и увлекательным агентным рабочим процессом.

- SmartСтруктурированные процессы превращают намерение в исполнимый путь от идеи до готового изменения.
- EfficientМультиагентные процессы параллельно продвигают исследование, реализацию и проверку.
- FunHero Dungeon делает длительную совместную разработку наглядной и увлекательной.