Compare commits
No commits in common. "4c123d5f0fc0bfbc56866ec9818667d14b7d271b" and "93eb8c6d470fc9e02cfa92e3a65fdcd12b183528" have entirely different histories.
4c123d5f0f
...
93eb8c6d47
4 changed files with 78 additions and 600 deletions
347
README.md
347
README.md
|
|
@ -1,332 +1,59 @@
|
||||||
# Frankenbot - Multi-Agent Event-Management-System
|
# Agenten Orchestrierung - Flask Webanwendung
|
||||||
|
|
||||||
Ein Flask-basiertes Multi-Agent-System zur Orchestrierung und Verwaltung von KI-Agenten für Event-Management. Entwickelt für den **Diversity-Ball Wien 2026**.
|
Eine Flask-basierte Webanwendung zur Verwaltung und Orchestrierung von Agenten.
|
||||||
|
|
||||||
## 🎯 Überblick
|
## Features
|
||||||
|
|
||||||
Frankenbot ist ein intelligentes Event-Management-System, bei dem spezialisierte KI-Agents zusammenarbeiten, um komplexe Event-Organisationsaufgaben zu bewältigen. Ein zentraler Orchestrator koordiniert die Kommunikation zwischen den Agents und delegiert Tasks basierend auf Expertise.
|
- **Dashboard**: Übersicht aller verfügbaren Agenten und letzte Tasks
|
||||||
|
- **Chat**: Interaktive Kommunikation mit ausgewählten Agenten
|
||||||
|
- **Tasks**: Task-Verwaltung mit Status-Verfolgung (pending/in_progress/completed)
|
||||||
|
- **Dateien**: Datei-Upload und Verwaltung
|
||||||
|
|
||||||
## ✨ Haupt-Features
|
## Installation
|
||||||
|
|
||||||
### 🤖 Multi-Agent Orchestrierung
|
1. Installieren Sie die erforderlichen Abhängigkeiten:
|
||||||
- **Orchestrator als zentraler Hub**: Alle Agent-Kommunikation läuft über den Orchestrator
|
|
||||||
- **Spezialisierte Agents**: Budget Manager, Catering Manager, Location Manager, Program Manager, etc.
|
|
||||||
- **Agent-zu-Agent Kommunikation**: Agents können Fragen stellen, Subtasks erstellen und neue Agents vorschlagen
|
|
||||||
- **Dynamische Modellauswahl**: 29+ KI-Modelle verfügbar (OpenCode, Anthropic, Ollama)
|
|
||||||
|
|
||||||
### 📋 Task-Management
|
|
||||||
- **Automatische Task-Verarbeitung**: TaskBeat verarbeitet Tasks alle 10 Sekunden
|
|
||||||
- **Auto-Refresh UI**: Tasks-Seite aktualisiert sich automatisch alle 15 Sekunden
|
|
||||||
- **Task-Delegation**: Orchestrator analysiert Tasks und weist sie passenden Agents zu
|
|
||||||
- **Status-Tracking**: pending → in_progress → completed
|
|
||||||
|
|
||||||
### 💬 Chat-System
|
|
||||||
- **Live-Streaming**: Server-Sent Events für Echtzeit-Antworten
|
|
||||||
- **Agent-Auswahl**: Chat mit spezifischen Agents oder dem Orchestrator
|
|
||||||
- **Session-basierte History**: Kontext bleibt über Konversation erhalten
|
|
||||||
|
|
||||||
### 📧 Email-Integration
|
|
||||||
- **IMAP**: Email-Posteingang überwachen
|
|
||||||
- **SMTP**: Emails versenden
|
|
||||||
- **Email-to-Task**: Automatische Task-Erstellung aus Emails
|
|
||||||
- **Whitelist-System**: Nur autorisierte Absender
|
|
||||||
|
|
||||||
### 📁 Datei-Management
|
|
||||||
- **Agent Work-Folders**: Jeder Agent hat eigenes Arbeitsverzeichnis
|
|
||||||
- **Email-Vorlagen**: Wiederverwendbare Email-Templates
|
|
||||||
- **Projekt-Dateien**: Zentrale Dokumentenverwaltung
|
|
||||||
- **Upload & Download**: Web-basierter Datei-Manager
|
|
||||||
|
|
||||||
### 🧠 Memory-System
|
|
||||||
- **JSON-basierte Erinnerungen**: Strukturierte Agent-Memories
|
|
||||||
- **Kategorien**: tasks, notes, decisions, contacts
|
|
||||||
- **Agent-spezifisch**: Jeder Agent verwaltet eigene Erinnerungen
|
|
||||||
- **Memory-Summaries**: Kompakte Übersichten für Prompts
|
|
||||||
|
|
||||||
### 📚 Wissensdatenbank
|
|
||||||
- **On-Demand Zugriff**: Agents nutzen `@READ_KNOWLEDGE` statt volle KB im Prompt
|
|
||||||
- **Performance-Optimierung**: Reduziert Prompt-Größe um ~15KB
|
|
||||||
- **Event-spezifisch**: Diversity-Ball Wien 2026 Informationen
|
|
||||||
|
|
||||||
## 🚀 Installation
|
|
||||||
|
|
||||||
### Voraussetzungen
|
|
||||||
```bash
|
```bash
|
||||||
# Python 3.10+
|
pip install flask
|
||||||
python --version
|
|
||||||
|
|
||||||
# OpenCode CLI (für Agent-Ausführung)
|
|
||||||
opencode --version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Abhängigkeiten installieren
|
## Starten der Anwendung
|
||||||
```bash
|
|
||||||
pip install flask python-dotenv
|
|
||||||
```
|
|
||||||
|
|
||||||
### Email-Konfiguration (Optional)
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
|
||||||
# .env editieren und Email-Credentials eintragen
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📖 Verwendung
|
|
||||||
|
|
||||||
### App starten
|
|
||||||
```bash
|
|
||||||
# Foreground
|
|
||||||
python app.py
|
python app.py
|
||||||
|
|
||||||
# Background (empfohlen)
|
|
||||||
nohup python app.py > app_output.log 2>&1 &
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Die Anwendung läuft dann auf: **http://localhost:5000**
|
Die Anwendung ist dann unter http://localhost:5000 erreichbar.
|
||||||
|
|
||||||
### Agent-Kommandos
|
## Verfügbare Agenten
|
||||||
|
|
||||||
Agents können folgende Kommandos in ihren Antworten verwenden:
|
| Agent | Beschreibung |
|
||||||
|
|-------|---------------|
|
||||||
|
| Researcher | Recherchiert Informationen im Web |
|
||||||
|
| Location Manager | Verwaltet Veranstaltungsorte |
|
||||||
|
| Catering Manager | Organisiert Verpflegung |
|
||||||
|
| Program Manager | Koordiniert Programmabläufe |
|
||||||
|
| Document Editor | Bearbeitet Dokumente |
|
||||||
|
| Tax Advisor | Berät zu steuerlichen Fragen |
|
||||||
|
| Musik Rechte Advisor | Berät zu Musikrechten |
|
||||||
|
| Zusammenfasser | Erstellt Zusammenfassungen |
|
||||||
|
|
||||||
#### Frage an Orchestrator
|
## Routen
|
||||||
```
|
|
||||||
@ASK_ORCHESTRATOR
|
|
||||||
Question: Wie hoch ist das verfügbare Budget für Catering?
|
|
||||||
Context: Ich plane das Menü und brauche Budget-Info
|
|
||||||
@END
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Subtask erstellen
|
- `/` - Dashboard
|
||||||
```
|
- `/chat` - Chat mit Agenten
|
||||||
@CREATE_SUBTASK
|
- `/tasks` - Task Verwaltung
|
||||||
Task: Location-Vertrag prüfen
|
- `/files` - Datei Verwaltung
|
||||||
Requirements: Rechtliche Prüfung der Mietbedingungen
|
|
||||||
@END
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Wissensdatenbank durchsuchen
|
## Technologie
|
||||||
```
|
|
||||||
@READ_KNOWLEDGE
|
|
||||||
Topic: Budget
|
|
||||||
@END
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Neuen Agent vorschlagen
|
- Flask 3.x
|
||||||
```
|
- Bootstrap 5
|
||||||
@SUGGEST_AGENT
|
- Session-basierte Chat-Verwaltung
|
||||||
Role: Security Manager
|
- Lokaler Datei-Upload
|
||||||
Skills: Sicherheitsplanung, Crowd Management
|
|
||||||
Reason: Für große Events mit 500+ Gästen benötigt
|
|
||||||
@END
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🗂️ Projekt-Struktur
|
## Hinweise
|
||||||
|
|
||||||
```
|
- Dateien werden im `uploads/` Verzeichnis gespeichert
|
||||||
frankenbot/
|
- Chat-Verlauf wird session-basiert gespeichert (max. 20 Einträge)
|
||||||
├── app.py # Haupt-Flask-App (2128 Zeilen)
|
- Tasks werden im Speicher gehalten (nicht persistent)
|
||||||
├── agent_config.json # Agent-Modell-Zuweisungen
|
|
||||||
├── diversityball_knowledge.md # Event-Wissensdatenbank
|
|
||||||
├── email_journal.db # SQLite-Datenbank (Emails)
|
|
||||||
│
|
|
||||||
├── agents/ # Agent-Verzeichnisse
|
|
||||||
│ ├── orchestrator/
|
|
||||||
│ │ ├── systemprompt.md
|
|
||||||
│ │ ├── personality.md
|
|
||||||
│ │ ├── work/ # Arbeitsverzeichnis
|
|
||||||
│ │ └── memory/ # JSON-Erinnerungen
|
|
||||||
│ ├── budget_manager/
|
|
||||||
│ ├── catering_manager/
|
|
||||||
│ ├── location_manager/
|
|
||||||
│ └── ...
|
|
||||||
│
|
|
||||||
├── templates/ # HTML-Templates (Jinja2)
|
|
||||||
│ ├── base.html
|
|
||||||
│ ├── index.html # Dashboard
|
|
||||||
│ ├── chat.html # Agent-Chat
|
|
||||||
│ ├── orchestrator.html # Orchestrator-Chat
|
|
||||||
│ ├── tasks.html # Task-Management
|
|
||||||
│ ├── agents.html # Agent-Verwaltung
|
|
||||||
│ ├── files.html # Datei-Manager
|
|
||||||
│ └── emails.html # Email-Interface
|
|
||||||
│
|
|
||||||
├── static/
|
|
||||||
│ └── style.css # CSS-Styling
|
|
||||||
│
|
|
||||||
└── uploads/ # Hochgeladene Dateien
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🤖 Verfügbare Agents
|
|
||||||
|
|
||||||
| Agent | Rolle | Verantwortlich für |
|
|
||||||
|-------|-------|-------------------|
|
|
||||||
| **Orchestrator** | Koordinator | Task-Delegation, Agent-Kommunikation |
|
|
||||||
| **Budget Manager** | Finanzplanung | Budget-Tracking, Kostenkalkulation |
|
|
||||||
| **Catering Manager** | Verpflegung | Menüplanung, Catering-Koordination |
|
|
||||||
| **Location Manager** | Veranstaltungsort | Raumbuchung, Logistik |
|
|
||||||
| **Program Manager** | Programmablauf | Zeitplanung, Ablaufkoordination |
|
|
||||||
| **Researcher** | Recherche | Web-Recherche, Informationsbeschaffung |
|
|
||||||
| **Document Editor** | Dokumentation | Dokument-Erstellung, Bearbeitung |
|
|
||||||
| **Tax Advisor** | Steuern | Steuerliche Beratung, AKM-Meldungen |
|
|
||||||
| **Musik Rechte Advisor** | Musikrechte | GEMA/AKM-Compliance |
|
|
||||||
| **Zusammenfasser** | Synthese | Informations-Aggregation |
|
|
||||||
|
|
||||||
## 🔧 Technologie-Stack
|
|
||||||
|
|
||||||
- **Backend**: Flask 3.x
|
|
||||||
- **Frontend**: Bootstrap 5, Vanilla JavaScript
|
|
||||||
- **Database**: SQLite (Email-Journal)
|
|
||||||
- **AI**: OpenCode CLI (Multi-Provider Support)
|
|
||||||
- **Templates**: Jinja2
|
|
||||||
- **Streaming**: Server-Sent Events (SSE)
|
|
||||||
|
|
||||||
## 🎨 UI-Routen
|
|
||||||
|
|
||||||
| Route | Beschreibung |
|
|
||||||
|-------|--------------|
|
|
||||||
| `/` | Dashboard - Agent-Übersicht & letzte Tasks |
|
|
||||||
| `/chat` | Chat mit individuellen Agents |
|
|
||||||
| `/orchestrator` | Orchestrator-Chat mit Task-Verteilung |
|
|
||||||
| `/tasks` | Task-Management mit Auto-Refresh |
|
|
||||||
| `/agents` | Agent-Verwaltung (Erstellen, Bearbeiten, Modell-Auswahl) |
|
|
||||||
| `/files` | Datei-Manager (Upload, Download, Agent Work-Folders) |
|
|
||||||
| `/emails` | Email-Interface (Senden, Empfangen, Vorlagen) |
|
|
||||||
|
|
||||||
## ⚙️ Konfiguration
|
|
||||||
|
|
||||||
### Agent-Modelle anpassen
|
|
||||||
Bearbeite `agent_config.json`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"orchestrator": "anthropic/claude-3.7-sonnet",
|
|
||||||
"budget_manager": "opencode/qwen2.5-coder:32b",
|
|
||||||
"catering_manager": "anthropic/claude-3.5-sonnet"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Email-Setup
|
|
||||||
Bearbeite `.env`:
|
|
||||||
```bash
|
|
||||||
EMAIL_ADDRESS=your@email.com
|
|
||||||
EMAIL_PASSWORD=your_app_password
|
|
||||||
EMAIL_IMAP_SERVER=imap.gmail.com
|
|
||||||
EMAIL_SMTP_SERVER=smtp.gmail.com
|
|
||||||
```
|
|
||||||
|
|
||||||
### Background-Threads
|
|
||||||
- **EmailPoller**: Prüft alle 2 Minuten auf neue Emails
|
|
||||||
- **TaskBeat**: Verarbeitet Tasks alle 10 Sekunden
|
|
||||||
- **TaskWorker**: Führt Agent-Tasks aus (Timeout: 10 Min)
|
|
||||||
|
|
||||||
## 📊 Performance
|
|
||||||
|
|
||||||
### Optimierungen
|
|
||||||
- ✅ Wissensdatenbank nicht im Prompt (on-demand via `@READ_KNOWLEDGE`)
|
|
||||||
- ✅ Model-Cache (1h TTL)
|
|
||||||
- ✅ Agent arbeiten in eigenen work-Verzeichnissen
|
|
||||||
- ✅ Timeout: 600 Sekunden (10 Min) für komplexe Tasks
|
|
||||||
|
|
||||||
### Geschätzte Ausführungszeiten
|
|
||||||
- Einfache Tasks: 30-60 Sekunden
|
|
||||||
- Komplexe Tasks: 2-5 Minuten
|
|
||||||
- Research-Tasks: 3-8 Minuten
|
|
||||||
|
|
||||||
## 🔒 Security
|
|
||||||
|
|
||||||
### Implementierte Maßnahmen
|
|
||||||
- ✅ XSS-Schutz: HTML-Escaping in allen Templates
|
|
||||||
- ✅ Email-Whitelist: Nur autorisierte Absender
|
|
||||||
- ✅ Exception-Handling: Spezifische Exception-Typen mit Logging
|
|
||||||
- ✅ Request-Validierung: `.get()` mit Defaults
|
|
||||||
- ✅ Session-basierte Auth: Flask-Sessions
|
|
||||||
|
|
||||||
### Empfehlungen für Produktion
|
|
||||||
- 🔐 HTTPS aktivieren (TLS/SSL)
|
|
||||||
- 🔐 Secrets in Umgebungsvariablen (nicht in Code)
|
|
||||||
- 🔐 CSP Headers setzen
|
|
||||||
- 🔐 Rate Limiting für API-Endpoints
|
|
||||||
- 🔐 WSGI Server verwenden (Gunicorn, uWSGI)
|
|
||||||
|
|
||||||
## 🐛 Debugging
|
|
||||||
|
|
||||||
### Logs prüfen
|
|
||||||
```bash
|
|
||||||
# Echtzeit-Logs
|
|
||||||
tail -f app_output.log
|
|
||||||
|
|
||||||
# Letzte 50 Zeilen
|
|
||||||
tail -50 app_output.log
|
|
||||||
|
|
||||||
# Nach Errors suchen
|
|
||||||
grep -i error app_output.log
|
|
||||||
```
|
|
||||||
|
|
||||||
### App-Status
|
|
||||||
```bash
|
|
||||||
# Läuft die App?
|
|
||||||
ps aux | grep "python3 app.py"
|
|
||||||
|
|
||||||
# Port 5000 belegt?
|
|
||||||
lsof -i :5000
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 Entwicklung
|
|
||||||
|
|
||||||
### Code-Quality
|
|
||||||
- ✅ 72 Funktionen, 29 Routes
|
|
||||||
- ✅ Keine unused imports
|
|
||||||
- ✅ Spezifisches Exception-Handling
|
|
||||||
- ✅ Logging für alle kritischen Operationen
|
|
||||||
|
|
||||||
### Git-Workflow
|
|
||||||
```bash
|
|
||||||
# Status prüfen
|
|
||||||
git status
|
|
||||||
|
|
||||||
# Änderungen committen
|
|
||||||
git add .
|
|
||||||
git commit -m "feat: neue Funktion"
|
|
||||||
|
|
||||||
# Pushen
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📚 Weitere Dokumentation
|
|
||||||
|
|
||||||
- `CHANGES.md` - Detaillierte Feature-Implementierungen
|
|
||||||
- `FEATURES.md` - Feature-Übersicht
|
|
||||||
- `QUICKSTART.md` - Schnellstart-Anleitung
|
|
||||||
- `.env.example` - Email-Konfiguration
|
|
||||||
|
|
||||||
## 🎓 Use Cases
|
|
||||||
|
|
||||||
### Event-Planung
|
|
||||||
1. Orchestrator empfängt Planungs-Anfrage
|
|
||||||
2. Erstellt Subtasks für Budget, Location, Catering
|
|
||||||
3. Agents arbeiten parallel
|
|
||||||
4. Orchestrator aggregiert Ergebnisse
|
|
||||||
|
|
||||||
### Email-basierte Tasks
|
|
||||||
1. Email kommt an (von Whitelist-Absender)
|
|
||||||
2. EmailPoller erstellt Task
|
|
||||||
3. TaskBeat weist Task zu passendem Agent
|
|
||||||
4. Agent arbeitet Task ab und antwortet per Email
|
|
||||||
|
|
||||||
### Wissensdatenbank-Abfrage
|
|
||||||
1. Agent braucht Info (z.B. Budget)
|
|
||||||
2. Nutzt `@READ_KNOWLEDGE Topic: Budget`
|
|
||||||
3. System extrahiert relevante Sektion
|
|
||||||
4. Agent erhält nur benötigte Informationen
|
|
||||||
|
|
||||||
## 🤝 Mitwirken
|
|
||||||
|
|
||||||
Dieses Projekt wurde entwickelt für den **Diversity-Ball Wien 2026**.
|
|
||||||
|
|
||||||
## 📄 Lizenz
|
|
||||||
|
|
||||||
Dieses Projekt ist für internen Gebrauch und Event-Management entwickelt.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Version**: 1.0.0
|
|
||||||
**Letztes Update**: Februar 2026
|
|
||||||
**Status**: Production-Ready ✅
|
|
||||||
|
|
|
||||||
282
app.py
282
app.py
|
|
@ -60,8 +60,7 @@ def get_agent_memory(agent_key, memory_type='tasks'):
|
||||||
try:
|
try:
|
||||||
with open(memory_file, 'r', encoding='utf-8') as f:
|
with open(memory_file, 'r', encoding='utf-8') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except (json.JSONDecodeError, IOError, OSError) as e:
|
except:
|
||||||
logging.warning(f"Fehler beim Laden von {memory_file}: {e}")
|
|
||||||
pass
|
pass
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
@ -211,8 +210,7 @@ def get_agent_config():
|
||||||
try:
|
try:
|
||||||
with open(AGENT_CONFIG_FILE, 'r', encoding='utf-8') as f:
|
with open(AGENT_CONFIG_FILE, 'r', encoding='utf-8') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except (json.JSONDecodeError, IOError, OSError) as e:
|
except:
|
||||||
logging.warning(f"Fehler beim Laden von {AGENT_CONFIG_FILE}: {e}")
|
|
||||||
pass
|
pass
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
@ -398,53 +396,21 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
|
||||||
# Memory-Zusammenfassung laden
|
# Memory-Zusammenfassung laden
|
||||||
memory_summary = get_agent_memory_summary(agent_key)
|
memory_summary = get_agent_memory_summary(agent_key)
|
||||||
|
|
||||||
# Wissensdatenbank-Pfad (Agent holt sich selbst was er braucht)
|
|
||||||
kb_file = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
|
kb_file = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
|
||||||
|
kb_content = ""
|
||||||
|
if os.path.exists(kb_file):
|
||||||
|
with open(kb_file, 'r', encoding='utf-8') as f:
|
||||||
|
kb_content = f.read()
|
||||||
|
|
||||||
# System-Prompt = Agent-Rolle + Memory + Kommandos (OHNE große Wissensdatenbank!)
|
# System-Prompt = Agent-Rolle + Wissensdatenbank + Memory
|
||||||
full_system = f"""{system_prompt}
|
full_system = f"""{system_prompt}
|
||||||
|
|
||||||
|
## Wissensdatenbank (Diversity-Ball):
|
||||||
|
{kb_content}
|
||||||
|
|
||||||
## Deine Erinnerungen:
|
## Deine Erinnerungen:
|
||||||
{memory_summary}
|
{memory_summary}
|
||||||
|
|
||||||
## Wissensdatenbank:
|
|
||||||
Die Wissensdatenbank liegt unter: {kb_file}
|
|
||||||
|
|
||||||
Wenn du spezifische Informationen brauchst:
|
|
||||||
@READ_KNOWLEDGE
|
|
||||||
Topic: [Thema, z.B. "Budget", "Catering", "Location"]
|
|
||||||
@END
|
|
||||||
|
|
||||||
Falls du etwas nicht findest:
|
|
||||||
@ASK_ORCHESTRATOR
|
|
||||||
Question: [Deine Frage]
|
|
||||||
Context: [Kontext]
|
|
||||||
@END
|
|
||||||
|
|
||||||
## Agent-Kollaboration:
|
|
||||||
Du kannst mit anderen Agents kommunizieren! Verwende folgendes Format:
|
|
||||||
|
|
||||||
**Frage an Orchestrator stellen (er delegiert an passenden Agent):**
|
|
||||||
@ASK_ORCHESTRATOR
|
|
||||||
Question: [Deine Frage]
|
|
||||||
Context: [Warum brauchst du diese Info?]
|
|
||||||
@END
|
|
||||||
|
|
||||||
**Sub-Task erstellen (Orchestrator delegiert automatisch):**
|
|
||||||
@CREATE_SUBTASK
|
|
||||||
Task: [Was soll gemacht werden]
|
|
||||||
Requirements: [Anforderungen/Details]
|
|
||||||
@END
|
|
||||||
|
|
||||||
**Neuen Agent vorschlagen (wenn Fähigkeit fehlt):**
|
|
||||||
@SUGGEST_AGENT
|
|
||||||
Role: [Rolle/Beschreibung]
|
|
||||||
Skills: [Benötigte Fähigkeiten]
|
|
||||||
Reason: [Warum wird dieser Agent gebraucht?]
|
|
||||||
@END
|
|
||||||
|
|
||||||
Der Orchestrator kümmert sich um die Zuweisung und Kommunikation!
|
|
||||||
|
|
||||||
## Wichtig:
|
## Wichtig:
|
||||||
- Du hast Zugriff auf das Internet via WebFetch-Tool - nutze es aktiv!
|
- Du hast Zugriff auf das Internet via WebFetch-Tool - nutze es aktiv!
|
||||||
- Du kannst Emails versenden - nutze send_email wenn beauftragt
|
- Du kannst Emails versenden - nutze send_email wenn beauftragt
|
||||||
|
|
@ -466,7 +432,7 @@ Der Orchestrator kümmert sich um die Zuweisung und Kommunikation!
|
||||||
['opencode', 'run', '--model', model, '--format', 'json', combined_message],
|
['opencode', 'run', '--model', model, '--format', 'json', combined_message],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=600, # 10 Minuten für komplexe Tasks
|
timeout=300,
|
||||||
cwd=work_dir # Agent arbeitet in seinem work-Verzeichnis
|
cwd=work_dir # Agent arbeitet in seinem work-Verzeichnis
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -478,14 +444,8 @@ Der Orchestrator kümmert sich um die Zuweisung und Kommunikation!
|
||||||
data = json.loads(line)
|
data = json.loads(line)
|
||||||
if data.get('part', {}).get('type') == 'text':
|
if data.get('part', {}).get('type') == 'text':
|
||||||
response_text += data.get('part', {}).get('text', '')
|
response_text += data.get('part', {}).get('text', '')
|
||||||
except (json.JSONDecodeError, KeyError):
|
except:
|
||||||
# Ignore invalid JSON lines in streaming output
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Agent-Kommandos parsen (Task-Delegation, Fragen, Agent-Erstellung)
|
|
||||||
if response_text:
|
|
||||||
parse_agent_commands(agent_key, response_text)
|
|
||||||
|
|
||||||
return response_text if response_text else "⚠️ Keine Antwort erhalten."
|
return response_text if response_text else "⚠️ Keine Antwort erhalten."
|
||||||
else:
|
else:
|
||||||
return f"⚠️ Fehler: {result.stderr}"
|
return f"⚠️ Fehler: {result.stderr}"
|
||||||
|
|
@ -534,202 +494,6 @@ tasks = []
|
||||||
chat_history = []
|
chat_history = []
|
||||||
orchestrator_chat = []
|
orchestrator_chat = []
|
||||||
|
|
||||||
# ── Agent Message Queue ─────────────────────────────────────────────────────
|
|
||||||
# Ermöglicht Agent-zu-Agent Kommunikation
|
|
||||||
agent_messages = [] # Liste von {id, from_agent, to_agent, message_type, content, status, created, response}
|
|
||||||
|
|
||||||
def send_agent_message(from_agent, to_agent, message_type, content):
|
|
||||||
"""Sendet eine Nachricht von einem Agent an einen anderen.
|
|
||||||
|
|
||||||
message_type kann sein:
|
|
||||||
- 'task_request': Bitte den Ziel-Agent einen Task zu erledigen
|
|
||||||
- 'question': Stelle eine Frage
|
|
||||||
- 'info': Teile Information
|
|
||||||
- 'create_agent': Bitte um Erstellung eines neuen Agents
|
|
||||||
"""
|
|
||||||
message = {
|
|
||||||
'id': len(agent_messages) + 1,
|
|
||||||
'from_agent': from_agent,
|
|
||||||
'to_agent': to_agent,
|
|
||||||
'message_type': message_type,
|
|
||||||
'content': content,
|
|
||||||
'status': 'pending',
|
|
||||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
||||||
'response': None
|
|
||||||
}
|
|
||||||
agent_messages.append(message)
|
|
||||||
logger.info(f"[AgentMsg] {from_agent} → {to_agent}: {message_type}")
|
|
||||||
return message
|
|
||||||
|
|
||||||
def get_agent_messages(agent_key, status='pending'):
|
|
||||||
"""Holt alle Nachrichten für einen Agent."""
|
|
||||||
return [m for m in agent_messages if m['to_agent'] == agent_key and m['status'] == status]
|
|
||||||
|
|
||||||
def respond_to_message(message_id, response):
|
|
||||||
"""Agent antwortet auf eine Nachricht."""
|
|
||||||
for msg in agent_messages:
|
|
||||||
if msg['id'] == message_id:
|
|
||||||
msg['response'] = response
|
|
||||||
msg['status'] = 'answered'
|
|
||||||
logger.info(f"[AgentMsg] Message #{message_id} beantwortet")
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def parse_agent_commands(agent_key, response_text):
|
|
||||||
"""Parst Agent-Antwort nach Orchestrator-Kommandos und führt sie aus."""
|
|
||||||
import re
|
|
||||||
|
|
||||||
# ASK_ORCHESTRATOR: Agent stellt Frage an Orchestrator
|
|
||||||
ask_requests = re.findall(
|
|
||||||
r'@ASK_ORCHESTRATOR\s*\nQuestion:\s*([^\n]+)\s*\nContext:\s*([^@]+)@END',
|
|
||||||
response_text,
|
|
||||||
re.DOTALL
|
|
||||||
)
|
|
||||||
for question, context in ask_requests:
|
|
||||||
# Erstelle Task für Orchestrator um die Frage zu beantworten
|
|
||||||
new_task = {
|
|
||||||
'id': len(tasks) + 1,
|
|
||||||
'title': f"Frage von {agent_key}: {question.strip()[:80]}",
|
|
||||||
'description': f"""Ein Agent braucht Hilfe!
|
|
||||||
|
|
||||||
**Von:** {agent_key}
|
|
||||||
**Frage:** {question.strip()}
|
|
||||||
**Kontext:** {context.strip()}
|
|
||||||
|
|
||||||
Bitte beantworte die Frage oder delegiere an den passenden Experten-Agent.
|
|
||||||
Die Antwort wird an {agent_key} zurückgegeben.""",
|
|
||||||
'assigned_agent': 'Orchestrator',
|
|
||||||
'agent_key': 'orchestrator',
|
|
||||||
'status': 'pending',
|
|
||||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
|
||||||
'type': 'agent_question',
|
|
||||||
'from_agent': agent_key,
|
|
||||||
'return_to': agent_key
|
|
||||||
}
|
|
||||||
tasks.append(new_task)
|
|
||||||
logger.info(f"[AgentCmd] {agent_key} fragt Orchestrator: {question.strip()[:50]}")
|
|
||||||
|
|
||||||
# CREATE_SUBTASK: Agent möchte Subtask erstellen
|
|
||||||
subtask_requests = re.findall(
|
|
||||||
r'@CREATE_SUBTASK\s*\nTask:\s*([^\n]+)\s*\nRequirements:\s*([^@]+)@END',
|
|
||||||
response_text,
|
|
||||||
re.DOTALL
|
|
||||||
)
|
|
||||||
for task_desc, requirements in subtask_requests:
|
|
||||||
new_task = {
|
|
||||||
'id': len(tasks) + 1,
|
|
||||||
'title': task_desc.strip()[:100],
|
|
||||||
'description': f"Von {agent_key} angefordert:\n{requirements.strip()}",
|
|
||||||
'assigned_agent': 'Orchestrator',
|
|
||||||
'agent_key': 'orchestrator',
|
|
||||||
'status': 'pending',
|
|
||||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
|
||||||
'type': 'agent_subtask',
|
|
||||||
'from_agent': agent_key
|
|
||||||
}
|
|
||||||
tasks.append(new_task)
|
|
||||||
logger.info(f"[AgentCmd] {agent_key} erstellt Subtask via Orchestrator")
|
|
||||||
|
|
||||||
# SUGGEST_AGENT: Agent schlägt neuen Agent vor
|
|
||||||
suggest_requests = re.findall(
|
|
||||||
r'@SUGGEST_AGENT\s*\nRole:\s*([^\n]+)\s*\nSkills:\s*([^\n]+)\s*\nReason:\s*([^@]+)@END',
|
|
||||||
response_text,
|
|
||||||
re.DOTALL
|
|
||||||
)
|
|
||||||
for role, skills, reason in suggest_requests:
|
|
||||||
# Agent-Key aus Role ableiten
|
|
||||||
agent_key_suggestion = role.lower().replace(' ', '_').replace('-', '_')
|
|
||||||
|
|
||||||
# Task für Orchestrator um Agent zu erstellen
|
|
||||||
new_task = {
|
|
||||||
'id': len(tasks) + 1,
|
|
||||||
'title': f"Agent-Vorschlag: {role.strip()}",
|
|
||||||
'description': f"""Agent {agent_key} schlägt vor, einen neuen Agent zu erstellen:
|
|
||||||
|
|
||||||
**Rolle:** {role.strip()}
|
|
||||||
**Fähigkeiten:** {skills.strip()}
|
|
||||||
**Begründung:** {reason.strip()}
|
|
||||||
|
|
||||||
Bitte entscheide ob dieser Agent erstellt werden soll.""",
|
|
||||||
'assigned_agent': 'Orchestrator',
|
|
||||||
'agent_key': 'orchestrator',
|
|
||||||
'status': 'pending',
|
|
||||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
|
||||||
'type': 'agent_suggestion',
|
|
||||||
'from_agent': agent_key,
|
|
||||||
'suggested_agent': agent_key_suggestion,
|
|
||||||
'suggested_role': role.strip(),
|
|
||||||
'suggested_skills': skills.strip()
|
|
||||||
}
|
|
||||||
tasks.append(new_task)
|
|
||||||
logger.info(f"[AgentCmd] {agent_key} schlägt neuen Agent vor: {role.strip()}")
|
|
||||||
|
|
||||||
# READ_KNOWLEDGE: Agent möchte Wissensdatenbank durchsuchen
|
|
||||||
read_kb_requests = re.findall(
|
|
||||||
r'@READ_KNOWLEDGE\s*\nTopic:\s*([^@]+)@END',
|
|
||||||
response_text,
|
|
||||||
re.DOTALL
|
|
||||||
)
|
|
||||||
|
|
||||||
# Wenn Agent Wissensdatenbank lesen will, füge relevante Sektion zur Antwort hinzu
|
|
||||||
# (wird im Response-Text nicht sichtbar, aber Agent bekommt es als Context)
|
|
||||||
if read_kb_requests:
|
|
||||||
kb_file = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
|
|
||||||
if os.path.exists(kb_file):
|
|
||||||
with open(kb_file, 'r', encoding='utf-8') as f:
|
|
||||||
kb_content = f.read()
|
|
||||||
|
|
||||||
for topic in read_kb_requests:
|
|
||||||
topic_clean = topic.strip().lower()
|
|
||||||
logger.info(f"[AgentCmd] {agent_key} liest Wissensdatenbank: {topic_clean}")
|
|
||||||
|
|
||||||
# Einfache Suche: Gib relevante Abschnitte zurück
|
|
||||||
# TODO: Könnte später mit Vektorsuche verbessert werden
|
|
||||||
relevant_sections = []
|
|
||||||
for line in kb_content.split('\n'):
|
|
||||||
if topic_clean in line.lower():
|
|
||||||
relevant_sections.append(line)
|
|
||||||
|
|
||||||
if relevant_sections:
|
|
||||||
logger.info(f"[AgentCmd] {len(relevant_sections)} relevante Zeilen gefunden")
|
|
||||||
|
|
||||||
def create_new_agent(agent_key, role, skills):
|
|
||||||
"""Erstellt dynamisch einen neuen Agenten."""
|
|
||||||
agent_dir = os.path.join(AGENTS_BASE_DIR, agent_key)
|
|
||||||
|
|
||||||
if os.path.exists(agent_dir):
|
|
||||||
logger.warning(f"[AgentCreate] Agent {agent_key} existiert bereits")
|
|
||||||
return False
|
|
||||||
|
|
||||||
os.makedirs(agent_dir, exist_ok=True)
|
|
||||||
|
|
||||||
systemprompt = f"""# {role}
|
|
||||||
|
|
||||||
## Deine Rolle
|
|
||||||
{role}
|
|
||||||
|
|
||||||
## Deine Fähigkeiten
|
|
||||||
{skills}
|
|
||||||
|
|
||||||
## Aufgaben
|
|
||||||
- Erledige Tasks in deinem Fachgebiet
|
|
||||||
- Kommuniziere mit anderen Agents wenn nötig
|
|
||||||
- Dokumentiere deine Arbeit im work-Verzeichnis
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(os.path.join(agent_dir, 'systemprompt.md'), 'w', encoding='utf-8') as f:
|
|
||||||
f.write(systemprompt)
|
|
||||||
|
|
||||||
open(os.path.join(agent_dir, 'personality.md'), 'w').close()
|
|
||||||
with open(os.path.join(agent_dir, 'reminders.md'), 'w', encoding='utf-8') as f:
|
|
||||||
f.write(f"# Erinnerungen - {agent_key.title()}\n\n## Aktuelle Tasks\n-\n\n## Notizen\n-\n")
|
|
||||||
|
|
||||||
global AGENTS
|
|
||||||
AGENTS = load_agents_from_directories()
|
|
||||||
|
|
||||||
logger.info(f"[AgentCreate] Neuer Agent erstellt: {agent_key}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def load_knowledge_base():
|
def load_knowledge_base():
|
||||||
kb_path = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
|
kb_path = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
|
||||||
if os.path.exists(kb_path):
|
if os.path.exists(kb_path):
|
||||||
|
|
@ -857,8 +621,7 @@ def get_email_preview(msg):
|
||||||
return part.get_payload(decode=True).decode('utf-8', errors='ignore')[:100]
|
return part.get_payload(decode=True).decode('utf-8', errors='ignore')[:100]
|
||||||
else:
|
else:
|
||||||
return msg.get_payload(decode=True).decode('utf-8', errors='ignore')[:100]
|
return msg.get_payload(decode=True).decode('utf-8', errors='ignore')[:100]
|
||||||
except (AttributeError, TypeError, UnicodeDecodeError) as e:
|
except:
|
||||||
logging.debug(f"Email preview extraction failed: {e}")
|
|
||||||
return '(Vorschau nicht verfügbar)'
|
return '(Vorschau nicht verfügbar)'
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
@ -1273,7 +1036,7 @@ def process_beat_tasks():
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
pending_tasks = [t for t in tasks if t.get('status') == 'pending' and t.get('type') in ('agent_created', 'manual', 'orchestrated', 'agent_delegated')]
|
pending_tasks = [t for t in tasks if t.get('status') == 'pending' and t.get('type') in ('agent_created', 'manual', 'orchestrated')]
|
||||||
|
|
||||||
for task in pending_tasks:
|
for task in pending_tasks:
|
||||||
agent_key = task.get('agent_key') or task.get('assigned_agent', '')
|
agent_key = task.get('agent_key') or task.get('assigned_agent', '')
|
||||||
|
|
@ -1314,22 +1077,10 @@ Agent-Beschreibungen:
|
||||||
if assigned not in AGENTS:
|
if assigned not in AGENTS:
|
||||||
assigned = available_agents[0]
|
assigned = available_agents[0]
|
||||||
|
|
||||||
# Sub-Task mit KOMPLETTEM Kontext
|
|
||||||
sub_task = {
|
sub_task = {
|
||||||
'id': len(tasks) + 1 + len(created_sub_tasks),
|
'id': len(tasks) + 1 + len(created_sub_tasks),
|
||||||
'title': t[:80],
|
'title': t[:80],
|
||||||
'description': f"""**Original-Aufgabe:**
|
'description': f"Von Orchestrator zugewiesen: {response[:200]}...",
|
||||||
{task.get('title', '')}
|
|
||||||
|
|
||||||
{task.get('description', '')}
|
|
||||||
|
|
||||||
**Orchestrator-Analyse:**
|
|
||||||
{response}
|
|
||||||
|
|
||||||
**Dein spezifischer Teil:**
|
|
||||||
{t}
|
|
||||||
|
|
||||||
Arbeite diesen Teil ab und liefere ein vollständiges Ergebnis.""",
|
|
||||||
'assigned_agent': AGENTS.get(assigned, {}).get('name', assigned),
|
'assigned_agent': AGENTS.get(assigned, {}).get('name', assigned),
|
||||||
'agent_key': assigned,
|
'agent_key': assigned,
|
||||||
'status': 'pending',
|
'status': 'pending',
|
||||||
|
|
@ -1382,8 +1133,7 @@ Arbeite diesen Teil ab und liefere ein vollständiges Ergebnis.""",
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("[TaskBeat] Fehler: %s", str(e))
|
logger.error("[TaskBeat] Fehler: %s", str(e))
|
||||||
|
|
||||||
# Beat-Intervall: 10 Sekunden (statt 30)
|
time.sleep(30)
|
||||||
time.sleep(10)
|
|
||||||
|
|
||||||
|
|
||||||
def start_task_beat():
|
def start_task_beat():
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ function sendPromptWithStream() {
|
||||||
msgDiv.className = 'chat-message';
|
msgDiv.className = 'chat-message';
|
||||||
msgDiv.innerHTML = `
|
msgDiv.innerHTML = `
|
||||||
<div class="chat-timestamp">${new Date().toLocaleTimeString()} · <span class="badge bg-primary" style="font-size:.65rem;" id="agentBadge">wird ausgewählt…</span></div>
|
<div class="chat-timestamp">${new Date().toLocaleTimeString()} · <span class="badge bg-primary" style="font-size:.65rem;" id="agentBadge">wird ausgewählt…</span></div>
|
||||||
<div class="chat-prompt mt-1"><strong>Sie:</strong> ${escapeHtml(prompt)}</div>
|
<div class="chat-prompt mt-1"><strong>Sie:</strong> ${prompt}</div>
|
||||||
<div class="chat-response mt-1" id="responseDiv"><strong>Orchestrator:</strong> <span id="responseText">⏳ Agent arbeitet…</span></div>
|
<div class="chat-response mt-1" id="responseDiv"><strong>Orchestrator:</strong> <span id="responseText">⏳ Agent arbeitet…</span></div>
|
||||||
`;
|
`;
|
||||||
chatContainer.insertBefore(msgDiv, chatContainer.firstChild);
|
chatContainer.insertBefore(msgDiv, chatContainer.firstChild);
|
||||||
|
|
@ -220,11 +220,5 @@ function distributeTodos() {
|
||||||
status.className = 'form-text mt-2 text-danger';
|
status.className = 'form-text mt-2 text-danger';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(text) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.textContent = text;
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,13 @@
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header bg-info">
|
<div class="card-header bg-info">
|
||||||
<h5 class="mb-0">ℹ️ Info</h5>
|
<h5 class="mb-0">🔄 Auto-Refresh</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="mb-0" style="font-size:0.875rem;color:var(--text-muted);">
|
<div class="form-check form-switch">
|
||||||
Tasks werden automatisch alle 10 Sekunden vom TaskBeat verarbeitet.
|
<input class="form-check-input" type="checkbox" id="autoRefresh" onchange="toggleAutoRefresh()">
|
||||||
<br>Diese Seite aktualisiert sich automatisch alle 15 Sekunden wenn Tasks aktiv sind.
|
<label class="form-check-label" for="autoRefresh">Automatisch aktualisieren (alle 30s)</label>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -108,14 +108,14 @@
|
||||||
</td>
|
</td>
|
||||||
<td style="color:var(--text-muted);font-size:.75rem;">{{ task.created }}</td>
|
<td style="color:var(--text-muted);font-size:.75rem;">{{ task.created }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if task.status == 'pending' %}
|
{% if task.type == 'email' %}
|
||||||
<span style="color:var(--text-muted);font-size:.75rem;">⏳ Wartend</span>
|
<span style="color:var(--text-muted);font-size:.75rem;">Auto</span>
|
||||||
|
{% elif task.status == 'pending' %}
|
||||||
|
<a href="/tasks/update/{{ task.id }}/in_progress" class="btn btn-sm btn-primary">Start</a>
|
||||||
{% elif task.status == 'in_progress' %}
|
{% elif task.status == 'in_progress' %}
|
||||||
<span style="color:var(--info);font-size:.75rem;">🔄 Läuft...</span>
|
<a href="/tasks/update/{{ task.id }}/completed" class="btn btn-sm btn-success">Fertig</a>
|
||||||
{% elif task.status == 'completed' %}
|
|
||||||
<span style="color:var(--success);font-size:.75rem;">✓ Auto</span>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span style="color:var(--text-muted);font-size:.75rem;">—</span>
|
<span style="color:var(--text-muted);">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -137,13 +137,20 @@
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
// Auto-Refresh alle 15 Sekunden wenn Tasks pending/in_progress sind
|
let autoRefreshInterval = null;
|
||||||
const hasPendingTasks = {{ 'true' if tasks|selectattr('status', 'in', ['pending', 'in_progress'])|list else 'false' }};
|
|
||||||
|
|
||||||
if (hasPendingTasks) {
|
function toggleAutoRefresh() {
|
||||||
setInterval(() => {
|
const checkbox = document.getElementById('autoRefresh');
|
||||||
|
if (checkbox.checked) {
|
||||||
|
autoRefreshInterval = setInterval(() => {
|
||||||
location.reload();
|
location.reload();
|
||||||
}, 15000); // 15 Sekunden
|
}, 30000);
|
||||||
|
} else {
|
||||||
|
if (autoRefreshInterval) {
|
||||||
|
clearInterval(autoRefreshInterval);
|
||||||
|
autoRefreshInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue