Files
Portainer/litellm

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 litellm y a la red externa proxy.
  • 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 5432 en el host ni se une a proxy.
  • 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-ui protege la interfaz mediante Authentik y CrowdSec.
  • El router litellm-api expone /v1, /health, /key y /model sin Authentik y aplica el middleware de CrowdSec.
  • La API depende de la validación de LITELLM_MASTER_KEY realizada 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-sol
  • gpt-5.5
  • gpt-5.3-codex
  • gpt-5.4-mini
  • gpt-5.2

DeepSeek mediante su API:

  • thsllm-chat
  • thsllm-reasoner
  • thsllm-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-flash
  • thsllm-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 0600 en 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

  1. Un cliente llama a LiteLLM con LITELLM_MASTER_KEY y un alias.
  2. LiteLLM valida la clave y selecciona custom_llm_provider: codex.
  3. El proveedor lee CODEX_AUTH_FILE, configurado como /root/.codex/auth.json.
  4. Decodifica localmente el JWT para conocer su expiración.
  5. Si el access token vence en menos de 60 segundos, usa el refresh token contra https://auth.openai.com/oauth/token.
  6. Guarda los tokens renovados en el mismo archivo montado.
  7. Envía la petición a https://chatgpt.com/backend-api/codex/responses con el access token y el identificador de cuenta.
  8. 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 system y developer, unidos como instructions.
  • Nombre del modelo.
  • reasoning_effort, con valor predeterminado medium.
  • 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:

  1. Serializar la renovación mediante un lock.
  2. Escribir primero un archivo temporal con permisos restrictivos.
  3. Sincronizarlo y reemplazar auth.json atómicamente.
  4. Releer el archivo después de adquirir el lock por si otra petición ya lo renovó.
  5. 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:

  1. codex login status en el host.
  2. Que /home/felidae/.codex/auth.json exista y sea un archivo, no un directorio creado accidentalmente por Docker.
  3. Que tenga permisos 0600 y sea legible/escribible por el proceso esperado.
  4. Que el montaje aparezca en docker inspect litellm.
  5. Que el contenedor vea CODEX_AUTH_FILE=/root/.codex/auth.json.
  6. 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:

  1. Tratar LITELLM_MASTER_KEY como una credencial con acceso indirecto a todos los proveedores y rotarla si se comparte fuera de los consumidores autorizados.
  2. Restringir en Traefik /key y /model si no necesitan exposición pública.
  3. Cambiar los permisos de .env a 0600.
  4. Añadir bloqueo y escritura atómica a la renovación de auth.json.
  5. Evaluar una API key dedicada de OpenAI para automatización o uso multiusuario.

Prioridad operativa:

  1. Añadir healthchecks a LiteLLM y PostgreSQL.
  2. Documentar o eliminar de PostgreSQL los modelos que no estén en config.yaml.
  3. Probar explícitamente streaming, llamadas no streaming y errores de renovación tras cada actualización de la imagen.
  4. Revisar periódicamente que únicamente LiteLLM monte auth.json.

Backups y recuperación

  • Respaldar LITELLM_DB_DATA_PATH para 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.json desde varios hosts: la rotación del refresh token puede invalidar copias anteriores.
  • Después de una restauración, validar PostgreSQL, /v1/models y 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/liveliness respondía HTTP 200.
  • /v1/models respondía HTTP 200 y publicaba diez modelos.
  • PostgreSQL aceptaba conexiones.
  • Codex CLI indicaba una sesión de ChatGPT activa.
  • La renovación de auth.json había ocurrido correctamente.
  • auth.json tenía permisos 0600.
  • .env tenía permisos 0664, 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.