Frajosgro commited on
Commit
9eb915e
·
verified ·
1 Parent(s): d022e3f

Upload 5 files

Browse files
Files changed (5) hide show
  1. utils/api_handler.py +183 -0
  2. utils/constants.py +21 -0
  3. utils/helpers.py +15 -0
  4. utils/logger.py +143 -0
  5. utils/security.py +97 -0
utils/api_handler.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ utils/api_handler.py - API-Kommunikationsmodul für den Dr. Franz Psychochatbot
3
+
4
+ Dieses Modul verwaltet die Kommunikation mit der HuggingFace Inference API:
5
+ - Senden von Anfragen
6
+ - Verarbeiten von Antworten
7
+ - Fehlerbehandlung und Wiederholungslogik
8
+ """
9
+
10
+ import requests
11
+ import time
12
+ import random
13
+ import json
14
+ from typing import Dict, Any, Optional, List, Union
15
+
16
+ # Importieren der Konfiguration
17
+ import config
18
+
19
+ class ApiHandler:
20
+ """Klasse zur Verwaltung der API-Kommunikation"""
21
+
22
+ def __init__(self, api_token: str = config.API_TOKEN, model_id: str = config.MODEL_ID):
23
+ """
24
+ Initialisiert den API-Handler
25
+
26
+ Args:
27
+ api_token: Der API-Token für HuggingFace
28
+ model_id: Die ID des zu verwendenden Modells
29
+ """
30
+ self.api_token = api_token
31
+ self.model_id = model_id
32
+ self.api_url = f"{config.API_BASE_URL}{model_id}"
33
+ self.headers = {"Authorization": f"Bearer {api_token}"}
34
+ self.max_retries = 3
35
+ self.retry_delay = 2 # Sekunden
36
+
37
+ def generate_text(self, prompt: str, params: Optional[Dict[str, Any]] = None) -> str:
38
+ """
39
+ Generiert Text über die HuggingFace Inference API
40
+
41
+ Args:
42
+ prompt: Der Prompt für die Textgenerierung
43
+ params: Optional, Parameter für die Textgenerierung
44
+
45
+ Returns:
46
+ Der generierte Text
47
+ """
48
+ # Standardparameter verwenden, wenn keine angegeben sind
49
+ if params is None:
50
+ params = {
51
+ "max_new_tokens": config.MAX_NEW_TOKENS,
52
+ "do_sample": True,
53
+ "temperature": config.TEMPERATURE,
54
+ "top_p": config.TOP_P,
55
+ "top_k": config.TOP_K,
56
+ "repetition_penalty": config.REPETITION_PENALTY
57
+ }
58
+
59
+ payload = {
60
+ "inputs": prompt,
61
+ "parameters": params
62
+ }
63
+
64
+ return self._send_api_request(payload)
65
+
66
+ def _send_api_request(self, payload: Dict[str, Any]) -> str:
67
+ """
68
+ Sendet eine Anfrage an die API mit Wiederholungslogik
69
+
70
+ Args:
71
+ payload: Die Anfragedaten
72
+
73
+ Returns:
74
+ Die generierte Antwort
75
+ """
76
+ for attempt in range(self.max_retries):
77
+ try:
78
+ response = requests.post(
79
+ self.api_url,
80
+ headers=self.headers,
81
+ json=payload,
82
+ timeout=config.API_TIMEOUT
83
+ )
84
+
85
+ # Prüfen auf erfolgreiche Antwort
86
+ if response.status_code == 200:
87
+ return self._process_successful_response(response.json())
88
+
89
+ # Behandlung von Fehlercodes
90
+ if response.status_code == 401:
91
+ if config.DEBUG_MODE:
92
+ print("API-Fehler: Ungültiger API-Token")
93
+ return self._get_fallback_response("authentication_error")
94
+
95
+ if response.status_code == 503 or response.status_code == 429:
96
+ # Server überlastet oder Rate-Limit erreicht, erneuter Versuch
97
+ if config.DEBUG_MODE:
98
+ print(f"API-Fehler: Server überlastet (Versuch {attempt+1}/{self.max_retries})")
99
+ time.sleep(self.retry_delay * (attempt + 1)) # Exponentielles Backoff
100
+ continue
101
+
102
+ # Andere Fehler
103
+ if config.DEBUG_MODE:
104
+ print(f"API-Fehler: Statuscode {response.status_code}")
105
+ return self._get_fallback_response("general_error")
106
+
107
+ except requests.exceptions.Timeout:
108
+ if config.DEBUG_MODE:
109
+ print(f"API-Timeout (Versuch {attempt+1}/{self.max_retries})")
110
+ if attempt < self.max_retries - 1:
111
+ time.sleep(self.retry_delay * (attempt + 1))
112
+ else:
113
+ return self._get_fallback_response("timeout")
114
+
115
+ except Exception as e:
116
+ if config.DEBUG_MODE:
117
+ print(f"API-Anfragefehler: {e}")
118
+ return self._get_fallback_response("general_error")
119
+
120
+ # Alle Versuche fehlgeschlagen
121
+ return self._get_fallback_response("max_retries")
122
+
123
+ def _process_successful_response(self, response_data: Union[List[Dict[str, Any]], Dict[str, Any]]) -> str:
124
+ """
125
+ Verarbeitet eine erfolgreiche API-Antwort
126
+
127
+ Args:
128
+ response_data: Die Antwortdaten von der API
129
+
130
+ Returns:
131
+ Der extrahierte generierte Text
132
+ """
133
+ try:
134
+ if isinstance(response_data, list) and len(response_data) > 0 and "generated_text" in response_data[0]:
135
+ full_response = response_data[0]["generated_text"]
136
+ # Extrahieren der Antwort nach "Dr. Franz:"
137
+ if "Dr. Franz:" in full_response:
138
+ reply = full_response.split("Dr. Franz:")[-1].strip()
139
+ return reply
140
+ else:
141
+ return full_response
142
+ elif isinstance(response_data, dict) and "generated_text" in response_data:
143
+ return response_data["generated_text"]
144
+ else:
145
+ if config.DEBUG_MODE:
146
+ print(f"Unerwartetes API-Antwortformat: {response_data}")
147
+ return self._get_fallback_response("unexpected_format")
148
+ except Exception as e:
149
+ if config.DEBUG_MODE:
150
+ print(f"Fehler bei der Verarbeitung der API-Antwort: {e}")
151
+ return self._get_fallback_response("processing_error")
152
+
153
+ def _get_fallback_response(self, error_type: str) -> str:
154
+ """
155
+ Gibt eine Fallback-Antwort basierend auf dem Fehlertyp zurück
156
+
157
+ Args:
158
+ error_type: Der Typ des aufgetretenen Fehlers
159
+
160
+ Returns:
161
+ Eine passende Fallback-Antwort
162
+ """
163
+ # Spezifische Antworten je nach Fehlertyp
164
+ error_responses = {
165
+ "authentication_error": [
166
+ "Es scheint ein Problem mit meiner Verbindung zu geben. Erzählen Sie mir mehr über Ihre Gedanken.",
167
+ "Ich muss kurz nachdenken. Was meinen Sie genau mit Ihrer letzten Aussage?"
168
+ ],
169
+ "timeout": [
170
+ "Ihre Aussage ist komplex und bedarf tieferer Analyse. Könnten Sie Ihren Gedanken weiter ausführen?",
171
+ "Interessant, dass Sie das so formulieren. Ich brauche einen Moment, um das zu verarbeiten."
172
+ ],
173
+ "max_retries": [
174
+ "Ihre Gedankengänge sind bemerkenswert verschlungen. Lassen Sie uns einen Schritt zurückgehen.",
175
+ "Ich spüre Widerstand in unserer Kommunikation. Vielleicht sollten wir einen anderen Ansatz versuchen."
176
+ ]
177
+ }
178
+
179
+ # Spezifische Antwort für den Fehlertyp oder allgemeine Fallback-Antwort
180
+ if error_type in error_responses:
181
+ return random.choice(error_responses[error_type])
182
+ else:
183
+ return random.choice(config.FALLBACK_RESPONSES)
utils/constants.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Configuration constants
2
+ MAX_RESPONSE_LENGTH = 2000
3
+ DEFAULT_PROMPT = "Dr. Franz Psychochatbot:"
4
+
5
+ # Response types
6
+ RESPONSE_TYPES = {
7
+ "NORMAL": "normal",
8
+ "ANALYSIS": "analysis",
9
+ "ADVICE": "advice",
10
+ "EMOTIONAL": "emotional"
11
+ }
12
+
13
+ # Emotion categories
14
+ EMOTION_CATEGORIES = [
15
+ "angry",
16
+ "sad",
17
+ "happy",
18
+ "neutral",
19
+ "confused",
20
+ "excited"
21
+ ]
utils/helpers.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Dict, Any
3
+
4
+ def load_config() -> Dict[str, Any]:
5
+ """Load configuration from config.py"""
6
+ from config import CONFIG
7
+ return CONFIG
8
+
9
+ def get_asset_path(asset_name: str) -> str:
10
+ """Get the full path to an asset"""
11
+ return os.path.join(os.path.dirname(__file__), "../assets", asset_name)
12
+
13
+ def format_response(text: str) -> str:
14
+ """Format the bot's response text"""
15
+ return text.strip().capitalize()
utils/logger.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ utils/logger.py - Logging-Modul für den Dr. Franz Psychochatbot
3
+
4
+ Dieses Modul stellt Logging-Funktionalitäten bereit:
5
+ - Protokollierung von Aktivitäten
6
+ - Unterstützung beim Debugging
7
+ - Überwachung der Performance
8
+ """
9
+
10
+ import logging
11
+ import os
12
+ import time
13
+ from datetime import datetime
14
+ from typing import Optional, Dict, Any
15
+
16
+ # Importieren der Konfiguration
17
+ import config
18
+
19
+ class Logger:
20
+ """Klasse zur Verwaltung des Loggings"""
21
+
22
+ def __init__(self, log_level: str = config.LOG_LEVEL):
23
+ """
24
+ Initialisiert den Logger
25
+
26
+ Args:
27
+ log_level: Das Log-Level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
28
+ """
29
+ self.log_level = log_level
30
+ self.logger = self._setup_logger()
31
+ self.start_time = time.time()
32
+ self.interaction_count = 0
33
+
34
+ def _setup_logger(self) -> logging.Logger:
35
+ """
36
+ Richtet den Logger ein
37
+
38
+ Returns:
39
+ Konfigurierter Logger
40
+ """
41
+ # Log-Level aus String in Logging-Konstante umwandeln
42
+ numeric_level = getattr(logging, self.log_level.upper(), logging.INFO)
43
+
44
+ # Logger konfigurieren
45
+ logger = logging.getLogger("psychobot")
46
+ logger.setLevel(numeric_level)
47
+
48
+ # Handler für Konsolenausgabe
49
+ console_handler = logging.StreamHandler()
50
+ console_handler.setLevel(numeric_level)
51
+
52
+ # Formatter für lesbares Format
53
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
54
+ console_handler.setFormatter(formatter)
55
+
56
+ # Handler hinzufügen, falls noch nicht vorhanden
57
+ if not logger.handlers:
58
+ logger.addHandler(console_handler)
59
+
60
+ return logger
61
+
62
+ def log_startup(self) -> None:
63
+ """Protokolliert den Start der Anwendung"""
64
+ self.logger.info(f"=== {config.CHATBOT_TITLE} gestartet ===")
65
+ self.logger.info(f"Verwende Modell: {config.MODEL_ID}")
66
+ self.logger.info(f"Debug-Modus: {config.DEBUG_MODE}")
67
+ self.logger.info(f"API-Token vorhanden: {bool(config.API_TOKEN)}")
68
+
69
+ def log_user_input(self, user_input: str) -> None:
70
+ """
71
+ Protokolliert eine Nutzereingabe
72
+
73
+ Args:
74
+ user_input: Die Eingabe des Nutzers
75
+ """
76
+ self.interaction_count += 1
77
+ self.logger.info(f"Nutzeranfrage #{self.interaction_count}: {user_input[:50]}...")
78
+
79
+ def log_bot_response(self, response: str, generation_time: float) -> None:
80
+ """
81
+ Protokolliert eine Bot-Antwort
82
+
83
+ Args:
84
+ response: Die Antwort des Bots
85
+ generation_time: Die Zeit zur Generierung der Antwort in Sekunden
86
+ """
87
+ self.logger.info(f"Bot-Antwort #{self.interaction_count}: {response[:50]}... (Generiert in {generation_time:.2f}s)")
88
+
89
+ def log_api_request(self, model_id: str, prompt_length: int) -> None:
90
+ """
91
+ Protokolliert eine API-Anfrage
92
+
93
+ Args:
94
+ model_id: Die ID des verwendeten Modells
95
+ prompt_length: Die Länge des Prompts in Zeichen
96
+ """
97
+ self.logger.debug(f"API-Anfrage an {model_id} mit Prompt-Länge: {prompt_length} Zeichen")
98
+
99
+ def log_api_response(self, status_code: int, response_time: float) -> None:
100
+ """
101
+ Protokolliert eine API-Antwort
102
+
103
+ Args:
104
+ status_code: Der HTTP-Statuscode
105
+ response_time: Die Antwortzeit in Sekunden
106
+ """
107
+ self.logger.debug(f"API-Antwort erhalten: Status {status_code}, Zeit: {response_time:.2f}s")
108
+
109
+ def log_error(self, error_type: str, error_message: str, details: Optional[Dict[str, Any]] = None) -> None:
110
+ """
111
+ Protokolliert einen Fehler
112
+
113
+ Args:
114
+ error_type: Der Typ des Fehlers
115
+ error_message: Die Fehlermeldung
116
+ details: Optional, zusätzliche Details zum Fehler
117
+ """
118
+ self.logger.error(f"FEHLER - {error_type}: {error_message}")
119
+ if details and config.DEBUG_MODE:
120
+ self.logger.error(f"Details: {details}")
121
+
122
+ def log_performance_stats(self) -> Dict[str, Any]:
123
+ """
124
+ Protokolliert Performance-Statistiken
125
+
126
+ Returns:
127
+ Dictionary mit Performance-Statistiken
128
+ """
129
+ uptime = time.time() - self.start_time
130
+ hours, remainder = divmod(uptime, 3600)
131
+ minutes, seconds = divmod(remainder, 60)
132
+
133
+ stats = {
134
+ "uptime": f"{int(hours)}h {int(minutes)}m {int(seconds)}s",
135
+ "interactions": self.interaction_count,
136
+ "interactions_per_hour": round(self.interaction_count / (uptime / 3600), 2) if uptime > 0 else 0
137
+ }
138
+
139
+ self.logger.info(f"Performance-Statistiken: Laufzeit {stats['uptime']}, "
140
+ f"{stats['interactions']} Interaktionen, "
141
+ f"{stats['interactions_per_hour']} Interaktionen/Stunde")
142
+
143
+ return stats
utils/security.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ utils/security.py - Sicherheitsmodul für den Dr. Franz Psychochatbot
3
+
4
+ Dieses Modul verwaltet die Sicherheitsaspekte des Chatbots:
5
+ - Sichere Verwaltung von API-Tokens
6
+ - Laden von Umgebungsvariablen
7
+ - Schutz sensibler Daten
8
+ """
9
+
10
+ import os
11
+ import re
12
+ from typing import Optional, Dict, Any
13
+
14
+ class Security:
15
+ """Klasse zur Verwaltung der Sicherheitsaspekte"""
16
+
17
+ def __init__(self):
18
+ """Initialisiert die Sicherheitskomponente"""
19
+ self.sensitive_patterns = [
20
+ r'\b(?:\d[ -]*?){13,16}\b', # Kreditkartennummern
21
+ r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', # E-Mail-Adressen
22
+ r'\b(?:\+\d{1,3}[ -]?)?(?:\(?\d{2,4}\)?[ -]?)?[\d -]{7,10}\b', # Telefonnummern
23
+ r'\bhf_\w+\b', # HuggingFace API-Tokens
24
+ r'\bsk-\w+\b' # OpenAI API-Tokens
25
+ ]
26
+
27
+ def get_api_token(self, env_var_name: str = "HF_API_TOKEN", default: str = "") -> str:
28
+ """
29
+ Holt den API-Token aus den Umgebungsvariablen
30
+
31
+ Args:
32
+ env_var_name: Name der Umgebungsvariable
33
+ default: Standardwert, falls die Variable nicht existiert
34
+
35
+ Returns:
36
+ Der API-Token oder der Standardwert
37
+ """
38
+ return os.environ.get(env_var_name, default)
39
+
40
+ def sanitize_user_input(self, text: str) -> str:
41
+ """
42
+ Entfernt potenziell sensible Informationen aus der Nutzereingabe
43
+
44
+ Args:
45
+ text: Die zu bereinigende Nutzereingabe
46
+
47
+ Returns:
48
+ Die bereinigte Nutzereingabe
49
+ """
50
+ sanitized_text = text
51
+
52
+ # Ersetzen sensibler Muster
53
+ for pattern in self.sensitive_patterns:
54
+ sanitized_text = re.sub(pattern, "[ENTFERNT]", sanitized_text)
55
+
56
+ return sanitized_text
57
+
58
+ def validate_api_response(self, response: Any) -> bool:
59
+ """
60
+ Überprüft, ob eine API-Antwort sicher ist
61
+
62
+ Args:
63
+ response: Die zu überprüfende API-Antwort
64
+
65
+ Returns:
66
+ True, wenn die Antwort sicher ist, sonst False
67
+ """
68
+ # Konvertieren der Antwort in einen String für die Überprüfung
69
+ if not isinstance(response, str):
70
+ try:
71
+ response_str = str(response)
72
+ except:
73
+ return False
74
+ else:
75
+ response_str = response
76
+
77
+ # Überprüfen auf sensible Informationen
78
+ for pattern in self.sensitive_patterns:
79
+ if re.search(pattern, response_str):
80
+ return False
81
+
82
+ return True
83
+
84
+ def is_valid_api_token(self, token: str) -> bool:
85
+ """
86
+ Überprüft, ob ein API-Token gültig erscheint
87
+
88
+ Args:
89
+ token: Der zu überprüfende API-Token
90
+
91
+ Returns:
92
+ True, wenn der Token gültig erscheint, sonst False
93
+ """
94
+ # HuggingFace-Tokens beginnen mit "hf_"
95
+ if token.startswith("hf_") and len(token) > 10:
96
+ return True
97
+ return False