Fix: Gemini API modernization, dynamic model selection, and config path corrections
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||
14
config.py
14
config.py
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
google-genai
|
||||
google-generativeai
|
||||
requests
|
||||
beautifulsoup4
|
||||
werkzeug
|
||||
|
||||
@@ -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.
|
||||
|
||||
149
helpers.py
149
helpers.py
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user