254 lines
9.5 KiB
Markdown
254 lines
9.5 KiB
Markdown
# 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.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 (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 openai` installiert standardmäßig v1.x, was inkompatibel ist.
|
|
* **Fix:** In `requirements.txt` zwingend die Version pinnen:
|
|
```text
|
|
openai==0.28.1
|
|
# weitere deps...
|
|
### 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`).
|
|
|
|
---
|
|
|
|
## 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)
|
|
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!
|
|
|
|
```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
|
|
```
|
|
|
|
---
|
|
|
|
## 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` hat `openai==0.28.1`?
|
|
- [ ] `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.
|
|
3. **JSON im Dockerfile:** Vergesst nicht, die externen Prompt-Files mit `COPY` in den Container zu holen!
|