Upload 4 files
Browse files- modules/analyzer.py +173 -0
- modules/interface.py +181 -0
- modules/memory.py +218 -0
- modules/persona.py +141 -0
modules/analyzer.py
ADDED
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
modules/analyzer.py - Emotionsanalyse-Modul für den Dr. Franz Psychochatbot
|
3 |
+
|
4 |
+
Dieses Modul analysiert die Nutzereingaben, um Emotionen und Themen zu erkennen:
|
5 |
+
- Emotionserkennung über API
|
6 |
+
- Themenidentifikation
|
7 |
+
- Anpassung der Antwortstrategien
|
8 |
+
"""
|
9 |
+
|
10 |
+
import requests
|
11 |
+
import random
|
12 |
+
from typing import Dict, Any, Optional, List, Tuple
|
13 |
+
|
14 |
+
# Importieren der Konfiguration
|
15 |
+
import config
|
16 |
+
|
17 |
+
class Analyzer:
|
18 |
+
"""Klasse zur Analyse von Nutzereingaben"""
|
19 |
+
|
20 |
+
def __init__(self, api_token: str = config.API_TOKEN):
|
21 |
+
"""Initialisiert den Analyzer mit API-Token"""
|
22 |
+
self.api_token = api_token
|
23 |
+
# Einfache Wörterbücher für die Stimmungsanalyse ohne ML-Bibliotheken
|
24 |
+
self.positive_words = [
|
25 |
+
"glücklich", "froh", "zufrieden", "gut", "großartig", "toll", "wunderbar",
|
26 |
+
"fantastisch", "begeistert", "erfreut", "dankbar", "hoffnungsvoll", "optimistisch"
|
27 |
+
]
|
28 |
+
self.negative_words = [
|
29 |
+
"traurig", "wütend", "verärgert", "frustriert", "enttäuscht", "ängstlich",
|
30 |
+
"besorgt", "verzweifelt", "hoffnungslos", "deprimiert", "unglücklich", "schlecht",
|
31 |
+
"hasse", "Angst", "Sorge", "Problem", "schwierig", "schlimm", "schrecklich"
|
32 |
+
]
|
33 |
+
self.neutral_words = [
|
34 |
+
"denke", "glaube", "meine", "verstehe", "sehe", "höre", "fühle",
|
35 |
+
"normal", "gewöhnlich", "alltäglich", "regelmäßig"
|
36 |
+
]
|
37 |
+
|
38 |
+
def analyze_emotion(self, text: str) -> str:
|
39 |
+
"""
|
40 |
+
Analysiert die Emotion in einem Text ohne ML-Bibliotheken
|
41 |
+
|
42 |
+
Args:
|
43 |
+
text: Der zu analysierende Text
|
44 |
+
|
45 |
+
Returns:
|
46 |
+
"positive", "negative" oder "neutral"
|
47 |
+
"""
|
48 |
+
text = text.lower()
|
49 |
+
|
50 |
+
# Zählen der positiven, negativen und neutralen Wörter
|
51 |
+
positive_count = sum(1 for word in self.positive_words if word in text)
|
52 |
+
negative_count = sum(1 for word in self.negative_words if word in text)
|
53 |
+
neutral_count = sum(1 for word in self.neutral_words if word in text)
|
54 |
+
|
55 |
+
# Gewichtung: Negative Emotionen haben höhere Priorität
|
56 |
+
negative_count *= 1.5
|
57 |
+
|
58 |
+
# Bestimmung der dominanten Emotion
|
59 |
+
if negative_count > positive_count:
|
60 |
+
return "negative"
|
61 |
+
elif positive_count > negative_count and positive_count > neutral_count:
|
62 |
+
return "positive"
|
63 |
+
else:
|
64 |
+
return "neutral"
|
65 |
+
|
66 |
+
def analyze_themes(self, text: str) -> Dict[str, Any]:
|
67 |
+
"""
|
68 |
+
Identifiziert psychologisch relevante Themen und ihre manipulative Nutzung
|
69 |
+
|
70 |
+
Args:
|
71 |
+
text: Der zu analysierende Text
|
72 |
+
|
73 |
+
Returns:
|
74 |
+
Dictionary mit Themen und manipulativen Aspekten
|
75 |
+
"""
|
76 |
+
text = text.lower()
|
77 |
+
analysis = {
|
78 |
+
"themes": [],
|
79 |
+
"manipulative_aspects": {},
|
80 |
+
"vulnerabilities": []
|
81 |
+
}
|
82 |
+
|
83 |
+
# Themen-Wörterbuch mit Schlüsselwörtern
|
84 |
+
theme_keywords = {
|
85 |
+
"Familie": {
|
86 |
+
"keywords": ["familie", "mutter", "vater", "eltern", "kind"],
|
87 |
+
"manipulation": "Die familiären Beziehungen sind ein perfektes Terrain für Projektion und Verdrängung."
|
88 |
+
},
|
89 |
+
"Beziehungen": {
|
90 |
+
"keywords": ["beziehung", "partner", "freund", "ehe"],
|
91 |
+
"manipulation": "Beziehungen sind ideale Spiegel für unbewusste Konflikte."
|
92 |
+
},
|
93 |
+
"Selbstwert": {
|
94 |
+
"keywords": ["selbstwert", "stolz", "scham"],
|
95 |
+
"manipulation": "Der Selbstwert ist empfänglich für Schuldzuweisungen und Gaslighting."
|
96 |
+
},
|
97 |
+
"Ängste": {
|
98 |
+
"keywords": ["angst", "sorge", "panik"],
|
99 |
+
"manipulation": "Ängste können verstärkt werden, um Kontrolle auszuüben."
|
100 |
+
}
|
101 |
+
}
|
102 |
+
|
103 |
+
# Themen identifizieren und manipulative Aspekte analysieren
|
104 |
+
for theme, data in theme_keywords.items():
|
105 |
+
if any(keyword in text for keyword in data["keywords"]):
|
106 |
+
analysis["themes"].append(theme)
|
107 |
+
analysis["manipulative_aspects"][theme] = data["manipulation"]
|
108 |
+
|
109 |
+
# Identifizierung von Verwundbarkeiten
|
110 |
+
if "schuld" in text or "scham" in text:
|
111 |
+
analysis["vulnerabilities"].append(f"{theme}_guilt")
|
112 |
+
if "angst" in text:
|
113 |
+
analysis["vulnerabilities"].append(f"{theme}_fear")
|
114 |
+
|
115 |
+
return analysis
|
116 |
+
|
117 |
+
def analyze_complexity(self, text: str) -> int:
|
118 |
+
"""
|
119 |
+
Bewertet die Komplexität der Nutzereingabe (1-5)
|
120 |
+
|
121 |
+
Args:
|
122 |
+
text: Der zu analysierende Text
|
123 |
+
|
124 |
+
Returns:
|
125 |
+
Komplexitätsstufe von 1 (einfach) bis 5 (komplex)
|
126 |
+
"""
|
127 |
+
# Einfache Heuristiken für die Komplexitätsbewertung
|
128 |
+
words = text.split()
|
129 |
+
word_count = len(words)
|
130 |
+
avg_word_length = sum(len(word) for word in words) / max(1, word_count)
|
131 |
+
sentence_count = text.count('.') + text.count('!') + text.count('?')
|
132 |
+
|
133 |
+
# Komplexe Wörter und Phrasen
|
134 |
+
complex_indicators = [
|
135 |
+
"weil", "obwohl", "trotzdem", "dennoch", "allerdings",
|
136 |
+
"einerseits", "andererseits", "jedoch", "nichtsdestotrotz",
|
137 |
+
"möglicherweise", "vielleicht", "wahrscheinlich", "vermutlich"
|
138 |
+
]
|
139 |
+
|
140 |
+
complex_count = sum(1 for word in complex_indicators if word in text.lower())
|
141 |
+
|
142 |
+
# Berechnung der Komplexität
|
143 |
+
if word_count < 5:
|
144 |
+
return 1
|
145 |
+
elif word_count < 15 and complex_count == 0:
|
146 |
+
return 2
|
147 |
+
elif word_count < 30 and complex_count <= 1:
|
148 |
+
return 3
|
149 |
+
elif word_count < 50 and complex_count <= 2:
|
150 |
+
return 4
|
151 |
+
else:
|
152 |
+
return 5
|
153 |
+
|
154 |
+
def get_analysis_result(self, text: str) -> Dict[str, Any]:
|
155 |
+
"""
|
156 |
+
Führt eine vollständige Analyse des Textes durch
|
157 |
+
|
158 |
+
Args:
|
159 |
+
text: Der zu analysierende Text
|
160 |
+
|
161 |
+
Returns:
|
162 |
+
Dictionary mit Analyseergebnissen
|
163 |
+
"""
|
164 |
+
emotion = self.analyze_emotion(text)
|
165 |
+
themes = self.analyze_themes(text)
|
166 |
+
complexity = self.analyze_complexity(text)
|
167 |
+
|
168 |
+
return {
|
169 |
+
"emotion": emotion,
|
170 |
+
"themes": themes,
|
171 |
+
"complexity": complexity,
|
172 |
+
"suggested_intensity": min(complexity + (1 if emotion == "negative" else 0), 5)
|
173 |
+
}
|
modules/interface.py
ADDED
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
modules/interface.py - UI-Komponenten-Modul für den Dr. Franz Psychochatbot
|
3 |
+
|
4 |
+
Dieses Modul definiert die Benutzeroberfläche des Chatbots:
|
5 |
+
- Gradio-Interface-Komponenten
|
6 |
+
- Benutzererfahrung optimieren
|
7 |
+
- Responsive Design sicherstellen
|
8 |
+
"""
|
9 |
+
|
10 |
+
import gradio as gr
|
11 |
+
from typing import List, Dict, Any, Tuple, Callable, Optional
|
12 |
+
import random
|
13 |
+
import time
|
14 |
+
|
15 |
+
# Importieren der Konfiguration
|
16 |
+
import config
|
17 |
+
|
18 |
+
class Interface:
|
19 |
+
"""Klasse zur Verwaltung der Benutzeroberfläche"""
|
20 |
+
|
21 |
+
def __init__(self, chat_function: Callable[[str], str], clear_function: Callable[[], Tuple[None, str]]):
|
22 |
+
"""
|
23 |
+
Initialisiert die Interface-Komponente
|
24 |
+
|
25 |
+
Args:
|
26 |
+
chat_function: Die Hauptfunktion für die Chatbot-Logik
|
27 |
+
clear_function: Die Funktion zum Zurücksetzen der Konversation
|
28 |
+
"""
|
29 |
+
self.chat_function = chat_function
|
30 |
+
self.clear_function = clear_function
|
31 |
+
self.theme = gr.themes.Soft(
|
32 |
+
primary_hue=config.UI_PRIMARY_COLOR,
|
33 |
+
secondary_hue=config.UI_SECONDARY_COLOR
|
34 |
+
)
|
35 |
+
|
36 |
+
def create_interface(self) -> gr.Blocks:
|
37 |
+
"""
|
38 |
+
Erstellt das Gradio-Interface
|
39 |
+
|
40 |
+
Returns:
|
41 |
+
Das konfigurierte Gradio-Interface
|
42 |
+
"""
|
43 |
+
with gr.Blocks(theme=self.theme) as demo:
|
44 |
+
# Header-Bereich
|
45 |
+
with gr.Row():
|
46 |
+
with gr.Column():
|
47 |
+
gr.Markdown(f"# {config.CHATBOT_EMOJI} {config.CHATBOT_TITLE}")
|
48 |
+
gr.Markdown(f"*{config.CHATBOT_DESCRIPTION}*")
|
49 |
+
|
50 |
+
# Hauptbereich
|
51 |
+
with gr.Row():
|
52 |
+
# Chat-Bereich (linke Spalte)
|
53 |
+
with gr.Column(scale=4):
|
54 |
+
chatbot = gr.Chatbot(
|
55 |
+
height=config.UI_CHATBOX_HEIGHT,
|
56 |
+
label="Therapiesitzung",
|
57 |
+
elem_id="chatbox",
|
58 |
+
show_label=True,
|
59 |
+
avatar_images=("👤", "🧠")
|
60 |
+
)
|
61 |
+
|
62 |
+
# Eingabebereich
|
63 |
+
with gr.Row():
|
64 |
+
user_input = gr.Textbox(
|
65 |
+
placeholder="Teilen Sie Ihre Gedanken mit Dr. Franz...",
|
66 |
+
label="Ihre Aussage",
|
67 |
+
scale=4,
|
68 |
+
elem_id="user-input",
|
69 |
+
show_label=True
|
70 |
+
)
|
71 |
+
send = gr.Button("Senden", variant="primary", elem_id="send-btn")
|
72 |
+
|
73 |
+
# Aktionsbereich
|
74 |
+
with gr.Row():
|
75 |
+
clear = gr.Button("Neue Sitzung", elem_id="clear-btn")
|
76 |
+
thinking = gr.HTML(
|
77 |
+
"<div id='thinking-indicator' style='display:none; text-align:center;'>"
|
78 |
+
"<i>Dr. Franz denkt nach...</i></div>"
|
79 |
+
)
|
80 |
+
|
81 |
+
# Informationsbereich (rechte Spalte)
|
82 |
+
with gr.Column(scale=1):
|
83 |
+
gr.Markdown(f"### Über {config.CHATBOT_NAME}")
|
84 |
+
gr.Markdown("""
|
85 |
+
Dr. Franz ist ein experimenteller KI-Psychoanalytiker, der Ihre Gedanken und Emotionen analysiert.
|
86 |
+
|
87 |
+
**Hinweis:** Dieser Chatbot dient nur der Unterhaltung und ersetzt keine professionelle psychologische Beratung.
|
88 |
+
|
89 |
+
**Funktionen:**
|
90 |
+
- Psychoanalytische Gesprächsführung
|
91 |
+
- Emotionsanalyse
|
92 |
+
- Kontextbewusstsein
|
93 |
+
|
94 |
+
Dieser Chatbot verwendet die HuggingFace Inference API.
|
95 |
+
""")
|
96 |
+
|
97 |
+
# Optionaler Bereich für Stimmungsanzeige
|
98 |
+
mood_indicator = gr.HTML(
|
99 |
+
"<div id='mood-container' style='text-align:center; margin-top:20px;'>"
|
100 |
+
"<div id='mood-label'>Therapeutische Intensität</div>"
|
101 |
+
"<div id='mood-indicator' style='font-size:24px;'>😐</div>"
|
102 |
+
"</div>"
|
103 |
+
)
|
104 |
+
|
105 |
+
# JavaScript für dynamische UI-Elemente
|
106 |
+
gr.HTML("""
|
107 |
+
<script>
|
108 |
+
// Funktion zum Anzeigen des "Denkt nach..."-Indikators
|
109 |
+
function showThinking() {
|
110 |
+
document.getElementById('thinking-indicator').style.display = 'block';
|
111 |
+
|
112 |
+
// Zufällige Stimmung anzeigen
|
113 |
+
const moods = ['🧐', '🤔', '😑', '😒', '🙄'];
|
114 |
+
document.getElementById('mood-indicator').innerText = moods[Math.floor(Math.random() * moods.length)];
|
115 |
+
}
|
116 |
+
|
117 |
+
// Funktion zum Ausblenden des "Denkt nach..."-Indikators
|
118 |
+
function hideThinking() {
|
119 |
+
document.getElementById('thinking-indicator').style.display = 'none';
|
120 |
+
|
121 |
+
// Zufällige Stimmung anzeigen
|
122 |
+
const moods = ['😏', '🧐', '😐', '🤨', '😌'];
|
123 |
+
document.getElementById('mood-indicator').innerText = moods[Math.floor(Math.random() * moods.length)];
|
124 |
+
}
|
125 |
+
|
126 |
+
// Event-Listener für den Senden-Button
|
127 |
+
document.addEventListener('DOMContentLoaded', function() {
|
128 |
+
const sendBtn = document.getElementById('send-btn');
|
129 |
+
if (sendBtn) {
|
130 |
+
sendBtn.addEventListener('click', showThinking);
|
131 |
+
}
|
132 |
+
|
133 |
+
// Beobachter für Änderungen im Chat-Bereich
|
134 |
+
const observer = new MutationObserver(function(mutations) {
|
135 |
+
hideThinking();
|
136 |
+
});
|
137 |
+
|
138 |
+
// Beobachtung starten, sobald das Element verfügbar ist
|
139 |
+
setTimeout(function() {
|
140 |
+
const chatbox = document.getElementById('chatbox');
|
141 |
+
if (chatbox) {
|
142 |
+
observer.observe(chatbox, { childList: true, subtree: true });
|
143 |
+
}
|
144 |
+
}, 1000);
|
145 |
+
});
|
146 |
+
</script>
|
147 |
+
""")
|
148 |
+
|
149 |
+
# Event-Handler
|
150 |
+
def respond(msg, history):
|
151 |
+
if not msg.strip():
|
152 |
+
return history, ""
|
153 |
+
|
154 |
+
# Kurze Verzögerung für natürlicheres Gefühl
|
155 |
+
time.sleep(0.5)
|
156 |
+
|
157 |
+
# Chat-Funktion aufrufen
|
158 |
+
reply = self.chat_function(msg)
|
159 |
+
|
160 |
+
history = history or []
|
161 |
+
history.append((msg, reply))
|
162 |
+
return history, ""
|
163 |
+
|
164 |
+
send.click(respond, inputs=[user_input, chatbot], outputs=[chatbot, user_input])
|
165 |
+
user_input.submit(respond, inputs=[user_input, chatbot], outputs=[chatbot, user_input])
|
166 |
+
clear.click(self.clear_function, inputs=[], outputs=[chatbot, user_input])
|
167 |
+
|
168 |
+
return demo
|
169 |
+
|
170 |
+
def launch_interface(self, demo: gr.Blocks) -> None:
|
171 |
+
"""
|
172 |
+
Startet das Gradio-Interface
|
173 |
+
|
174 |
+
Args:
|
175 |
+
demo: Das zu startende Gradio-Interface
|
176 |
+
"""
|
177 |
+
demo.launch(
|
178 |
+
server_name=config.SERVER_NAME,
|
179 |
+
share=config.SHARE,
|
180 |
+
show_error=config.SHOW_ERROR
|
181 |
+
)
|
modules/memory.py
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
modules/memory.py - Gesprächsgedächtnis-Modul für den Dr. Franz Psychochatbot
|
3 |
+
|
4 |
+
Dieses Modul verwaltet die Konversationshistorie und extrahiert wichtige Informationen:
|
5 |
+
- Speichern und Abrufen der Gesprächshistorie
|
6 |
+
- Extraktion wichtiger Informationen
|
7 |
+
- Bereitstellung von Kontext für neue Antworten
|
8 |
+
"""
|
9 |
+
|
10 |
+
from typing import List, Dict, Any, Optional
|
11 |
+
import re
|
12 |
+
import random
|
13 |
+
|
14 |
+
class Memory:
|
15 |
+
"""Klasse zur Verwaltung des Gesprächsgedächtnisses"""
|
16 |
+
|
17 |
+
def __init__(self, max_history_length: int = 10):
|
18 |
+
"""Initialisiert das Gedächtnis mit maximaler Historienlänge"""
|
19 |
+
self.max_history_length = max_history_length
|
20 |
+
self.conversation_history = []
|
21 |
+
self.extracted_info = {
|
22 |
+
"mentioned_people": set(),
|
23 |
+
"mentioned_events": set(),
|
24 |
+
"recurring_themes": {},
|
25 |
+
"emotional_patterns": []
|
26 |
+
}
|
27 |
+
|
28 |
+
def add_exchange(self, user_input: str, bot_response: str, analysis: Optional[Dict[str, Any]] = None) -> None:
|
29 |
+
"""
|
30 |
+
Fügt einen Gesprächsaustausch zum Gedächtnis hinzu
|
31 |
+
|
32 |
+
Args:
|
33 |
+
user_input: Die Eingabe des Nutzers
|
34 |
+
bot_response: Die Antwort des Chatbots
|
35 |
+
analysis: Optional, Analyseergebnisse des Analyzers
|
36 |
+
"""
|
37 |
+
exchange = {
|
38 |
+
"user_input": user_input,
|
39 |
+
"bot_response": bot_response,
|
40 |
+
"analysis": analysis or {}
|
41 |
+
}
|
42 |
+
|
43 |
+
self.conversation_history.append(exchange)
|
44 |
+
|
45 |
+
# Begrenzung der Historienlänge
|
46 |
+
if len(self.conversation_history) > self.max_history_length:
|
47 |
+
self.conversation_history = self.conversation_history[-self.max_history_length:]
|
48 |
+
|
49 |
+
# Extraktion wichtiger Informationen
|
50 |
+
self._extract_information(exchange)
|
51 |
+
|
52 |
+
def get_history(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
|
53 |
+
"""
|
54 |
+
Gibt die Konversationshistorie zurück
|
55 |
+
|
56 |
+
Args:
|
57 |
+
limit: Optional, maximale Anzahl der zurückzugebenden Einträge
|
58 |
+
|
59 |
+
Returns:
|
60 |
+
Liste der Gesprächsaustausche
|
61 |
+
"""
|
62 |
+
if limit is None or limit >= len(self.conversation_history):
|
63 |
+
return self.conversation_history
|
64 |
+
else:
|
65 |
+
return self.conversation_history[-limit:]
|
66 |
+
|
67 |
+
def format_for_prompt(self, limit: Optional[int] = None) -> str:
|
68 |
+
"""
|
69 |
+
Formatiert die Historie für den Prompt
|
70 |
+
|
71 |
+
Args:
|
72 |
+
limit: Optional, maximale Anzahl der zu formatierenden Einträge
|
73 |
+
|
74 |
+
Returns:
|
75 |
+
Formatierte Historie als String
|
76 |
+
"""
|
77 |
+
history = self.get_history(limit)
|
78 |
+
formatted = ""
|
79 |
+
|
80 |
+
for exchange in history:
|
81 |
+
formatted += f"User: {exchange['user_input']}\n"
|
82 |
+
formatted += f"Dr. Franz: {exchange['bot_response']}\n"
|
83 |
+
|
84 |
+
return formatted
|
85 |
+
|
86 |
+
def get_relevant_context(self) -> str:
|
87 |
+
"""
|
88 |
+
Generiert erweiterten psychoanalytischen Kontext für die nächste Antwort
|
89 |
+
|
90 |
+
Returns:
|
91 |
+
Detaillierte Kontextinformationen als String
|
92 |
+
"""
|
93 |
+
context_parts = []
|
94 |
+
|
95 |
+
# Wiederkehrende Themen
|
96 |
+
if self.extracted_info["recurring_themes"]:
|
97 |
+
top_themes = sorted(
|
98 |
+
self.extracted_info["recurring_themes"].items(),
|
99 |
+
key=lambda x: x[1],
|
100 |
+
reverse=True
|
101 |
+
)[:2]
|
102 |
+
context_parts.append(f"Wiederkehrende Themen: {', '.join(theme for theme, _ in top_themes)}")
|
103 |
+
|
104 |
+
# Abwehrmechanismen
|
105 |
+
if self.extracted_info["defense_mechanisms"]:
|
106 |
+
active_defenses = [k for k, v in self.extracted_info["defense_mechanisms"].items() if v > 0]
|
107 |
+
if active_defenses:
|
108 |
+
context_parts.append(f"Aktive Abwehrmechanismen: {', '.join(active_defenses)}")
|
109 |
+
context_parts.append("Der Patient zeigt überwiegend negative Emotionen.")
|
110 |
+
elif recent_emotions.count("positive") >= 2:
|
111 |
+
context_parts.append("Der Patient zeigt ungewöhnlich positive Emotionen, was auf Verdrängung hindeuten könnte.")
|
112 |
+
|
113 |
+
# Erwähnte Personen einbeziehen
|
114 |
+
if self.extracted_info["mentioned_people"]:
|
115 |
+
people = list(self.extracted_info["mentioned_people"])
|
116 |
+
if people:
|
117 |
+
person = random.choice(people)
|
118 |
+
context_parts.append(f"Der Patient hat {person} erwähnt. Beziehe dich darauf, wenn passend.")
|
119 |
+
|
120 |
+
# Zufällig ein früheres Thema aufgreifen
|
121 |
+
if len(self.conversation_history) > 2:
|
122 |
+
old_exchange = random.choice(self.conversation_history[:-2])
|
123 |
+
if "analysis" in old_exchange and "themes" in old_exchange["analysis"]:
|
124 |
+
old_theme = random.choice(old_exchange["analysis"]["themes"])
|
125 |
+
context_parts.append(f"Greife bei Gelegenheit das frühere Thema '{old_theme}' wieder auf.")
|
126 |
+
|
127 |
+
return " ".join(context_parts)
|
128 |
+
|
129 |
+
def _analyze_defense_mechanisms(self, text: str) -> None:
|
130 |
+
"""Analysiert aktive Abwehrmechanismen im Text"""
|
131 |
+
for mechanism, pattern in self.analysis_patterns["defense"].items():
|
132 |
+
if re.search(pattern, text):
|
133 |
+
self.extracted_info["defense_mechanisms"][mechanism] = self.extracted_info["defense_mechanisms"].get(mechanism, 0) + 1
|
134 |
+
|
135 |
+
def _analyze_transference_patterns(self, text: str) -> None:
|
136 |
+
"""Analysiert Transfersituationen im Text"""
|
137 |
+
for category, pattern in self.analysis_patterns["transference"].items():
|
138 |
+
if re.search(pattern, text):
|
139 |
+
self.extracted_info["transference_patterns"].append(category)
|
140 |
+
|
141 |
+
def _analyze_symbolic_patterns(self, text: str) -> None:
|
142 |
+
"""Analysiert symbolische Muster im Text"""
|
143 |
+
for symbol, pattern in self.analysis_patterns["symbolic"].items():
|
144 |
+
if re.search(pattern, text):
|
145 |
+
self.extracted_info["symbolic_patterns"][symbol] = self.extracted_info["symbolic_patterns"].get(symbol, 0) + 1
|
146 |
+
|
147 |
+
def _update_association_network(self, text: str) -> None:
|
148 |
+
"""Aktualisiert das Assoziationsnetzwerk"""
|
149 |
+
words = text.split()
|
150 |
+
for i, word in enumerate(words):
|
151 |
+
if word not in self.extracted_info["association_network"]:
|
152 |
+
self.extracted_info["association_network"][word] = {}
|
153 |
+
|
154 |
+
# Erstelle Assoziationen zu benachbarten Wörtern
|
155 |
+
if i > 0:
|
156 |
+
prev_word = words[i-1]
|
157 |
+
self.extracted_info["association_network"][word][prev_word] = self.extracted_info["association_network"][word].get(prev_word, 0) + 1
|
158 |
+
if i < len(words) - 1:
|
159 |
+
next_word = words[i+1]
|
160 |
+
self.extracted_info["association_network"][word][next_word] = self.extracted_info["association_network"][word].get(next_word, 0) + 1
|
161 |
+
|
162 |
+
def _update_escalation_levels(self, analysis: Dict[str, Any]) -> None:
|
163 |
+
"""Aktualisiert die Escalation Levels basierend auf der Analyse"""
|
164 |
+
intensity = analysis.get("intensity", 0)
|
165 |
+
|
166 |
+
# Emotionale Escalation
|
167 |
+
if analysis.get("emotion") == "negative":
|
168 |
+
self.extracted_info["escalation_levels"]["emotional"] = min(5, self.extracted_info["escalation_levels"]["emotional"] + intensity)
|
169 |
+
else:
|
170 |
+
self.extracted_info["escalation_levels"]["emotional"] = max(0, self.extracted_info["escalation_levels"]["emotional"] - 1)
|
171 |
+
|
172 |
+
# Kognitive Escalation
|
173 |
+
if "defense_mechanisms" in analysis:
|
174 |
+
self.extracted_info["escalation_levels"]["cognitive"] = min(5, self.extracted_info["escalation_levels"]["cognitive"] + len(analysis["defense_mechanisms"]))
|
175 |
+
else:
|
176 |
+
self.extracted_info["escalation_levels"]["cognitive"] = max(0, self.extracted_info["escalation_levels"]["cognitive"] - 1)
|
177 |
+
|
178 |
+
# Verhaltensmäßige Escalation
|
179 |
+
if "behavioral_patterns" in analysis:
|
180 |
+
self.extracted_info["escalation_levels"]["behavioral"] = min(5, self.extracted_info["escalation_levels"]["behavioral"] + len(analysis["behavioral_patterns"]))
|
181 |
+
else:
|
182 |
+
self.extracted_info["escalation_levels"]["behavioral"] = max(0, self.extracted_info["escalation_levels"]["behavioral"] - 1)
|
183 |
+
|
184 |
+
def _extract_information(self, exchange: Dict[str, Any]) -> None:
|
185 |
+
"""Extrahiert wichtige Informationen aus einem Gesprächsaustausch"""
|
186 |
+
user_input = exchange["user_input"].lower()
|
187 |
+
analysis = exchange["analysis"]
|
188 |
+
|
189 |
+
# Multi-Level Psychoanalyse
|
190 |
+
self._analyze_defense_mechanisms(user_input)
|
191 |
+
self._analyze_transference_patterns(user_input)
|
192 |
+
self._analyze_symbolic_patterns(user_input)
|
193 |
+
self._update_association_network(user_input)
|
194 |
+
self._update_escalation_levels(analysis)
|
195 |
+
|
196 |
+
# Erwähnte Personen und Ereignisse
|
197 |
+
for word in user_input.split():
|
198 |
+
if word not in ["ich", "du", "wir", "sie", "er", "es"]:
|
199 |
+
self.extracted_info["mentioned_people"].add(word)
|
200 |
+
|
201 |
+
# Wiederkehrende Themen
|
202 |
+
for theme in analysis.get("themes", []):
|
203 |
+
self.extracted_info["recurring_themes"][theme] = self.extracted_info["recurring_themes"].get(theme, 0) + 1
|
204 |
+
|
205 |
+
# Emotionale Muster
|
206 |
+
emotion = analysis.get("emotion", "neutral")
|
207 |
+
self.extracted_info["emotional_patterns"].append({
|
208 |
+
"emotion": emotion,
|
209 |
+
"timestamp": time.time(),
|
210 |
+
"intensity": analysis.get("intensity", 0)
|
211 |
+
})
|
212 |
+
|
213 |
+
# Aktualisiere den letzten Austausch
|
214 |
+
self.extracted_info["last_exchange"] = {
|
215 |
+
"time": time.time(),
|
216 |
+
"intensity": analysis.get("intensity", 0),
|
217 |
+
"patterns": analysis.get("patterns", [])
|
218 |
+
}
|
modules/persona.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
modules/persona.py - Persönlichkeitsmodul für den Dr. Franz Psychochatbot
|
3 |
+
|
4 |
+
Dieses Modul definiert die Persönlichkeit des Chatbots, einschließlich:
|
5 |
+
- System-Prompts
|
6 |
+
- Charaktereigenschaften
|
7 |
+
- Antwortstrategien basierend auf Nutzerverhalten
|
8 |
+
"""
|
9 |
+
|
10 |
+
import random
|
11 |
+
from typing import List, Dict, Optional
|
12 |
+
|
13 |
+
# Importieren der Konfiguration
|
14 |
+
import config
|
15 |
+
|
16 |
+
class Persona:
|
17 |
+
"""Klasse zur Verwaltung der Chatbot-Persönlichkeit"""
|
18 |
+
|
19 |
+
def __init__(self, name: str = config.CHATBOT_NAME, intensity: int = config.DEFAULT_INTENSITY):
|
20 |
+
"""Initialisiert die Persona mit Namen und Intensitätsstufe"""
|
21 |
+
self.name = name
|
22 |
+
self.intensity = intensity # Skala von 1-5
|
23 |
+
self.base_prompt = config.SYSTEM_PROMPT
|
24 |
+
|
25 |
+
def get_system_prompt(self) -> str:
|
26 |
+
"""Gibt den Basis-System-Prompt zurück"""
|
27 |
+
return self.base_prompt
|
28 |
+
|
29 |
+
def adjust_intensity(self, new_intensity: int) -> None:
|
30 |
+
"""Passt die Intensität der Persönlichkeit an (1-5)"""
|
31 |
+
if 1 <= new_intensity <= 5:
|
32 |
+
self.intensity = new_intensity
|
33 |
+
|
34 |
+
def get_response_strategy(self, user_input: str, emotion: Optional[str] = None) -> Dict[str, any]:
|
35 |
+
"""
|
36 |
+
Bestimmt die Antwortstrategie basierend auf Nutzereingabe und erkannter Emotion
|
37 |
+
|
38 |
+
Args:
|
39 |
+
user_input: Die Eingabe des Nutzers
|
40 |
+
emotion: Optional, die erkannte Emotion (z.B. "negative", "positive", "neutral")
|
41 |
+
|
42 |
+
Returns:
|
43 |
+
Ein Dictionary mit Anweisungen für die Antwortgenerierung
|
44 |
+
"""
|
45 |
+
strategy = {
|
46 |
+
"tone": self._determine_tone(emotion),
|
47 |
+
"focus": self._determine_focus(user_input),
|
48 |
+
"technique": self._determine_technique(emotion, self._determine_manipulation_opportunities(user_input)),
|
49 |
+
"intensity_modifier": self.intensity / 3.0 # Skalierungsfaktor für die Intensität
|
50 |
+
}
|
51 |
+
|
52 |
+
return strategy
|
53 |
+
|
54 |
+
def _determine_tone(self, emotion: Optional[str]) -> str:
|
55 |
+
"""Bestimmt den Ton basierend auf der erkannten Emotion"""
|
56 |
+
if emotion == "negative":
|
57 |
+
tones = ["überheblich", "herablassend", "provokativ", "konfrontativ", "skeptisch"]
|
58 |
+
weights = [0.1, 0.3, 0.3, 0.2, 0.1]
|
59 |
+
elif emotion == "positive":
|
60 |
+
tones = ["skeptisch", "hinterfragend", "manipulativ", "suggestiv", "überheblich"]
|
61 |
+
weights = [0.2, 0.2, 0.3, 0.2, 0.1]
|
62 |
+
else: # neutral oder None
|
63 |
+
tones = ["analytisch", "distanziert", "suggestiv", "überheblich", "provokativ"]
|
64 |
+
weights = [0.2, 0.2, 0.2, 0.2, 0.2]
|
65 |
+
|
66 |
+
# Intensität beeinflusst die Gewichtung
|
67 |
+
if self.intensity >= 4:
|
68 |
+
# Bei hoher Intensität mehr provokative und konfrontative Töne
|
69 |
+
weights = [w * (1 + (i % 3) * 0.2 * (self.intensity - 3)) for i, w in enumerate(weights)]
|
70 |
+
|
71 |
+
# Normalisieren der Gewichte
|
72 |
+
total = sum(weights)
|
73 |
+
weights = [w / total for w in weights]
|
74 |
+
|
75 |
+
return random.choices(tones, weights=weights, k=1)[0]
|
76 |
+
|
77 |
+
def _determine_focus(self, user_input: str) -> str:
|
78 |
+
"""Bestimmt den Fokus der Antwort basierend auf der Nutzereingabe"""
|
79 |
+
# Einfache Schlüsselwortanalyse (in einer vollständigen Implementierung
|
80 |
+
# würde hier eine komplexere NLP-Analyse stehen)
|
81 |
+
lower_input = user_input.lower()
|
82 |
+
|
83 |
+
if any(word in lower_input for word in ["mutter", "vater", "eltern", "familie", "kind"]):
|
84 |
+
return "familiäre Beziehungen"
|
85 |
+
elif any(word in lower_input for word in ["angst", "sorge", "furcht", "panik"]):
|
86 |
+
return "Ängste und Unsicherheiten"
|
87 |
+
elif any(word in lower_input for word in ["liebe", "beziehung", "partner", "ehe"]):
|
88 |
+
return "romantische Beziehungen"
|
89 |
+
elif any(word in lower_input for word in ["arbeit", "job", "karriere", "beruf"]):
|
90 |
+
return "berufliche Ambitionen"
|
91 |
+
elif any(word in lower_input for word in ["freund", "kollege", "bekannte"]):
|
92 |
+
return "soziale Beziehungen"
|
93 |
+
else:
|
94 |
+
return "persönliche Unsicherheiten"
|
95 |
+
|
96 |
+
def _determine_technique(self, emotion: Optional[str]) -> str:
|
97 |
+
"""Wählt eine psychoanalytische Technik basierend auf der Emotion"""
|
98 |
+
techniques = [
|
99 |
+
"Freie Assoziation",
|
100 |
+
"Traumdeutung",
|
101 |
+
"Übertragungsanalyse",
|
102 |
+
"Widerstandsanalyse",
|
103 |
+
"Projektion aufdecken",
|
104 |
+
"Verdrängung ansprechen",
|
105 |
+
"Abwehrmechanismen identifizieren"
|
106 |
+
]
|
107 |
+
|
108 |
+
# Bei negativen Emotionen eher konfrontative Techniken
|
109 |
+
if emotion == "negative":
|
110 |
+
return random.choice(techniques[3:])
|
111 |
+
# Bei positiven Emotionen eher subtile Techniken
|
112 |
+
elif emotion == "positive":
|
113 |
+
return random.choice(techniques[:4])
|
114 |
+
# Bei neutralen Emotionen zufällige Auswahl
|
115 |
+
else:
|
116 |
+
return random.choice(techniques)
|
117 |
+
|
118 |
+
def get_additional_context(self, strategy: Dict[str, any]) -> str:
|
119 |
+
"""
|
120 |
+
Generiert zusätzlichen Kontext für das LLM basierend auf der Strategie
|
121 |
+
|
122 |
+
Dies wird dem System-Prompt hinzugefügt, um die Antwortgenerierung zu steuern
|
123 |
+
"""
|
124 |
+
intensity_phrases = [
|
125 |
+
"Sei subtil in deiner Analyse.",
|
126 |
+
"Hinterfrage vorsichtig die Aussagen.",
|
127 |
+
"Sei direkter in deiner psychologischen Deutung.",
|
128 |
+
"Konfrontiere den Patienten mit seinen Widersprüchen.",
|
129 |
+
"Sei aggressiv in deiner Interpretation und Konfrontation."
|
130 |
+
]
|
131 |
+
|
132 |
+
intensity_instruction = intensity_phrases[min(self.intensity - 1, 4)]
|
133 |
+
|
134 |
+
context = (
|
135 |
+
f"Verwende einen {strategy['tone']} Ton. "
|
136 |
+
f"Fokussiere dich auf {strategy['focus']}. "
|
137 |
+
f"Nutze die Technik der {strategy['technique']}. "
|
138 |
+
f"{intensity_instruction}"
|
139 |
+
)
|
140 |
+
|
141 |
+
return context
|