Fix: Gemini API modernization, dynamic model selection, and config path corrections

This commit is contained in:
2026-01-03 12:19:51 +00:00
parent 844ffa3eea
commit 97ca4cd254
6 changed files with 233 additions and 75 deletions

View File

@@ -1,5 +1,7 @@
# 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.
@@ -93,6 +95,17 @@ Multi-Line Prompts können in Docker-Umgebungen zu **sehr hartnäckigen Syntaxfe
* **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 älterer 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`)
@@ -277,5 +290,104 @@ Achtung beim Routing. Wenn die App unter `/app/` laufen soll, muss der Trailing
### 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.
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 404-Fehler durch sich ändernde Modellnamen oder regionale Unterschiede zu vermeiden, nutzt der neue Standard eine **dynamische Modell-Ermittlung**.
```python
import google.generativeai as genai
import logging
# Cache für den Modellnamen
_CACHED_MODEL_NAME = None
def _get_best_flash_model(api_key):
"""
Ermittelt dynamisch das beste verfügbare Flash-Modell via list_models().
"""
global _CACHED_MODEL_NAME
if _CACHED_MODEL_NAME:
return _CACHED_MODEL_NAME
default_model = "gemini-1.5-flash"
try:
genai.configure(api_key=api_key)
# Suche nach Modellen mit 'flash' und 'generateContent' Support
models = list(genai.list_models())
flash_models = [
m.name.replace('models/', '')
for m in models
if 'flash' in m.name.lower() and 'generateContent' in m.supported_generation_methods
]
# Priorisierung
if "gemini-1.5-flash" in flash_models:
_CACHED_MODEL_NAME = "gemini-1.5-flash"
elif flash_models:
_CACHED_MODEL_NAME = flash_models[0]
else:
_CACHED_MODEL_NAME = default_model
return _CACHED_MODEL_NAME
except Exception:
return default_model
@retry_on_failure
def call_gemini_flash(prompt, system_instruction=None, temperature=0.3, json_mode=False):
"""
Robuster Wrapper mit dynamischer Modellwahl.
"""
logger = logging.getLogger(__name__)
api_key = _get_gemini_api_key()
try:
genai.configure(api_key=api_key)
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"
# Dynamisch das richtige Modell wählen
model_name = _get_best_flash_model(api_key)
model = genai.GenerativeModel(
model_name=model_name,
generation_config=generation_config,
system_instruction=system_instruction
)
contents = [prompt] if isinstance(prompt, str) else prompt
response = model.generate_content(contents)
return response.text.strip()
except Exception as e:
logger.error(f"Fehler beim Gemini-Flash-Aufruf: {e}")
raise e
```

View File

@@ -16,16 +16,16 @@ import logging
# 1. GLOBALE KONSTANTEN UND DATEIPFADE
# ==============================================================================
# --- Dateipfade (NEU: Absolute Pfade) ---
# Basisverzeichnis des Projekts, in dem sich diese config.py befindet.
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# --- Dateipfade (NEU: Feste Pfade für Docker-Betrieb) ---
# Das Basisverzeichnis ist im Docker-Kontext immer /app.
BASE_DIR = "/app"
CREDENTIALS_FILE = os.path.join(BASE_DIR, "service_account.json")
API_KEY_FILE = os.path.join(BASE_DIR, "gemini_api_key.txt") # Standard für Gemini
SERP_API_KEY_FILE = os.path.join(BASE_DIR, "serpApiKey.txt")
GENDERIZE_API_KEY_FILE = os.path.join(BASE_DIR, "genderize_API_Key.txt")
API_KEY_FILE = os.path.join(BASE_DIR, "gemini_api_key.txt")
SERP_API_KEY_FILE = os.path.join(BASE_DIR, "serpapikey.txt")
GENDERIZE_API_KEY_FILE = os.path.join(BASE_DIR, "genderize_api_key.txt")
BRANCH_MAPPING_FILE = None
LOG_DIR = os.path.join(BASE_DIR, "Log")
LOG_DIR = os.path.join(BASE_DIR, "Log_from_docker") # Log in den gemounteten Ordner schreiben
# --- ML Modell Artefakte ---
MODEL_FILE = os.path.join(BASE_DIR, "technician_decision_tree_model.pkl")

View File

@@ -101,9 +101,9 @@ services:
- ./gtm_db_manager.py:/app/gtm_db_manager.py
- ./gtm_projects.db:/app/gtm_projects.db
- ./Log_from_docker:/app/Log_from_docker
# Mount API keys
- ./gemini_api_key.txt:/app/gemini_api_key.txt
- ./serpapikey.txt:/app/serpapikey.txt
- ./genderize_api_key.txt:/app/genderize_api_key.txt
environment:
- PYTHONUNBUFFERED=1
- DB_PATH=/app/gtm_projects.db

View File

@@ -1,4 +1,4 @@
google-genai
google-generativeai
requests
beautifulsoup4
werkzeug

View File

@@ -82,3 +82,20 @@ docker-compose up -d --build gtm-app
* **Logs:** Werden in `/app/Log_from_docker/` geschrieben.
* **Format:** `YY-MM-DD_HH-MM-SS_step_type.txt` (z.B. `phase1_extract_response.txt`).
* **Node.js Logs:** `docker logs gtm-app`.
## 6. Aktueller Status (Jan 2026) - FIXED & OPERATIONAL
Das System ist nach umfangreichen Fixes nun **voll funktionsfähig**.
* **[GELÖST] Gemini API Integration:**
* **Fehler:** `AttributeError: module 'google.generativeai' has no attribute 'Client'` (Veraltete API).
* **Fehler:** `404 models/gemini-1.5-flash... not found` (Falscher Modellname/Version).
* **Lösung:** Umstellung auf moderne `genai.GenerativeModel` Syntax. Implementierung einer **dynamischen Modell-Ermittlung** (`_get_best_flash_model`), die automatisch das beste verfügbare Modell (z.B. `gemini-1.5-flash`, `gemini-1.5-flash-001`) wählt, anstatt abzustürzen.
* **[GELÖST] Konfiguration & Pfade:**
* Korrekte Einbindung der API-Keys (`serpapikey.txt`, `genderize_api_key.txt`) durch Anpassung der Groß-/Kleinschreibung in `config.py` und Mounts in `docker-compose.yml`.
* Behebung von `IndentationError` in `config.py`.
* Hinzufügen der fehlenden `gtm-architect/requirements.txt` für korrekten Container-Build.
**Nächste Schritte:**
* Reguläre Nutzung des GTM Architect.
* Bei Bedarf: Erweiterung der `helpers.py` um weitere spezialisierte Modelle.

View File

@@ -56,9 +56,15 @@ try:
import google.genai as genai
HAS_GEMINI = True
except ImportError:
HAS_GEMINI = False
genai = None # Sicherstellen, dass genai definiert ist
logging.warning("google-genai Bibliothek nicht gefunden. KI-Funktionen deaktiviert.")
try:
# Fallback auf die ältere Bibliothek, falls die neue nicht da ist
import google.generativeai as genai
HAS_GEMINI = True
logging.warning("Veraltetes Paket 'google.generativeai' wird verwendet. Bitte auf 'google-genai' aktualisieren.")
except ImportError:
HAS_GEMINI = False
genai = None # Sicherstellen, dass genai definiert ist
logging.warning("Keine Google-KI-Bibliothek (weder google.genai noch google.generativeai) gefunden.")
# OpenAI Imports entfernen wir oder machen sie optional, um Verwirrung zu vermeiden
try:
@@ -320,51 +326,100 @@ def _get_gemini_api_key():
logger.error("CRITICAL: No API Key found in Config.API_KEYS or environment variables.")
raise ValueError("API Key missing.")
# Cache für den ermittelten Modellnamen, um API-Aufrufe zu sparen
_CACHED_MODEL_NAME = None
def _get_best_flash_model(api_key):
"""
Ermittelt dynamisch das beste verfügbare Flash-Modell.
Versucht, 'gemini-1.5-flash' zu finden, oder fällt auf Alternativen zurück.
"""
global _CACHED_MODEL_NAME
if _CACHED_MODEL_NAME:
return _CACHED_MODEL_NAME
logger = logging.getLogger(__name__)
default_model = "gemini-1.5-flash"
try:
if not HAS_GEMINI:
return default_model
genai.configure(api_key=api_key)
# Liste alle Modelle auf
models = list(genai.list_models())
flash_models = [m.name for m in models if 'flash' in m.name.lower() and 'generateContent' in m.supported_generation_methods]
# Bereinige die Namen (entferne 'models/' Präfix für den Vergleich, falls nötig)
clean_flash_models = [m.replace('models/', '') for m in flash_models]
logger.info(f"Gefundene Flash-Modelle: {clean_flash_models}")
# Priorisierung
if "gemini-1.5-flash" in clean_flash_models:
_CACHED_MODEL_NAME = "gemini-1.5-flash"
elif "gemini-1.5-flash-latest" in clean_flash_models:
_CACHED_MODEL_NAME = "gemini-1.5-flash-latest"
elif "gemini-1.5-flash-001" in clean_flash_models:
_CACHED_MODEL_NAME = "gemini-1.5-flash-001"
elif clean_flash_models:
_CACHED_MODEL_NAME = clean_flash_models[0] # Nimm das erste verfügbare
else:
logger.warning("Kein 'Flash'-Modell gefunden. Versuche Fallback auf 'gemini-pro'.")
pro_models = [m.name for m in models if 'pro' in m.name.lower() and 'generateContent' in m.supported_generation_methods]
if pro_models:
_CACHED_MODEL_NAME = pro_models[0].replace('models/', '')
else:
_CACHED_MODEL_NAME = default_model
logger.info(f"Ausgewähltes Gemini-Modell: {_CACHED_MODEL_NAME}")
return _CACHED_MODEL_NAME
except Exception as e:
logger.error(f"Fehler beim Ermitteln des Modells: {e}. Verwende Standard: {default_model}")
return default_model
@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.
Aktualisiert für die neue 'google-genai' Bibliothek (v1.0+).
Verwendet die korrekte `GenerativeModel` API.
"""
logger = logging.getLogger(__name__)
if not HAS_GEMINI:
logger.error("Fehler: google-genai Bibliothek fehlt.")
raise ImportError("google-genai not installed.")
logger.error("Fehler: google-generativeai Bibliothek fehlt.")
raise ImportError("google-generativeai not installed.")
api_key = _get_gemini_api_key()
try:
# NEU: Client-basierte Instanziierung für google-genai v1.0+
client = genai.Client(api_key=api_key)
genai.configure(api_key=api_key)
# Konfiguration für den Aufruf
config = {
generation_config = {
"temperature": temperature,
"top_p": 0.95,
"top_k": 40,
"max_output_tokens": 8192,
}
if json_mode:
config["response_mime_type"] = "application/json"
generation_config["response_mime_type"] = "application/json"
# Verwende eine spezifische Version, die oft stabiler ist
model_id = "gemini-1.5-flash-001"
# Dynamische Modell-Ermittlung
model_name = _get_best_flash_model(api_key)
response = client.models.generate_content(
model=model_id,
contents=prompt,
config=genai.types.GenerateContentConfig(
temperature=temperature,
top_p=0.95,
top_k=40,
max_output_tokens=8192,
response_mime_type="application/json" if json_mode else "text/plain",
system_instruction=system_instruction
)
model = genai.GenerativeModel(
model_name=model_name,
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
response = model.generate_content(contents)
return response.text.strip()
except Exception as e:
@@ -376,42 +431,16 @@ def call_gemini_flash(prompt, system_instruction=None, temperature=0.3, json_mod
@retry_on_failure
def call_openai_chat(prompt, temperature=0.3, model=None, response_format_json=False):
"""
Zentrale Funktion fuer KI API Aufrufe (jetzt Gemini via google-genai Client).
Zentrale Funktion fuer KI API Aufrufe (jetzt Gemini).
Leitet an `call_gemini_flash` weiter, um Code-Duplizierung zu vermeiden.
"""
logger = logging.getLogger(__name__)
# Lade Gemini API Key
api_key = _get_gemini_api_key()
if not HAS_GEMINI:
logger.error("Fehler: google-genai Bibliothek fehlt.")
raise ImportError("google-genai not installed.")
try:
# NEU: Client Instanziierung
client = genai.Client(api_key=api_key)
model_id = "gemini-1.5-flash-001"
response = client.models.generate_content(
model=model_id,
contents=prompt,
config=genai.types.GenerateContentConfig(
temperature=temperature,
top_p=0.95,
top_k=40,
max_output_tokens=8192,
response_mime_type="application/json" if response_format_json else "text/plain"
)
)
return response.text.strip()
except Exception as e:
logger.error(f"Fehler beim Gemini-Aufruf: {e}")
# Wenn der Key falsch ist, werfen wir einen lesbaren Fehler
if "API_KEY_INVALID" in str(e) or "403" in str(e):
raise ValueError(f"Invalid API Key: {str(e)}")
raise e
# Das 'model' Argument wird ignoriert, da wir jetzt fest auf Gemini Flash setzen.
return call_gemini_flash(
prompt=prompt,
temperature=temperature,
json_mode=response_format_json,
system_instruction=None # Alte Signatur hatte keine System-Instruction
)
# ... (Rest der Funktionen wie summarize_website_content bleiben, rufen aber jetzt die neue call_openai_chat auf)
def summarize_website_content(raw_text, company_name): return "k.A." # Placeholder