bugfix
This commit is contained in:
@@ -228,10 +228,10 @@ logger = logging.getLogger(__name__)
|
|||||||
import random # Für Jitter im Backoff
|
import random # Für Jitter im Backoff
|
||||||
import time # Für sleep
|
import time # Für sleep
|
||||||
# logging ist bereits importiert
|
# logging ist bereits importiert
|
||||||
import requests # Für requests.exceptions
|
import requests # Für requests.exceptions (RequestException, HTTPError)
|
||||||
import gspread # Für gspread.exceptions
|
import gspread # Für gspread.exceptions (APIError, SpreadsheetNotFound)
|
||||||
import openai # Für openai.error
|
import openai # Für openai.error (OpenAIError, AuthenticationError, InvalidRequestError etc.)
|
||||||
import wikipedia # Für wikipedia.exceptions
|
import wikipedia # Für wikipedia.exceptions (WikipediaException, PageError, DisambiguationError etc.)
|
||||||
# traceback ist bereits importiert
|
# traceback ist bereits importiert
|
||||||
# re ist bereits importiert
|
# re ist bereits importiert
|
||||||
# csv ist bereits importiert
|
# csv ist bereits importiert
|
||||||
@@ -252,7 +252,7 @@ decorator_logger = logging.getLogger(__name__ + ".Retry")
|
|||||||
|
|
||||||
|
|
||||||
# --- Retry Decorator ---
|
# --- Retry Decorator ---
|
||||||
# KORRIGIERTE Version
|
# KORRIGIERTE Version (Behandelt SpreadsheetNotFound und 404 HTTPError explizit)
|
||||||
def retry_on_failure(func):
|
def retry_on_failure(func):
|
||||||
"""
|
"""
|
||||||
Decorator, der eine Funktion bei bestimmten Fehlern mehrmals wiederholt.
|
Decorator, der eine Funktion bei bestimmten Fehlern mehrmals wiederholt.
|
||||||
@@ -276,7 +276,7 @@ def retry_on_failure(func):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Fehler loggen und weitergeben, wenn keine Retries konfiguriert sind
|
# Fehler loggen und weitergeben, wenn keine Retries konfiguriert sind
|
||||||
decorator_logger.error(f"FEHLER bei '{effective_func_name}' (keine Retries konfiguriert). {type(e).__name__} - {str(e)[:150]}...")
|
decorator_logger.error(f"FEHLER bei '{effective_func_name}' (keine Retries konfiguriert). {type(e).__name__} - {str(e)[:150]}...")
|
||||||
# Log traceback für unerwartete Fehler
|
# Log traceback für unerwartete Fehler (nicht die spezifischen API/Netzwerkfehler)
|
||||||
if not isinstance(e, (requests.exceptions.RequestException, gspread.exceptions.APIError, openai.error.OpenAIError, wikipedia.exceptions.WikipediaException)):
|
if not isinstance(e, (requests.exceptions.RequestException, gspread.exceptions.APIError, openai.error.OpenAIError, wikipedia.exceptions.WikipediaException)):
|
||||||
decorator_logger.exception("Details zum Fehler:")
|
decorator_logger.exception("Details zum Fehler:")
|
||||||
raise e # Re-raise the exception
|
raise e # Re-raise the exception
|
||||||
@@ -293,6 +293,28 @@ def retry_on_failure(func):
|
|||||||
return func(*args, **kwargs) # Call the original function
|
return func(*args, **kwargs) # Call the original function
|
||||||
|
|
||||||
# Spezifische Exceptions, die ein Retry rechtfertigen
|
# Spezifische Exceptions, die ein Retry rechtfertigen
|
||||||
|
# Fangen Sie nicht-wiederholbare Fehler separat
|
||||||
|
except (gspread.exceptions.SpreadsheetNotFound, openai.error.AuthenticationError, ValueError) as e:
|
||||||
|
# Diese Fehler deuten auf ein permanentes Problem hin (falsche URL, falscher Key, falsche Eingabe)
|
||||||
|
decorator_logger.critical(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}': Permanentes Problem erkannt. {type(e).__name__} - {str(e)[:150]}...")
|
||||||
|
decorator_logger.exception("Details:") # Log traceback für permanente Fehler
|
||||||
|
raise e # Leiten Sie diese Exception sofort weiter
|
||||||
|
|
||||||
|
# Fangen Sie Requests HTTP Errors (wie 404)
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response is not None:
|
||||||
|
status_code = e.response.status_code
|
||||||
|
if status_code in [404, 400]: # Fügen Sie hier weitere Status-Codes hinzu, die NICHT wiederholt werden sollen
|
||||||
|
decorator_logger.critical(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}': HTTP Fehler {status_code} erhalten ({e.response.reason}). Nicht wiederholbar. {str(e)[:150]}...")
|
||||||
|
decorator_logger.exception("Details:") # Log traceback
|
||||||
|
raise e # Leiten Sie diese nicht-wiederholbare Exception sofort weiter
|
||||||
|
# Ansonsten behandle HTTP Errors wie andere RequestExceptions (weiter unten)
|
||||||
|
|
||||||
|
# Wenn kein Response-Objekt oder kein spezifischer Statuscode gehandhabt wurde,
|
||||||
|
# lassen Sie diesen Fehler durchfallen zur allgemeinen RequestException Behandlung.
|
||||||
|
|
||||||
|
|
||||||
|
# Fangen Sie andere wiederholbare Exceptions (Netzwerk, Rate Limit, Timeout etc.)
|
||||||
except (requests.exceptions.RequestException, gspread.exceptions.APIError, openai.error.OpenAIError, wikipedia.exceptions.WikipediaException) as e:
|
except (requests.exceptions.RequestException, gspread.exceptions.APIError, openai.error.OpenAIError, wikipedia.exceptions.WikipediaException) as e:
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
error_type = type(e).__name__
|
error_type = type(e).__name__
|
||||||
@@ -304,20 +326,19 @@ def retry_on_failure(func):
|
|||||||
decorator_logger.warning(f"🚦 RATE LIMIT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
decorator_logger.warning(f"🚦 RATE LIMIT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
||||||
elif isinstance(e, requests.exceptions.Timeout):
|
elif isinstance(e, requests.exceptions.Timeout):
|
||||||
decorator_logger.warning(f"⏰ TIMEOUT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
decorator_logger.warning(f"⏰ TIMEOUT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
||||||
elif isinstance(e, requests.exceptions.RequestException):
|
elif isinstance(e, requests.exceptions.RequestException): # Allgemeine RequestException
|
||||||
decorator_logger.warning(f"🌐 NETZWERKFEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
decorator_logger.warning(f"🌐 NETZWERKFEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
||||||
elif isinstance(e, openai.error.OpenAIError):
|
elif isinstance(e, openai.error.OpenAIError): # Allgemeine OpenAI Fehler
|
||||||
decorator_logger.warning(f"🤖 OPENAI FEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
decorator_logger.warning(f"🤖 OPENAI FEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
||||||
elif isinstance(e, wikipedia.exceptions.WikipediaException):
|
elif isinstance(e, wikipedia.exceptions.WikipediaException): # Allgemeine Wikipedia Fehler
|
||||||
decorator_logger.warning(f"📚 WIKIPEDIA FEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
decorator_logger.warning(f"📚 WIKIPEDIA FEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
||||||
else: # Andere spezifisch behandelte Exceptions
|
else: # Andere wiederholbare Exceptions
|
||||||
decorator_logger.warning(f"⚠️ BEHANDELTER FEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
decorator_logger.warning(f"♻️ WIEDERHOLBARER FEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...")
|
||||||
|
|
||||||
time.sleep(wait_time) # Warte vor dem nächsten Versuch
|
time.sleep(wait_time) # Warte vor dem nächsten Versuch
|
||||||
else: # Letzter Versuch fehlgeschlagen
|
else: # Letzter Versuch fehlgeschlagen
|
||||||
decorator_logger.error(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}' nach {max_retries_config} Versuchen.")
|
decorator_logger.error(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}' nach {max_retries_config} Versuchen.")
|
||||||
# Log traceback für erwartete Fehler beim endgültigen Scheitern
|
# Log traceback für erwartete Fehler beim endgültigen Scheitern
|
||||||
# Log traceback nur bei unerwarteten Fehlern (nicht den, der zum Retry führte)
|
|
||||||
# decorator_logger.exception("Details zum endgültigen Fehler:") # Kann zu viel Lärm machen
|
# decorator_logger.exception("Details zum endgültigen Fehler:") # Kann zu viel Lärm machen
|
||||||
raise e # Leite die ursprüngliche Exception weiter
|
raise e # Leite die ursprüngliche Exception weiter
|
||||||
|
|
||||||
@@ -332,21 +353,10 @@ def retry_on_failure(func):
|
|||||||
# und eine Exception immer zu einer raise e Anweisung führt.
|
# und eine Exception immer zu einer raise e Anweisung führt.
|
||||||
# Wenn die Schleife endet, ohne zurückzukehren oder eine Exception zu werfen,
|
# Wenn die Schleife endet, ohne zurückzukehren oder eine Exception zu werfen,
|
||||||
# deutet dies auf einen Logikfehler im Decorator hin.
|
# deutet dies auf einen Logikfehler im Decorator hin.
|
||||||
# Als Schutzmechanismus werfen wir hier eine RuntimeError, falls dieser Punkt doch erreicht wird.
|
raise RuntimeError(f"Retry decorator logic error: Loop completed unexpectedly for {effective_func_name}. This should not happen.")
|
||||||
# Oder geben None zurück, falls das erwartete Verhalten bei endgültigem Fehler None ist.
|
|
||||||
# Angesichts der Tatsache, dass die aufgerufenen Funktionen oft None bei Fehler zurückgeben,
|
|
||||||
# geben wir hier None zurück, anstatt eine RuntimeError zu werfen.
|
|
||||||
# Dies erfordert, dass die aufgerufene Funktion im Falle eines Fehlers IMMER eine Exception wirft,
|
|
||||||
# die vom Decorator gefangen und (beim letzten Versuch) weitergeleitet wird.
|
|
||||||
# Wenn die dekorierte Funktion selbst interne Fehler fängt und None zurückgibt,
|
|
||||||
# wird der Decorator dies als erfolgreichen Durchlauf interpretieren und None zurückgeben.
|
|
||||||
# Das ist das erwartete Verhalten.
|
|
||||||
# Entfernen Sie die RuntimeError und lassen Sie die Funktion implizit None zurückgeben,
|
|
||||||
# wenn der try/except/raise Block nicht erreicht wurde.
|
|
||||||
pass # Dies sollte nicht erreicht werden, wenn eine Exception geworfen wird.
|
|
||||||
|
|
||||||
# Die wrapper Funktion muss am Ende zurückgegeben werden
|
|
||||||
return wrapper
|
return wrapper # Gibt die Wrapper-Funktion zurück
|
||||||
|
|
||||||
|
|
||||||
# --- Token Count Funktion ---
|
# --- Token Count Funktion ---
|
||||||
@@ -471,7 +481,7 @@ def normalize_company_name(name):
|
|||||||
"""Entfernt gängige Rechtsformzusätze etc. für Vergleiche."""
|
"""Entfernt gängige Rechtsformzusätze etc. für Vergleiche."""
|
||||||
if not name: return ""
|
if not name: return ""
|
||||||
name = clean_text(name)
|
name = clean_text(name)
|
||||||
forms = [ r'gmbh', r'ges\.?\s*m\.?\s*b\.?\s*h\.?', r'gesellschaft mit beschränkter haftung', r'ug', r'u\.g\.', r'unternehmergesellschaft', r'haftungsbeschränkt', r'ag', r'a\.g\.', r'aktiengesellschaft', r'ohg', r'o\.h\.g\.', r'offene handelsgesellschaft', r'kg', r'k\.g\.', r'kommanditgesellschaft', r'gmbh\s*&\s*co\.?\s*kg', r'ges\.?\s*m\.?\s*b\.?\s*h\.?\s*&\s*co\.?\s*k\.g\.?', r'ag\s*&\s*co\.?\s*kg', r'a\.g\.?\s*&\s*co\.?\s*k\.g\.?', r'e\.k\.', r'e\.kfm\.', r'e\.kfr\.', r'eingetragene[rn]? kauffrau', r'eingetragene[rn]? kaufmann', r'ltd\.?', r'limited', r'ltd\s*&\s*co\.?\s*kg', r's\.?a\.?r\.?l\.?', r'sàrl', r'sagl', r's\.?a\.?', r'société anonyme', r'sociedad anónima', r's\.?p\.?a\.?', r'società per azioni', r'b\.?v\.?', r'besloten vennootschap', r'n\.?v\.?', r'naamloze vennootschap', r'plc\.?', r'public limited company', 'inc', 'incorporated', r'corp\.?', 'corporation', 'llc', 'limited liability company', r'kgaa', r'kommanditgesellschaft auf aktien', 'se', 'societas europaea', r'e\.?g\.?', r'eingetragene genossenschaft', 'genossenschaft', 'genmbh', r'e\.?v\.?', r'eingetragener verein', 'verein', 'stiftung', 'ggmbh', r'gemeinnützige gmbh', 'gug', 'partg', 'partnerschaftsgesellschaft', 'partgmbb', 'og', r'o\.g\.', 'offene gesellschaft', r'e\.u\.', 'eingetragenes unternehmen', r'ges\.?n\.?b\.?r\.?', r'gesellschaft nach bürgerlichem recht', 'kollektivgesellschaft', 'einzelfirma', 'gruppe', 'holding', 'international', 'systeme', 'technik', 'logistik', 'solutions', 'services', 'management', 'consulting', 'produktion', 'vertrieb', 'entwicklung', 'maschinenbau', 'anlagenbau'
|
forms = [ r'gmbh', r'ges\.?\s*m\.?\s*b\.?\s*h\.?', r'gesellschaft mit beschränkter haftung', r'ug', r'u\.g\.', r'unternehmergesellschaft', r'haftungsbeschränkt', r'ag', r'a\.g\.', r'aktiengesellschaft', r'ohg', r'o\.h\.g\.', r'offene handelsgesellschaft', r'kg', r'k\.g\.', r'kommanditgesellschaft', r'gmbh\s*&\s*co\.?\s*kg', r'ges\.?\s*m\.?\s*b\.?\s*h\.?\s*&\s*co\.?\s*k\.g\.?', r'ag\s*&\s*co\.?\s*kg', r'a\.g\.?\s*&\s*co\.?\s*k\.g\.?', r'e\.k\.', r'e\.kfm\.', r'e\.kfr\.', r'eingetragene[rn]? kauffrau', r'eingetragene[rn]? kaufmann', r'ltd\.?', r'limited', r'ltd\s*&\s*co\.?\s*kg', r's\.?a\.?r\.?l\.?', r'sàrl', r'sagl', r's\.?a\.?', r'société anonyme', r'sociedad anónima', r's\.?p\.?a\.?', r'società per azioni', r'b\.?v\.?', r'besloten vennootschap', r'n\.?v\.?', r'naamloze vennootschap', r'plc\.?', r'public limited company', 'inc', 'incorporated', r'corp\.?', 'corporation', 'llc', 'limited liability company', r'kgaa', r'kommanditgesellschaft auf aktien', 'se', 'societas europaea', r'e\.?g\.?', r'eingetragene genossenschaft', 'genossenschaft', 'genmbh', r'e\.?v\.?', r'eingetragener verein', 'verein', 'stiftung', 'ggmbh', r'gemeinnützige gmbh', r'gemeinnützige[rn]? gmbh', 'gug', 'partg', 'partnerschaftsgesellschaft', 'partgmbb', 'og', r'o\.g\.', 'offene gesellschaft', r'e\.u\.', 'eingetragenes unternehmen', r'ges\.?n\.?b\.?r\.?', r'gesellschaft nach bürgerlichem recht', 'kollektivgesellschaft', 'einzelfirma', 'gruppe', 'holding', 'international', 'systeme', 'technik', 'logistik', 'solutions', 'services', 'management', 'consulting', 'produktion', 'vertrieb', 'entwicklung', 'maschinenbau', 'anlagenbau'
|
||||||
]
|
]
|
||||||
# Pattern für ganze Wörter (case-insensitive)
|
# Pattern für ganze Wörter (case-insensitive)
|
||||||
# Fügen Sie \b hinzu, um sicherzustellen, dass ganze Wörter gematcht werden (z.B. nicht "ag" in "manage")
|
# Fügen Sie \b hinzu, um sicherzustellen, dass ganze Wörter gematcht werden (z.B. nicht "ag" in "manage")
|
||||||
@@ -514,7 +524,7 @@ def extract_numeric_value(raw_value, is_umsatz=False):
|
|||||||
# logger.debug(f"extract_numeric_value: Verarbeite Wert: '{raw_value_str}' -> '{processed_value}' (is_umsatz={is_umsatz})") # Zu viel Lärm
|
# logger.debug(f"extract_numeric_value: Verarbeite Wert: '{raw_value_str}' -> '{processed_value}' (is_umsatz={is_umsatz})") # Zu viel Lärm
|
||||||
|
|
||||||
# Entferne gängige Präfixe/Suffixe und Spannen-Trennzeichen
|
# Entferne gängige Präfixe/Suffixe und Spannen-Trennzeichen
|
||||||
processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|über|unter|mehr als|weniger als|bis zu)\s+', '', processed_value)
|
processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|über|under|mehr als|weniger als|bis zu)\s+', '', processed_value)
|
||||||
processed_value = re.sub(r'[€$£¥]', '', processed_value).strip()
|
processed_value = re.sub(r'[€$£¥]', '', processed_value).strip()
|
||||||
processed_value = re.split(r'\s*(-|–|bis)\s*', processed_value, 1)[0].strip() # Nimm nur den ersten Teil bei Spannen
|
processed_value = re.split(r'\s*(-|–|bis)\s*', processed_value, 1)[0].strip() # Nimm nur den ersten Teil bei Spannen
|
||||||
|
|
||||||
@@ -823,15 +833,16 @@ def call_openai_chat(prompt, temperature=0.3, model=None):
|
|||||||
|
|
||||||
if not prompt:
|
if not prompt:
|
||||||
logger.error("Fehler: Leerer Prompt für OpenAI.")
|
logger.error("Fehler: Leerer Prompt für OpenAI.")
|
||||||
# Werfen Sie eine Value Error Exception
|
# Werfen Sie einen Value Error Exception
|
||||||
raise ValueError("Leerer Prompt für OpenAI.")
|
raise ValueError("Leerer Prompt für OpenAI.")
|
||||||
|
|
||||||
current_model = model if model else getattr(Config, 'TOKEN_MODEL', 'gpt-3.5-turbo')
|
current_model = model if model else getattr(Config, 'TOKEN_MODEL', 'gpt-3.5-turbo')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Token zählen vor dem Senden (optional, gut für Debugging/Monitoring)
|
# Token zählen vor dem Senden (optional, gut für Debugging/Monitoring)
|
||||||
# prompt_tokens = token_count(prompt, model=current_model)
|
# try: prompt_tokens = token_count(prompt, model=current_model); logger.debug(f"Sende Prompt an OpenAI ({current_model}, geschätzt {prompt_tokens} Tokens)...");
|
||||||
# logger.debug(f"Sende Prompt an OpenAI ({current_model}, geschätzt {prompt_tokens} Tokens)...") # Zu viel Lärm
|
# except Exception as e_tc: logger.debug(f"Fehler beim Token-Zählen: {e_tc}"); # Logge Fehler beim Token-Zählen
|
||||||
|
|
||||||
|
|
||||||
# Der OpenAI Call selbst kann Exceptions werfen (APIError, RateLimitError, InvalidRequestError etc.)
|
# Der OpenAI Call selbst kann Exceptions werfen (APIError, RateLimitError, InvalidRequestError etc.)
|
||||||
# Diese werden vom @retry_on_failure Decorator behandelt.
|
# Diese werden vom @retry_on_failure Decorator behandelt.
|
||||||
@@ -850,9 +861,8 @@ def call_openai_chat(prompt, temperature=0.3, model=None):
|
|||||||
result = response.choices[0].message.content.strip()
|
result = response.choices[0].message.content.strip()
|
||||||
|
|
||||||
# Token zählen für die Antwort (optional)
|
# Token zählen für die Antwort (optional)
|
||||||
# completion_tokens = token_count(result, model=current_model)
|
# try: completion_tokens = token_count(result, model=current_model); total_tokens = response.usage.total_tokens; logger.debug(f"OpenAI Antwort erhalten ({completion_tokens} Completion Tokens, {total_tokens} Gesamt).");
|
||||||
# total_tokens = response.usage.total_tokens # Usage info kann direkt aus response kommen
|
# except Exception as e_tc: logger.debug(f"Fehler beim Token-Zählen der Antwort: {e_tc}"); # Logge Fehler beim Token-Zählen
|
||||||
# logger.debug(f"OpenAI Antwort erhalten ({completion_tokens} Completion Tokens, {total_tokens} Gesamt).") # Zu viel Lärm
|
|
||||||
|
|
||||||
return result # Gibt den bereinigten Antwortstring zurück
|
return result # Gibt den bereinigten Antwortstring zurück
|
||||||
|
|
||||||
@@ -861,11 +871,7 @@ def call_openai_chat(prompt, temperature=0.3, model=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Loggen Sie den unerwarteten Fehler
|
# Loggen Sie den unerwarteten Fehler
|
||||||
logger.error(f"Allgemeiner Fehler während OpenAI-Aufruf: {type(e).__name__} - {e}")
|
logger.error(f"Allgemeiner Fehler während OpenAI-Aufruf: {type(e).__name__} - {e}")
|
||||||
# Loggen Sie den Traceback für unerwartete Fehler
|
|
||||||
# logger.exception("Traceback des allgemeinen Fehlers:") # Der Retry-Decorator loggt das
|
|
||||||
|
|
||||||
# Werfen Sie die Exception erneut, damit der retry_on_failure Decorator sie fangen kann.
|
# Werfen Sie die Exception erneut, damit der retry_on_failure Decorator sie fangen kann.
|
||||||
# Dies ist wichtig, damit der Decorator den Fehler erkennt und Retries durchführt.
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
@@ -886,7 +892,7 @@ def summarize_website_content(raw_text):
|
|||||||
prompt = (
|
prompt = (
|
||||||
"Du bist ein KI-Assistent, der Webinhalte analysiert.\n"
|
"Du bist ein KI-Assistent, der Webinhalte analysiert.\n"
|
||||||
"Fasse den folgenden Text einer Unternehmenswebsite prägnant zusammen. "
|
"Fasse den folgenden Text einer Unternehmenswebsite prägnant zusammen. "
|
||||||
"Konzentriere dich auf:\n"
|
"Konzentriere dich dabei auf:\n"
|
||||||
"- Haupttätigkeitsfeld des Unternehmens\n"
|
"- Haupttätigkeitsfeld des Unternehmens\n"
|
||||||
"- Wichtigste Produkte und/oder Dienstleistungen\n"
|
"- Wichtigste Produkte und/oder Dienstleistungen\n"
|
||||||
"- Zielgruppe (falls erkennbar)\n\n"
|
"- Zielgruppe (falls erkennbar)\n\n"
|
||||||
@@ -897,7 +903,6 @@ def summarize_website_content(raw_text):
|
|||||||
# Call_openai_chat nutzt den retry_on_failure Decorator.
|
# Call_openai_chat nutzt den retry_on_failure Decorator.
|
||||||
# Wenn call_openai_chat nach Retries eine Exception wirft, wird diese hier nicht gefangen,
|
# Wenn call_openai_chat nach Retries eine Exception wirft, wird diese hier nicht gefangen,
|
||||||
# sondern weitergereicht (z.B. an _process_single_row), was gut ist.
|
# sondern weitergereicht (z.B. an _process_single_row), was gut ist.
|
||||||
# Wenn call_openai_chat erfolgreich ist, gibt es den String oder None zurück (obwohl es jetzt Exception bei Fehlern wirft).
|
|
||||||
try:
|
try:
|
||||||
summary = call_openai_chat(prompt, temperature=0.2)
|
summary = call_openai_chat(prompt, temperature=0.2)
|
||||||
return summary if summary and summary.strip() else "k.A. (Keine Zusammenfassung erhalten)"
|
return summary if summary and summary.strip() else "k.A. (Keine Zusammenfassung erhalten)"
|
||||||
@@ -922,6 +927,7 @@ def summarize_batch_openai(tasks_data):
|
|||||||
dict: Ein Dictionary, das Zeilennummern auf ihre Zusammenfassungen mappt.
|
dict: Ein Dictionary, das Zeilennummern auf ihre Zusammenfassungen mappt.
|
||||||
z.B. {2122: "Zusammenfassung A", 2123: "Zusammenfassung B"}
|
z.B. {2122: "Zusammenfassung A", 2123: "Zusammenfassung B"}
|
||||||
Bei Fehlern oder fehlenden Zusammenfassungen wird ein Fehlerstring verwendet.
|
Bei Fehlern oder fehlenden Zusammenfassungen wird ein Fehlerstring verwendet.
|
||||||
|
Wirft Exception bei API-Fehlern nach Retries.
|
||||||
"""
|
"""
|
||||||
if not tasks_data: return {}
|
if not tasks_data: return {}
|
||||||
|
|
||||||
@@ -975,28 +981,30 @@ def summarize_batch_openai(tasks_data):
|
|||||||
final_prompt = "\n".join(prompt_parts)
|
final_prompt = "\n".join(prompt_parts)
|
||||||
|
|
||||||
# Optional: Token zählen zur Info, aber nicht zur Blockade
|
# Optional: Token zählen zur Info, aber nicht zur Blockade
|
||||||
# try: prompt_tokens = token_count(final_prompt); logger.debug(f"Geschätzte Prompt-Tokens für Batch: {prompt_tokens} (Limit ca. 4096 für gpt-3.5-turbo)");
|
# try: prompt_tokens = token_count(final_prompt, model=getattr(Config, 'TOKEN_MODEL', 'gpt-3.5-turbo')); logger.debug(f"Geschätzt Prompt-Tokens für Batch: {prompt_tokens}.");
|
||||||
# except Exception as e_tc: logger.debug(f"Fehler beim Token-Zählen: {e_tc}");
|
# except Exception as e_tc: logger.debug(f"Fehler beim Token-Zählen: {e_tc}");
|
||||||
|
|
||||||
|
|
||||||
# --- OpenAI API Call (Die API wirft Fehler bei Token-Limit oder anderen Problemen) ---
|
# --- OpenAI API Call ---
|
||||||
# call_openai_chat nutzt den retry_on_failure Decorator und wirft bei endgültigem Fehler eine Exception
|
# call_openai_chat nutzt den retry_on_failure Decorator und wirft bei endgültigem Fehler eine Exception.
|
||||||
chat_response = None
|
# Der retry_on_failure Decorator DIESER summarize_batch_openai Funktion fängt die Exception
|
||||||
|
# und führt die Retries für die GESAMTE Batch-Funktion durch.
|
||||||
try:
|
try:
|
||||||
chat_response = call_openai_chat(final_prompt, temperature=0.2)
|
chat_response = call_openai_chat(final_prompt, temperature=0.2)
|
||||||
# Wenn call_openai_chat erfolgreich ist, gibt es den String zurück.
|
# Wenn call_openai_chat erfolgreich ist, gibt es den String zurück.
|
||||||
# Exceptions werden nach Retries geworfen.
|
# Exceptions werden nach Retries geworfen und vom äußeren retry_on_failure dieser Funktion gefangen.
|
||||||
|
|
||||||
if not chat_response:
|
if not chat_response:
|
||||||
# Dieser Fall sollte nach der Änderung in call_openai_chat nicht mehr auftreten (würde Exception werfen)
|
# Dieser Fall sollte nach der Änderung in call_openai_chat nicht mehr auftreten (würde Exception werfen)
|
||||||
logger.error("call_openai_chat gab unerwarteterweise None zurück für Batch-Zusammenfassung.")
|
logger.error("call_openai_chat gab unerwarteterweise None zurück für Batch-Zusammenfassung.")
|
||||||
# Behandeln Sie dies als Fehler für alle Zeilen im Batch
|
# Werfen Sie eine spezifische Exception, damit der äußere Decorator sie fängt
|
||||||
return {row_num: "FEHLER (OpenAI None Antwort)" for row_num in row_numbers_in_batch}
|
raise openai.error.APIError("Keine Antwort von OpenAI erhalten für Batch-Zusammenfassung.")
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Wenn call_openai_chat nach Retries eine Exception wirft
|
# Wenn call_openai_chat oder der äußere retry_on_failure eine Exception wirft
|
||||||
logger.error(f"Endgültiger FEHLER beim OpenAI-Batch-Aufruf für Zusammenfassung: {e}")
|
# Die Exception wird hier gefangen, bevor sie an den Aufrufer (DataProcessor Methode) weitergeleitet wird.
|
||||||
|
logger.error(f"Endgültiger FEHLER beim OpenAI-Batch-Aufruf für Zusammenfassung (innerhalb Batch Decorator): {e}")
|
||||||
# Geben Sie ein Dictionary zurück, das signalisiert, dass für alle Zeilen im Batch ein Fehler aufgetreten ist
|
# Geben Sie ein Dictionary zurück, das signalisiert, dass für alle Zeilen im Batch ein Fehler aufgetreten ist
|
||||||
return {row_num: f"FEHLER API: {str(e)[:100]}" for row_num in row_numbers_in_batch}
|
return {row_num: f"FEHLER API: {str(e)[:100]}" for row_num in row_numbers_in_batch}
|
||||||
|
|
||||||
@@ -1033,7 +1041,7 @@ def summarize_batch_openai(tasks_data):
|
|||||||
original_row_nums = {t['row_num'] for t in tasks_data}
|
original_row_nums = {t['row_num'] for t in tasks_data}
|
||||||
for row_num in original_row_nums:
|
for row_num in original_row_nums:
|
||||||
if row_num not in summaries:
|
if row_num not in summaries:
|
||||||
summaries[row_num] = "k.A. (Ungültiger Rohtext im Batch)"
|
summaries[row_num] = "k.A. (Kein gültiger Rohtext im Batch)"
|
||||||
|
|
||||||
|
|
||||||
return summaries # Rückgabe des Dictionarys mit Ergebnissen oder Fehlern
|
return summaries # Rückgabe des Dictionarys mit Ergebnissen oder Fehlern
|
||||||
@@ -1060,7 +1068,7 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg
|
|||||||
dict: Enthält "branch" (die finale, gültige Kurzform oder Fehler),
|
dict: Enthält "branch" (die finale, gültige Kurzform oder Fehler),
|
||||||
"consistency" ('ok', 'X', 'fallback_crm_valid', 'fallback_invalid', 'error_...'),
|
"consistency" ('ok', 'X', 'fallback_crm_valid', 'fallback_invalid', 'error_...'),
|
||||||
"justification" (Begründung von ChatGPT oder Fallback-Info).
|
"justification" (Begründung von ChatGPT oder Fallback-Info).
|
||||||
Wirft Exception bei API-Fehlern.
|
Wirft Exception bei API-Fehlern (von call_openai_chat nach Retries).
|
||||||
"""
|
"""
|
||||||
global ALLOWED_TARGET_BRANCHES, TARGET_SCHEMA_STRING
|
global ALLOWED_TARGET_BRANCHES, TARGET_SCHEMA_STRING
|
||||||
|
|
||||||
@@ -1245,7 +1253,7 @@ def serp_website_lookup(company_name):
|
|||||||
serp_key = Config.API_KEYS.get('serpapi')
|
serp_key = Config.API_KEYS.get('serpapi')
|
||||||
if not serp_key:
|
if not serp_key:
|
||||||
logger.error("Fehler: SerpAPI Key nicht verfügbar für Website Lookup.")
|
logger.error("Fehler: SerpAPI Key nicht verfügbar für Website Lookup.")
|
||||||
# Werfen Sie eine Exception, damit retry_on_failure dies behandeln kann
|
# Werfen Sie eine Exception, damit retry_on_failure dies behandelt (oder nicht, je nach Config)
|
||||||
raise ConnectionRefusedError("SerpAPI Key nicht konfiguriert.")
|
raise ConnectionRefusedError("SerpAPI Key nicht konfiguriert.")
|
||||||
|
|
||||||
if not company_name or str(company_name).strip() == "":
|
if not company_name or str(company_name).strip() == "":
|
||||||
|
|||||||
Reference in New Issue
Block a user