Desarrollo IATutorial

LiteLLM como Gateway de LLMs: Presupuestos, Fallbacks y un Setup Seguro

14 de octubre de 2026
12 min de lectura
Gateway LiteLLM con virtual keys, presupuestos y fallbacks
Compartir:

Todo equipo que le habla a más de un proveedor de LLMs termina en el mismo quilombo: API keys pegadas en cinco repos, una cuenta compartida donde nadie sabe quién se gastó el presupuesto, y una caída de un proveedor que se lleva puesto tu producto. Un gateway pone una sola puerta delante de todo eso.

En esta guía te muestro cómo hostear tu propio proxy LiteLLM: un endpoint compatible con OpenAI, virtual keys por equipo, presupuestos y rate limits, fallbacks entre proveedores y spend logs, más cómo correrlo de forma segura después del incidente de PyPI de marzo de 2026. Cada key y endpoint sale de la documentación oficial (LiteLLM 1.105.1, 14 de octubre de 2026); los chequeé contra los docs, no les hice pruebas de carga.

1. El Problema: Keys de Proveedores por Todos Lados, Visibilidad por Ninguno

Llamar a los proveedores directo parece simple hasta la tercera integración. Cada servicio guarda su propia copia de las keys, cada SDK tiene su formato de errores y su lógica de reintentos, y finanzas recibe una factura por proveedor sin desglose por feature ni por equipo. Cuando un proveedor tiene una mala hora, cada servicio lo maneja distinto, casi siempre fallando.

El Error Más Común

Darle la master key a las apps. La master key es la credencial de admin del proxy: quien la tiene puede generar keys, leer el gasto y cambiar presupuestos. Las aplicaciones solo deberían ver virtual keys acotadas, una por workload, cada una con su lista de modelos, presupuesto y rate limit. Si hay una sola key sk- en cada .env, armaste un secreto compartido, no un gateway.

Un gateway lo resuelve en la capa de red. Los clientes le hablan la API de OpenAI a una sola base URL; el proxy mapea nombres de modelos a proveedores, impone quién puede gastar qué, reintenta y hace fallback cuando un proveedor falla, y escribe cada request con su costo en una base de datos. LiteLLM es la opción open source a la que recurren la mayoría de los equipos, y el resto de la guía la arma paso a paso.

2. Conceptos Mínimos Antes de Tocar Código

Seis ideas cubren el 90% de lo que necesitás. Con esto claro, la config de abajo se lee como castellano simple.

🔀

model_name vs litellm_params

tu alias, su modelo

model_name es el alias que piden los clientes; litellm_params.model es el upstream, como anthropic/claude-haiku-5-5. Cambiás el proveedor detrás de un alias sin tocar un solo cliente.

🔑

Virtual keys

una key por workload

Keys que emite el proxy (sk-...) con /key/generate. Cada una lleva sus modelos permitidos, presupuesto, rate limits y vencimiento. Necesitan una base Postgres; la master key las genera.

👥

Teams y users

de quién es el gasto

Un team agrupa keys bajo un presupuesto. El gasto se registra por key, user y team, así que ‘quién se gastó el presupuesto’ por fin tiene respuesta.

💸

Presupuestos y rate limits

topes en USD, TPM y RPM

max_budget más budget_duration fija un tope en USD que se resetea en un ciclo; tpm_limit, rpm_limit y max_parallel_requests frenan el ritmo. Se setean en una key, un user o un team.

🛟

Reintentos vs fallbacks

mismo modelo, después otro

num_retries reintenta el mismo grupo de modelos; fallbacks pasa a otro grupo cuando se acaban los reintentos. Los cooldowns sacan de rotación por un rato a un deployment que falla.

🧾

Spend logs

el registro de auditoría

Cada request escribe una fila en la tabla LiteLLM_SpendLogs: key hasheada, user, team, tags, tokens y costo. Esa tabla es la que consultás, exportás y podás.

El modelo mental a conservar

Los clientes conocen una URL y una key. El proxy conoce proveedores, precios y políticas. Mantené ese límite limpio (sin keys de proveedor en las apps, sin políticas en el código de la app) y cambiar un modelo pasa a ser un cambio de config, no un deploy.

3. Paso 1 — Correr el Proxy: Imagen Pineada + config.yaml

Arrancá por la config. Los alias son el contrato con tus clientes, así que nombralos por rol (fast, smart), no por proveedor. Las keys de los proveedores se leen del entorno con os.environ/..., nunca se escriben en el archivo. Las virtual keys, los presupuestos y los spend logs necesitan una base de datos, así que el proxy también necesita DATABASE_URL.

litellm_config.yaml — alias, master key, higiene de logs

# litellm_config.yaml
model_list:
  - model_name: fast            # alias clients request, named by role
    litellm_params:
      model: anthropic/claude-haiku-5-5
      api_key: os.environ/ANTHROPIC_API_KEY
  - model_name: fast-backup     # same role, different provider
    litellm_params:
      model: openai/gpt-5-mini  # swap for the IDs your providers list today
      api_key: os.environ/OPENAI_API_KEY
  - model_name: smart
    litellm_params:
      model: anthropic/claude-sonnet-5
      api_key: os.environ/ANTHROPIC_API_KEY

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
  disable_error_logs: true
  maximum_spend_logs_retention_period: "30d"
  maximum_spend_logs_retention_interval: "1d"

Ahora corrélo. Pineá un tag exacto de la imagen en vez de latest (1.105.1 es la última estable en la página de releases mientras escribo; hay release candidates de 1.106, así que chequeá cuando leas esto) y bindeá el puerto a localhost detrás de tu propio reverse proxy con TLS. Generá la master key y la salt key una sola vez y guardalas en un secret manager.

Correrlo — master key, salt key, imagen pineada

export LITELLM_MASTER_KEY="sk-$(openssl rand -hex 32)"
export LITELLM_SALT_KEY="sk-$(openssl rand -hex 32)"  # keep it; never change after adding models
export DATABASE_URL="postgresql://litellm:<password>@<db-host>:5432/litellm"

docker run -d --name litellm \
  -p 127.0.0.1:4000:4000 \
  -v "$(pwd)/litellm_config.yaml:/app/config.yaml:ro" \
  -e LITELLM_MASTER_KEY -e LITELLM_SALT_KEY -e DATABASE_URL \
  -e LITELLM_MODE=PRODUCTION \
  -e ANTHROPIC_API_KEY -e OPENAI_API_KEY \
  ghcr.io/berriai/litellm:v1.105.1 \
  --config /app/config.yaml

Tip: sin base de datos, sin presupuestos

Sin base de datos el proxy igual sirve la API de OpenAI, pero las virtual keys, los presupuestos por team y el tracking de gasto no funcionan. Los docs advierten que litellm_settings.max_budget no se aplica sin DB: los requests siguen pasando el tope y la única señal es un warning al arrancar. Además, LITELLM_SALT_KEY cifra las credenciales que guardás, así que si la regenerás quedan ilegibles.

4. Paso 2 — Teams, Virtual Keys, Presupuestos y Rate Limits

Con el proxy arriba, usá la master key para crear la estructura y después nunca más desde una app. Un team lleva el presupuesto compartido; las keys debajo llevan límites más ajustados por workload. Cada campo de abajo está documentado en las páginas de virtual keys y de presupuestos.

curl — un team y una key acotada

# 1) team: shared budget + defaults (the response includes a team_id)
curl -s http://localhost:4000/team/new \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "team_alias": "support-bot",
    "max_budget": 50,
    "budget_duration": "30d",
    "rpm_limit": 60,
    "models": ["fast", "fast-backup"]
  }'

# 2) key for ONE workload inside that team (the response includes the sk-... key)
curl -s http://localhost:4000/key/generate \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "<team_id from step 1>",
    "models": ["fast", "fast-backup"],
    "max_budget": 10,
    "budget_duration": "7d",
    "rpm_limit": 30,
    "tpm_limit": 100000,
    "duration": "30d",
    "metadata": {"owner": "support-team"}
  }'

Qué te da esto

La key solo puede llamar a fast y fast-backup, gastar como máximo 10 USD por ventana de 7 días, está limitada a 30 requests y 100k tokens por minuto y vence en 30 días. El team topea todo el workload en 50 USD cada 30 días. Dejá la key más ajustada que el team para que una key ruidosa no te vacíe el presupuesto de todos. Sin budget_duration un presupuesto nunca se resetea.

Después los clientes usan el SDK de OpenAI tal cual. Apuntá base_url al gateway, pasá la virtual key como api_key y etiquetá los requests con metadata.tags para poder cortar los spend logs por feature.

Python — SDK de OpenAI contra el gateway, con tags de gasto

from openai import OpenAI

client = OpenAI(
    api_key="sk-<virtual-key>",          # the scoped key, never the master key
    base_url="http://localhost:4000",    # the gateway
)

resp = client.chat.completions.create(
    model="fast",                        # alias from model_list
    messages=[{"role": "user", "content": "Summarise this ticket in one line."}],
    extra_body={"metadata": {"tags": ["feature:ticket-summary"]}},
)
print(resp.choices[0].message.content)

5. Paso 3 — Fallbacks entre Proveedores y Spend Logs

La confiabilidad vive en router_settings. num_retries reintenta el mismo grupo de modelos; si sigue fallando, fallbacks manda el request al próximo grupo, acá otro proveedor. Tras allowed_fails fallos, un deployment entra en cooldown por cooldown_time segundos, así dejás de martillar un endpoint caído. Los números son placeholders: dimensionalos con tu propio tráfico.

router_settings — reintentos, cooldown, fallback entre proveedores

router_settings:
  routing_strategy: simple-shuffle   # default, recommended for production
  num_retries: 2                     # per model group, before falling back
  allowed_fails: 3                   # failures per minute before cooldown
  cooldown_time: 30                  # seconds out of rotation
  fallbacks: [{"fast": ["fast-backup"]}]

Dos keys relacionadas: context_window_fallbacks (necesita enable_pre_call_checks: true) reenruta un prompt que no entra en el contexto del modelo, y content_policy_fallbacks cubre los rechazos de contenido del proveedor. También podés mandar fallbacks por request en el body, o bloquearlos para una key con metadata.disable_fallbacks. Y después leés la plata de vuelta:

curl — leer el gasto de vuelta

# per-request rows, paginated (page_size max 1000)
curl -s "http://localhost:4000/spend/logs/v2?start_date=2026-10-07%2000:00:00&end_date=2026-10-14%2023:59:59&page=1&page_size=100" \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY"

# spend grouped by team
curl -s "http://localhost:4000/global/spend/report?start_date=2026-10-01&end_date=2026-10-14&group_by=team" \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY"

# one key: spend so far and its limits
curl -s "http://localhost:4000/key/info?key=sk-<virtual-key>" \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY"

Los fallbacks respetan permisos y presupuestos

Por defecto, según los docs, los fallbacks chequean el acceso a modelos y el presupuesto de quien llama, así que una key limitada a fast puede necesitar fast-backup en su lista. Probalo en staging. Dos trampas más: mock_testing_fallbacks se descarta en los requests al proxy desde la v1.85.0, así que probá el failover con un error real de proveedor en un entorno no productivo; y /spend/logs trunca en 10.000 filas, así que usá /spend/logs/v2 con page y page_size para exportar.

6. Correrlo Seguro: Lo Que Enseña el Incidente de Marzo de 2026

El 24 de marzo de 2026 se publicaron en PyPI litellm 1.82.7 y 1.82.8 con un ladrón de credenciales adentro. El security update del propio LiteLLM, NetSPI, Snyk y el hilo de GitHub coinciden en lo central: dos releases maliciosos, un payload que juntaba variables de entorno, SSH keys y credenciales cloud y de Kubernetes y las mandaba a models.litellm.cloud (un dominio no oficial), y un archivo litellm_init.pth en 1.82.8 que se ejecuta en cada arranque de Python, sin importar litellm.

LiteLLM informa que la imagen Docker oficial del proxy y la 1.82.6 o anteriores no fueron afectadas, que el compromiso arrancó por una dependencia de Trivy en su pipeline de CI, y que el 30 de marzo salió un 1.83.0 limpio con un pipeline reconstruido. Las fuentes difieren en cuánto tiempo estuvieron publicados los paquetes (unos 40 minutos según LiteLLM, cerca de tres horas según Snyk), así que no te apoyes en la ventana: si alguna de las dos versiones pasó por una máquina, asumí exposición.

Si alguna vez instalaste 1.82.7 o 1.82.8

Tratá la máquina como comprometida aunque no haya corrido código de litellm: el .pth se ejecuta al iniciar el intérprete, y Snyk aclara que eso incluye el propio pip install. Buscá litellm_init.pth en site-packages, rotá cada secreto que el host podía leer (keys de proveedores, credenciales cloud, SSH keys, contraseñas de bases, tokens de CI, service accounts de Kubernetes) y vigilá el egress hacia models.litellm.cloud y checkmarx.zone. Volver a pinear 1.82.6 no deshace una exposición.

Verificá antes de correr

# A) Container: verify the signature (key pinned to an immutable commit)
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.105.1

# B) pip: exact pin + hashes for the whole dependency tree
#    requirements.in contains: litellm[proxy]==1.105.1
pip-compile --generate-hashes requirements.in -o requirements.txt
pip install --require-hashes -r requirements.txt

# C) Audit: was a bad build ever installed here?
pip show litellm | head -n 2
find / -name litellm_init.pth 2>/dev/null

🧱 Antes de que corra (supply chain)

  1. 1. Pineá una versión exacta o un digest de imagen, nunca latest, y actualizá a propósito después de leer las release notes
  2. 2. Verificá la firma del contenedor con cosign contra el commit pineado de la key antes de cada deploy
  3. 3. En pip, instalá desde un lockfile con hashes y --require-hashes, así un artefacto cambiado rompe la instalación

🔒 Mientras corre (radio de explosión)

  1. Allowlist de egress: el proxy necesita las APIs de tus proveedores y Postgres, nada más; denegá el resto para que un secreto robado no tenga adónde ir
  2. Mínimos secretos: las keys de proveedores y la master key salen de un secret manager, y el paso de build no ve ninguna
  3. Keys acotadas: las apps reciben virtual keys, nunca la master key; una key por workload para revocar una sin hacer simulacro de incendio

🚪 Qué exponés (superficie de ataque)

  1. Admin UI: seteá DISABLE_ADMIN_UI=True, o creá un usuario proxy_admin, sumá SSO y activá disable_env_credential_login para que la master key deje de servir como contraseña de la UI
  2. Docs de la API: poné NO_DOCS, NO_REDOC y NO_OPENAPI en True en producción
  3. Red: mantené el gateway privado detrás de TLS y seteá trusted_proxy_ranges con los CIDR de tu ingress

7. Errores Comunes (Todos Arreglables en Minutos)

Casi cada incidente de gateway que leí cae en uno de estos baldes. La columna izquierda es lo que hacen los deployments sensatos; la derecha es lo que te despierta de noche.

✅ Hacé esto

  • • Pineá la versión del proxy y verificá la firma de la imagen; actualizá a propósito
  • • Una virtual key por workload, con models, max_budget, budget_duration y rpm_limit seteados
  • • Nombrá los alias por rol y guardá los model IDs upstream en un solo archivo de config
  • • Sumá un fallback en otro proveedor y probalo con una falla real en staging
  • • Etiquetá requests con metadata.tags y podá los spend logs con un período de retención
  • • Corré en Python 3.10 o más nuevo, que es lo que piden los releases actuales

❌ No esto

  • • Darle la master key a apps, jobs de CI o compañeros
  • • Correr sin base de datos y confiar en max_budget
  • • Usar el campo user como identidad: los usuarios finales pueden setearlo y mover el gasto; seteá user_id en cada key
  • • Rangos de versión sueltos o imágenes :latest en producción
  • • Exponer el puerto 4000 y la Admin UI a internet
  • • Instalar en un Python viejo y creer que tenés la última: pip baja en silencio al último release compatible

Regla de oro

Un gateway concentra todos los secretos y todos los requests que tenés, lo que lo vuelve una de las cosas más valiosas de tu red. Corrélo como tal: pineado, verificado, privado, con mínimo privilegio y vigilado con sus propios spend logs.

Conclusión

Ya tenés el loop completo: un proxy LiteLLM pineado con alias por rol, un team y virtual keys acotadas con presupuestos y rate limits, fallbacks entre proveedores con cooldowns, gasto que podés consultar por key, team y tag, y una checklist para correrlo después del incidente de marzo.

Hacé una sola cosa esta semana: poné un servicio interno detrás del gateway con su propia key, un presupuesto chico y un fallback, y leé sus spend logs después de unos días. SSO, Redis y Kubernetes pueden venir después. No le hice pruebas de carga a este setup, así que dimensioná workers, pool de conexiones y Redis con la guía de producción antes de recibir tráfico real.

Machete del Tutorial

Proxy

  • • versión pineada + cosign verify
  • • alias en config.yaml por rol
  • • Postgres + master + salt key

Políticas

  • • jerarquía team → key
  • • max_budget + budget_duration
  • • límites rpm/tpm + fallbacks

Seguridad

  • • instalaciones con hashes
  • • allowlist de egress
  • • Admin UI + docs de API apagados

Fuentes

Diego Rodriguez

Diego Rodriguez

Ingeniero Senior Full-Stack & AI

Diego tiene mas de 9 anos de experiencia construyendo aplicaciones potenciadas por IA de produccion, desde orquestacion de LLMs y pipelines RAG hasta deteccion de riesgos con ML y sistemas de trading algoritmico.

Conoce mas sobre Diego →