8.9 KiB
Migration Guide: Google AI Builder Apps -> Local Docker Stack
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.jsonder App. - Prüfung: Steht
expressunterdependencies? - Fix:
"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:
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:
baseauf./setzen.export default defineConfig({ base: './', // WICHTIG für Sub-Pfad Deployment // ... });
1.4 Python Dependencies (OpenAI Version)
Das Projekt nutzt ein geteiltes helpers.py, das auf der alten OpenAI Python Library (v0.28.1) basiert.
- Fehler:
ModuleNotFoundError: No module named 'openai.error' - Ursache:
pip install openaiinstalliert standardmäßig v1.x, was inkompatibel ist. - Fix: In
requirements.txtzwingend die Version pinnen:openai==0.28.1 # weitere deps...
1.5 Python Syntax & F-Strings
Multi-Line f-Strings sind in Docker-Umgebungen fehleranfällig, besonders wenn sie JSON-Strukturen oder komplexe Texte enthalten.
-
Wiederkehrender Fehler:
SyntaxError: invalid decimal literal(oft bei2. Deriveoder ähnlichen Zeilen in Prompts)- Symptom: Python meldet einen Syntaxfehler innerhalb eines scheinbar korrekten Multi-Line-Strings (
f"""..."""). - Ursachen:
- Falsches Escaping von
{}: Wenn geschweifte Klammern ({}) als Teil eines JSON-Beispiels in einem f-String verwendet werden, müssen sie oft doppelt escaped werden ({{und}}). Wenn es aber kein f-String ist oder die Klammern nicht zur Variableneinfügung dienen, dürfen sie nicht escaped werden (eindict()-Literal ist{}). Das ist eine häufige Quelle für Verwirrung. - Fehlende/Falsche String-Delimiter: Der Multi-Line-String wurde nicht korrekt mit
"""geöffnet oder geschlossen, oder eine Zeile davor/danach bricht die String-Definition. - Unsichtbare Zeichen: Manchmal können unsichtbare Unicode-Zeichen (z.B. Zero-Width Space) den Parser stören.
- Ternary Operators in f-Strings: Das ist ein bekannter Anti-Pattern (siehe unten).
- Falsches Escaping von
- Symptom: Python meldet einen Syntaxfehler innerhalb eines scheinbar korrekten Multi-Line-Strings (
-
Best Practice zur Fehlervermeidung bei Prompts:
- Nutze explizite
if/elseBlöcke zur Definition des Prompts, anstatt Ternary Operators (... if x else ...) innerhalb von Multi-Line f-Strings. Dies ist die sicherste Methode. - Definiere komplexe JSON-Strukturen oder sehr lange Textblöcke vorher als separate Variablen und füge sie dann in den f-String ein (siehe Beispiel unten).
- Prüfe akribisch die
"""Delimiter und die Einrückung des Multi-Line-Strings.
# Beispiel: So sollte es NICHT gemacht werden (fehleranfällig) # prompt = f"""Daten: {json.dumps(data)}""" if lang=='de' else f"""Data: ...""" # Beispiel: So ist es robust (bevorzugte Methode) json_data_for_prompt = json.dumps(data) # JSON vorformatieren if lang == 'de': prompt_template = f""" Dies ist ein deutscher Prompt mit Daten: {json_data_for_prompt} """ else: prompt_template = f""" This is an English prompt with data: {json_data_for_prompt} """ prompt = sys_instr + "\n\n" + prompt_template # System-Instruktion voranstellen - Nutze explizite
-
Signaturen prüfen: Shared Libraries (
helpers.py) haben oft ältere Signaturen. Immer die tatsächliche Definition prüfen!- Beispiel:
call_openai_chatunterstützt oft keinsystem_messageArgument. Stattdessen Prompt manuell zusammenbauen (sys_instr + "\n\n" + prompt).
- 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:
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:
# 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)
RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm && rm -rf /var/lib/apt/lists/*
# Python Deps
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
Beim Sideloading (Entwicklung ohne Rebuild) 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!
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
5. Nginx Proxy
Achtung beim Routing. Wenn die App unter /app/ laufen soll, muss der Trailing Slash (/) stimmen.
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)
- API Calls: Alle direkten Aufrufe an
GoogleGenAIentfernen. Stattdessenfetch('/api/run', ...)nutzen. - Base URL: In
vite.config.tsbase: './'setzen (siehe Punkt 1.3). - Router: Falls
react-routergenutzt wird, muss derbasenamegesetzt werden (z.B./gtm/). Bei einfachem State-Routing (wie in den aktuellen Apps) reicht derbaseConfig Eintrag.
Checkliste vor dem Commit
expressinpackage.json?vite.config.tshatbase: './'?requirements.txthatopenai==0.28.1?server.cjshat Timeouts (>600s)?docker-compose.ymlmountet auchhelpers.pyundconfig.py?- Leere
.dbDatei auf dem Host erstellt? - Dockerfile nutzt Multi-Stage Build?