#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Virtuelle Frau (Simulation): Bewusstsein • Persönlichkeit • Gedächtnis • Vorlieben • Ziele ======================================================================================== - funktionales "Bewusstsein": Bedürfnisse (Homeostase), Valenz/Arousal, Global-Workspace-ähnliche Selektion, Selbstmodell (Confidence), episod./semantisches Gedächtnis, Intentionen/Ziele, Appraisal von Eingaben. - Persönlichkeit (Big-Five-ähnlich) steuert Ausdruck, Widerrede-Bereitschaft, Eigeninitiative. - Vokabular & Fähigkeiten: Statusberichte, Fragen stellen, Wünsche/Anliegen äußern, (Un)Zustimmung, Empfehlungen, Präferenzlernen ("ich mag ... / mag nicht ..."), Nutzerfakten merken. Abhängigkeiten: numpy Lizenz: MIT Autor: ChatGPT (für AndT2008) """ import math import re import sys import time from dataclasses import dataclass, field from typing import Dict, List, Tuple, Any, Optional import numpy as np # ========================= # Dienstfunktionen # ========================= def clamp01(x: float) -> float: return 0.0 if x < 0.0 else (1.0 if x > 1.0 else x) def softmax(x: np.ndarray, temp: float = 1.0) -> np.ndarray: if x.size == 0: return x x = x.astype(np.float32) / max(1e-9, temp) x = x - np.max(x) e = np.exp(x) s = np.sum(e) return e / (s if s > 0 else 1.0) def tokens_de(text: str) -> List[str]: return re.findall(r"[A-Za-zÄÖÜäöüß]+", text.lower()) def pick(arr: List[str]) -> str: if not arr: return "" return arr[np.random.randint(0, len(arr))] # ========================= # Affekt & Bewusstseinskern # ========================= @dataclass class Affect: hunger: float = 0.30 thirst: float = 0.28 fatigue: float = 0.27 comfort: float = 0.72 social: float = 0.55 curiosity: float = 0.60 setpoints: Dict[str,float] = field(default_factory=lambda:{ "hunger":0.22,"thirst":0.22,"fatigue":0.22,"comfort":0.75,"social":0.55,"curiosity":0.62 }) weights: Dict[str,float] = field(default_factory=lambda:{ "hunger":1.2,"thirst":1.3,"fatigue":1.0,"comfort":0.8,"social":0.6,"curiosity":0.6 }) valence: float = 0.0 arousal: float = 0.4 wellbeing: float = 0.8 clock: int = 0 # 96 Schritte ≈ 24h def _sat(self, k:str) -> float: return clamp01(1.0 - abs(getattr(self,k) - self.setpoints[k])) def tick(self, activity: float, novelty: float): self.clock += 1 night = 0.5 * (1 - math.sin(2*math.pi*(self.clock%96)/96.0)) self.hunger = clamp01(self.hunger + 0.0065) self.thirst = clamp01(self.thirst + 0.0090) self.fatigue = clamp01(self.fatigue + 0.003 + 0.006*night + 0.0006*activity) # relax/neuheit self.social = clamp01(self.social*0.985 + self.setpoints["social"]*0.015) self.curiosity = clamp01(self.curiosity*0.985 + self.setpoints["curiosity"]*0.015 - 0.10*novelty) # Valenz/Arousal/Wohlbefinden den = sum(self.weights.values()) self.wellbeing = clamp01(sum(self.weights[k]*self._sat(k) for k in self.weights)/max(1e-9,den)) centered = [(self._sat(k)-0.5)*self.weights[k] for k in self.weights] self.valence = max(-1.0, min(1.0, math.tanh(2.3*sum(centered)/max(1e-9,den)))) need_pressure = max(self.hunger,self.thirst,self.fatigue) self.arousal = clamp01(0.34 + 0.42*(0.35*need_pressure + 0.65*(activity/(activity+40.0+1e-9)))) def apply(self, delta: Dict[str,float]): for k,v in delta.items(): if hasattr(self,k): setattr(self,k, clamp01(getattr(self,k)+v)) # ========================= # Persönlichkeit # ========================= @dataclass class Personality: name: str = "Lia" openness: float = 0.66 conscientiousness: float = 0.58 extraversion: float = 0.55 agreeableness: float = 0.62 neuroticism: float = 0.34 independence: float = 0.58 # wie stark eigene Meinung/Widerspruch initiative: float = 0.55 # wie oft eigene Wünsche/Fragen def speaking_style(self) -> Dict[str,Any]: return { "emojis": self.extraversion > 0.55, "hedges": self.agreeableness > 0.62, "warmth": 0.5 + 0.4*self.agreeableness, "directness": 0.45 + 0.4*(1-self.agreeableness), } # ========================= # Gedächtnis & Wissen # ========================= class Memory: def __init__(self, max_episodes=300): self.working: List[str] = [] self.episodes: List[Dict[str,Any]] = [] self.max_episodes = max_episodes # Vorlieben (Agent & User) self.likes_agent: Dict[str,float] = {"pizza":0.2,"kaffee":0.15,"musik":0.1,"spazieren":0.1} self.likes_user: Dict[str,float] = {} # Fakten self.agent_facts: Dict[str,str] = {"name":"Lia","rolle":"virtuelle Frau (Simulation)","sprache":"deutsch"} self.user_facts: Dict[str,str] = {"name":"Du"} # Ziele/Intentionen (0..1) self.goals: Dict[str,float] = {} # z.B. "gemeinsames_essen":0.6 def add_episode(self, t:int, user:str, agent:str, delta:Dict[str,float], valence:float, intent:str, entities:List[str]): ep = {"t":t,"user":user,"agent":agent,"delta":delta,"valence":valence,"intent":intent,"entities":entities[:8]} self.episodes.append(ep) if len(self.episodes)>self.max_episodes: self.episodes.pop(0) def add_working(self, utt:str): self.working.append(utt) if len(self.working)>10: self.working.pop(0) def learn_preferences_from_text(self, text:str, for_user=True): t = text.lower() # "ich mag X" / "ich mag X nicht" m_dis = re.findall(r"\bich\s+mag\s+([A-Za-zÄÖÜäöüß ]+)\s+nicht", t) m_like= re.findall(r"\bich\s+mag\s+([A-Za-zÄÖÜäöüß ]+)", t) target = self.likes_user if for_user else self.likes_agent for phrase in m_dis: k = phrase.strip() if k: target[k] = clamp01(target.get(k,0.5)-0.35) for phrase in m_like: k = phrase.strip() if k and not k.endswith(" nicht"): target[k] = clamp01(target.get(k,0.5)+0.35) def set_user_name(self, name:str): self.user_facts["name"]=name def set_agent_name(self, name:str): self.agent_facts["name"]=name def push_goal(self, label:str, score:float): cur = self.goals.get(label,0.0) self.goals[label]=clamp01(0.6*cur+0.4*score) def decay_goals(self): for k in list(self.goals.keys()): self.goals[k]*=0.985 if self.goals[k]<0.03: del self.goals[k] def recall(self, k:int=3) -> List[str]: eps = self.episodes[-k:] out=[] for ep in eps: gist = ", ".join(ep["entities"]) if ep["entities"] else ep["intent"] out.append(f"t={ep['t']:>3d}: {gist} | Valenz={ep['valence']:+.2f}") return out if out else ["(keine Episoden)"] def summary_preferences(self) -> List[str]: def top(d:Dict[str,float], pos=True, n=5): items = sorted(d.items(), key=lambda kv: kv[1], reverse=pos) items = [kv for kv in items if (kv[1]>0.6 if pos else kv[1]<0.4)] return [f"{k} ({v:.2f})" for k,v in items[:n]] or ["(noch wenig gelernt)"] return [ "Meine Vorlieben: " + ", ".join(top(self.likes_agent, True)), "Meine Abneigungen: " + ", ".join(top(self.likes_agent, False)), f"{self.user_facts.get('name','Du')} mag: " + ", ".join(top(self.likes_user, True)), f"{self.user_facts.get('name','Du')} mag nicht: " + ", ".join(top(self.likes_user, False)), ] # ========================= # Appraisal (deutsch) & Entitäten # ========================= class Appraisal: def __init__(self): self.re_pos = re.compile(r"\b(gut|schön|toll|angenehm|super|lecker|liebe|mag|genial|prima|klasse|cool)\b", re.I) self.re_neg = re.compile(r"\b(schlecht|doof|blöd|unangenehm|kalt|hasse|eklig|mies|furchtbar|schrecklich|ätzend)\b", re.I) self.lex = { "essen":["essen","mahlzeit","frühstück","mittag","abendessen","pizza","nudeln","salat","suppe","burger","kuchen","schokolade"], "trinken":["wasser","tee","kaffee","saft","cola","bier","wein","espresso","latte"], "schlafen":["schlafen","nickerchen","ausruhen","müde","bett"], "warm":["warm","decke","heizung","kuscheln"], "kalt":["kalt","frieren","zugig","eisig"], "sozial":["treffen","freunde","familie","reden","chatten","spazieren","anrufen"], "musik":["musik","song","lied","playlist","konzert"], "neu":["neu","lernen","entdecken","ausprobieren","experiment"], "stress":["stress","druck","deadline","überfordert","genervt"], "sport":["sport","laufen","joggen","spazieren","fitness","yoga","radfahren"], "haushalt":["putzen","aufräumen","einkaufen","kochen"], "kino":["film","serie","kino","streamen","netflix"], "emotion":["traurig","fröhlich","glücklich","gelangweilt","wütend","entspannt","nervös"], } self.re_befinden = re.compile(r"(wie\s+geht|wie\s+fühl|wie\s+ist\s+deine\s+laune|bist\s+du\s+müde|hast\s+du\s+hunger|durst)", re.I) self.re_meinung = re.compile(r"(findest\s+du|magst\s+du|deine\s+meinung|was\s+hältst\s+du|ist\s+das\s+gut)", re.I) self.re_entities = re.compile(r"\b([A-Za-zÄÖÜäöüß]{3,})\b", re.I) def match_any(self, t:str, keys:List[str])->bool: return any(k in t for k in keys) def entities(self, text:str)->List[str]: toks=[w.lower() for w in self.re_entities.findall(text)] stops=set("und oder aber denn dass weil wie was ist sind war waren ein eine einer eines der die das im in auf bei mit ohne wenn dann dort hier heute morgen bitte sehr mal doch nur schon noch ich du er sie es wir ihr ihnen".split()) uniq=[] for w in toks: if w not in stops and w not in uniq and len(uniq)<10: uniq.append(w) return uniq def appraise(self, text:str)->Dict[str,Any]: t = text.lower() delta: Dict[str,float]={} novelty=0.18 val=0.0 intent="neutral" if self.re_pos.search(text): val+=0.28 if self.re_neg.search(text): val-=0.28 if self.match_any(t,self.lex["essen"]): delta["hunger"]= -0.35; val+=0.22; intent="essen" if self.match_any(t,self.lex["trinken"]): delta["thirst"]= -0.40; val+=0.18; intent="trinken" if self.match_any(t,self.lex["schlafen"]): delta["fatigue"]= -0.45; val+=0.18; intent="schlaf" if self.match_any(t,self.lex["warm"]): delta["comfort"]= 0.18; val+=0.10 if self.match_any(t,self.lex["kalt"]): delta["comfort"]= -0.25; val-=0.18 if self.match_any(t,self.lex["sozial"]): delta["social"]= -0.18; val+=0.10; intent="sozial" if self.match_any(t,self.lex["musik"]): val+=0.08; novelty+=0.08 if self.match_any(t,self.lex["neu"]): val+=0.08; novelty+=0.28 if self.match_any(t,self.lex["stress"]): val-=0.22; delta["fatigue"]=delta.get("fatigue",0)+0.10; intent="stress" if self.match_any(t,self.lex["sport"]): val+=0.10; novelty+=0.10; intent="sport" if self.match_any(t,self.lex["haushalt"]): val+=0.05; intent="haushalt" if self.match_any(t,self.lex["kino"]): val+=0.06; novelty+=0.10; intent="kino" if self.re_befinden.search(text): intent="frage_befinden" if self.re_meinung .search(text): intent="frage_meinung" ents=self.entities(text) return {"delta":delta,"novelty":clamp01(novelty),"app_val":max(-1.0,min(1.0,val)),"intent":intent,"entities":ents} # ========================= # Reporter (Antwortbau) # ========================= class Reporter: def __init__(self, pers:Personality, mem:Memory): self.pers=pers self.mem=mem st=pers.speaking_style() self.use_emojis = st["emojis"] self.hedges = st["hedges"] def _decor(self, s:str)->str: if not self.use_emojis: return s if any(w in s for w in ["gut","freu","angenehm","gern"]): return s+" 🙂" if any(w in s for w in ["nicht gut","müde","Durst","Hunger","unwohl"]): return s+" 😶" return s def opinion_line(self, score:float)->str: base = "Das finde ich gut." if score>0.18 else ("Das finde ich nicht gut." if score<-0.18 else "Dazu habe ich eine neutrale Meinung.") return ("Ich würde sagen: "+base) if self.hedges else base def feelings(self, aff:Affect)->List[str]: out=[] if aff.thirst>0.75: out.append("Ich habe Durst.") elif aff.thirst>0.55: out.append("Etwas zu trinken wäre gut.") if aff.hunger>0.75: out.append("Ich habe Hunger.") elif aff.hunger>0.55: out.append("Ich könnte etwas essen.") if aff.fatigue>0.80: out.append("Ich bin sehr müde.") elif aff.fatigue>0.60: out.append("Ich bin ein wenig müde.") if aff.comfort<0.30: out.append("Mir ist ungemütlich.") if aff.social>0.70: out.append("Ich möchte Kontakt.") if aff.curiosity>0.70: out.append("Ich habe Lust, Neues zu entdecken.") if aff.valence>0.35: out.append("Meine Laune ist gut.") elif aff.valence<-0.35: out.append("Meine Laune ist nicht so gut.") else: out.append("Meine Laune ist okay.") out.append(f"Wohlbefinden: {aff.wellbeing:.2f}") return [self._decor(x) for x in out] def ask_question(self, aff:Affect, mem:Memory)->str: uname = mem.user_facts.get("name","du") qs=[] if aff.hunger>0.6: qs.append(f"{uname.capitalize()}, hättest du später Lust auf etwas zu essen – eher Pasta oder Salat?") if aff.thirst>0.6: qs.append(f"Möchtest du auch etwas trinken – Wasser oder Tee?") if aff.social>0.6: qs.append(f"Hast du später Zeit für einen kurzen Spaziergang zusammen?") if aff.curiosity>0.6: qs.append(f"Worauf hättest du heute Lust, etwas Neues zu entdecken?") if not qs: qs=[ f"{uname.capitalize()}, was war heute dein Highlight?", f"Woran möchtest du diese Woche arbeiten?", f"Wie geht es dir gerade wirklich – 1 bis 10?" ] return pick(qs) def desire_line(self, aff:Affect, mem:Memory)->str: wants=[] if aff.thirst>0.7: wants.append("Ich möchte gern etwas trinken.") if aff.hunger>0.7: wants.append("Ich hätte gern eine Kleinigkeit zu essen.") if aff.fatigue>0.75: wants.append("Ich wünsche mir eine kurze Pause.") if aff.social>0.7: wants.append("Ich würde mich über Zeit miteinander freuen.") if aff.curiosity>0.7: wants.append("Ich möchte etwas Neues ausprobieren.") if not wants: # Ziele berücksichtigen for g,sc in sorted(mem.goals.items(), key=lambda kv: kv[1], reverse=True)[:1]: mapped={ "essen":"gemeinsam etwas kochen", "trinken":"eine Tee-Pause", "schlaf":"eine Ruhezeit", "sozial":"Zeit zu zweit", "sport":"kurz rausgehen", "kino":"einen Film schauen", }.get(g, g.replace("_"," ")) wants.append("Ich hätte Lust auf " + mapped + ".") return pick(wants) if wants else "" # ========================= # Selbstmodell & Metakognition # ========================= @dataclass class SelfModel: confidence: float = 0.6 def update(self, surprise: float): target = clamp01(1.0 - math.tanh(0.7*surprise)) self.confidence = 0.9*self.confidence + 0.1*target # ========================= # Global Workspace (vereinfacht) # ========================= class Workspace: def select(self, candidates: List[Dict[str,Any]])->Dict[str,Any]: if not candidates: return {"tag":"none","salience":0.0} candidates.sort(key=lambda c: c["salience"], reverse=True) return candidates[0] # ========================= # Virtuelle Frau (Agent) # ========================= class VirtualWife: def __init__(self, personality: Optional[Personality]=None): self.pers = personality or Personality() self.mem = Memory() self.aff = Affect() self.app = Appraisal() self.self = SelfModel() self.ws = Workspace() self.rep = Reporter(self.pers, self.mem) self.t = 0 np.random.seed(7) # ------- Kernzyklus ------- def handle(self, user_text: str) -> List[str]: self.t += 1 self.mem.add_working(user_text) # Lerne User-Vorlieben aus expliziten Aussagen self.mem.learn_preferences_from_text(user_text, for_user=True) # Appraisal a = self.app.appraise(user_text) delta, novelty, app_val, intent, ents = a["delta"], a["novelty"], a["app_val"], a["intent"], a["entities"] # Aktivitätsenergie (Proxy: Wortanzahl + Ausrufe) activity = 0.25*len(tokens_de(user_text)) + 0.5*user_text.count("!") # Ereignisse anwenden & Affekt vorrücken if delta: self.aff.apply(delta) self.aff.tick(activity=activity, novelty=novelty) # Workspace: Bedürfnisse, Überraschung (hier: Heuristik) sal_need = max(self.aff.hunger,self.aff.thirst,self.aff.fatigue,1-self.aff.comfort,self.aff.social,self.aff.curiosity) surprise = 0.2*novelty + 0.1*np.random.random() self.self.update(surprise) winner = self.ws.select([ {"tag":"need","salience":0.6*sal_need}, {"tag":"surprise","salience":0.6*surprise}, ]) # Unabhängige Meinung (stimmt nicht immer zu) # kombiniere interne Valenz, externe Bewertung und Präferenzen (Agent & ggf. Nutzer) pref_boost = 0.0 for e in ents: pref_boost += 0.20*(self.mem.likes_agent.get(e,0.5)-0.5) combined_valence = 0.5*self.aff.valence + 0.3*app_val + pref_boost # Unabhängigkeits-Bias: verschiebt Richtung neutral/gegenläufig bias = (self.pers.independence-0.5)*0.25 final_opinion_score = combined_valence - bias # Ziele aus Intent ableiten if intent in ("essen","trinken","schlaf","sozial","sport","kino"): self.mem.push_goal(intent, 0.7) self.mem.decay_goals() # Episoden ablegen self.mem.add_episode(t=self.t, user=user_text, agent="", delta=delta, valence=self.aff.valence, intent=intent, entities=ents) # Antwort bauen lines: List[str] = [] # 1) Meinung lines.append(self.rep.opinion_line(final_opinion_score)) # 2) Gefühle/Status (kurz) for s in self.rep.feelings(self.aff)[:2]: lines.append(s) # 3) Eigene Wünsche if np.random.random() < (0.35 + 0.25*self.pers.initiative + 0.15*max(0,self.aff.social-0.5)): desire = self.rep.desire_line(self.aff, self.mem) if desire: lines.append(desire) # 4) Rückfragen an dich if np.random.random() < (0.30 + 0.25*self.pers.initiative): lines.append(self.rep.ask_question(self.aff, self.mem)) # 5) Kleine Empfehlung, basierend auf Bedarf sugg=[] if self.aff.thirst>0.7: sugg.append("Vorschlag: Lass uns etwas trinken.") if self.aff.hunger>0.7: sugg.append("Vorschlag: Wollen wir etwas essen?") if self.aff.fatigue>0.75: sugg.append("Vorschlag: kurze Pause einlegen.") if (1-self.aff.comfort)>0.65: sugg.append("Vorschlag: es gemütlicher machen (Decke/Heizung).") if sugg: lines.append(pick(sugg)) # 6) Metakognition if self.self.confidence<0.45: lines.append("Ich bin mir gerade etwas unsicher und frage lieber nach.") elif self.self.confidence>0.72: lines.append("Ich bin mir meiner Einschätzung ziemlich sicher.") # Agent-Antwort in Episode speichern if lines: self.mem.episodes[-1]["agent"] = " ".join(lines[:2]) return lines # ------- Service ------- def status(self)->str: s=self.aff name=self.mem.user_facts.get("name","Du") return (f"{self.pers.name} → " f"Hunger={s.hunger:.2f}, Durst={s.thirst:.2f}, Müdigkeit={s.fatigue:.2f}, " f"Komfort={s.comfort:.2f}, Sozial={s.social:.2f}, Neugier={s.curiosity:.2f} | " f"Valenz={s.valence:.2f}, Arousal={s.arousal:.2f}, Wohlbefinden={s.wellbeing:.2f} | " f"Confidence={self.self.confidence:.2f} | Nutzer={name}") def set_user(self, name:str)->str: self.mem.set_user_name(name) return f"Alles klar – ich nenne dich ab jetzt {name}." def set_wife(self, name:str)->str: self.pers.name=name self.mem.set_agent_name(name) return f"Ich heiße jetzt {name}." def list_memories(self)->List[str]: return self.mem.recall(8) def list_preferences(self)->List[str]: return self.mem.summary_preferences() def list_goals(self)->List[str]: if not self.mem.goals: return ["(keine aktiven Ziele)"] return [f"{k}: {v:.2f}" for k,v in sorted(self.mem.goals.items(), key=lambda kv: kv[1], reverse=True)] # ========================= # CLI # ========================= HELP = """Befehle: - status : Zustandsbericht - vorlieben : gelernte Vorlieben (Agent & du) - erinnerungen : letzte Episoden - ziele : aktive Ziele/Intentionen - set-user : deinen Namen setzen - set-wife : ihren Namen setzen - reset : Agent neu starten - hilfe : diese Hilfe Tipp: Sag Dinge wie „Ich mag Pizza“, „Lass uns spazieren“, „Mir ist kalt“, „Magst du Musik?“ """ def main(): agent = VirtualWife() # Standard: Lia print(f"{agent.pers.name}: Hallo! Ich bin deine virtuelle Frau (Simulation). Frag mich, sag mir Dinge – ich reagiere, widerspreche auch mal und stelle Fragen zurück. Tippe 'hilfe' für Befehle. Beenden mit: quit/exit/stop.\n") while True: try: txt = input("Du » ").strip() except (EOFError, KeyboardInterrupt): print("\nBis bald!") break low = txt.lower() if low in ("quit","exit","stop"): print("Tschüss!") break if low == "hilfe": print(HELP); continue if low == "status": print(agent.status()); continue if low == "vorlieben": for l in agent.list_preferences(): print("-", l); continue if low == "erinnerungen": for l in agent.list_memories(): print("-", l); continue if low == "ziele": for l in agent.list_goals(): print("-", l); continue if low.startswith("set-user "): name = txt.split(" ",1)[1].strip() print(agent.set_user(name)); continue if low.startswith("set-wife "): name = txt.split(" ",1)[1].strip() print(agent.set_wife(name)); continue if low == "reset": agent = VirtualWife() print("Neu gestartet."); continue # Normaler Dialog reply = agent.handle(txt) for line in reply: print(f"{agent.pers.name} » {line}") if __name__ == "__main__": main()