From 52d568e924309d016e157336cf653fc2a0d34255 Mon Sep 17 00:00:00 2001 From: Francwa Date: Tue, 9 Dec 2025 16:12:58 +0100 Subject: [PATCH] Dockerizing the app (WIP) --- brain/.dockerignore | 57 +++++++++++ brain/Dockerfile | 91 ++++++++++++++++++ brain/manifests/add_torrent_by_index.json | 16 ++++ brain/manifests/find_media_imdb_id.json | 16 ++++ brain/manifests/find_torrent.json | 16 ++++ brain/manifests/set_language.json | 16 ++++ docker-compose.yml | 34 +++++++ librechat/Dockerfile | 4 + librechat/librechat.yaml | 110 ++++++++++++++++++++++ librechat/librechat_providers.js | 10 ++ 10 files changed, 370 insertions(+) create mode 100644 brain/.dockerignore create mode 100644 brain/Dockerfile create mode 100644 brain/manifests/add_torrent_by_index.json create mode 100644 brain/manifests/find_media_imdb_id.json create mode 100644 brain/manifests/find_torrent.json create mode 100644 brain/manifests/set_language.json create mode 100644 docker-compose.yml create mode 100644 librechat/Dockerfile create mode 100644 librechat/librechat.yaml create mode 100644 librechat/librechat_providers.js diff --git a/brain/.dockerignore b/brain/.dockerignore new file mode 100644 index 0000000..3338a74 --- /dev/null +++ b/brain/.dockerignore @@ -0,0 +1,57 @@ +# Git +.git +.gitignore +.gitea + +# Python +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +*.so +.pytest_cache +.coverage +htmlcov +.tox +.nox +.hypothesis + +# Virtual environments +venv +.venv +env +.env +.env.* + +# IDE +.vscode +.idea +*.swp +*.swo +.qodo + +# Build +build +dist +*.egg-info + +# Documentation +docs/ +*.md +!README.md + +# Tests +tests/ +pytest.ini + +# Data (will be mounted as volumes) +memory_data/ +logs/ +*.log + +# Misc +*.bak +*.tmp +.DS_Store +Thumbs.db diff --git a/brain/Dockerfile b/brain/Dockerfile new file mode 100644 index 0000000..a00ef64 --- /dev/null +++ b/brain/Dockerfile @@ -0,0 +1,91 @@ +# Dockerfile for Agent Media +# Multi-stage build for smaller image size + +# =========================================== +# Stage 1: Builder +# =========================================== +FROM python:3.12.7-slim as builder + +# STFU (please) +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies (needs root) +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Install Poetry globally (needs root) +RUN pip install --no-cache-dir poetry + +# Copy dependency files (as root for now) +COPY pyproject.toml poetry.lock* /tmp/ + +# Install dependencies as root (to avoid permission issues with system packages) +WORKDIR /tmp +RUN poetry config virtualenvs.create false \ + && poetry install --only main --no-root --no-cache + +# Create non-root user +RUN useradd -m -u 1000 -s /bin/bash appuser + +# Switch to non-root user +USER appuser + +# Set working directory (owned by appuser) +WORKDIR /home/appuser/app + +# =========================================== +# Stage 2: Runtime +# =========================================== +FROM python:3.12.7-slim as runtime + +# Install runtime dependencies (needs root) +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Create non-root user +RUN useradd -m -u 1000 -s /bin/bash appuser + +# Create data directories (needs root for /data) +RUN mkdir -p /data/memory /data/logs \ + && chown -R appuser:appuser /data + +# Switch to non-root user +USER appuser + +# Set working directory (owned by appuser) +WORKDIR /home/appuser/app + +# Copy Python packages from builder stage +COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages +COPY --from=builder /usr/local/bin /usr/local/bin + +# Copy application code (already owned by appuser) +COPY --chown=appuser:appuser agent/ ./agent/ +COPY --chown=appuser:appuser application/ ./application/ +COPY --chown=appuser:appuser domain/ ./domain/ +COPY --chown=appuser:appuser infrastructure/ ./infrastructure/ +COPY --chown=appuser:appuser app.py . + +# Create volumes for persistent data +VOLUME ["/data/memory", "/data/logs"] + +# Expose port +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +# Environment variables (can be overridden) +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONPATH=/home/appuser/app \ + LLM_PROVIDER=deepseek \ + MEMORY_STORAGE_DIR=/data/memory + +# Run the application +CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/brain/manifests/add_torrent_by_index.json b/brain/manifests/add_torrent_by_index.json new file mode 100644 index 0000000..75d7c9c --- /dev/null +++ b/brain/manifests/add_torrent_by_index.json @@ -0,0 +1,16 @@ +{ + "name": "add_torrent_by_index", + "description": "Ajoute un torrent à la file d'attente de qBittorrent en utilisant l'index (1-basé) d'un résultat de recherche précédent (par exemple, 'download the 3rd one').", + "parameters": { + "type": "object", + "properties": { + "index": { + "type": "integer", + "description": "L'index (1-basé) du torrent dans les derniers résultats de recherche." + } + }, + "required": [ + "index" + ] + } +} diff --git a/brain/manifests/find_media_imdb_id.json b/brain/manifests/find_media_imdb_id.json new file mode 100644 index 0000000..3359000 --- /dev/null +++ b/brain/manifests/find_media_imdb_id.json @@ -0,0 +1,16 @@ +{ + "name": "find_media_imdb_id", + "description": "Trouve l'ID IMDb et les informations d'un film ou d'une série télévisée à partir de son titre en utilisant l'API TMDB. À utiliser comme première étape avant de chercher des torrents.", + "parameters": { + "type": "object", + "properties": { + "media_title": { + "type": "string", + "description": "Le titre exact du média à rechercher (par exemple, 'Inception', 'Breaking Bad')." + } + }, + "required": [ + "media_title" + ] + } +} diff --git a/brain/manifests/find_torrent.json b/brain/manifests/find_torrent.json new file mode 100644 index 0000000..88df4a4 --- /dev/null +++ b/brain/manifests/find_torrent.json @@ -0,0 +1,16 @@ +{ + "name": "find_torrent", + "description": "Recherche des fichiers torrent pour un titre de média donné. Les résultats sont stockés dans la mémoire de l'agent pour une référence ultérieure par index (e.g., 'download the 3rd one').", + "parameters": { + "type": "object", + "properties": { + "media_title": { + "type": "string", + "description": "Le titre du média pour lequel rechercher des torrents (par exemple, 'Inception 2010')." + } + }, + "required": [ + "media_title" + ] + } +} diff --git a/brain/manifests/set_language.json b/brain/manifests/set_language.json new file mode 100644 index 0000000..6d01cc9 --- /dev/null +++ b/brain/manifests/set_language.json @@ -0,0 +1,16 @@ +{ + "name": "set_language", + "description": "Définit la langue de la conversation pour l'agent.", + "parameters": { + "type": "object", + "properties": { + "language": { + "type": "string", + "description": "Le code de la langue (par exemple, 'en' pour Anglais, 'fr' pour Français, 'es' pour Espagnol)." + } + }, + "required": [ + "language" + ] + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ba3409e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +version: "3.4" + +services: + # Da brain + agent-brain: + build: + context: ./brain + dockerfile: Dockerfile + env_file: .env + ports: + - "8000:8000" + volumes: + # Persistent data volumes (outside container /app) + - agent-memory:/data/memory + - agent-logs:/data/logs + # Development: mount code for hot reload (comment out in production) + # - ./brain:/app + + # Da face + frontend: + build: + context: ./librechat + dockerfile: Dockerfile + ports: + - "3080:3080" + depends_on: + - agent-brain + +# Named volumes for persistent data +volumes: + agent-memory: + driver: local + agent-logs: + driver: local \ No newline at end of file diff --git a/librechat/Dockerfile b/librechat/Dockerfile new file mode 100644 index 0000000..1fb04a2 --- /dev/null +++ b/librechat/Dockerfile @@ -0,0 +1,4 @@ +FROM ghcr.io/danny-avila/librechat:latest + +COPY librechat.yaml /app/librechat.yaml +COPY librechat_providers.js /app/librechat_providers.js \ No newline at end of file diff --git a/librechat/librechat.yaml b/librechat/librechat.yaml new file mode 100644 index 0000000..76a872c --- /dev/null +++ b/librechat/librechat.yaml @@ -0,0 +1,110 @@ +# For more information, see the Configuration Guide: +# https://www.librechat.ai/docs/configuration/librechat_yaml + +version: 1.2.1 +cache: true +endpoints: + custom: + # Deepseek + - name: "Deepseek" + apiKey: "${DEEPSEEK_API_KEY}" + baseURL: "https://api.deepseek.com/v1" + models: + default: ["deepseek-chat", "deepseek-coder", "deepseek-reasoner"] + fetch: false + titleConvo: true + titleModel: "deepseek-chat" + modelDisplayLabel: "Deepseek" + streamRate: 1 + tools: + - toolName: media_finder + pluginKey: "media_finder_key" + manifest: + schema: + type: openapi + url: "http://agent-brain:8000/manifests/find_media_imdb_id.json" + auth: + type: none + + # Outil 2: find_torrent + - toolName: torrent_search + pluginKey: "torrent_search_key" + manifest: + schema: + type: openapi + url: "http://agent-brain:8000/manifests/find_torrent.json" + auth: + type: none + + # Outil 3: add_torrent_by_index + - toolName: torrent_downloader + pluginKey: "torrent_downloader_key" + manifest: + schema: + type: openapi + url: "http://agent-brain:8000/manifests/add_torrent_by_index.json" + auth: + type: none + + # Outil 4: set_language + - toolName: lang_setter + pluginKey: "lang_setter_key" + manifest: + schema: + type: openapi + url: "http://agent-brain:8000/manifests/set_language.json" + auth: + type: none + + + # Backend Local Agent + - name: "Local Agent" + apiKey: "dummy_key" + baseURL: "http://host.docker.internal:8000/v1" + models: + default: ["local-deepseek-agent"] + fetch: false + titleConvo: false + titleModel: "current_model" + forcePrompt: true + modelDisplayLabel: "Local Agent" + streamRate: 1 + tools: + - toolName: media_finder + pluginKey: "media_finder_key" + manifest: + schema: + type: openapi + url: "http://agent-brain:8000/manifests/find_media_imdb_id.json" + auth: + type: none + + # Outil 2: find_torrent + - toolName: torrent_search + pluginKey: "torrent_search_key" + manifest: + schema: + type: openapi + url: "http://agent-brain:8000/manifests/find_torrent.json" + auth: + type: none + + # Outil 3: add_torrent_by_index + - toolName: torrent_downloader + pluginKey: "torrent_downloader_key" + manifest: + schema: + type: openapi + url: "http://agent-brain:8000/manifests/add_torrent_by_index.json" + auth: + type: none + + # Outil 4: set_language + - toolName: lang_setter + pluginKey: "lang_setter_key" + manifest: + schema: + type: openapi + url: "http://agent-brain:8000/manifests/set_language.json" + auth: + type: none diff --git a/librechat/librechat_providers.js b/librechat/librechat_providers.js new file mode 100644 index 0000000..a073eb7 --- /dev/null +++ b/librechat/librechat_providers.js @@ -0,0 +1,10 @@ +[ + { + "name": "LocalAgent", + "type": "openai", + "baseURL": "http://host.docker.internal:8000", + "apiKey": "sk-8b00d72c417740ea96efd9c3eeddd148", + "model": "local-deepseek-agent", + "custom": true + } +]