# Migration Guide: Google AI Builder Apps -> Local Docker Stack > **WICHTIGER HINWEIS:** Der Gemini-Agent f"."hrt Code **innerhalb** dieses Docker-Containers aus. Er hat keinen Zugriff auf den Docker-Daemon des Host-Systems. Daher kann und wird der Agent **NIEMALS** in der Lage sein, Befehle wie `docker build`, `docker-compose up` oder andere Docker-Management-Aufgaben auszuf.hren. Diese Befehle m.ssen immer vom Benutzer auf dem Host-System ausgef.hrt werden. **Ziel:** Standardisierter Prozess, um eine von Google AI Studio generierte React-App schnell, robust und fehlerfrei in die lokale Docker/Python-Architektur zu integrieren. **Grundsatz:** "Minimalset & Robustheit". Wir bauen keine aufgebl.hten Container, und wir verhindern Timeouts und fehlende Abh.ngigkeiten proaktiv. --- ## 1. Vorbereitung & Abh.ngigkeiten (Common Pitfalls) Bevor Code kopiert wird, m.ssen die Grundlagen stimmen. ### 1.1 Package.json Check Generierte Apps haben oft kein `express`, da sie keinen Server erwarten. * **Aktion:** "."ffne `package.json` der App. * **Pr.fung:** Steht `express` unter `dependencies`? * **Fix:** ```json "dependencies": { ... "express": "^4.18.2", "cors": "^2.8.5" } ``` ### 1.2 Datenbank-Datei Docker kann keine einzelne Datei mounten, wenn sie auf dem Host nicht existiert. * **Fehler:** "Bind mount failed: ... does not exist" * **Fix:** VOR dem ersten Start die Datei anlegen: ```bash touch mein_neues_projekt.db ``` ### 1.3 Vite Base Path (White Screen Fix) Wenn die App unter einem Unterverzeichnis (z.B. `/gtm/`) l.uft, findet sie ihre JS/CSS-Dateien nicht, wenn Vite Standard-Pfade (`/`) nutzt. * **Datei:** `vite.config.ts` * **Fix:** `base` auf `./` setzen. ```typescript export default defineConfig({ base: './', // WICHTIG f.r Sub-Pfad Deployment // ... }); ``` ### 1.4 Python Dependencies & Shared Libraries (Critical Pitfall) Das Projekt nutzt ein zentrales `helpers.py`, das von mehreren Services geteilt wird. Dies f.hrt oft zu `ModuleNotFoundError`, da eine kleine App (wie `gtm-architect`) nicht alle Bibliotheken ben.tigt, die in `helpers.py` importiert werden (z.B. `gspread`, `pandas`). * **Fehler:** `ModuleNotFoundError: No module named 'gspread'` * **Ursache:** Die `gtm-architect/requirements.txt` enth.lt `gspread` nicht, aber `helpers.py` versucht es zu importieren. * **Fix (in `helpers.py`):** Machen Sie "exotische" Importe optional. Dies ist die robusteste Methode, um die Kompatibilit.t zu wahren, ohne die `requirements.txt` kleiner Apps aufzubl.hen. ```python # Beispiel in helpers.py try: import gspread GSPREAD_AVAILABLE = True except ImportError: GSPREAD_AVAILABLE = False gspread = None # Wichtig, damit Referenzen nicht fehlschlagen ``` * **Fix (in `requirements.txt`):** Stellen Sie sicher, dass die f.r die App **unmittelbar** ben.tigten Bibliotheken vorhanden sind. F.r `gtm-architect` sind das: ```text google-generativeai requests beautifulsoup4 ``` ### 1.5 Python Syntax & F-Strings Multi-Line Prompts k.nnen in Docker-Umgebungen zu **sehr hartn.ckigen Syntaxfehlern** f.hren, selbst wenn sie lokal korrekt aussehen. * **Das Problem:** Der Python-Parser (insbesondere bei `f-strings` in Kombination mit Zahlen/Punkten am Zeilenanfang oder verschachtelten Klammern) kann Multi-Line-Strings (`f"""..."""`) falsch interpretieren, was zu Fehlern wie `SyntaxError: invalid decimal literal` oder `unmatched ')'` f.hrt, auch wenn der Code scheinbar korrekt ist. * **ULTIMATIVE L.SUNG (Maximale Robustheit):** 1. **Vermeide `f"""` komplett f.r komplexe Multi-Line-Prompts.** Definiere stattdessen den Prompt als **Liste von einzelnen String-Zeilen** und f.ge sie mit `"\n".join(prompt_parts)` zusammen. 2. **Nutze die `.format()` Methode oder f-Strings in EINZEILIGEN Strings** zur Variablen-Injektion. Dies trennt die String-Definition komplett von der Variablen-Interpolation und ist die robusteste Methode. ```python # Beispiel: Maximal robust prompt_template_parts = [ "1) Mache dies: {variable_1}", "2) Mache das: {variable_2}", ] prompt_template = "\n".join(prompt_template_parts) prompt = prompt_template.format(variable_1=wert1, variable_2=wert2) # System-Instruktion muss immer noch vorangestellt werden: full_prompt = sys_instr + "\n\n" + prompt ``` * **Versionierung f.r Debugging:** Um sicherzustellen, dass die korrekte Version des Codes l.uft, f.ge Versionsnummern in die Start-Logs des Node.js Servers (`server.cjs`) und des Python Orchestrators (`gtm_architect_orchestrator.py`) ein. * `server.cjs`: `console.log(`... (Version: ${VERSION})`);` * `gtm_architect_orchestrator.py`: `print(f"DEBUG: Orchestrator v{__version__} loaded ...")` * **Signaturen pr.fen:** Shared Libraries (`helpers.py`) haben oft .ltere Signaturen. Immer die tats.chliche Definition pr.fen! * Beispiel: `call_openai_chat` unterst.tzt oft kein `system_message` Argument. Stattdessen Prompt manuell zusammenbauen (`sys_instr + "\n\n" + prompt`). ### 1.6 Pitfall: Veraltete API-Nutzung & Bibliotheksnamen **ACHTUNG:** Dies ist eine der h.ufigsten Fehlerquellen bei der Migration .ltterer KI-Skripte. * **Das Problem 1 (Name):** Der Name des Pakets (`google-generativeai`) stimmt nicht mit dem Import (`import google.genai`) .berein, den neuere Versionen erwarten. In unserem Fall bleiben wir vorerst beim Import von `google.generativeai`. * **Installation (`requirements.txt`):** `google-generativeai` * **Import (z.B. in `helpers.py`):** `import google.generativeai as genai` * **Das Problem 2 (API-Nutzung):** "."lterer Code verwendet eine `genai.Client`-Klasse, die **nicht mehr existiert**. Dies f.hrt zu einem Absturz. * **Fehlerbild im Log:** `AttributeError: module 'google.generativeai' has no attribute 'Client'` * **L.sung:** Der Code MUSS auf die moderne `GenerativeModel`-API umgestellt werden. Siehe **Appendix A.4** f.r ein Code-Beispiel. --- ## 2. Die Backend-Bridge (`server.cjs`) Dies ist der Node.js Server im Container. Er muss **robust** gegen Timeouts sein und Pfade dynamisch erkennen (Dev vs. Prod). **Gold-Standard Template:** ```javascript const express = require('express'); const { spawn } = require('child_process'); const fs = require('fs'); const path = require('path'); const app = express(); const port = 3005; // ANPASSEN! app.use(express.json({ limit: '50mb' })); // 1. Statische Dateien: Robustheit f.r Docker (Flat) vs. Local (Nested) const distPath = path.join(__dirname, 'dist'); // Docker Standard const isProduction = fs.existsSync(distPath); const staticDir = isProduction ? distPath : __dirname; console.log(`[Init] Serving static files from: ${staticDir}`); app.use(express.static(staticDir)); // 2. Python Pfad: Robustheit f.r Sideloading let pythonScriptPath = path.join(__dirname, 'mein_orchestrator.py'); // ANPASSEN! if (!fs.existsSync(pythonScriptPath)) { pythonScriptPath = path.join(__dirname, '../mein_orchestrator.py'); } // 3. API Routing app.post('/api/run', (req, res) => { // ... spawn logic ... }); // 4. SPA Fallback app.get('*', (req, res) => { res.sendFile(path.join(staticDir, 'index.html')); }); // 5. Timeout-H.rtung (CRITICAL!) const server = app.listen(port, () => { console.log(`Server listening on ${port}`); }); server.setTimeout(600000); // 10 Minuten server.keepAliveTimeout = 610000; server.headersTimeout = 620000; ``` --- ## 3. Docker Optimierung (Multi-Stage) Wir nutzen **Multi-Stage Builds**, um das Image klein zu halten (kein `src`, keine Dev-Tools im Final Image). **Gold-Standard Dockerfile:** ```dockerfile # Stage 1: Frontend Build FROM node:20-slim AS frontend-builder WORKDIR /app COPY mein-app-ordner/package.json ./ RUN npm install COPY mein-app-ordner/ . RUN npm run build # Stage 2: Runtime FROM python:3.11-slim WORKDIR /app # Node.js installieren (f.r Server Bridge, optimierte Methode) RUN apt-get update && \ apt-get install -y --no-install-recommends curl ca-certificates && \ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ apt-get install -y --no-install-recommends nodejs && \ rm -rf /var/lib/apt/lists/* # Python Deps (aus der app-spezifischen requirements.txt) COPY mein-app-ordner/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Server & Frontend Artifacts (Flat Structure!) COPY mein-app-ordner/server.cjs . COPY mein-app-ordner/package.json . RUN npm install --omit=dev COPY --from=frontend-builder /app/dist ./dist # Python Logic & Shared Libs COPY mein_orchestrator.py . COPY helpers.py . COPY config.py . COPY market_db_manager.py . EXPOSE 3005 CMD ["node", "server.cjs"] ``` --- ## 4. Docker Compose & Mounts (WICHTIGER PITFALL) **WARNUNG: Lokale Dateien .berschreiben den Container-Code!** Wenn Sie `volumes` f.r das Sideloading verwenden (wie unten gezeigt), werden die Dateien aus Ihrem lokalen Verzeichnis direkt in den Container geladen. Das bedeutet: **Wenn Sie nicht `git pull` auf dem Host-System ausf.hren, bevor Sie den Container neu bauen, wird der Container weiterhin den alten, lokalen Code ausf.hren.** **Workflow:** 1. "."nderungen im Git-Repository pushen (oder von einem Agent pushen lassen). 2. **`git pull` auf dem Host-System ausf.hren.** (Dieser Schritt ist entscheidend!) 3. `docker-compose up -d --build ` ausf.hren. Beim Sideloading m.ssen **alle** Abh.ngigkeiten gemountet werden, nicht nur das Hauptskript. **Wichtig:** Der Pfad zu `server.cjs` .ndert sich durch die "Flat Structure" im Dockerfile! ```yaml my-new-app: # ... build context ... volumes: # Logic Sideloading (ALLE Skripte!) - ./mein_orchestrator.py:/app/mein_orchestrator.py - ./helpers.py:/app/helpers.py # WICHTIG: Shared Libs - ./config.py:/app/config.py # WICHTIG: Shared Libs - ./market_db_manager.py:/app/market_db_manager.py # Server Sideloading (Ziel ist Root /app/server.cjs!) - ./mein-app-ordner/server.cjs:/app/server.cjs # Persistence - ./mein_projekt.db:/app/mein_projekt.db environment: - PYTHONUNBUFFERED=1 - DB_PATH=/app/mein_projekt.db environment: - PYTHONUNBUFFERED=1 - DB_PATH=/app/mein_projekt.db ``` --- ## 5. Nginx Proxy Achtung beim Routing. Wenn die App unter `/app/` laufen soll, muss der Trailing Slash (`/`) stimmen. ```nginx location /app/ { proxy_pass http://my-new-app:3005/; # Slash am Ende wichtig! # ... headers ... proxy_read_timeout 1200s; # Timeout passend zum Node Server } ``` --- ## 6. Frontend Anpassungen (React) 1. **API Calls:** Alle direkten Aufrufe an `GoogleGenAI` entfernen. Stattdessen `fetch('/api/run', ...)` nutzen. 2. **Base URL:** In `vite.config.ts` `base: './'` setzen (siehe Punkt 1.3). 3. **Router:** Falls `react-router` genutzt wird, muss der `basename` gesetzt werden (z.B. `/gtm/`). Bei einfachem State-Routing (wie in den aktuellen Apps) reicht der `base` Config Eintrag. --- ## Checkliste vor dem Commit - [ ] `express` in `package.json`? - [ ] `vite.config.ts` hat `base: './'`? - [ ] `requirements.txt` enth.lt die korrekten (minimalen) Dependencies? - [ ] `server.cjs` hat Timeouts (>600s)? - [ ] `docker-compose.yml` mountet auch `helpers.py` und `config.py`? - [ ] Leere `.db` Datei auf dem Host erstellt? - [ ] Dockerfile nutzt Multi-Stage Build? --- ## Appendix A: GTM Architect Fixes & Gemini Migration (Jan 2026) ### A.1 Problemstellung - **SyntaxError bei gro.en Prompts:** Python-Parser (3.11) hatte massive Probleme mit f-Strings, die 100+ Zeilen lang waren und Sonderzeichen enthielten. - **Library Deprecation:** `google.generativeai` hat Support eingestellt? Nein, aber die Fehlermeldung im Log deutete auf einen Konflikt zwischen alten `openai`-Wrappern und neuen Gemini-Paketen hin. - **L.sung:** 1. **Prompts ausgelagert:** System-Prompts liegen jetzt in `gtm_prompts.json` und werden zur Laufzeit geladen. Kein Code-Parsing mehr notwendig. 2. **Native Gemini Lib:** Statt OpenAI-Wrapper nutzen wir jetzt `google.generativeai` direkt via `helpers.call_gemini_flash`. 3. **Config:** `gtm-architect/Dockerfile` kopiert nun explizit `gtm_prompts.json`. ### A.2 Neuer Standard f.r KI-Apps F.r zuk.nftige Apps gilt: 1. **Prompts in JSON/Text-Files:** Niemals riesige Strings im Python-Code hardcoden. 2. **`helpers.call_gemini_flash` nutzen:** Diese Funktion ist nun der Gold-Standard f.r einfache, stateless Calls. Siehe **Appendix A.4** f.r die korrekte Implementierung. 3. **JSON im Dockerfile:** Vergesst nicht, die externen Prompt-Files mit `COPY` in den Container zu holen! ### A.3 Kritisches Problem & L.sung: `AttributeError` bei Gemini API (Jan 2026) - **Problem:** Nach der Migration auf die `google-generativeai` Bibliothek schlugen alle API-Aufrufe mit einem `AttributeError: module 'google.generativeai' has no attribute 'Client'` Fehler fehl. - **Log-Analyse:** ``` ERROR:helpers:Fehler beim Gemini-Flash-Aufruf: module 'google.generativeai' has no attribute 'Client' ``` - **Untersuchung:** 1. Die erste Annahme, es handle sich um einen falschen Modellnamen (`404 NOT_FOUND`), erwies sich als irref.hrend. 2. Die Analyse des `helpers.py`-Skripts zeigte, dass der Code versuchte, eine `genai.Client`-Klasse zu verwenden. - **Schlussfolgerung & L.sung:** Der Fehler lag in der Verwendung einer **veralteten API-Initialisierungsmethode**. Die `google-generativeai`-Bibliothek hat die `Client`-Klasse entfernt und erfordert nun einen modernen Ansatz. **Die Korrektur (implementiert in `helpers.py`):** 1. **Konfigurieren des API-Schl.ssels:** `genai.configure(api_key="IHR_KEY")` 2. **Instanziieren des Modells:** `model = genai.GenerativeModel('gemini-1.5-flash-latest')` 3. **Aufrufen der Generierung:** `response = model.generate_content(...)` Dieser Fix wurde in `helpers.py` umgesetzt und hat die Funktionalit.t des GTM Architect wiederhergestellt. Alle neuen KI-Anwendungen m.ssen diesem Muster folgen. ### A.4 Gold-Standard: Gemini API Wrapper (Robust & Dynamic) Um die korrekte Implementierung zu standardisieren, hier der "Gold-Standard" Wrapper, wie er in `helpers.py` verwendet wird. Dieser Code ist die Vorlage f.r alle zuk.nftigen Gemini-Aufrufe. ```python import google.generativeai as genai import logging # Annahme: logger und _get_gemini_api_key() sind definiert # Annahme: Ein @retry_on_failure Decorator existiert @retry_on_failure def call_gemini_flash(prompt, system_instruction=None, temperature=0.3, json_mode=False): """ Spezifische Funktion f.r Gemini 1.5 Flash Aufrufe mit System-Instruction Support. Verwendet die korrekte `GenerativeModel` API. """ logger = logging.getLogger(__name__) # HAS_GEMINI Check hier weggelassen f.r die Lesbarkeit api_key = _get_gemini_api_key() try: # 1. API-Schl.ssel konfigurieren genai.configure(api_key=api_key) # 2. Generierungs-Konfiguration definieren generation_config = { "temperature": temperature, "top_p": 0.95, "top_k": 40, "max_output_tokens": 8192, } if json_mode: generation_config["response_mime_type"] = "application/json" # 3. Modell instanziieren (mit System-Instruktion) model = genai.GenerativeModel( model_name="gemini-1.5-flash-latest", # Eine stabile, spezifische Version verwenden generation_config=generation_config, system_instruction=system_instruction ) # Der Prompt kann ein String oder eine Liste von Teilen sein contents = [prompt] if isinstance(prompt, str) else prompt # 4. Inhalt generieren response = model.generate_content(contents) return response.text.strip() except Exception as e: logger.error(f"Fehler beim Gemini-Flash-Aufruf: {e}") if "API_KEY_INVALID" in str(e) or "403" in str(e): raise ValueError(f"Invalid API Key: {str(e)}") raise e ``` --- ## 7. Troubleshooting & Lessons Learned (Jan 2026) Diese Fehler traten bei der GTM Architect Migration auf und m.ssen zuk.nftig vermieden werden. ### 7.1 Database Initialization (`OperationalError: no such table`) * **Problem:** Neue SQLite-Datenbanken (`.db` Files) werden zwar als leere Dateien via `touch` erstellt, enthalten aber keine Tabellen. Der Python-Code st.rzt ab, wenn er versucht, in eine nicht existierende Tabelle zu schreiben. * **L.sung:** Die Initialisierungs-Logik (z.B. `python gtm_db_manager.py init`) MUSS beim Start des `server.cjs` aufgerufen werden. Verlasse dich nicht darauf, dass der User dies manuell tut. * **Best Practice:** In `server.cjs`: ```javascript const dbScript = path.join(__dirname, 'gtm_db_manager.py'); require('child_process').spawn('python3', [dbScript, 'init']); ``` ### 7.2 Dependency Injection (`ModuleNotFoundError`) * **Problem:** Wenn eine `requirements.txt` nachtr.glich hinzugef.gt oder ge.ndert wird, reicht ein einfacher Container-Neustart nicht aus. `pip install` l.uft normalerweise nur w.hrend der Build-Phase. * **L.sung:** 1. **Immer Rebuild:** `docker-compose up -d --build `. 2. **Startup-Check:** F.r maximale Robustheit kann der `CMD` im Dockerfile so angepasst werden, dass er `pip install` vor dem Server-Start ausf.hrt: `CMD ["/bin/bash", "-c", "pip install -r requirements.txt && node server.cjs"]`. ### 7.3 Python Indentation (`IndentationError`) * **Problem:** Beim Copy-Paste von Code in `config.py` entstehen oft versehentliche Einr.ckungen (Leerzeichen am Zeilenanfang) bei globalen Variablen. Dies f.hrt dazu, dass der gesamte Python-Prozess beim Import abst.rzt. * **L.sung:** `.py` Dateien immer auf strikte Linksb.ndigkeit bei globalen Definitionen pr.fen. Tools wie `flake8` helfen, sind aber im Container oft nicht verf.gbar. Sorgfalt ist entscheidend. ### 7.4 Server File Location & Volume Mounts (Sideloading Trap) * **Problem:** Im Dockerfile wird `server.cjs` oft in das Root-Verzeichnis kopiert (`COPY gtm-app/server.cjs .`), aber in `docker-compose.yml` wird nur der Unterordner gemountet (`- ./gtm-app:/app/gtm-app`). Da die Node-App im Root läuft, führt sie weiterhin die alte, im Image "eingebackene" Version von `server.cjs` aus. Änderungen auf dem Host werden ignoriert. * **Symptom:** Code-Fixes im Node-Server (z.B. Timeouts, E2BIG Fixes) greifen nicht, obwohl die Datei auf dem Host korrekt aussieht. * **Lösung:** Den Mount in `docker-compose.yml` explizit auf die ausgeführte Datei lenken: ```yaml volumes: - ./mein-app-ordner/server.cjs:/app/server.cjs ``` ```