18 KiB
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 upoder 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.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 & 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.txtenth.ltgspreadnicht, aberhelpers.pyversucht es zu importieren. -
Fix (in
helpers.py): Machen Sie "exotische" Importe optional. Dies ist die robusteste Methode, um die Kompatibilit.t zu wahren, ohne dierequirements.txtkleiner Apps aufzubl.hen.# 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.rgtm-architectsind das:
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:
# 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:
- "."nderungen im Git-Repository pushen (oder von einem Agent pushen lassen).
git pullauf dem Host-System ausf.hren. (Dieser Schritt ist entscheidend!)docker-compose up -d --build <service-name>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!
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.
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.txtenth.lt die korrekten (minimalen) Dependencies?server.cjshat Timeouts (>600s)?docker-compose.ymlmountet auchhelpers.pyundconfig.py?- Leere
.dbDatei 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.generativeaihat Support eingestellt? Nein, aber die Fehlermeldung im Log deutete auf einen Konflikt zwischen altenopenai-Wrappern und neuen Gemini-Paketen hin. - L.sung:
- Prompts ausgelagert: System-Prompts liegen jetzt in
gtm_prompts.jsonund werden zur Laufzeit geladen. Kein Code-Parsing mehr notwendig. - Native Gemini Lib: Statt OpenAI-Wrapper nutzen wir jetzt
google.generativeaidirekt viahelpers.call_gemini_flash. - Config:
gtm-architect/Dockerfilekopiert nun explizitgtm_prompts.json.
- Prompts ausgelagert: System-Prompts liegen jetzt in
A.2 Neuer Standard f.r KI-Apps
F.r zuk.nftige Apps gilt:
- Prompts in JSON/Text-Files: Niemals riesige Strings im Python-Code hardcoden.
helpers.call_gemini_flashnutzen: Diese Funktion ist nun der Gold-Standard f.r einfache, stateless Calls. Siehe Appendix A.4 f.r die korrekte Implementierung.- JSON im Dockerfile: Vergesst nicht, die externen Prompt-Files mit
COPYin den Container zu holen!
A.3 Kritisches Problem & L.sung: AttributeError bei Gemini API (Jan 2026)
-
Problem: Nach der Migration auf die
google-generativeaiBibliothek schlugen alle API-Aufrufe mit einemAttributeError: 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:
- Die erste Annahme, es handle sich um einen falschen Modellnamen (
404 NOT_FOUND), erwies sich als irref.hrend. - Die Analyse des
helpers.py-Skripts zeigte, dass der Code versuchte, einegenai.Client-Klasse zu verwenden.
- Die erste Annahme, es handle sich um einen falschen Modellnamen (
-
Schlussfolgerung & L.sung: Der Fehler lag in der Verwendung einer veralteten API-Initialisierungsmethode. Die
google-generativeai-Bibliothek hat dieClient-Klasse entfernt und erfordert nun einen modernen Ansatz.Die Korrektur (implementiert in
helpers.py):- Konfigurieren des API-Schl.ssels:
genai.configure(api_key="IHR_KEY") - Instanziieren des Modells:
model = genai.GenerativeModel('gemini-1.5-flash-latest') - Aufrufen der Generierung:
response = model.generate_content(...)
Dieser Fix wurde in
helpers.pyumgesetzt und hat die Funktionalit.t des GTM Architect wiederhergestellt. Alle neuen KI-Anwendungen m.ssen diesem Muster folgen. - Konfigurieren des API-Schl.ssels:
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.
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 (
.dbFiles) werden zwar als leere Dateien viatoucherstellt, 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 desserver.cjsaufgerufen werden. Verlasse dich nicht darauf, dass der User dies manuell tut. - Best Practice: In
server.cjs: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.txtnachtr.glich hinzugef.gt oder ge.ndert wird, reicht ein einfacher Container-Neustart nicht aus.pip installl.uft normalerweise nur w.hrend der Build-Phase. - L.sung:
- Immer Rebuild:
docker-compose up -d --build <service>. - Startup-Check: F.r maximale Robustheit kann der
CMDim Dockerfile so angepasst werden, dass erpip installvor dem Server-Start ausf.hrt:CMD ["/bin/bash", "-c", "pip install -r requirements.txt && node server.cjs"].
- Immer Rebuild:
7.3 Python Indentation (IndentationError)
- Problem: Beim Copy-Paste von Code in
config.pyentstehen 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:
.pyDateien immer auf strikte Linksb.ndigkeit bei globalen Definitionen pr.fen. Tools wieflake8helfen, 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.cjsoft in das Root-Verzeichnis kopiert (COPY gtm-app/server.cjs .), aber indocker-compose.ymlwird 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 vonserver.cjsaus. Ä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.ymlexplizit auf die ausgeführte Datei lenken:volumes: - ./mein-app-ordner/server.cjs:/app/server.cjs