id: litellm-proxy-budget-and-rpm-fallback-circuit-breaker
namespace: company.ai
description: |
Audit LiteLLM Proxy admin endpoints, evaluate user spend budgets against spend caps,
detect HTTP 429 rate limits across model deployments, trip circuit breakers to reroute
traffic to fallback models, and record active routing state in Kestra KV store.
concurrency:
limit: 1
behavior: QUEUE
inputs:
- id: litellm_proxy_url
type: STRING
displayName: LiteLLM Proxy URL
description: Base URL of the LiteLLM Proxy administration endpoint.
defaults: "http://localhost:4000"
- id: user_id
type: STRING
displayName: LiteLLM User ID
description: User or team identifier to audit for spend budget and rate limit
consumption.
defaults: "team-data-science"
- id: primary_model
type: STRING
displayName: Primary Model Deployment
description: Default high-capability LLM deployment prioritized for production
routing (e.g., Anthropic Claude 3.5 Sonnet).
defaults: "claude-3-5-sonnet"
- id: fallback_model
type: STRING
displayName: Fallback Model Deployment
description: First fallback LLM deployment routed when primary deployment
encounters rate limits or budget thresholds (e.g., OpenAI GPT-4o).
defaults: "gpt-4o"
- id: secondary_fallback_model
type: STRING
displayName: Emergency Fallback Model Deployment
description: High-throughput, cost-effective fallback LLM routed during critical
budget exhaustion (e.g., DeepSeek Chat).
defaults: "deepseek-chat"
- id: budget_exhaustion_threshold_pct
type: FLOAT
displayName: Budget Warning Threshold (%)
description: Percentage threshold of spend against max budget (spend /
max_budget * 100) that trips the cost circuit breaker.
defaults: 90.0
- id: rpm_headroom_threshold_pct
type: FLOAT
displayName: Minimum RPM Headroom Threshold (%)
description: Minimum percentage of remaining RPM capacity required before
tripping the rate limit circuit breaker.
defaults: 10.0
- id: fail_on_exhaustion
type: BOOL
displayName: Fail on Hard Cap Exhaustion
description: Terminate workflow with failure status when user spend reaches or
exceeds 100% of maximum budget cap.
defaults: false
- id: slack_channel
type: STRING
displayName: Slack Incident Channel
description: Target Slack channel for circuit breaker state change alerts and
quota notifications.
defaults: "#ai-gateway-ops"
triggers:
- id: periodic_litellm_audit
type: io.kestra.plugin.core.trigger.Schedule
cron: "*/5 * * * *"
disabled: true
- id: proxy_event_webhook
type: io.kestra.plugin.core.trigger.Webhook
key: "{{ secret('LITELLM_WEBHOOK_KEY') }}"
tasks:
- id: get_user_info
type: io.kestra.plugin.core.http.Request
description: Queries LiteLLM Proxy /user/info endpoint for team budget
allocations, current spend, and active API keys.
uri: "{{ inputs.litellm_proxy_url }}/user/info?user_id={{ inputs.user_id }}"
method: GET
headers:
Authorization: "Bearer {{ secret('LITELLM_MASTER_KEY') }}"
Accept: "application/json"
options:
allowFailed: true
timeout:
readIdleTimeout: PT15S
- id: get_model_info
type: io.kestra.plugin.core.http.Request
description: Queries LiteLLM Proxy /model/info endpoint for configured model
deployments, throughput limits, and RPM quotas.
uri: "{{ inputs.litellm_proxy_url }}/model/info"
method: GET
headers:
Authorization: "Bearer {{ secret('LITELLM_MASTER_KEY') }}"
Accept: "application/json"
options:
allowFailed: true
timeout:
readIdleTimeout: PT15S
- id: get_spend_user
type: io.kestra.plugin.core.http.Request
description: Queries LiteLLM Proxy /spend/user endpoint for itemized spend
tracking across active API keys and models.
uri: "{{ inputs.litellm_proxy_url }}/spend/user?user_id={{ inputs.user_id }}"
method: GET
headers:
Authorization: "Bearer {{ secret('LITELLM_MASTER_KEY') }}"
Accept: "application/json"
options:
allowFailed: true
timeout:
readIdleTimeout: PT15S
- id: probe_primary_model_health
type: io.kestra.plugin.core.http.Request
description: Probes LiteLLM Proxy health endpoint or model readiness to detect
upstream provider rate limits or HTTP 429 errors.
uri: "{{ inputs.litellm_proxy_url }}/health/services"
method: GET
headers:
Authorization: "Bearer {{ secret('LITELLM_MASTER_KEY') }}"
Accept: "application/json"
options:
allowFailed: true
timeout:
readIdleTimeout: PT15S
- id: evaluate_circuit_breaker
type: io.kestra.plugin.scripts.python.Script
description: Analyzes LiteLLM responses, compares spend against max budget,
verifies RPM limits, and determines active circuit breaker state and
target routing model.
containerImage: python:3.11-slim
beforeCommands:
- pip install -q kestra
env:
USER_INFO_BODY: "{{ (outputs.get_user_info.body ?? {}) | toJson }}"
USER_INFO_CODE: "{{ outputs.get_user_info.code }}"
MODEL_INFO_BODY: "{{ (outputs.get_model_info.body ?? {}) | toJson }}"
MODEL_INFO_CODE: "{{ outputs.get_model_info.code }}"
SPEND_USER_BODY: "{{ (outputs.get_spend_user.body ?? {}) | toJson }}"
SPEND_USER_CODE: "{{ outputs.get_spend_user.code }}"
HEALTH_BODY: "{{ (outputs.probe_primary_model_health.body ?? {}) | toJson }}"
HEALTH_CODE: "{{ outputs.probe_primary_model_health.code }}"
PRIMARY_MODEL: "{{ inputs.primary_model }}"
FALLBACK_MODEL: "{{ inputs.fallback_model }}"
SECONDARY_FALLBACK_MODEL: "{{ inputs.secondary_fallback_model }}"
BUDGET_THRESHOLD_PCT: "{{ inputs.budget_exhaustion_threshold_pct }}"
RPM_HEADROOM_PCT: "{{ inputs.rpm_headroom_threshold_pct }}"
PREVIOUS_CIRCUIT_STATE: "{{ kv('litellm_circuit_state', errorOnMissing=false) ?? 'CLOSED' }}"
PREVIOUS_ROUTED_MODEL: "{{ kv('litellm_active_model', errorOnMissing=false) ??
inputs.primary_model }}"
script: |
import json
import os
import sys
from datetime import datetime, timezone
def parse_json(raw_text):
if not raw_text:
return {}
if isinstance(raw_text, dict):
return raw_text
try:
return json.loads(raw_text)
except Exception:
return {}
user_body = parse_json(os.environ.get("USER_INFO_BODY", "{}"))
user_code = int(os.environ.get("USER_INFO_CODE", "0") or 0)
model_body = parse_json(os.environ.get("MODEL_INFO_BODY", "{}"))
model_code = int(os.environ.get("MODEL_INFO_CODE", "0") or 0)
spend_body = parse_json(os.environ.get("SPEND_USER_BODY", "{}"))
spend_code = int(os.environ.get("SPEND_USER_CODE", "0") or 0)
health_body = parse_json(os.environ.get("HEALTH_BODY", "{}"))
health_code = int(os.environ.get("HEALTH_CODE", "0") or 0)
primary_model = os.environ.get("PRIMARY_MODEL", "claude-3-5-sonnet")
fallback_model = os.environ.get("FALLBACK_MODEL", "gpt-4o")
secondary_fallback_model = os.environ.get("SECONDARY_FALLBACK_MODEL", "deepseek-chat")
budget_threshold_pct = float(os.environ.get("BUDGET_THRESHOLD_PCT", "90.0"))
rpm_headroom_pct = float(os.environ.get("RPM_HEADROOM_PCT", "10.0"))
previous_circuit_state = os.environ.get("PREVIOUS_CIRCUIT_STATE", "CLOSED").strip() or "CLOSED"
previous_routed_model = os.environ.get("PREVIOUS_ROUTED_MODEL", primary_model).strip() or primary_model
# Extract spend and max_budget
user_info = user_body.get("user_info", user_body) if isinstance(user_body, dict) else {}
spend_val = user_info.get("spend")
if spend_val is None:
spend_val = spend_body.get("total_spend", spend_body.get("spend", 0.0))
try:
current_spend = float(spend_val or 0.0)
except (ValueError, TypeError):
current_spend = 0.0
max_budget_val = user_info.get("max_budget")
try:
max_budget = float(max_budget_val or 0.0) if max_budget_val is not None else 0.0
except (ValueError, TypeError):
max_budget = 0.0
# Calculate budget utilization
if max_budget > 0:
budget_utilization_pct = round((current_spend / max_budget) * 100.0, 2)
else:
budget_utilization_pct = 0.0
# Rate limit detection across probe and admin endpoints
is_rate_limited = False
rate_limit_source = "none"
if health_code == 429:
is_rate_limited = True
rate_limit_source = "probe_health_endpoint_429"
elif user_code == 429 or model_code == 429 or spend_code == 429:
is_rate_limited = True
rate_limit_source = "admin_api_endpoint_429"
# Inspect model deployment RPM headroom from model info
models_data = model_body.get("data", []) if isinstance(model_body, dict) else []
primary_rpm_limit = None
for item in models_data:
if isinstance(item, dict) and item.get("model_name") == primary_model:
litellm_params = item.get("litellm_params", {})
primary_rpm_limit = litellm_params.get("rpm")
# Inspect active API keys
active_keys = user_info.get("key_aliases", {})
key_count = len(active_keys) if isinstance(active_keys, dict) else 0
# Circuit Breaker Evaluation Logic
is_budget_hard_cap = (max_budget > 0) and (current_spend >= max_budget)
is_budget_warning = (max_budget > 0) and (budget_utilization_pct >= budget_threshold_pct)
if is_budget_hard_cap:
circuit_state = "OPEN"
trip_reason = "BUDGET_HARD_CAP_EXHAUSTED"
active_model = secondary_fallback_model
elif is_budget_warning:
circuit_state = "OPEN"
trip_reason = "BUDGET_WARNING_THRESHOLD_EXCEEDED"
active_model = fallback_model
elif is_rate_limited:
circuit_state = "OPEN"
trip_reason = f"PRIMARY_PROVIDER_HTTP_429 ({rate_limit_source})"
active_model = fallback_model
else:
if previous_circuit_state == "OPEN":
circuit_state = "HALF_OPEN"
trip_reason = "RECOVERY_COOLDOWN_PROBE"
active_model = primary_model
else:
circuit_state = "CLOSED"
trip_reason = "NOMINAL"
active_model = primary_model
metrics_summary = {
"circuit_state": circuit_state,
"active_model": active_model,
"trip_reason": trip_reason,
"current_spend_usd": current_spend,
"max_budget_usd": max_budget,
"budget_utilization_pct": budget_utilization_pct,
"rate_limit_detected": is_rate_limited,
"active_key_count": key_count,
"previous_state": previous_circuit_state,
"evaluated_at": datetime.now(timezone.utc).isoformat()
}
print(f"LiteLLM Circuit Breaker Evaluation:")
print(f" State: {circuit_state} (Reason: {trip_reason})")
print(f" Active Route: {active_model} (Primary: {primary_model})")
print(f" Spend: ${current_spend:.2f} / ${max_budget:.2f} ({budget_utilization_pct:.1f}%)")
print(f" Rate Limit Detected: {is_rate_limited}")
output_payload = {
"circuit_state": circuit_state,
"active_model": active_model,
"trip_reason": trip_reason,
"current_spend": current_spend,
"max_budget": max_budget,
"budget_utilization_pct": budget_utilization_pct,
"rate_limit_detected": is_rate_limited,
"metrics_summary": metrics_summary,
"evaluated_at": metrics_summary["evaluated_at"]
}
try:
from kestra import Kestra
Kestra.outputs(output_payload)
except ImportError:
print("::" + json.dumps({"outputs": output_payload}) + "::")
- id: set_circuit_state_kv
type: io.kestra.plugin.core.kv.Set
description: Updates the circuit breaker state (CLOSED, OPEN, HALF_OPEN) in
Kestra KV store.
key: "litellm_circuit_state"
value: "{{ outputs.evaluate_circuit_breaker.vars.circuit_state }}"
kvType: STRING
- id: set_active_model_kv
type: io.kestra.plugin.core.kv.Set
description: Updates the active routed LLM model endpoint in Kestra KV store for
downstream application lookup.
key: "litellm_active_model"
value: "{{ outputs.evaluate_circuit_breaker.vars.active_model }}"
kvType: STRING
- id: set_spend_metrics_kv
type: io.kestra.plugin.core.kv.Set
description: Persists telemetry metrics including current spend, budget limit,
and rate limit indicators in Kestra KV store.
key: "litellm_spend_metrics"
value: "{{ outputs.evaluate_circuit_breaker.vars.metrics_summary | toJson }}"
kvType: JSON
- id: check_circuit_trip_alert
type: io.kestra.plugin.core.flow.If
description: Evaluates whether the circuit breaker tripped or entered OPEN state
to dispatch notifications.
condition: "{{ outputs.evaluate_circuit_breaker.vars.circuit_state == 'OPEN' }}"
then:
- id: notify_slack_circuit_trip
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Dispatches incident notification to Slack detailing budget quota
exhaustion or rate limits with automatic model rerouting.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"channel": "{{ inputs.slack_channel }}",
"username": "LiteLLM-Circuit-Breaker",
"text": "🚨 *ALERT: LiteLLM Circuit Breaker Tripped - Traffic Rerouted!*",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "🚨 LiteLLM Quota Exceeded: Model Fallback Activated"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*User / Team:*\n`{{ inputs.user_id }}`"
},
{
"type": "mrkdwn",
"text": "*Circuit State:*\n`{{ outputs.evaluate_circuit_breaker.vars.circuit_state }}`"
},
{
"type": "mrkdwn",
"text": "*Trip Reason:*\n`{{ outputs.evaluate_circuit_breaker.vars.trip_reason }}`"
},
{
"type": "mrkdwn",
"text": "*Active Route:*\n`{{ outputs.evaluate_circuit_breaker.vars.active_model }}`"
},
{
"type": "mrkdwn",
"text": "*Spend / Budget:*\n`${{ outputs.evaluate_circuit_breaker.vars.current_spend }} / ${{ outputs.evaluate_circuit_breaker.vars.max_budget }} ({{ outputs.evaluate_circuit_breaker.vars.budget_utilization_pct }}%)`"
},
{
"type": "mrkdwn",
"text": "*Primary Model:*\n`{{ inputs.primary_model }}`"
}
]
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Routing state recorded in Kestra KV store under `litellm_active_model`. Downstream consumers redirected to fallback model endpoint."
}
]
}
]
}
- id: check_hard_cap_exhaustion
type: io.kestra.plugin.core.flow.If
description: Evaluates whether to halt execution with failure if
fail_on_exhaustion is enabled and hard cap is breached.
condition: "{{ inputs.fail_on_exhaustion and
outputs.evaluate_circuit_breaker.vars.trip_reason ==
'BUDGET_HARD_CAP_EXHAUSTED' }}"
then:
- id: fail_on_hard_cap
type: io.kestra.plugin.core.execution.Fail
description: Halts workflow with failure to enforce budget cap protection.
else:
- id: log_nominal_state
type: io.kestra.plugin.core.log.Log
description: Records healthy operating metrics when primary model deployment is
operational within quota boundaries.
message: "[NOMINAL] LiteLLM Proxy circuit breaker is CLOSED. Active route: {{
outputs.evaluate_circuit_breaker.vars.active_model }} (Spend: ${{
outputs.evaluate_circuit_breaker.vars.current_spend }} / ${{
outputs.evaluate_circuit_breaker.vars.max_budget }}, Utilization: {{
outputs.evaluate_circuit_breaker.vars.budget_utilization_pct }}%, Rate
Limited: {{ outputs.evaluate_circuit_breaker.vars.rate_limit_detected
}})."
- id: export_circuit_breaker_telemetry
type: io.kestra.plugin.core.output.OutputValues
description: Exposes telemetry variables to execution outputs for downstream
workflows or dashboard consumption.
values:
circuit_state: "{{ outputs.evaluate_circuit_breaker.vars.circuit_state }}"
active_model: "{{ outputs.evaluate_circuit_breaker.vars.active_model }}"
trip_reason: "{{ outputs.evaluate_circuit_breaker.vars.trip_reason }}"
current_spend_usd: "{{ outputs.evaluate_circuit_breaker.vars.current_spend }}"
max_budget_usd: "{{ outputs.evaluate_circuit_breaker.vars.max_budget }}"
budget_utilization_pct: "{{ outputs.evaluate_circuit_breaker.vars.budget_utilization_pct }}"
rate_limit_detected: "{{ outputs.evaluate_circuit_breaker.vars.rate_limit_detected }}"
evaluated_at: "{{ outputs.evaluate_circuit_breaker.vars.evaluated_at }}"
errors:
- id: notify_circuit_breaker_error
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Dispatches high-priority Slack notification if the LiteLLM circuit
breaker audit workflow encounters an unhandled execution error.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"channel": "{{ inputs.slack_channel }}",
"username": "LiteLLM-Circuit-Breaker-Error",
"text": "🚨 *CRITICAL: LiteLLM Circuit Breaker Audit Failed!*\n• *Execution:* `{{ execution.id }}`\n• *Flow:* `{{ flow.id }}`\n• *Namespace:* `{{ flow.namespace }}`\n• *LiteLLM Host:* `{{ inputs.litellm_proxy_url }}`\n• *Action Required:* Check Kestra logs for connection or parsing failures."
}