feat: Team-Member Lernsystem & Automatische Vorstellung

Team-Member Updates:
- Korrigierte Rollen und Verantwortlichkeiten:
  * Eric Fischer: Lead Programming & Tech
  * Georg Tschare: CEO & Kundenkommunikation
  * Piotr Dyderski: SysAdmin, TechLead, 3D Artist & R&D

Neue Funktionen:
- update_team_member() - Aktualisiert Team-Member-Daten per Email/Name
- is_known_team_member() - Prüft ob Email in Team-DB existiert
- @UPDATE_TEAM_MEMBER Kommando für Orchestrator
- @ADD_TEAM_MEMBER Kommando für Orchestrator

Intelligentes Lernsystem:
- Erkennt neue Email-Absender automatisch
- Leitet unbekannte Absender automatisch an Orchestrator
- Orchestrator bittet um Vorstellung (Name, Rolle, Aufgaben)
- Informationen werden in Team-DB gespeichert
- Verbessert zukünftige Koordination

Email-Workflow für neue Absender:
1. Email von unbekanntem @diversityball.at kommt an
2. System erkennt: "Nicht in Team-DB"
3. Orchestrator übernimmt automatisch
4. Fragt freundlich nach Vorstellung
5. Beantwortet auch die eigentliche Anfrage
6. Speichert Infos mit @ADD_TEAM_MEMBER

Vorteile:
✓ Orchestrator baut automatisch Wissensdatenbank auf
✓ Bessere Koordination durch bekannte Verantwortlichkeiten
✓ Keine manuellen Team-Member Einträge nötig
✓ Freundlicher, professioneller Erstkontakt
✓ Lernfähiges System das besser wird

Context-Weiterleitung:
- extra_context aus Tasks wird jetzt durchgereicht
- Kombiniert Email-Kontext + Team-Member-Status
- Agents haben volle Informationen über Absender
This commit is contained in:
pdyde 2026-02-21 14:01:41 +01:00
parent 5b0cf6e640
commit 632a6c253a

132
app.py
View file

@ -515,6 +515,23 @@ TelegramID: [Telegram-ID des Team-Members]
Message: [Nachricht] Message: [Nachricht]
@END @END
**Team-Member Informationen aktualisieren:**
@UPDATE_TEAM_MEMBER
Identifier: [Email oder Name des Team-Members]
Role: [Neue Rolle] (optional)
Responsibilities: [Neue Verantwortlichkeiten] (optional)
TelegramID: [Telegram-ID] (optional)
Phone: [Telefon] (optional)
@END
**Neuen Team-Member hinzufügen:**
@ADD_TEAM_MEMBER
Name: [Vollständiger Name]
Role: [Rolle/Position]
Responsibilities: [Verantwortlichkeiten]
Email: [Email-Adresse]
@END
Der Orchestrator kümmert sich um die Zuweisung und Kommunikation! Der Orchestrator kümmert sich um die Zuweisung und Kommunikation!
## Wichtig: ## Wichtig:
@ -746,6 +763,47 @@ def add_team_member(name, role, responsibilities='', email='', telegram_id=None,
return member_id return member_id
def update_team_member(identifier, **kwargs):
"""Aktualisiert ein Team-Mitglied (per Email oder Name)."""
con = sqlite3.connect(EMAIL_JOURNAL_DB)
# Finde Member per Email oder Name
member = con.execute(
"SELECT id FROM team_members WHERE email = ? OR name = ? LIMIT 1",
(identifier, identifier)
).fetchone()
if not member:
con.close()
logging.warning(f"[DB] Team-Member '{identifier}' nicht gefunden für Update")
return False
member_id = member[0]
# Baue UPDATE query
updates = []
params = []
for key, value in kwargs.items():
if key in ['name', 'role', 'responsibilities', 'email', 'telegram_id', 'phone', 'active']:
updates.append(f"{key} = ?")
params.append(value)
if not updates:
con.close()
return False
params.append(member_id)
query = f"UPDATE team_members SET {', '.join(updates)} WHERE id = ?"
con.execute(query, params)
con.commit()
con.close()
logging.info(f"[DB] Team-Member '{identifier}' aktualisiert")
return True
def get_team_member_summary(): def get_team_member_summary():
"""Erstellt eine Text-Zusammenfassung aller Team-Members für den Orchestrator.""" """Erstellt eine Text-Zusammenfassung aller Team-Members für den Orchestrator."""
members = get_team_members(active_only=True) members = get_team_members(active_only=True)
@ -754,6 +812,8 @@ def get_team_member_summary():
return "Keine Team-Mitglieder definiert." return "Keine Team-Mitglieder definiert."
summary = "## Reale Team-Mitglieder:\n\n" summary = "## Reale Team-Mitglieder:\n\n"
summary += "Du kannst Team-Member-Informationen jederzeit mit @UPDATE_TEAM_MEMBER aktualisieren!\n\n"
for member in members: for member in members:
summary += f"**{member['name']}** ({member['role']})\n" summary += f"**{member['name']}** ({member['role']})\n"
if member['responsibilities']: if member['responsibilities']:
@ -1172,6 +1232,22 @@ def is_whitelisted(sender_address):
return False return False
def is_known_team_member(sender_address):
"""Prüft ob Absender ein bekanntes Team-Mitglied ist."""
match = re.search(r'<([^>]+)>', sender_address)
addr = match.group(1).lower() if match else sender_address.lower().strip()
# Prüfe ob Email in Team-Members DB
con = sqlite3.connect(EMAIL_JOURNAL_DB)
result = con.execute(
"SELECT name, role FROM team_members WHERE LOWER(email) = ? AND active = 1",
(addr,)
).fetchone()
con.close()
return result # Returns (name, role) or None
def decode_email_header_value(value): def decode_email_header_value(value):
"""Dekodiert Email-Header (z.B. encoded Subject/From).""" """Dekodiert Email-Header (z.B. encoded Subject/From)."""
if not value: if not value:
@ -1487,7 +1563,37 @@ def poll_emails():
addr_match = re.search(r'<([^>]+)>', sender) addr_match = re.search(r'<([^>]+)>', sender)
sender_email = addr_match.group(1) if addr_match else sender.strip() sender_email = addr_match.group(1) if addr_match else sender.strip()
# Agent bestimmen # Prüfe ob Absender bekannt ist (in Team-Members DB)
known_member = is_known_team_member(sender)
# Wenn unbekannt: Bitte um Vorstellung
extra_context = ""
if not known_member:
extra_context = f"""
## ⚠️ WICHTIG - Neuer Absender:
Diese Email kommt von **{sender}** - diese Person ist noch nicht in der Team-Datenbank!
Bitte in deiner Antwort:
1. Freundlich begrüßen
2. Um Vorstellung bitten: Name, Rolle, Verantwortlichkeiten
3. Fragen was sie im Projekt macht
4. Dann ihre eigentliche Anfrage beantworten
Der Orchestrator wird die Informationen dann mit @ADD_TEAM_MEMBER oder @UPDATE_TEAM_MEMBER in die Datenbank eintragen.
"""
else:
extra_context = f"""
## Team-Member erkannt:
Diese Email kommt von **{known_member[0]}** ({known_member[1]})
"""
# Agent bestimmen - bei unbekannten Sendern immer Orchestrator
if not known_member:
agent_key = 'orchestrator'
logger.info("[EmailPoller] Unbekannter Absender → Orchestrator übernimmt")
else:
full_prompt_for_routing = f"{subject} {body}" full_prompt_for_routing = f"{subject} {body}"
agent_key = delegate_to_agent(full_prompt_for_routing) agent_key = delegate_to_agent(full_prompt_for_routing)
@ -1511,6 +1617,7 @@ def poll_emails():
'original_body': body, 'original_body': body,
'message_id': message_id, # für Journal-Update durch TaskWorker 'message_id': message_id, # für Journal-Update durch TaskWorker
'imap_uid': email_id.decode(), 'imap_uid': email_id.decode(),
'extra_context': extra_context, # Für unbekannte Absender
} }
tasks.append(task) tasks.append(task)
with task_queue_lock: with task_queue_lock:
@ -1570,14 +1677,19 @@ Bitte bearbeite diese Anfrage vollständig:
3. Formuliere eine vollständige, hilfreiche Antwort 3. Formuliere eine vollständige, hilfreiche Antwort
4. Die Antwort wird automatisch an {sender_email} zurückgeschickt""" 4. Die Antwort wird automatisch an {sender_email} zurückgeschickt"""
extra_context = f""" email_context = f"""
## Email-Kontext: ## Email-Kontext:
- Diese Anfrage kommt per Email von: {sender_email} - Diese Anfrage kommt per Email von: {sender_email}
- Die Antwort wird automatisch per Email zurückgeschickt - Die Antwort wird automatisch per Email zurückgeschickt
- Formuliere die Antwort daher als Email-Text (freundlich, professionell) - Formuliere die Antwort daher als Email-Text (freundlich, professionell)
- Wenn gebeten wird, eine Email an jemanden zu schicken: gib die Adresse in der Form "An: adresse@example.com" oder "To: adresse@example.com" an""" - Wenn gebeten wird, eine Email an jemanden zu schicken: gib die Adresse in der Form "An: adresse@example.com" oder "To: adresse@example.com" an"""
agent_response = execute_agent_task(agent_key, full_prompt, extra_context=extra_context) # Kombiniere Email-Kontext mit Task-Kontext (z.B. unbekannter Absender)
combined_context = email_context
if task.get('extra_context'):
combined_context += "\n\n" + task['extra_context']
agent_response = execute_agent_task(agent_key, full_prompt, extra_context=combined_context)
# Zusätzliche Empfänger aus der Agenten-Antwort extrahieren # Zusätzliche Empfänger aus der Agenten-Antwort extrahieren
extra_recipients = re.findall( extra_recipients = re.findall(
@ -2762,24 +2874,24 @@ def init_default_team_members():
if len(existing) == 0: if len(existing) == 0:
add_team_member( add_team_member(
name="Eric Fischer", name="Eric Fischer",
role="Projektleiter & Event-Koordinator", role="Lead Programming & Tech",
responsibilities="Gesamtkoordination, Budget-Verwaltung, Location-Management", responsibilities="Technische Entwicklung, Programmierung, System-Architektur",
email="eric.fischer@signtime.media", email="eric.fischer@signtime.media",
telegram_id=None, # Wird bei Bedarf ergänzt telegram_id=None,
phone="" phone=""
) )
add_team_member( add_team_member(
name="Georg Tschare", name="Georg Tschare",
role="Content & Marketing", role="CEO & Kundenkommunikation",
responsibilities="Marketing, Social Media, Presse, Kommunikation", responsibilities="Geschäftsführung, Kundenkontakt, Kommunikation, Strategie",
email="georg.tschare@signtime.media", email="georg.tschare@signtime.media",
telegram_id=None, telegram_id=None,
phone="" phone=""
) )
add_team_member( add_team_member(
name="Piotr Dyderski", name="Piotr Dyderski",
role="System-Administrator & Tech-Lead", role="SysAdmin, TechLead, 3D Artist & R&D",
responsibilities="Technische Infrastruktur, AI-Agenten, Automatisierung", responsibilities="System-Administration, Technische Leitung, 3D-Visualisierung, AI-Agenten, Research & Development",
email="p.dyderski@live.at", email="p.dyderski@live.at",
telegram_id=None, telegram_id=None,
phone="" phone=""