Skip to content

oleg494/multiproxy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

53 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Multiproxy

Multiproxy — лёгкий, самодостаточный OpenAI-совместимый прокси к любому числу AI-провайдеров через один endpoint. Ротация API-ключей, авто-бан «мёртвых» ключей, fallback между провайдерами, стриминг, кэш ответов, лимиты по токенам и двусторонний мост Anthropic ↔ OpenAI (Claude Code и Anthropic SDK).

Multiproxy провайдер-агностичен: он не «знает» ни одного провайдера в коде — ты описываешь их в config.json. Работает с любым апстримом, который говорит на OpenAI Chat Completions (OpenAI, OpenRouter, Groq, Together, локальный Ollama/LM Studio, любые reseller/aggregator-эндпоинты и т.д.).

Версия 1.7.4. Стек: FastAPI + httpx + uvicorn. Zero-infra: ни БД, ни Redis. Аудит кода — docs/REVIEW-1.5.md (полный аудит перед 1.6.0, security-ревью админки — там же, #26–#40), docs/REVIEW.md. План развития — docs/ROADMAP.md. Позиционирование относительно LiteLLM — раздел ниже.

Обновляетесь с 1.5.0? В 1.6.0 три несовместимых изменения: rpm_limit: 0 теперь действительно выключает лимитер (а не даёт 100), by_day считается по UTC (вернуть прежнее — usage_day_tz: "local"), терминальное исчерпание rate-limit доходит до клиента как 429 вместо 502 (вернуть — retry.propagate_rate_limit: false). Подробнее — CHANGELOG.md.

(English: see README.en.md.)


Возможности

  • Единый OpenAI endpoint к произвольному набору провайдеров.
  • Ротация ключей round-robin с временным баном ключей на key_dead_for_s при 401/402/403 и признаках исчерпания баланса/квоты; общий дедлайн 30 с.
  • Cross-provider fallback для «голых» моделей при падении провайдера (502/503).
  • Ретраи и устойчивость (с 1.5.0): один «горячий» ключ ретраится с экспоненциальным backoff (retry.max_attempts/backoff_base_s/backoff_max_s, уважая Retry-After и общий дедлайн); терминальное 429-исчерпание нейтрально для circuit breaker; оборванный апстрим-стрим завершается явным error-событием (chat) / event: error+message_stop (Anthropic), а не тихим обрывом.
  • Маршрутизация из конфига: provider/model-префикс, алиасы, детерминированный выбор при коллизии имён (default_provider).
  • Мост /v1/messages (Anthropic Messages API): Claude Code / Anthropic SDK работают с любой моделью каталога; tools, tool_use/tool_result, system, reasoning/thinking, SSE-стриминг.
  • /v1/embeddings — OpenAI-совместимый passthrough эмбеддингов через тот же механизм ротации ключей, fallback и кэша, что и чат.
  • Кэш ответов (LRU + TTL) для детерминированных не-streaming запросов без tools (включая эмбеддинги), с ограничением по байтам (max_bytes/max_entry_bytes), инвалидацией при смене маршрутизации и POST /admin/cache/clear.
  • Лимиты и защита: per-token request-count, token-budget, model-ACL, скользящее окно RPM (глобальное, пер-токенное proxy_token_rpm и пер-IP rpm_limit_per_ip), in-process fail2ban по IP, опциональная авторизация /health.
  • Транзакционный hot-reload (с 1.6.0): конфиг применяется целиком или не применяется вообще — провалившаяся перезагрузка не оставляет систему в полу-обновлённом состоянии. --print-config показывает эффективные значения всех дефолтов с замаскированными секретами.
  • Точный учёт токенов в стриминге: force_stream_usage домешивает stream_options.include_usage в OpenAI-стриминг, если клиент сам его не запросил.
  • Наблюдаемость: GET /metrics в формате Prometheus (опц. metrics_token), структурные JSON-логи (log_format: json) и оценка стоимости в $ (model_pricing).
  • Скорость генерации (TPS): честная скорость стрима (ewma_tps) и более грубая не-стримовая оценка (ewma_tps_effective) по провайдеру, плюс системная пропускная способность tps_1m — в GET /health, GET /metrics (multiproxy_provider_ewma_tps[_effective], multiproxy_completion_tokens_total, multiproxy_tps_1m) и на дашборде.
  • Дашборд (dashboard.py) с графиками, метриками, панелью TPS и живой проверкой ключей — список провайдеров строится динамически из конфига.
  • Готовая упаковка: Dockerfile + docker-compose.yml, GitHub Actions CI (ruff + pytest + --check-config на 3.10/3.11/3.12), Makefile, pyproject.toml.

Требования

  • Python 3.10+ (рекомендуется). Импортируется и на 3.9 (from __future__ import annotations).
  • Зависимости — requirements.txt: fastapi, uvicorn, httpx, socksio (для SOCKS-провайдеров), pytest (тесты).
python3 -m venv venv && . venv/bin/activate
pip install -r requirements.txt

Для воспроизводимых сборок (прод, CI) добавьте пины из constraints.txt — там зафиксированы точные версии, на которых проверен набор тестов 1.6.0, включая транзитивные starlette/httpx (от их поведения зависят обработка отменённого StreamingResponse и завершение оборванного апстрим-стрима):

pip install -r requirements.txt -c constraints.txt

Быстрый старт

cp config.example.json config.json     # опиши своих провайдеров и токены
mkdir -p keys
cp keys.example.txt keys/openai.txt     # положи реальные ключи (по одному в строке)
# ...по файлу ключей на каждого провайдера (см. keys_file в конфиге)

python3 multiproxy.py                    # слушает 127.0.0.1:11440
# либо: uvicorn multiproxy:app --host 127.0.0.1 --port 11440

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

Переменная По умолчанию Назначение
PORT 11440 Порт прослушивания
HOST 127.0.0.1 Хост. 0.0.0.0только за файрволом/reverse-proxy
CONFIG_FILE ./config.json Путь к конфигу (hot-reload раз в 2 с)

Как работает маршрутизация

  1. Голое имя модели (glm-5.2) → провайдер, у которого она есть; при коллизии имён побеждает default_provider, остальные становятся fallback-кандидатами. Работает и когда само имя модели содержит / (конвенция OpenRouter/HF, например meta-llama/llama-3.3-70b-instruct) — начиная с 1.3.1 такая модель, если задублирована на нескольких провайдерах, тоже получает честный cross-provider fallback, а не только явные provider/model-роуты.
  2. Явный маршрут провайдер/модель (openrouter/openai/gpt-4o) → жёстко этот провайдер, без fallback.
  3. Алиас из model_aliases (fastgroq/llama-3.3-70b-versatile) → удобное имя.
  4. Авто-алиасы (с 1.4.0): для каждого включённого провайдера и каждой его модели автоматически создаётся алиас provider-model → provider/model (например openai-gpt-4o → openai/gpt-4o) — весь каталог доступен по явным именам без ручного заполнения model_aliases; ручные записи побеждают при совпадении имени. Видны в GET /v1/models.

Клиент всегда шлёт обычный OpenAI-запрос; провайдера выбирает multiproxy.


Пример конфигурации провайдеров

Ниже — пример из config.example.json (замени своими). Провайдеры в коде не зашиты: любой OpenAI-совместимый эндпоинт добавляется одной записью.

Провайдер base_url (пример) Особенность в примере
openai api.openai.com базовый провайдер, default_provider
openrouter openrouter.ai модели со слэшем (openai/gpt-4o)
groq api.groq.com ключи gsk_…raw_keys: true
local 127.0.0.1:11434 (Ollama) локальный, без реального ключа
via_socks (любой) доступ через proxy: socks5://…

Каждый провайдер:

"openai": {
  "base_url": "https://api.openai.com/v1/chat/completions",
  "keys_file": "keys/openai.txt",             // опционально — дефолт keys/openai.txt
  "models": ["gpt-4o-mini", "gpt-4o"],
  "connect_timeout": 5.0,
  "read_timeout": 300.0,
  "key_dead_for_s": 600,
  "proxy": "socks5://127.0.0.1:1080",         // опционально
  "raw_keys": false,                            // true = слать ключи без нормализации
  "embeddings_url": "https://api.openai.com/v1/embeddings",  // опционально, см. ниже
  "enabled": true                               // опционально; false — провайдер пропускается целиком
}

embeddings_url — опциональный явный URL эмбеддингов для провайдера. Если не задан, эффективный URL выводится из base_url заменой /chat/completions/embeddings (для нестандартных апстримов задай явно).

keys_file необязателен (дефолт — keys/{провайдер}.txt рядом с конфигом). Альтернатива — keys_dir: директория, где все *.txt сливаются в один список ключей (по имени файла), а hot-reload реагирует и на правку, и на добавление/удаление файла — удобно, когда ключи приходят пачками и не хочется вручную сводить их в один файл. Если заданы оба — побеждает keys_dir.

Интерполяция переменных окружения${env:VAR_NAME} работает в base_url/embeddings_url/anthropic_base_url и прямо в строках файла ключей (keys_file/keys_dir), например ключ ${env:OPENAI_KEY_1} в файле резолвится из переменной окружения при загрузке. Узкий, сознательно нешаблонизированный механизм — только эти поля. Незаданная переменная — это жёсткий отказ (--check-config вернёт FAIL, прямой запуск завершится с ошибкой), а не тихая подстановка пустой строки.


Подключение клиента

Параметр Значение
Base URL http://<host>:11440/v1
API Key один из proxy_tokens (Authorization: Bearer <token> или x-api-key)
Формат OpenAI-совместимый
curl -s http://127.0.0.1:11440/v1/chat/completions \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"model":"smart","messages":[{"role":"user","content":"Привет"}]}'

Эмбеддинги — тот же токен, та же ротация ключей/fallback/кэш:

curl -s http://127.0.0.1:11440/v1/embeddings \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"model":"openai/text-embedding-3-small","input":"Привет, мир"}'

Endpoints

  • POST /v1/chat/completions — основной OpenAI-эндпоинт (stream и non-stream).
  • POST /v1/messages — Anthropic Messages API (для Claude Code).
  • POST /v1/embeddings (алиас POST /embeddings) — OpenAI-совместимый passthrough эмбеддингов: ротация ключей, cross-provider fallback на 502/503, кэш ответов.
  • GET /v1/models — модели, доступные токену (требует авторизации).
  • GET /v1/usage (алиас GET /usage) — токен смотрит собственные бюджет/расход: budget, used, remaining, used_pct, exceeded, requests, allowed_models (токен маскируется). Аутентификация как везде, но бюджет не enforced — исчерпанный токен всё равно видит свой статус.
  • GET /health — статус провайдеров (включая ewma_tps/ewma_tps_effective на провайдера) и fail2ban, плюс системная tps_1m (без авторизации по умолчанию; опционально требует health_token, см. «Безопасность»).
  • GET /metrics — метрики в формате Prometheus (text-exposition v0.0.4): по провайдерам (включая скорость генерации multiproxy_provider_ewma_tps[_effective] и multiproxy_completion_tokens_total{provider,model}), прокси-токенам (значение токена маскируется), моделям, системной пропускной способности (multiproxy_tps_1m) и всему прокси, плюс опциональная оценка стоимости в $. Открыт по умолчанию; опционально защищается metrics_token.

Конфигурация (config.json)

Ключ Тип Описание
providers object Провайдеры: base_url, models, таймауты, key_dead_for_s, опц. proxy, raw_keys, embeddings_url, enabled (по умолчанию true; false — провайдер пропускается целиком), keys_file (опц., дефолт keys/{provider}.txt) или keys_dir (опц., сливает все *.txt из директории; побеждает при заданных обоих)
default_provider string Победитель при коллизии имён моделей
proxy_tokens object { "<token>": <лимит запросов> }; -1 = без лимита. Пусто = auth выключен
proxy_token_budgets object { "<token>": <лимит суммарных токенов> }
proxy_token_models object { "<token>": ["provider", "provider/model", ...] } — ACL по моделям
model_aliases object { "alias": "provider/model" }; ручные записи побеждают авто-сгенерированные provider-model алиасы (см. «Как работает маршрутизация»)
response_cache object { enabled, max_entries, ttl_s, max_bytes, max_entry_bytes }. ttl_s <= 0 выключает кэш. max_bytes (256 MiB) ограничивает суммарный объём, max_entry_bytes (1 MiB) — один ответ: без них max_entries считал штуки, а не байты
rpm_limit int Глобальный лимит запросов/мин на токен (по умолчанию 100). 0 выключает лимитер (в 1.5.0 0 молча превращался в 100)
rpm_limit_per_ip int Лимит запросов/мин на IP-адрес, 0 = выключен. Работает и при выключенной авторизации, где пер-токенный лимитер бесполезен
proxy_token_rpm object { "<token>": <RPM> } — пер-токенный override глобального rpm_limit
fail2ban object { enabled, max_failures, fail_window_s, ban_duration_s }
trust_forwarded_for bool Доверять X-Forwarded-For/X-Real-IP (только за reverse-proxy). По умолчанию false. Без непустого trusted_proxies заголовки игнорируются и в лог пишется warning
trusted_proxies array IP/CIDR доверенных прокси (["10.0.0.1", "172.18.0.0/16"]). Заголовки читаются только если peer-сокет входит в этот список; цепочка XFF разбирается справа налево, доверенные хопы отбрасываются, первый недоверенный адрес = клиент. Мусор вместо IP → берётся адрес соединения
log_tokens array Токены, чьи промпты/ответы писать в prompts_log.jsonl
force_stream_usage bool Домешивать stream_options.include_usage: true в OpenAI-стриминг (не Anthropic-мост), если клиент сам не запросил usage. По умолчанию true
health_token string Токен для GET /health (Authorization: Bearer или x-api-key, constant-time сравнение). По умолчанию не задан — /health открыт
state_backend string Хранилище durable-состояния: sqlite (по умолчанию, один файл) или memory (без персистентности). Счётчики usage/бюджетов переживают рестарт
state_path string Путь к SQLite-файлу состояния. Пусто при sqlitestate.db рядом с token_usage.json
routing_strategy string Порядок провайдеров-кандидатов для bare-model коллизий: default (порядок конфига), round_robin, latency_weighted, least_error. По умолчанию default
circuit_breaker object { enabled, failure_threshold, cooldown_s } — provider-level breaker: после N подряд сбоев 502/503 провайдер уводится в конец на cooldown_s; один успех закрывает. По умолчанию enabled:true, failure_threshold:5, cooldown_s:30
retry object { max_attempts, backoff_base_s, backoff_max_s, backoff_jitter, propagate_rate_limit } — ретраи внутри одного провайдера с экспоненциальным backoff (уважает Retry-After, кэпируется общим дедлайном запроса); 429-исчерпание нейтрально для circuit breaker. backoff_jitter (доля в [0,1), по умолчанию 0) де-синхронизирует волны ретраев. propagate_rate_limit (по умолчанию true) отдаёт клиенту реальный 429 + Retry-After вместо непрозрачного 502
usage_day_tz string Часовой пояс дневных бакетов by_day: utc (по умолчанию с 1.6.0) или local (прежнее поведение — при смене TZ/DST давало «сутки» в 23 или 25 часов)
usage_retention_days int Сколько дней хранить бакеты by_day (90; 0 = хранить всё). Файл перезаписывается целиком на каждом флаше, поэтому бесконечная история — растущая цена
prompts_log_max_bytes int Размер, после которого prompts_log.jsonl ротируется в .1 (64 MiB; 0 = без ротации). В 1.5.0 файл рос без предела
prompts_log_max_response_chars int Обрезка тела ответа в логе промптов (1 MiB; 0 = без обрезки)
models_hide_unavailable bool Скрывать в /v1/models модели и алиасы провайдера без ключей или с открытым брейкером (по умолчанию true)
strict_config bool Отвергать конфиг с неизвестными ключами вместо WARNING (по умолчанию false)
strict_alias_collisions bool Отвергать конфиг при коллизии авто-алиасов вместо WARNING (по умолчанию false)
request_deadline_s float Общий дедлайн запроса на все провайдеры-кандидаты и все ретраи (30.0)
max_provider_candidates int Сколько провайдеров пробовать при fallback (3)
max_request_bytes int Лимит тела запроса (16 MiB)
max_response_bytes int Лимит буферизуемого не-стримового ответа (32 MiB)
config_check_interval_s float Как часто проверять mtime конфига (2.0)
inflight_ttl_s float Через сколько истекает «повисшая» бюджетная резервация (300.0)
detail_flush_interval_s float Интервал коалесцирующего сброса файлов usage на диск (2.0)
cb_half_open_probe_s float Сколько ждать результата half-open-пробы, прежде чем выдать новую (35.0)
admin_token string Токен для Admin API виртуальных ключей (/admin/keys/*, constant-time сравнение). Пустой = Admin API выключен (403)
metrics_token string Токен для GET /metrics (Authorization: Bearer или x-api-key, constant-time сравнение). Пустой = /metrics открыт
log_format string Формат логов: text (по умолчанию) или json (структурные single-line JSON-записи)
model_pricing object Цены моделей для оценки стоимости в /metrics: { model: { prompt: $/1M, completion: $/1M } }. Пусто = метрики стоимости выключены

Токены из proxy_token_budgets/proxy_token_models авто-регистрируются в proxy_tokens (безлимитный счётчик) с предупреждением — лучше добавлять их явно.

Виртуальные ключи (Admin API)

Помимо статичных proxy_tokens из конфига можно создавать виртуальные ключи (vk-...) на лету, без правки config.json. Каждый ключ несёт: owner, budget_tokens (-1 = без лимита), request_limit (-1 = без лимита), models (ACL; пусто = все), expires_at (Unix-время или ttl_s при создании) и enabled. Проверяются в том же пути аутентификации как fallback после proxy_tokens. Реестр хранится в durable state store и переживает рестарт при sqlite.

Эндпоинты (требуют Authorization: Bearer <admin_token>):

Метод Путь Действие
GET /admin/keys Список ключей (с текущим расходом)
POST /admin/keys Создать ключ (опц. token, owner, budget_tokens, request_limit, models, ttl_s/expires_at)
GET /admin/keys/{token} Ключ + расход
PATCH /admin/keys/{token} Обновить поля (бюджет/ACL/enabled/...)
DELETE /admin/keys/{token} Отозвать ключ
POST /admin/keys/{token}/rotate Выдать новый токен, сохранив метаданные и расход (?carry_usage=false — начать с нуля)
POST /admin/usage/reset Сбросить счётчики: {"token": "..."} или {"all": true}
POST /admin/cache/clear Выбросить все закэшированные ответы

С 1.6.0 payload Admin API валидируется строго: неверный тип поля даёт 400, а не молчаливое приведение. Раньше {"models": "model-b"} (строка вместо списка) превращалось в пустой список — то есть в разрешение всех моделей, — а {"enabled": "false"} оставляло ключ включённым, потому что bool("false") is True.

Ключи провайдеров

Файл ключей (keys_file, опционально — или keys_dir для нескольких файлов сразу) — по одному ключу в строке; # и пустые строки игнорируются, строки вида ${env:VAR} резолвятся из окружения при загрузке. «Голому» токену дописывается sk-. Ключи со структурой отправляются как есть: sk-…, tp-…/xai-… (дефис), gsk_…/hf_… (подчёркивание), id.secret (точка). Полное отключение нормализации — "raw_keys": true у провайдера (см. keys.example.txt).


Чем отличается от LiteLLM (и когда что выбирать)

Multiproxy закрывает те же повседневные сценарии, что и proxy-режим LiteLLM (один endpoint, много провайдеров, ротация ключей, бюджеты, лимиты, кэш, OpenAI+Anthropic), но осознанно не гонится за паритетом.

Выбирай multiproxy, если нужно: лёгкий self-hosted шлюз без инфраструктуры (ни Postgres, ни Redis), первоклассная ротация десятков ключей на провайдера, запуск в один процесс (~50 МБ), hot-reload конфига, простой дашборд.

Выбирай LiteLLM, если нужно: нативная трансляция к не-OpenAI API (Bedrock, Vertex/Gemini, Azure, Cohere), много типов эндпоинтов (embeddings, images, audio, rerank, batch, files), спенд-трекинг и virtual keys в БД, teams/orgs, горизонтальное масштабирование, готовые интеграции логирования.

Осознанные ограничения 1.0: passthrough (работает с уже-OpenAI-совместимыми апстреймами; из трансляций — только OpenAI↔Anthropic); эндпоинты — /chat/completions, /v1/messages, /v1/embeddings (без images/audio/rerank/batch/files); состояние in-process (не шарится между воркерами, кэш/лимиты не переживают рестарт); учёт токенов best-effort. Планы по снятию части этих ограничений — в docs/ROADMAP.md.


Дашборд

DASH_HOST=127.0.0.1 DASH_USER=admin DASH_PASS=<пароль> python3 dashboard.py
# http://127.0.0.1:8766

Статус прокси/провайдеров, живые/мёртвые ключи, расход по токенам и моделям, скорость генерации (TPS) по провайдерам и системная пропускная способность, графики за час/день/неделю, кнопка живой проверки ключей. Без DASH_PASS авторизация выключена — не поднимай дашборд на публичный интерфейс без пароля. Дашборд рассчитан на Linux/systemd (использует /proc, journalctl).


Docker / запуск

Секреты (config.json, keys/) не запекаются в образ — монтируй их read-only.

docker build -t multiproxy:1.0.0 .
docker run -d --name multiproxy -p 11440:11440 \
  -v "$(pwd)/config.json:/app/config.json:ro" \
  -v "$(pwd)/keys:/app/keys:ro" \
  multiproxy:1.0.0

Либо через docker-compose.yml (сервис multiproxy + опциональный профиль dashboard для dashboard.py на :8766):

docker compose up -d --build              # только прокси
docker compose --profile dashboard up -d  # прокси + дашборд

Образ — non-root пользователь, HEALTHCHECK на GET /health (учитывает опциональный health_token, см. комментарий в Dockerfile).


Разработка / CI

make install        # зависимости из requirements.txt
make dev             # + requirements-dev.txt (ruff, pytest)
make test            # pytest -q
make lint            # ruff check .
make check-config    # python multiproxy.py --check-config config.example.json
make run             # python multiproxy.py
make docker-build    # docker build -t multiproxy:1.0.0 .

GitHub Actions (.github/workflows/ci.yml) на каждый push/PR гоняет ruff, pytest и --check-config на Python 3.10, 3.11 и 3.12.


Безопасность

  • Секреты не коммитятся. config.json (токены) и keys/ (ключи) — в .gitignore. В репозиторий идут config.example.json и keys.example.txt.
  • Токены сравниваются в constant-time (hmac.compare_digest); используй длинные случайные.
  • trust_forwarded_for: false по умолчанию — при прямом доступе клиент не подделает X-Forwarded-For для обхода fail2ban. Включай только за доверенным reverse-proxy.
  • Bind 127.0.0.1 по умолчанию; для внешнего доступа — reverse-proxy (TLS) + firewall.
  • /health по умолчанию отдаётся без авторизации — не выставляй наружу без ограничения. Задай health_token в конфиге, чтобы требовать Authorization: Bearer <token> или x-api-key: <token>.

Тесты

python3 -m pytest -q                                     # 491 тест
python3 -m py_compile multiproxy.py dashboard.py
python3 multiproxy.py --check-config config.example.json  # валидация конфига, exit 0/1
python3 multiproxy.py --print-config config.example.json  # эффективный конфиг, секреты замаскированы

--check-config [путь] (по умолчанию — CONFIG_FILE/config.json) загружает и проверяет конфиг офлайн, без старта сервера: печатает PASS или список найденных проблем и завершается кодом 0/1. Тот же шаг гоняется в CI на каждый push/PR (см. «Разработка / CI» ниже).

--print-config [путь] печатает конфиг после применения всех значений по умолчанию, с замаскированными секретами и числом реально загруженных ключей по каждому провайдеру: около двух десятков ключей дефолтятся молча, и до 1.6.0 узнать их фактические значения можно было только чтением исходника.

Дополнительно: check_all_keys.py (живая проверка ключей), audit_multiproxy_runtime.py (нагрузка/стриминг/фаззер/hot-reload), backup_to_drive.sh (бэкап через rclone).


Лицензия

MIT © 2026 Oleg Alioshin (oleg494).

About

Multi-provider LLM proxy (OpenAI/Anthropic-compatible) with key rotation, failover, caching, metrics and a zero-dependency ops dashboard

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages