LiteLLM
LiteLLM es la pasarela central de modelos del repositorio. Expone una API compatible con OpenAI a consumidores como Open WebUI, n8n y Paperless AI y centraliza las credenciales de los proveedores.
Este servicio se administra directamente con Docker Compose. No depende de
Portainer y no debe desplegarse usando stack.env.
Arquitectura
Open WebUI / n8n / Paperless AI / clientes externos
|
| API OpenAI-compatible
| LITELLM_MASTER_KEY
v
LiteLLM :4000
| |
| +----> DeepSeek API
|
+----> codex_provider.py
| |
| +----> Codex backend API
| +----> auth.json de Codex
|
+----> PostgreSQL 16
Los consumidores nunca reciben una credencial de DeepSeek ni los tokens de Codex. Sólo conocen la URL de LiteLLM, una clave de LiteLLM y un alias de modelo.
Contenido de la carpeta
| Archivo | Función |
|---|---|
docker-compose.yml |
Define LiteLLM, PostgreSQL, redes y montajes. |
.env |
Configuración real local. Contiene secretos y no está versionado. |
stack.env |
Plantilla histórica versionada. No usar para desplegar. |
config.yaml |
Catálogo declarativo de modelos y registro del proveedor Codex. |
codex_provider.py |
Adaptador entre LiteLLM y la API Responses usada por Codex. |
README.md |
Arquitectura, operación, seguridad y diagnóstico. |
Servicios y persistencia
litellm
- Imagen fijada mediante digest de
ghcr.io/berriai/litellm. - Arranca con
--config /app/config.yaml. - Escucha en el puerto interno
4000; no publica un puerto directamente en el host. - Está unido a la red interna
litellmy a la red externaproxy. - Guarda configuración y estado en PostgreSQL porque
STORE_MODEL_IN_DB=True. - Reinicia con la política
unless-stopped.
Montajes:
./config.yaml -> /app/config.yaml (solo lectura)
./codex_provider.py -> /app/codex_provider.py (solo lectura)
/home/felidae/.codex/auth.json -> /root/.codex/auth.json (lectura/escritura)
El montaje de auth.json es deliberadamente escribible porque el proveedor
renueva el token y persiste la sesión actualizada. Esto también significa que
el contenedor tiene acceso al refresh token de la cuenta de ChatGPT.
litellm-db
- Usa PostgreSQL 16.
- Sólo pertenece a la red interna
litellm. - No publica
5432en el host ni se une aproxy. - Persiste sus datos desde
LITELLM_DB_DATA_PATH. - Se resuelve dentro de la red como
litellm-db.
depends_on establece el orden de arranque, pero no espera a que PostgreSQL
esté listo. Actualmente ninguno de los dos servicios tiene un healthcheck
de Docker.
Variables de entorno
El .env local debe definir:
| Variable | Función |
|---|---|
LITELLM_MASTER_KEY |
Autentica a los clientes de la API de LiteLLM. |
LITELLM_SALT_KEY |
Cifra datos sensibles guardados por LiteLLM; debe permanecer estable. |
DEEPSEEK_API_KEY |
Credencial usada por los alias thsllm-*. |
POSTGRES_USER |
Usuario de PostgreSQL. |
POSTGRES_PASSWORD |
Contraseña de PostgreSQL. |
POSTGRES_DB |
Base de datos de LiteLLM. |
LITELLM_DB_DATA_PATH |
Ruta persistente del host para PostgreSQL. |
LITELLM_DOMAIN |
Dominio público documentado para el servicio. |
No imprimas el contenido de .env durante verificaciones. Comprueba sólo que
las variables existen:
awk -F= '/^[A-Za-z_][A-Za-z0-9_]*=/{print $1}' .env
stack.env contiene valores de plantilla. Aunque permanece en el repositorio
por compatibilidad histórica, el despliegue soportado usa exclusivamente
.env mediante --env-file .env.
Redes y exposición con Traefik
Los contenedores conectados a proxy pueden acceder por DNS interno a:
http://litellm:4000/v1
El dominio público es:
https://llm.sherlockhomeless.net
El enrutamiento vive fuera de esta carpeta, en
/opt/traefik/dynamic/litellm.yml:
- El router
litellm-uiprotege la interfaz mediante Authentik y CrowdSec. - El router
litellm-apiexpone/v1,/health,/keyy/modelsin Authentik y aplica el middleware de CrowdSec. - La API depende de la validación de
LITELLM_MASTER_KEYrealizada por LiteLLM. Authentik no sustituye esa clave.
Las rutas administrativas /key y /model forman parte de la superficie
pública. Antes de ampliar el uso del servicio conviene confirmar qué
operaciones permite cada versión de LiteLLM y restringirlas en Traefik si no
son necesarias.
Catálogo de modelos
Declarado en config.yaml
Codex mediante el proveedor personalizado:
gpt-5.6-solgpt-5.5gpt-5.3-codexgpt-5.4-minigpt-5.2
DeepSeek mediante su API:
thsllm-chatthsllm-reasonerthsllm-v4-pro
Los nombres thsllm-* evitan exponer el proveedor real en los consumidores y
permiten cambiar el backend manteniendo un alias estable.
Para los modelos Codex se usa internamente el prefijo cx-. Esto evita que
LiteLLM los clasifique como modelos OpenAI nativos. codex_provider.py vuelve
a convertir cx-* en gpt-* antes de enviar la petición.
Catálogo efectivo
LiteLLM puede conservar modelos en PostgreSQL. Por eso config.yaml no es
necesariamente la única fuente de verdad cuando STORE_MODEL_IN_DB=True.
En la revisión del 16 de agosto de 2026, /v1/models devolvía además:
thsllm-flashthsllm-pro
Esos alias no aparecen en el YAML actual y proceden previsiblemente del estado
persistido en la base de datos. Antes de eliminar o renombrar modelos, revisa
tanto config.yaml como el catálogo efectivo de LiteLLM.
Autenticación de Codex
Estado y almacenamiento
La integración usa una sesión de ChatGPT creada por Codex CLI, no una API key de OpenAI Platform. El archivo del host es:
/home/felidae/.codex/auth.json
Codex CLI informa del método activo con:
codex login status
El archivo contiene material reutilizable, incluidos access token, refresh token y el identificador de cuenta. Debe tratarse como una contraseña:
- No añadirlo al repositorio.
- No copiarlo a imágenes Docker.
- No mostrarlo en logs, incidencias o documentación.
- No incluirlo en backups sin cifrar.
- No montarlo en Open WebUI, n8n, Paperless ni otros consumidores.
- Mantener permisos
0600en el host.
La documentación oficial de OpenAI describe ~/.codex/auth.json como una de
las ubicaciones de caché de credenciales de Codex y advierte que contiene
tokens de acceso. También recomienda autenticación mediante API key para la
automatización general. Referencia:
OpenAI Docs: Authentication.
Flujo de una petición
- Un cliente llama a LiteLLM con
LITELLM_MASTER_KEYy un alias. - LiteLLM valida la clave y selecciona
custom_llm_provider: codex. - El proveedor lee
CODEX_AUTH_FILE, configurado como/root/.codex/auth.json. - Decodifica localmente el JWT para conocer su expiración.
- Si el access token vence en menos de 60 segundos, usa el refresh token
contra
https://auth.openai.com/oauth/token. - Guarda los tokens renovados en el mismo archivo montado.
- Envía la petición a
https://chatgpt.com/backend-api/codex/responsescon el access token y el identificador de cuenta. - Convierte el flujo SSE recibido en una respuesta compatible con LiteLLM.
La renovación observada estaba operativa durante la revisión del 16 de agosto de 2026. No se documentan aquí fechas de expiración, identificadores ni valores de tokens.
Implicaciones
Este diseño convierte indirectamente la sesión de ChatGPT del usuario
felidae en el backend de un proxy compartido. Cualquier consumidor que posea
LITELLM_MASTER_KEY puede utilizar esa cuenta y sus límites a través de los
alias Codex.
Además, el endpoint chatgpt.com/backend-api/codex/responses, el client_id
embebido y las cabeceras de compatibilidad son detalles internos y pueden
cambiar sin mantener compatibilidad con este proveedor. Esta ruta no debe
confundirse con una integración basada en la API pública de OpenAI Platform.
Para un servicio multiusuario o una automatización crítica, la opción más estable es evaluar la API pública de OpenAI con una API key dedicada y límites propios, manteniendo LiteLLM como pasarela.
Comportamiento y límites de codex_provider.py
El proveedor implementa llamadas síncronas, asíncronas y streaming. El backend se consume siempre mediante streaming; en llamadas no streaming se agregan los fragmentos antes de devolver la respuesta.
La traducción actual sólo conserva principalmente:
- Mensajes de usuario y asistente.
- Mensajes
systemydeveloper, unidos comoinstructions. - Nombre del modelo.
reasoning_effort, con valor predeterminadomedium.- Texto generado y contadores básicos de tokens.
No es una implementación completa de Responses ni de Chat Completions. Entre otros, no reenvía explícitamente herramientas/function calling, límites de tokens, temperatura, formatos estructurados ni la mayoría de parámetros opcionales. El parser SSE sólo procesa deltas de texto y el evento final de uso. Un cliente puede ser compatible con la API de LiteLLM y aun así perder funciones al usar un alias Codex.
Riesgo de concurrencia en la renovación
La renovación hace una lectura y una escritura directa de auth.json, sin
bloqueo entre peticiones y sin reemplazo atómico. Dos solicitudes simultáneas
cerca de la expiración podrían intentar rotar el mismo refresh token. Una
interrupción mientras se escribe también podría dejar el JSON incompleto.
Si el servicio va a recibir concurrencia significativa, el proveedor debería:
- Serializar la renovación mediante un lock.
- Escribir primero un archivo temporal con permisos restrictivos.
- Sincronizarlo y reemplazar
auth.jsonatómicamente. - Releer el archivo después de adquirir el lock por si otra petición ya lo renovó.
- Evitar que errores y cuerpos HTTP incluyan tokens en logs.
Consumidores
Un cliente compatible con OpenAI necesita:
Base URL: http://litellm:4000/v1
API key: valor de LITELLM_MASTER_KEY
Model: uno de los alias publicados
Usa la URL interna cuando ambos contenedores compartan proxy. Para clientes
externos usa HTTPS. Nunca configures el token de Codex como API key de un
consumidor.
Flujo de Paperless AI:
Paperless -> Paperless AI -> LiteLLM -> proveedor Codex -> Codex
Open WebUI y n8n siguen el mismo patrón desde la red proxy.
Despliegue
Desde esta carpeta:
docker compose --env-file .env config
docker compose --env-file .env up -d
docker compose --env-file .env ps
No usar:
docker compose --env-file stack.env up -d
Tras modificar config.yaml o codex_provider.py, recrea LiteLLM para evitar
depender del estado importado por el proceso anterior:
docker compose --env-file .env up -d --force-recreate litellm
Verificación segura
Validar la configuración sin mostrar secretos:
docker compose --env-file .env config --quiet
Comprobar contenedores:
docker compose --env-file .env ps --all
docker compose --env-file .env logs --tail 100 litellm
docker compose --env-file .env exec litellm-db pg_isready
Comprobar autenticación de Codex en el host:
codex login status
stat -c '%a %U:%G %n' /home/felidae/.codex/auth.json
Consultar el catálogo desde dentro del contenedor sin imprimir la clave:
docker compose --env-file .env exec litellm python -c '
import json, os, urllib.request
request = urllib.request.Request(
"http://127.0.0.1:4000/v1/models",
headers={"Authorization": "Bearer " + os.environ["LITELLM_MASTER_KEY"]},
)
with urllib.request.urlopen(request, timeout=15) as response:
print([item["id"] for item in json.load(response)["data"]])
'
Consultar desde un cliente externo:
curl -sS https://llm.sherlockhomeless.net/v1/models \
-H "Authorization: Bearer $LITELLM_MASTER_KEY"
No uses set -x al trabajar con las claves.
Diagnóstico
401 antes de seleccionar un modelo
Normalmente indica que el consumidor no envió la LITELLM_MASTER_KEY
correcta. Revisa la clave del cliente y que no contenga espacios o saltos de
línea.
401 o error de autenticación sólo en modelos Codex
Comprueba:
codex login statusen el host.- Que
/home/felidae/.codex/auth.jsonexista y sea un archivo, no un directorio creado accidentalmente por Docker. - Que tenga permisos
0600y sea legible/escribible por el proceso esperado. - Que el montaje aparezca en
docker inspect litellm. - Que el contenedor vea
CODEX_AUTH_FILE=/root/.codex/auth.json. - Los logs del contenedor, sin copiar tokens a una incidencia.
Si el refresh token fue revocado, ejecuta codex login de nuevo en el host y
recrea el contenedor si fuera necesario.
Los modelos del YAML no coinciden con /v1/models
Revisa el estado persistido en PostgreSQL. Con STORE_MODEL_IN_DB=True, los
modelos guardados en la base de datos pueden ampliar o modificar el catálogo
efectivo.
PostgreSQL arranca después que LiteLLM
depends_on no verifica disponibilidad. Revisa pg_isready y los logs. Si el
problema se repite, añade un healthcheck a PostgreSQL y una dependencia con
condición de servicio saludable.
Un cliente acepta texto pero fallan herramientas
El proveedor Codex personalizado no reenvía actualmente todas las propiedades
de Chat Completions o Responses. Verifica el cuerpo construido en
_build_body() antes de atribuir el fallo a LiteLLM o al modelo.
Seguridad y endurecimiento recomendado
Prioridad alta:
- Tratar
LITELLM_MASTER_KEYcomo una credencial con acceso indirecto a todos los proveedores y rotarla si se comparte fuera de los consumidores autorizados. - Restringir en Traefik
/keyy/modelsi no necesitan exposición pública. - Cambiar los permisos de
.enva0600. - Añadir bloqueo y escritura atómica a la renovación de
auth.json. - Evaluar una API key dedicada de OpenAI para automatización o uso multiusuario.
Prioridad operativa:
- Añadir healthchecks a LiteLLM y PostgreSQL.
- Documentar o eliminar de PostgreSQL los modelos que no estén en
config.yaml. - Probar explícitamente streaming, llamadas no streaming y errores de renovación tras cada actualización de la imagen.
- Revisar periódicamente que únicamente LiteLLM monte
auth.json.
Backups y recuperación
- Respaldar
LITELLM_DB_DATA_PATHpara conservar configuración y estado. - Mantener estable
LITELLM_SALT_KEY; perderla puede impedir descifrar datos persistidos. - Si se respalda
auth.json, hacerlo cifrado y con acceso muy restringido. - No restaurar simultáneamente copias distintas de
auth.jsondesde varios hosts: la rotación del refresh token puede invalidar copias anteriores. - Después de una restauración, validar PostgreSQL,
/v1/modelsy un alias de cada proveedor por separado.
Estado observado el 16 de agosto de 2026
Esta sección es una fotografía, no una garantía permanente:
- Compose válido usando
.env. - LiteLLM y PostgreSQL activos desde hacía 13 días.
- LiteLLM versión
1.82.6. /health/livelinessrespondía HTTP 200./v1/modelsrespondía HTTP 200 y publicaba diez modelos.- PostgreSQL aceptaba conexiones.
- Codex CLI indicaba una sesión de ChatGPT activa.
- La renovación de
auth.jsonhabía ocurrido correctamente. auth.jsontenía permisos0600..envtenía permisos0664, pendiente de endurecimiento.- No había healthchecks Docker configurados.
Repite las verificaciones de este documento antes de usar esta fotografía para una intervención futura.