# 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 ```text 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: ```text ./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: ```bash 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: ```text http://litellm:4000/v1 ``` El dominio público es: ```text 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: ```text /home/felidae/.codex/auth.json ``` Codex CLI informa del método activo con: ```bash 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](https://learn.chatgpt.com/docs/auth). ### 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: ```text 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: ```text Paperless -> Paperless AI -> LiteLLM -> proveedor Codex -> Codex ``` Open WebUI y n8n siguen el mismo patrón desde la red `proxy`. ## Despliegue Desde esta carpeta: ```bash docker compose --env-file .env config docker compose --env-file .env up -d docker compose --env-file .env ps ``` No usar: ```bash 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: ```bash docker compose --env-file .env up -d --force-recreate litellm ``` ## Verificación segura Validar la configuración sin mostrar secretos: ```bash docker compose --env-file .env config --quiet ``` Comprobar contenedores: ```bash 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: ```bash 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: ```bash 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: ```bash 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.