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
|
+----> claude_provider.py
| |
| +----> api.anthropic.com/v1/messages
| +----> .credentials.json de Claude Code
|
+----> PostgreSQL 16
Los consumidores nunca reciben una credencial de DeepSeek ni los tokens de Codex o Claude. 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 de proveedores personalizados. |
codex_provider.py |
Adaptador entre LiteLLM y la API Responses usada por Codex. |
claude_provider.py |
Renovación OAuth de Claude y delegación al proveedor Anthropic nativo. |
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)
./claude_provider.py -> /app/claude_provider.py (solo lectura)
/home/felidae/.codex/auth.json -> /root/.codex/auth.json (lectura/escritura)
/home/felidae/.claude/.credentials.json
-> /root/.claude/.credentials.json (lectura/escritura)
Los dos archivos de autenticación son deliberadamente escribibles porque los proveedores renuevan tokens y persisten las sesiones actualizadas. Esto también significa que el contenedor tiene acceso a los refresh tokens de ChatGPT y Claude.ai.
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
Claude mediante la sesión OAuth de Claude Code:
claude-sonnet-5
Aunque la cuenta OAuth puede enumerar otros modelos Anthropic, sólo Sonnet 5 se publica deliberadamente a los consumidores de LiteLLM.
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.
Los modelos Claude usan internamente claudeoauth/clx-*. El proveedor
personalizado renueva la sesión, convierte clx-* en claude-* y delega la
traducción de mensajes, herramientas y respuestas al proveedor Anthropic
nativo de LiteLLM. Los reintentos están desactivados para estos modelos y así
un 429 de la suscripción no multiplica solicitudes contra la misma cuota.
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.
Autenticación de Claude
Endpoint y almacenamiento
Claude Code 2.1.233 está autenticado contra el proveedor first-party mediante una cuenta Claude.ai Pro. Su caché OAuth está en:
/home/felidae/.claude/.credentials.json
El estado se comprueba sin mostrar tokens mediante:
claude auth status
El objeto claudeAiOauth contiene accessToken, refreshToken, expiresAt,
refreshTokenExpiresAt, scopes y tipo de suscripción. El archivo tiene permisos
0600 y debe tratarse como una contraseña.
La inferencia usa:
POST https://api.anthropic.com/v1/messages
Authorization: Bearer <access token>
anthropic-beta: claude-code-20250219,oauth-2025-04-20
claude_provider.py entrega el token al proveedor Anthropic nativo mediante
ANTHROPIC_AUTH_TOKEN; no debe pasarlo como api_key, porque eso añadiría
x-api-key y Anthropic rechaza mezclar ambos esquemas. El adaptador añade la
identidad mínima de Claude Code (beta, x-app, user-agent y mensaje de
sistema) y delega la traducción de mensajes, herramientas y respuestas a
LiteLLM. Si falta esa identidad, Anthropic puede responder con un
429 rate_limit_error genérico aunque la misma cuenta funcione desde el CLI.
Desde abril de 2026 Anthropic separa la facturación en dos grupos: las apps oficiales (CLI de Claude Code, claude.ai) consumen los límites del plan, mientras que las aplicaciones de terceros consumen un saldo aparte de "extra usage". Cuando ese saldo está vacío, las peticiones clasificadas como third-party se rechazan con:
400 invalid_request_error:
Third-party apps now draw from your extra usage, not your plan limits.
Add more at claude.ai/settings/usage and keep going.
La clasificación la decide la identidad HTTP de la petición, y hay tres detalles que la delataban como third-party:
- El
user-agentdecía(external, sdk-cli). El binario de Claude Code usa(external, cli)cuando corre como CLI;sdk-clies el entrypoint del SDK (CLAUDE_CODE_ENTRYPOINT) y Anthropic lo factura como terceros. - Se enviaba
anthropic-dangerous-direct-browser-access: true, una cabecera que sólo usa la web de claude.ai y que también marca la petición como terceros. - LiteLLM 1.82 reescribe
anthropic-betacontra una lista blanca (anthropic_beta_headers_config.json) y descartabaclaude-code-20250219, que no figura en ella y es la betaclaude_codedel CLI.
claude_provider.py corrige los tres puntos: identifica cli en el
user-agent, omite la cabecera de navegador y registra claude-code-20250219
en la lista blanca al importarse (_patch_beta_headers). Si el error vuelve a
aparecer, revisa que esas cabeceras se conserven en el registro de LiteLLM y
que la cuenta tenga saldo de "extra usage" en claude.ai/settings/usage.
Renovación
Cinco minutos antes de expiresAt, el proveedor envía el refresh token a:
POST https://platform.claude.com/v1/oauth/token
La petición usa el flujo refresh_token y el identificador público del cliente
Claude Code. El refresh token puede rotar; por eso el archivo está montado como
lectura/escritura. La escritura se realiza bajo un lock exclusivo, conserva el
resto del JSON, trunca el contenido anterior y fuerza la sincronización a disco.
El lock evita dos renovaciones concurrentes dentro del contenedor. No coordina
formalmente con una versión de Claude Code que ignore ese mismo advisory lock;
evita ejecutar claude auth login o claude auth logout mientras LiteLLM esté
renovando la sesión.
Estado soportado
Anthropic documenta que Claude Code utiliza api.anthropic.com y documenta
LiteLLM como gateway, pero la configuración soportada del gateway usa API keys,
Amazon Bedrock o Google Vertex AI. Montar la sesión OAuth de una suscripción
Claude.ai dentro de un proxy es una integración experimental y no está
documentada por Anthropic como arquitectura de producción.
Referencias:
- Claude Code: configuración de LLM gateways
- Claude Code: configuración y autenticación
- Claude API: modelos
Para un servicio multiusuario o crítico debe preferirse una
ANTHROPIC_API_KEY dedicada, Bedrock o Vertex AI. Compartir
LITELLM_MASTER_KEY concede acceso indirecto a la cuota de la suscripción Pro.
Compatibilidad
La traducción principal la realiza LiteLLM, por lo que conserva más funciones
Anthropic que el proveedor Codex personalizado. La capa OAuth filtra
temperature, porque los modelos Claude 5 actuales lo rechazan aunque algunos
clientes OpenAI-compatible lo envían por defecto.
El streaming personalizado normaliza texto, razón de finalización y uso. Debe probarse expresamente el streaming con herramientas antes de depender de tool calls complejas, porque esa normalización puede no conservar todos los deltas especializados de Anthropic.
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 conserva principalmente:
- Mensajes de usuario y asistente.
- Mensajes
systemydeveloper, unidos comoinstructions. - Herramientas de función,
tool_choiceyparallel_tool_calls. - Llamadas de herramienta del asistente y sus resultados en turnos posteriores.
- Nombre del modelo.
reasoning_effort, con valor predeterminadomedium.- Texto, llamadas de herramienta y contadores básicos de tokens en la respuesta.
No es una implementación completa de Responses ni de Chat Completions. Entre otros, no reenvía límites de tokens, temperatura, formatos estructurados ni la mayoría de parámetros opcionales. El soporte de herramientas traduce las funciones personalizadas del formato Chat Completions usado por Open WebUI al formato Responses, y convierte los eventos SSE de function calling de vuelta a chunks compatibles con OpenAI. Otros tipos de herramienta alojada de Responses no se traducen explícitamente.
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 un token de Codex o Claude como API key de
un consumidor.
Flujo de Paperless AI:
Paperless -> Paperless AI -> LiteLLM -> proveedor seleccionado -> LLM
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, codex_provider.py o claude_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
Comprobar autenticación de Claude en el host:
claude auth status
stat -c '%a %U:%G %n' /home/felidae/.claude/.credentials.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.
401 o error de autenticación sólo en modelos Claude
Comprueba:
- Que
claude auth statusmuestreloggedIn: true. - Que
/home/felidae/.claude/.credentials.jsonexista y tenga permisos0600. - Que el montaje aparezca en
docker inspect litellm. - Que
CLAUDE_AUTH_FILEsea/root/.claude/.credentials.json. - Que el access token y el refresh token estén presentes, sin imprimirlos.
Si están vacíos o revocados, ejecuta claude auth login en el host. El mismo
archivo es compartido por Claude Code y LiteLLM, por lo que el contenedor verá
la sesión nueva sin copiar secretos al repositorio.
429 en modelos Claude
Primero ejecuta una petición mínima con Claude Code. Si el CLI también devuelve
429, la suscripción puede haber agotado temporalmente su cuota: espera a la
renovación o usa una API key dedicada. Si el CLI funciona pero LiteLLM falla,
revisa que el adaptador use ANTHROPIC_AUTH_TOKEN, no envíe x-api-key y
conserve las cabeceras e identidad de Claude Code descritas arriba. Los
reintentos permanecen desactivados para no multiplicar consumo.
Claude rechaza temperature
claude_provider.py elimina este parámetro antes de delegar en Anthropic. Si
otro parámetro queda obsoleto en modelos futuros, añádelo al filtrado sólo
después de confirmar el error devuelto por la API.
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.
- Sustituir el OAuth Claude.ai experimental por una
ANTHROPIC_API_KEY, Bedrock o Vertex AI para cargas multiusuario o críticas.
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.jsony.credentials.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. - Aplicar la misma protección a
.claude/.credentials.json. - 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, tras limitar el catálogo, publica un único modelo Claude OAuth:claude-sonnet-5.- PostgreSQL aceptaba conexiones.
- Codex CLI indicaba una sesión de ChatGPT activa.
- Claude Code 2.1.233 indicaba
loggedIn: true, proveedor first-party y suscripción Pro. - La consulta OAuth a
GET /v1/modelsde Anthropic respondía HTTP 200. - Las generaciones normal y streaming mediante LiteLLM respondieron HTTP 200 después de adoptar el esquema Bearer y la identidad requerida por Claude Code.
- 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.