Правила маршрутизации
Настройка динамических правил маршрутизации на CEL-выражениях для управления тем, как запросы распределяются между провайдерами.
Обзор
Правила маршрутизации обеспечивают динамический, выражаемый формулами контроль над маршрутизацией запросов. Они выполняются до выбора провайдера в governance и могут переопределить его — это позволяет принимать решения по маршрутизации на основе контекста запроса, заголовков, параметров, метрик потребления и организационной иерархии.
В отличие от governance routing (статические веса провайдеров), правила маршрутизации используют CEL-выражения (Common Expression Language) и оценивают условия в момент запроса.
Как это работает
Поток обработки запроса
Иерархия областей действия и приоритеты
Правила маршрутизации организованы по областям действия (scope) с принципом first-match-wins:
Virtual Key (наивысший приоритет)
↓
Team
↓
Customer
↓
Global (низший приоритет, применяется ко всем)Как это работает:
- Когда приходит запрос с виртуальным ключом, Meridian строит цепочку scope.
- Правила оцениваются по порядку scope (от наивысшего к низшему).
- Первое совпавшее правило побеждает — дальнейшие правила не оцениваются.
- Внутри одного scope правила сортируются по приоритету (по возрастанию: 0 оценивается раньше 10).
- Если ни одно правило не совпало, используется провайдер/модель из исходного запроса без изменений.
Пример:
Виртуальный ключ (vk-123) привязан к команде (team-456),
которая принадлежит клиенту (cust-789)
Порядок оценки:
1. Правила scope = virtual_key (vk-123)
2. Правила scope = team (team-456)
3. Правила scope = customer (cust-789)
4. Правила scope = global
Первое совпадение → решениеСправочник по CEL-выражениям
Доступные переменные
Правила оценивают CEL-выражения в контексте следующих переменных.
Контекст запроса
model // Запрошенная модель (string)
provider // Текущий провайдер (string)
request_type // Тип запроса (chat_completion, embedding, batch, image_generation, moderation, transcription, translation)Заголовки и параметры
headers["header-name"] // Заголовок запроса (поиск ключа без учёта регистра)
params["param-name"] // Query-параметрПримеры заголовков:
headers["x-tier"] == "premium"
headers["x-api-version"] == "v2"
headers["user-agent"].contains("mobile")Контекст организации
virtual_key_id // ID виртуального ключа (string, пустой если ключа нет)
virtual_key_name // Имя виртуального ключа (string)
team_id // ID команды (string, пустой если не в команде)
team_name // Имя команды (string)
customer_id // ID клиента (string)
customer_name // Имя клиента (string)Примеры:
team_name == "ml-research"
customer_id == "acme-corp"
virtual_key_name.startsWith("prod-")Метрики потребления (в процентах: 0–100)
budget_used // Процент использованного бюджета по провайдеру/модели (0.0–100.0)
tokens_used // Процент использованного rate limit по токенам (0.0–100.0)
request // Процент использованного rate limit по запросам (0.0–100.0)Примеры:
budget_used > 80 // Маршрутизировать в fallback при использовании >80% бюджета
tokens_used < 50 // Использовать быстрого провайдера при <50% токенового лимита
request > 90 // Сменить провайдера при близком к пределу лимите запросовОператоры и функции CEL
Операторы сравнения
== // Равно
!= // Не равно
> // Больше
< // Меньше
>= // Больше или равно
<= // Меньше или равноЛогические операторы
&& // AND
|| // OR
! // NOTСтроковые функции
.startsWith("prefix") // Начинается с префикса
.endsWith("suffix") // Заканчивается суффиксом
.contains("substring") // Содержит подстроку
.matches("regex") // Соответствует регулярному выражениюКоллекции
"value" in ["item1", "item2", "item3"] // Проверка вхожденияПримеры выражений
Простые условия
// По заголовку
headers["x-tier"] == "premium"
// По команде
team_name == "research"
// По модели
model == "gpt-4o"
// По типу запроса
request_type == "embedding"
// Маршрут в fallback при высокой загрузке бюджета
budget_used > 80Сложные условия (несколько критериев)
// Премиум-тариф для команды research
headers["x-tier"] == "premium" && team_name == "research"
// Высокая нагрузка ИЛИ премиум-приоритет
budget_used > 90 || headers["x-priority"] == "high"
// Конкретная команда и модель
team_name == "ml-ops" && model.startsWith("claude-")
// Регион и проверка нагрузки
headers["x-region"] == "us-east" && tokens_used < 75
// Embeddings → дешёвый провайдер при высокой нагрузке бюджета
request_type == "embedding" && budget_used > 50Сопоставление по шаблону
// Модели, начинающиеся с префикса
model.startsWith("gpt-4")
// Произвольные заголовки
headers["x-environment"] in ["staging", "testing"]
// Соответствие домена email
headers["x-user-email"].contains("@company.com")
// Регулярные выражения
headers["x-app-version"].matches("[0-9]+\\.[0-9]+\\.[0-9]+")Валидация и обработка ошибок
- Некорректный синтаксис CEL → правило логируется с предупреждением и пропускается; оценка продолжается.
- Отсутствует заголовок/параметр → выражение возвращает
false(мягкое отсутствие совпадения). - Несовпадение типов → логируется с предупреждением, правило пропускается.
- Пустое выражение → правило всегда совпадает (используйте
true/falseдля явного поведения).
Конфигурация
Раздел правил маршрутизации доступен из панели управления.
Дашборд правил маршрутизации

Возможности:
- список всех правил со scope, приоритетом и статусом включения;
- фильтрация по scope и
scope_id; - создание/редактирование/удаление правил;
- просмотр выражений и таргетов;
- включение/выключение без удаления;
- изменение приоритетов перетаскиванием.
Создание / редактирование правила

Поля:
- Name (обязательное) — уникальное имя правила.
- Description (опционально) — внутренние пометки.
- Enabled — включено или выключено.
- CEL Expression — визуальный конструктор или ручной ввод выражения.
- Targets (обязательно) — один или несколько взвешенных таргетов; у каждого: Provider (опционально), Model (опционально), API Key (опционально, требует Provider) и Weight (%). Сумма весов должна равняться 1. При нескольких таргетах один выбирается вероятностно на каждый запрос.
- Fallbacks (опционально) — массив резервных провайдеров.
- Scope — где применяется правило:
global,customer,team,virtual_key. - Scope ID — обязателен, если scope не
global. - Priority — меньше значит раньше (по умолчанию 0).
Визуальный конструктор CEL
В дашборде есть визуальный query-builder для CEL-выражений:
- Condition Builder — выбор поля, оператора и значения;
- Logical Operators — комбинирование условий через AND/OR;
- Manual Mode — переключение на ручное редактирование CEL;
- Validation — синтаксическая проверка в реальном времени;
- Conversion — автоматическое преобразование визуального правила в CEL.
Список правил
GET /api/governance/routing-rules
# Опциональные query-параметры:
?scope=global&scope_id=<id>&from_memory=trueОтвет:
{
"rules": [
{
"id": "rule-uuid-123",
"name": "Premium Tier Route",
"description": "Route premium users to fast provider",
"enabled": true,
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 0.7 },
{ "provider": "azure", "model": "gpt-4o", "weight": 0.3 }
],
"fallbacks": ["groq/gpt-3.5-turbo"],
"scope": "global",
"scope_id": null,
"priority": 10,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
],
"count": 1
}Получение правила
GET /api/governance/routing-rules/{rule_id}Создание правила
POST /api/governance/routing-rules
Content-Type: application/jsonТело запроса:
{
"name": "Budget Overflow Route",
"description": "When budget is high, route to cheaper provider",
"enabled": true,
"cel_expression": "budget_used > 85",
"targets": [
{ "provider": "groq", "weight": 1 }
],
"fallbacks": ["openai/gpt-4o"],
"scope": "team",
"scope_id": "team-uuid-456",
"priority": 5
}Ответ: 201 Created
{
"message": "Routing rule created successfully",
"rule": { /* объект правила */ }
}Обновление правила
PUT /api/governance/routing-rules/{rule_id}
Content-Type: application/jsonТело запроса (все поля опциональны):
{
"name": "Updated Rule Name",
"enabled": false,
"cel_expression": "budget_used > 90",
"priority": 20
}Удаление правила
DELETE /api/governance/routing-rules/{rule_id}Ответ: 200 OK
{
"message": "Routing rule deleted successfully"
}Правила маршрутизации задаются в config.json в секции governance:
Структура:
{
"governance": {
"routing_rules": [
{
"id": "rule-uuid-123",
"name": "Premium Tier Route",
"description": "Route premium users to fast provider",
"enabled": true,
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 0.7 },
{ "provider": "azure", "model": "gpt-4o", "weight": 0.3 }
],
"fallbacks": ["groq/gpt-3.5-turbo"],
"scope": "global",
"scope_id": null,
"priority": 10
},
{
"id": "rule-uuid-456",
"name": "Budget Overflow Route",
"description": "Route to cheaper provider when budget is high",
"enabled": true,
"cel_expression": "budget_used > 85",
"targets": [
{ "provider": "groq", "model": "llama-2-70b", "weight": 1 }
],
"fallbacks": [],
"scope": "team",
"scope_id": "team-ml-ops",
"priority": 5
}
]
}
}Поля:
- id (string, генерируется автоматически) — уникальный идентификатор (UUID).
- name (string, обязательное) — имя правила (уникальное в пределах scope).
- description (string, опционально) — внутренняя документация.
- enabled (boolean) — активно ли правило.
- cel_expression (string) — CEL-выражение для проверки совпадения.
- targets (array, обязательное) — один или несколько таргетов. У каждого:
provider(string, опционально) — целевой провайдер; пропустите, чтобы оставить провайдера из исходного запроса;model(string, опционально) — целевая модель; пропустите, чтобы оставить модель из запроса;key_id(string, опционально) — UUID API-ключа, который нужно зафиксировать; требует заданногоprovider; пропустите для load-balanced выбора ключа;weight(number, обязательное) — вероятностный вес; сумма весов в правиле должна равняться 1 (например,0.7 + 0.3 = 1.0).
- fallbacks (array[string]) — резервные провайдеры в формате
"provider/model". - scope (string) —
global,customer,teamилиvirtual_key. - scope_id (string, опционально) — ID соответствующей сущности (
nullдляglobal). - priority (number) — порядок оценки внутри scope (меньше значит раньше).
Загрузка из config.json: правила загружаются автоматически при старте из секции governance. Изменения требуют перезапуска приложения.
Пример с несколькими правилами:
{
"governance": {
"routing_rules": [
{
"id": "tier-based",
"name": "Premium Tier Fast Track",
"enabled": true,
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 1 }
],
"fallbacks": ["azure/gpt-4o"],
"scope": "global",
"priority": 0
},
{
"id": "capacity-failover",
"name": "Budget Exhaustion Fallback",
"enabled": true,
"cel_expression": "budget_used > 90",
"targets": [
{ "provider": "groq", "model": "llama-2-70b", "weight": 1 }
],
"fallbacks": [],
"scope": "global",
"priority": 5
},
{
"id": "team-preference",
"name": "ML Team Anthropic Route",
"enabled": true,
"cel_expression": "team_name == \"ml-research\"",
"targets": [
{ "provider": "anthropic", "model": "claude-3-opus-20240229", "weight": 1 }
],
"fallbacks": ["bedrock/claude-3-opus"],
"scope": "team",
"scope_id": "team-ml-research",
"priority": 0
}
]
}
}Сценарии использования
Когда применять правила маршрутизации:
- динамическая маршрутизация по заголовкам или параметрам;
- маршрутизация по нагрузке (fallback при росте бюджета или rate limit);
- организационная маршрутизация (разные правила для команд и клиентов);
- A/B-тесты и canary-деплои;
- условное переопределение провайдера на основе сложной логики.
Сценарий 1: Маршрутизация по тарифу
Маршрутизация запросов в зависимости от тарифа клиента через заголовки:
{
"name": "Premium Tier Fast Track",
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 1 }
],
"fallbacks": ["azure/gpt-4o"],
"scope": "global",
"priority": 10
}Сценарий 2: Failover при исчерпании бюджета
Перевод трафика на более дешёвого провайдера при росте использования бюджета:
{
"name": "Budget Exhaustion Fallback",
"cel_expression": "budget_used > 90",
"targets": [
{ "provider": "groq", "model": "llama-2-70b", "weight": 1 }
],
"fallbacks": [],
"scope": "global",
"priority": 5
}Сценарий 3: Маршрутизация по команде
Запросы конкретной команды направляются на её предпочтительного провайдера:
{
"name": "ML Team Anthropic Preference",
"cel_expression": "team_name == \"ml-research\"",
"targets": [
{ "provider": "anthropic", "model": "claude-3-opus-20240229", "weight": 1 }
],
"fallbacks": ["bedrock/claude-3-opus"],
"scope": "team",
"scope_id": "team-ml-research-uuid",
"priority": 0
}Сценарий 4: Сложная многокритериальная маршрутизация
Сочетание нескольких условий:
{
"name": "Production Premium Route",
"cel_expression": "headers[\"x-environment\"] == \"production\" && headers[\"x-priority\"] == \"high\" && tokens_used < 75",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 1 }
],
"fallbacks": ["azure/gpt-4o"],
"scope": "global",
"priority": 5
}Сценарий 5: Вероятностное A/B-тестирование
Деление трафика между провайдерами по весам — для canary-деплоев или оптимизации стоимости:
{
"name": "Split Traffic OpenAI vs Groq",
"cel_expression": "true",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 0.7 },
{ "provider": "groq", "model": "llama-3.1-70b", "weight": 0.3 }
],
"scope": "global",
"priority": 15
}Каждый запрос, попадающий под это правило, с вероятностью 70 % уходит в OpenAI и с вероятностью 30 % — в Groq. Сумма весов всегда должна быть равна 1.
Сценарий 6: Региональная маршрутизация
Маршрутизация по заголовку региона:
{
"name": "EU Data Residency",
"cel_expression": "headers[\"x-region\"] == \"eu\"",
"targets": [
{ "provider": "azure", "model": "gpt-4o", "weight": 1 }
],
"fallbacks": [],
"scope": "global",
"priority": 0
}Взаимодействие с governance и балансировкой
С governance routing
Правила маршрутизации выполняются до выбора провайдера в governance и могут переопределить его.
Если правило совпало:
1. Правила маршрутизации → оценка CEL (first-match-wins)
2. Совпадение → таргет выбирается вероятностно из массива targets
3. provider/model/key_id/fallbacks переопределяются из выбранного таргета
4. Governance provider_configs → ПРОПУСКАЕТСЯ
5. Балансировка → выбирает оптимальный ключ (если key_id не зафиксирован)Если ни одно правило не совпало:
1. Правила маршрутизации → оценка CEL
2. Совпадений нет → продолжаем
3. Governance routing → выбор provider/model (взвешенный random)
4. Балансировка → выбирает оптимальный ключПример:
- Governance настроен: 70 % Azure, 30 % OpenAI.
- Существует правило
budget_used > 85 → groq. - Приходит запрос с
budget_used = 90 %. - Результат: правилом выбран Groq, governance
provider_configsигнорируется.
С балансировкой
Правила определяют провайдера до того, как сработает adaptive load balancing:
1. Сначала оцениваются правила → определяют провайдера (если совпало)
ИЛИ
2. Governance выбирает провайдера (если правил нет)
↓
3. Load Balancing уровня 1 — пропускается (провайдер уже определён правилом или governance)
4. Load Balancing уровня 2 — выбор ключа (по производительности внутри выбранного провайдера)Ключевой момент. Уровень 2 балансировки (выбор ключа) выполняется всегда, независимо от того, кто определил провайдера — правила или governance. То есть автоматическая оптимизация на уровне ключа работает в обоих случаях.
Цепочка fallback
Правила могут задавать fallback'и, которые подхватываются балансировкой:
{
"provider": "openai",
"fallbacks": ["azure/gpt-4o", "groq/gpt-3.5-turbo"]
}Если OpenAI откажет:
- Уровень 2 балансировки оценит ключи Azure.
- Если все ключи Azure упадут — пробуется Groq.
Выполнение и производительность
Компиляция CEL
- Первая оценка — CEL-выражение компилируется в bytecode-программу.
- Последующие оценки — программа кешируется и переиспользуется.
- Производительность — оценка кешированной программы быстрая (микросекунды).
- Память — скомпилированные программы хранятся в памяти до перезапуска Meridian.
Приоритеты и порядок
Правила одного scope оцениваются по возрастанию приоритета:
Priority 0 → Priority 5 → Priority 10 → Priority 100 (first match wins)Лучшая практика: приоритеты 0–10 — для критичных правил, 100+ — для fallback.
Советы по оптимизации
- Сортируйте правила по вероятности срабатывания — часто совпадающие должны идти первыми.
- Используйте узкие scope — глобальный scope медленнее (чем уже scope, тем быстрее).
- Избегайте дорогих строковых операций —
==дешевле.matches()с регулярным выражением. - Держите выражения простыми — сложные условия увеличивают время оценки.
- Оставляйте промежутки в приоритетах (0, 10, 20) — это упрощает последующее переупорядочивание.
Лучшие практики
Поиск и устранение неполадок
Правило не совпадает
Симптом. Выражение правила корректно, но не срабатывает.
Диагностика:
- Проверьте, что правило включено (
enabled: true). - Убедитесь, что scope соответствует запросу (проверьте иерархию team/customer виртуального ключа).
- Проверьте приоритет правила относительно других в том же scope (меньше — раньше).
- Убедитесь, что значения переменных соответствуют ожиданиям. Используйте
from_memory=trueдля отладки.
Решение:
# Получить текущие правила в памяти
GET /api/governance/routing-rules?from_memory=true
# Проверьте, выставлены ли нужные переменные
# Например, действительно ли team_name установлен?
# Помните: ключи заголовков в CEL должны быть в нижнем регистре.Ошибка компиляции выражения
Симптом: Failed to compile rule: invalid CEL syntax.
Частые причины:
- незакрытые кавычки:
headers["x-tier(нет закрывающей кавычки); - недопустимые операторы:
headers["x"] ??(это не стандартный CEL); - ошибки экранирования:
headers["x-\type"].
Решение:
- Используйте визуальный CEL-конструктор, чтобы избежать опечаток.
- Тестируйте выражение по частям.
- Сверяйтесь со списком операторов выше.
- Сложные выражения оборачивайте в скобки:
(A && B) || (C && D).
Выбран не тот провайдер
Симптом. Запрос ушёл к неожиданному провайдеру.
Диагностика:
- Совпали ли несколько правил? (first-match-wins означает, что более раннее правило побеждает.)
- Был ли провайдер уже выбран governance? (Проверьте иерархию scope.)
- Изменила ли балансировка ключ? (Правило задаёт провайдера, балансировка — ключ.)
Решение:
- Понизьте приоритет совпадающих правил.
- Проверьте порядок precedence по scope (Virtual Key > Team > Customer > Global).
- Убедитесь, что нет другого правила с более низким приоритетом, которое срабатывает раньше.
- Изучите логи:
[RoutingEngine] Rule matched! Decision: provider=....
Заголовок или параметр не найден
Симптом: ошибка no such key при оценке CEL.
Это нормальное поведение — Meridian трактует отсутствующие заголовки как «не совпадает»:
headers["x-optional"] == "value" // Возвращает false, если заголовка нетЧтобы явно проверить наличие заголовка:
headers["x-optional"] != "" // True только если заголовок есть и не пустойОтладка через логи
Включите debug-логи, чтобы увидеть оценку правил:
[RoutingEngine] Starting rule evaluation for provider=openai, model=gpt-4o
[RoutingEngine] Scope chain: [virtual_key(vk-123) team(team-456) customer(cust-789) global]
[RoutingEngine] Evaluating scope=virtual_key, scopeID=vk-123, ruleCount=2
[RoutingEngine] Evaluating rule: id=rule-1, name=Premium Route, expression=headers["x-tier"]=="premium"
[RoutingEngine] Rule rule-1 evaluation result: matched=false
[RoutingEngine] Evaluating rule: id=rule-2, name=Budget Fallback, expression=budget_used>80
[RoutingEngine] Rule rule-2 evaluation result: matched=true
[RoutingEngine] Rule matched! Selected target: provider=groq, model=gpt-3.5-turbo (weight=1), fallbacks=[azure/gpt-4o]Справочник API
Примеры запросов и ответов
Создание правила по нагрузке
curl -X POST http://localhost:8080/api/governance/routing-rules \
-H "Content-Type: application/json" \
-d '{
"name": "High Budget Fallback",
"description": "Switch to cheaper provider when budget >85%",
"enabled": true,
"cel_expression": "budget_used > 85",
"targets": [
{ "provider": "groq", "model": "llama-2-70b", "weight": 1 }
],
"fallbacks": ["openai/gpt-3.5-turbo"],
"scope": "global",
"priority": 10
}'Создание правила с вероятностным сплитом
curl -X POST http://localhost:8080/api/governance/routing-rules \
-H "Content-Type: application/json" \
-d '{
"name": "Premium Tier Split",
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 0.7 },
{ "provider": "azure", "model": "gpt-4o", "weight": 0.3 }
],
"scope": "global",
"priority": 5
}'Создание правила с зафиксированным API-ключом
curl -X POST http://localhost:8080/api/governance/routing-rules \
-H "Content-Type: application/json" \
-d '{
"name": "Pin Production Key for Premium Tier",
"description": "Always use the dedicated production key for premium requests",
"enabled": true,
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{
"provider": "openai",
"model": "gpt-4o",
"key_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"weight": 1
}
],
"scope": "global",
"priority": 5
}'Список правил по team scope
curl http://localhost:8080/api/governance/routing-rules \
-H "Authorization: Bearer your-token" \
-G \
--data-urlencode "scope=team" \
--data-urlencode "scope_id=team-uuid-123"Получение in-memory правил (отладка)
curl http://localhost:8080/api/governance/routing-rules?from_memory=true \
-H "Authorization: Bearer your-token"