v1.1.3 - Bugfix Robuste KI-Antwortverarbeitung
- Bugfix: Behebt einen `JSONDecodeError`, der auftrat, wenn die OpenAI API eine Antwort mit einleitendem Text oder ohne JSON-Array zurückgab. - Die JSON-Extraktion in der KI-Klassifizierung wurde von einer einfachen String-Suche auf eine robuste Regex-Suche umgestellt. - Verbessertes Fehler-Logging: Bei einem Fehler bei der KI-Klassifizierung wird nun die vollständige, rohe API-Antwort ins Log geschrieben, um die Fehlersuche zu erleichtern.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# contact_grouping.py
|
||||
|
||||
__version__ = "v1.1.2" # Versionsnummer hochgezählt
|
||||
__version__ = "v1.1.3" # Versionsnummer hochgezählt
|
||||
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
import sys # NEU: Import für sauberen Abbruch
|
||||
import sys
|
||||
import pandas as pd
|
||||
|
||||
from google_sheet_handler import GoogleSheetHandler
|
||||
@@ -21,7 +21,6 @@ KEYWORD_RULES_FILE = "keyword_rules.json"
|
||||
DEFAULT_DEPARTMENT = "Undefined"
|
||||
|
||||
def setup_logging():
|
||||
"""Konfiguriert das Logging, um sowohl in der Konsole als auch in einer Datei zu loggen."""
|
||||
log_filename = create_log_filename("contact_grouping")
|
||||
log_level = logging.DEBUG
|
||||
|
||||
@@ -39,21 +38,12 @@ def setup_logging():
|
||||
|
||||
|
||||
class ContactGrouper:
|
||||
"""
|
||||
Kapselt die Logik zur automatischen Gruppierung von Kontakten
|
||||
basierend auf ihrem Jobtitel. Inklusive Lernfunktion via KI.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__ + ".ContactGrouper")
|
||||
# --- ÄNDERUNG: Initialisierung ist jetzt "faul" (lazy) ---
|
||||
self.exact_match_map = None
|
||||
self.keyword_rules = None
|
||||
|
||||
def load_knowledge_base(self):
|
||||
"""
|
||||
NEUE METHODE: Lädt die Wissensbasis-Dateien explizit.
|
||||
Gibt True bei Erfolg, False bei Fehlern zurück.
|
||||
"""
|
||||
self.logger.info("Lade Wissensbasis...")
|
||||
self.exact_match_map = self._load_json(EXACT_MATCH_FILE)
|
||||
self.keyword_rules = self._load_json(KEYWORD_RULES_FILE)
|
||||
@@ -66,7 +56,6 @@ class ContactGrouper:
|
||||
return True
|
||||
|
||||
def _load_json(self, file_path):
|
||||
"""Lädt eine JSON-Datei und gibt den Inhalt als Dictionary zurück."""
|
||||
if not os.path.exists(file_path):
|
||||
self.logger.error(f"Wissensbasis-Datei '{file_path}' nicht gefunden. Bitte 'knowledge_base_builder.py' ausführen.")
|
||||
return None
|
||||
@@ -79,8 +68,6 @@ class ContactGrouper:
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
self.logger.error(f"Fehler beim Laden oder Parsen der Datei '{file_path}': {e}")
|
||||
return None
|
||||
|
||||
# ... (alle anderen Methoden wie _normalize_job_title, _find_best_match etc. bleiben unverändert) ...
|
||||
|
||||
def _normalize_job_title(self, job_title):
|
||||
if not isinstance(job_title, str): return ""
|
||||
@@ -109,12 +96,12 @@ class ContactGrouper:
|
||||
top_departments = [dept for dept, score in scores.items() if score == max_score]
|
||||
|
||||
if len(top_departments) == 1:
|
||||
winner = top_departments[0]
|
||||
winner = top_departments
|
||||
self.logger.debug(f"'{job_title}' -> '{winner}' (Stufe 2: Keyword Match, Score {max_score})")
|
||||
return winner
|
||||
|
||||
best_priority = float('inf')
|
||||
winner = top_departments[0]
|
||||
winner = top_departments
|
||||
for department in top_departments:
|
||||
priority = self.keyword_rules[department].get("priority", 99)
|
||||
if priority < best_priority:
|
||||
@@ -129,24 +116,47 @@ class ContactGrouper:
|
||||
if not job_titles_to_classify: return {}
|
||||
|
||||
valid_departments = sorted([dept for dept in self.keyword_rules.keys() if dept != DEFAULT_DEPARTMENT])
|
||||
|
||||
prompt_parts = [
|
||||
"Du bist ein HR-Experte...",
|
||||
"Du bist ein HR-Experte, der Jobtitel präzise vordefinierten Abteilungen zuordnet.",
|
||||
"Analysiere die folgende Liste von Jobtiteln.",
|
||||
"Ordne JEDEN Jobtitel EINER der folgenden gültigen Abteilungen zu:",
|
||||
", ".join(valid_departments),
|
||||
"\nGib deine Antwort als valides JSON-Array von Objekten zurück, wobei jedes Objekt die Schlüssel 'job_title' und 'department' hat.",
|
||||
"Stelle sicher, dass die Antwort ausschließlich das JSON-Array enthält, ohne einleitenden Text oder Markdown-Formatierung.",
|
||||
"Beispiel: [{\"job_title\": \"Head of Fleet Management\", \"department\": \"Fuhrparkmanagement\"}]",
|
||||
"\n--- Zu klassifizierende Jobtitel ---",
|
||||
json.dumps(job_titles_to_classify, ensure_ascii=False)
|
||||
]
|
||||
prompt = "\n".join(prompt_parts)
|
||||
|
||||
|
||||
response_str = "" # Initialisieren für den Fehlerfall
|
||||
try:
|
||||
response_str = call_openai_chat(prompt, temperature=0.0, model="gpt-4o-mini", response_format_json=True)
|
||||
json_start = response_str.find('[')
|
||||
json_end = response_str.rfind(']')
|
||||
if json_start == -1 or json_end == -1: raise json.JSONDecodeError("Kein JSON-Array.", response_str, 0)
|
||||
json_str = response_str[json_start : json_end + 1]
|
||||
|
||||
# --- NEU: Robuste Regex-basierte JSON-Extraktion ---
|
||||
match = re.search(r'\[.*\]', response_str, re.DOTALL)
|
||||
|
||||
if not match:
|
||||
# NEU: Verbessertes Logging, um die Roh-Antwort zu sehen
|
||||
self.logger.error("Konnte kein JSON-Array in der KI-Antwort finden.")
|
||||
self.logger.debug(f"--- VOLLSTÄNDIGE ROH-ANTWORT DER API ---\n{response_str}\n------------------------------------")
|
||||
return {}
|
||||
|
||||
json_str = match.group(0)
|
||||
results_list = json.loads(json_str)
|
||||
|
||||
classified_map = {item['job_title']: item['department'] for item in results_list if item.get('department') in valid_departments}
|
||||
|
||||
self.logger.info(f"{len(classified_map)} Jobtitel erfolgreich von der KI klassifiziert.")
|
||||
return classified_map
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
self.logger.error(f"Fehler beim Parsen des extrahierten JSON aus der KI-Antwort: {e}")
|
||||
self.logger.debug(f"--- EXTRAHIERTER JSON-STRING, DER ZUM FEHLER FÜHRTE ---\n{json_str}\n------------------------------------")
|
||||
return {}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fehler bei der KI-Klassifizierung: {e}")
|
||||
self.logger.error(f"Ein unerwarteter Fehler ist bei der KI-Klassifizierung aufgetreten: {e}")
|
||||
return {}
|
||||
|
||||
def _append_learnings_to_source(self, gsh, new_mappings_df):
|
||||
@@ -158,9 +168,6 @@ class ContactGrouper:
|
||||
else: self.logger.error("Fehler beim Anhängen der Lern-Daten.")
|
||||
|
||||
def process_contacts(self):
|
||||
"""
|
||||
Führt den eigentlichen Verarbeitungsprozess aus, nachdem die Wissensbasis geladen wurde.
|
||||
"""
|
||||
self.logger.info("Starte Kontakt-Verarbeitung...")
|
||||
gsh = GoogleSheetHandler()
|
||||
df = gsh.get_sheet_as_dataframe(TARGET_SHEET_NAME)
|
||||
@@ -214,9 +221,8 @@ if __name__ == "__main__":
|
||||
|
||||
grouper = ContactGrouper()
|
||||
|
||||
# NEU: Expliziter Lade-Schritt mit Fehlerprüfung
|
||||
if not grouper.load_knowledge_base():
|
||||
logging.critical("Skript-Abbruch aufgrund von Fehlern beim Laden der Wissensbasis.")
|
||||
sys.exit(1) # Beendet das Skript mit einem Fehlercode
|
||||
sys.exit(1)
|
||||
|
||||
grouper.process_contacts()
|
||||
Reference in New Issue
Block a user