OpenRouter на днях анонсував 50% знижки на OpenAI, і вирішили спробувати його.
Переключення будемо робити на LiteLLM, на яку вже йдуть запити від всіх наших сервісів – тому переключення буде доволі простим.
До OpenRouter в LiteLLM налаштуємо fallbacks напряму до OpenAI – на ті випадки, коли реквести до OpenRouter фейляться.
Зміст
Підключення OpenRouter до LiteLLM
Взагалі все, що треба – це описати новий LiteLLM Deployment, модель, і для неї вказати api_base == OpenRouter та власний API Key.
Єдина відмінність – це ім’я моделі: якщо при прямих викликах до OpenAI ми вказуємо model = openai/gpt-4.1-nano, то для OpenRouter це буде model = openrouter/openai/gpt-4.1-nano, див. OpenRouter Completion Models.
Отримати список всіх моделей можна з OpenRouter API, див. документацію Models.
$ curl -s "https://openrouter.ai/api/v1/models" | jq ".data[].id" | head "qwen/qwen3.7-flash" "anthropic/claude-opus-5-fast" "anthropic/claude-opus-5" "inclusionai/ling-3.0-flash:free" "poolside/laguna-s-2.1" "poolside/laguna-s-2.1:free" "google/gemini-3.6-flash" "google/gemini-3.6-flash:batch" "google/gemini-3.5-flash-lite"
Створення OpenRouter API Key
Реєструємось в OpenRouter, переходимо в API Keys потрібного OpenRouter Workspace, створюємо OpenRouter API Key:
В OpenRouter, до речі, можна задати власні ліміти на ключі:
LiteLLM Proxy Config для OpenRouter
Додаємо нову модель, тут для тесту в model_name вкажемо власне ім’я – але в production використовуємо “загальне”, яке очікуємо від клієнтів.
В api_base перевизначаємо власний URL для OpenRouter API, див. Using the OpenRouter API, а в api_key – API Key, який створили вище:
...
model_list:
- model_name: or-gpt-4.1-mini
litellm_params:
model: openrouter/openai/gpt-4.1-mini
api_base: https://openrouter.ai/api/v1
api_key: os.environ/OPENROUTER_API_KEY
...
Поки тестуємо – змінну з ключем можна передати в Helm через envVars, в production це робиться через External Secrets Operator (див. AWS: Kubernetes та External Secrets Operator для AWS Secrets Manager та LiteLLM: AI Gateway в Kubernetes та метрики до VictoriaMetrics):
...
envVars:
OPENROUTER_API_KEY: "sk-or-v1-***"
...
Перевіряємо з curl:
$ curl -sS -D /tmp/headers 'https://aigw.test.example.co/chat/completions' -H 'Content-Type: application/json' -H "Authorization: Bearer $LITELLM_TESTING_KEY" --data ' { "model": "or-gpt-4.1-mini", "messages": [ { "role": "user", "content": "what llm are you" } ] } ' | jq
{
"id": "gen-1785490640-Tbfwc5Rlx7cyhqpxzSB6",
"created": 1785490640,
"model": "or-gpt-4.1-mini",
...
В заголовках маємо всю цікаву інформацію (роблю через -D у файл, щоб jq нормально працював):
$ cat /tmp/headers ... x-litellm-model-name: openrouter/openai/gpt-4.1-mini x-litellm-model-api-base: https://openrouter.ai/api/v1 ...
Але коли викотили OpenRouter на “тестовий production” – почали ловити 429 помилки, а тому треба було додати fallbacks на OpenAI.
LiteLLM Fallbacks
Ліміти описані в Credit Limits and Rate Limits, і хоча у нас не free моделі – але помилки ловимо.
Да і в будь-якому випадку fallbacks мати треба.
OpenRouter теж підтримує Automatic failover between models, але для цього треба робити зміни в коді клієнтів – а ми хочемо максимально прозоро і з мінімум змін на клієнтах.
Тому налаштуємо з LiteLLM, див. Fallbacks (Provider Failover).
В документації LiteLLM трохи mess (вже не перший раз, btw), бо в одному прикладі описано як litellm_settings.fallbacks – а в іншому в router_settings.fallbacks.
Але більш коректним саме router_settings, бо він має пріорітет – router.py:
...
_fallbacks = fallbacks or litellm.fallbacks
...
А fallbacks “приходить” саме із router_params в proxy_server.py:
...
router = litellm.Router(
**router_params,
...
Формат доволі простий – “модель, для якої вказуємо fallback : список моделей, на які роутимо при проблемах”:
router_settings:
fallbacks: [{"<QUERY_MODEL>": ["<FALLBACK_MODEL1>","<FALLBACK_MODEL2>"]}]
Плюс можна задати загальний, а не model-specific fallback, див. Default Fallbacks:
router_settings: default_fallbacks: ["claude-opus"]
(знов-таки – в документації він заданий в litellm_settings замість router_settings, хоча працює однаково в обох випадках)
Ну і давайте спробуємо, як це працює.
Додавання Model Fallback
Додаємо fallback для нашої тестової моделі:
router_settings:
fallbacks: [{"or-gpt-4.1-mini": ["gpt-4.1-mini"]}]
А в самій моделі – “ламаємо” api_base через неправильний порт:
model_list:
- model_name: or-gpt-4.1-mini
litellm_params:
model: openrouter/openai/gpt-4.1-mini
api_base: https://openrouter.ai:8000/api/v1
api_key: os.environ/OPENROUTER_API_KEY
Повторюємо запит:
$ curl -sS -D /tmp/headers 'https://aigw.test.example.co/chat/completions' -H 'Content-Type: application/json' -H "Authorization: Bearer $LITELLM_TESTING_KEY" --data ' { "model": "or-gpt-4.1-mini", "messages": [ { "role": "user", "content": "what llm are you" } ] } ' | jq
{
"id": "chatcmpl-E7eVkBzpdbWp5n6JagYlYHTlEEI1f",
"created": 1785492728,
"model": "gpt-4.1-mini-2025-04-14",
...
І заголовки – відповідь вже від OpenAI:
$ cat /tmp/headers | grep x-litellm-model x-litellm-model-id: 8b2d27fc9982e18f87ed59ca8c8b0d02c52546f26ece314f17d14433ed6498c2 x-litellm-model-name: openai/gpt-4.1-mini x-litellm-model-api-base: https://api.openai.com x-litellm-model-group: gpt-4.1-mini
Fallback Options
Див. Fallbacks + Retries + Timeouts + Cooldowns.
Для fallback routing можна задати кілька корисних опцій:
num_retries: скільки раз повторити запит в основній model group перед переходом до fallback-моделі- якщо в model group є кілька deployments, retry може піти до іншого deployment без fallback
timeout: скільки часу чекати відповіді перед тим, як перенаправити запити до fallback model- після кожного
timoutпочинається наступнийnum_retries, якщо там задано більше 1, і тільки потім – до fallback mode
- після кожного
allowed_fails: скільки failed requests до моделі допустимо перед тим, як модель перейти в cooldown- при значенні 3 cooldown спрацює на четвертій невдалій спробі
cooldown_time: скільки часу секундах модель буде “відключена” від загального роутингу
Тут знов-таки документація трохи… крива, бо, наприклад, описано “allowed_fails: 3 # cooldown model if it fails > 1 call in a minute“, ну і замість “in a minute“, мабуть, малось на увазі 30 секунд в прикладі з cooldown_time.
Fallbacks та Prometheus metrics
Звісно, спрацювання – і помилки – фолбеків бажано моніторити.
У LiteLLM з коробки є метрики, див. Fallback (Failover) Metrics:
Тут:
litellm_deployment_cooled_down: скільки раз deployment (модель) переводилась в стан cooldownlitellm_deployment_successful_fallbacks: кількість успішних спрацювань fallbackslitellm_deployment_failed_fallbacks: кількість помилок при fallbacks
І приклад алерту:
- alert: LiteLLM Deployment Failed Fallback
expr: |
sum by (requested_model, fallback_model, api_key_alias, team_alias, exception_class, exception_status) (
increase(litellm_deployment_failed_fallbacks_total[5m])
) > 0
for: 30s
labels:
component: devops
environment: ops
severity: critical
ilert_routingkey: devops-ops-critical
annotations:
summary: LiteLLM Deployment Failed Fallback
description: |-
LiteLLM failed to handle a request using a fallback model during the last 5 minutes.
*Failed fallbacks*: `{{ "{{" }} $value }}`
*Requested model*: `{{ "{{" }} $labels.requested_model }}`
*Fallback model*: `{{ "{{" }} $labels.fallback_model }}`
*API key alias*: `{{ "{{" }} $labels.api_key_alias }}`
*Team alias*: `{{ "{{" }} $labels.team_alias }}`
*Exception class*: `{{ "{{" }} $labels.exception_class }}`
*Exception status*: `{{ "{{" }} $labels.exception_status }}`
<https://{{ $.Values.monitoring.root_url }}/d/adtt9jj/adrmshg/litellm-system-overview |:grafana: LiteLLM System overview>
Advanced routing
У нас є сервіси, які хочуть продовжувати ходити на OpenAI – але ми не хочемо нічого міняти в їх коді, тобто model_name має залишитись, як є.
Тут є кілька варіантів, див. документацію Tag Based Routing:
- використати Tags в API Keys
- використати Tags з заголовків (але з нюансами)
Routing by API Key Tags
Створюємо новий ключ, правда при створенні ключа йому не мона відразу вказати Tags, бо “This feature is only available for LiteLLM Enterprise“.
Але можна створити ключ – а потім в його Settings вже задати потрібний Tag:
І аналогічно – ключ з тегом “direct-openrouter“.
До router_settings додаємо enable_tag_filtering=true, а в model_list описуємо model group – два деплоймента з однаковим model_name, але різними умовами в tags:
router_settings:
enable_tag_filtering: True
model_list:
- model_name: or-gpt-4.1-mini
litellm_params:
model: openrouter/openai/gpt-4.1-mini
api_base: https://openrouter.ai/api/v1
api_key: os.environ/OPENROUTER_API_KEY
tags: ["direct-openrouter"]
- model_name: or-gpt-4.1-mini
litellm_params:
model: openai/gpt-4.1-mini
api_key: os.environ/OPENAI_API_KEY
tags: ["direct-openai"]
Робимо запит з ключем “direct-openai“:
$ curl -sS -D /tmp/openai 'https://aigw.test.example.co/chat/completions' -H 'Content-Type: application/json' -H "Authorization: Bearer sk-***" --data ' { "model": "or-gpt-4.1-mini", "messages": [ { "role": "user", "content": "what llm are you" } ] } ' | jq
{
"id": "chatcmpl-E7f89zMEtza5liQnm8uBVQaaBcyVv",
"created": 1785495109,
"model": "gpt-4.1-mini-2025-04-14",
...
Маємо відповідь від OpenAI:
$ cat /tmp/openai | grep x-litellm-model x-litellm-model-id: b1cd50178d84ad641058301b47c9f171d86337bcc7f0c08dc425f9f44ec2dffd x-litellm-model-name: openai/gpt-4.1-mini x-litellm-model-api-base: https://api.openai.com
І з ключем “direct-openrouter“:
$ curl -sS -D /tmp/openrouter 'https://aigw.test.example.co/chat/completions' -H 'Content-Type: application/json' -H "Authorization: Bearer sk-***" --data ' { "model": "or-gpt-4.1-mini", "messages": [ { "role": "user", "content": "what llm are you" } ] } ' | jq
{
"id": "gen-1785495093-Toi3dLdjHJ692Ue8bFgl",
"created": 1785495093,
"model": "or-gpt-4.1-mini",
...
Маємо відповідь від OpenRouter:
$ cat /tmp/openrouter | grep x-litellm-model x-litellm-model-name: openrouter/openai/gpt-4.1-mini x-litellm-model-api-base: https://openrouter.ai/api/v1
Routing by the User Agent header
Інший варіант – не додавати теги вручну, а просто використати User Agent з headers.
В документації Regex-based tag routing (tag_regex) tag_regex описаний як “Use tag_regex on a deployment to match incoming requests by their headers” – тобто наче по будь-якому заголовку, але по факту враховується тільки User-Agent (якщо я правильно прочитав код):
...
# Build header strings for regex matching from what the proxy already stores.
# Currently we match against User-Agent; format matches "^User-Agent: claude-code/..."
user_agent = metadata.get("user_agent", "")
header_strings: list[str] = [f"User-Agent: {user_agent}"] if user_agent else []
...
Втім, в моєму випадку цього достатньо – бо один сервіс у нас це TypeScript з user-agent = "OpenAI/JS 6.26.0", а інший – Python з user-agent = "OpenAI/Python 2.45.0".
Міняємо умови в моделях, описуємо regex:
- model_name: or-gpt-4.1-mini
litellm_params:
model: openrouter/openai/gpt-4.1-mini
api_base: https://openrouter.ai/api/v1
api_key: os.environ/OPENROUTER_API_KEY
tag_regex:
- '^User-Agent: OpenAI/JS '
- model_name: or-gpt-4.1-mini
litellm_params:
model: openai/gpt-4.1-mini
api_key: os.environ/OPENAI_API_KEY
tag_regex:
- '^User-Agent: OpenAI/Python '
Робимо запит з “основним” тестовим ключем (де нема тегів), але явно передаємо -H 'User-Agent: OpenAI/JS 6.26.0':
$ curl -sS -D /tmp/js-headers 'https://aigw.test.example.co/chat/completions' -H 'Content-Type: application/json' -H "Authorization: Bearer $LITELLM_TESTING_KEY" -H 'User-Agent: OpenAI/JS 6.26.0' --data '{ "model": "or-gpt-4.1-mini",
"messages": [{
"role": "user",
"content": "JS routing test unique-001"
}]
}' | jq
{
"id": "gen-1785495531-69Gu3ziqrjBmlNyEx3Jo",
"created": 1785495531,
"model": "or-gpt-4.1-mini",
...
Відповідь отримали від OpenRouter:
$ cat /tmp/js-headers ... x-litellm-model-name: openrouter/openai/gpt-4.1-mini x-litellm-model-api-base: https://openrouter.ai/api/v1 ...
І аналогічний запит, але з -H 'User-Agent: OpenAI/Python 2.45.0' :
$ curl -sS -D /tmp/python-headers 'https://aigw.test.example.co/chat/completions' -H 'Content-Type: application/json' -H "Authorization: Bearer $LITELLM_TESTING_KEY" -H 'User-Agent: OpenAI/Python 2.45.0' --data '{
"model": "or-gpt-4.1-mini",
"messages": [{
"role": "user",
"content": "Python routing test unique-001"
}]
}' | jq
{
"id": "chatcmpl-E7fFeVdB0ZWynqc5JxO3d4lUA4Gqf",
"created": 1785495574,
"model": "gpt-4.1-mini-2025-04-14",
...
І отримали відповідь від OpenAI:
$ cat /tmp/python-headers x-litellm-model-name: openai/gpt-4.1-mini x-litellm-model-api-base: https://api.openai.com
Готово.
![]()



