780 lines
No EOL
35 KiB
Python
780 lines
No EOL
35 KiB
Python
import asyncio
|
|
import uuid
|
|
import os
|
|
import json
|
|
import logging
|
|
import time
|
|
import configparser
|
|
import re
|
|
from typing import List, Dict, Any, Optional
|
|
from datetime import datetime
|
|
import sys
|
|
import httpx
|
|
from fastapi import HTTPException
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
|
|
|
|
# Logger konfigurieren
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Konfiguration aus config.ini laden
|
|
def load_config():
|
|
config = configparser.ConfigParser()
|
|
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
|
|
|
|
# Standardkonfiguration
|
|
default_config = {
|
|
"openai": {
|
|
"api_key": "VOKEY",
|
|
"api_url": "https://api.openai.com/v1/chat/completions",
|
|
"model_name": "gpt-4o"
|
|
},
|
|
"application": {
|
|
"debug": "True",
|
|
"upload_dir": "./uploads",
|
|
"results_dir": "./results"
|
|
},
|
|
"webscraping": {
|
|
"timeout": "10",
|
|
"max_urls": "3",
|
|
"max_content_length": "3000",
|
|
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
}
|
|
}
|
|
|
|
if not os.path.exists(config_path):
|
|
logger.warning(f"Konfigurationsdatei nicht gefunden: {config_path}")
|
|
return default_config
|
|
|
|
try:
|
|
config.read(config_path)
|
|
|
|
# Konfiguration aus der Datei lesen
|
|
result = {
|
|
"openai": {
|
|
"api_key": config.get('OpenAI', 'API_KEY', fallback=default_config["openai"]["api_key"]),
|
|
"api_url": config.get('OpenAI', 'API_URL', fallback=default_config["openai"]["api_url"]),
|
|
"model_name": config.get('OpenAI', 'MODEL_NAME', fallback=default_config["openai"]["model_name"])
|
|
},
|
|
"application": {
|
|
"debug": config.get('Application', 'DEBUG', fallback=default_config["application"]["debug"]),
|
|
"upload_dir": config.get('Application', 'UPLOAD_DIR', fallback=default_config["application"]["upload_dir"]),
|
|
"results_dir": config.get('Application', 'RESULTS_DIR', fallback=default_config["application"]["results_dir"])
|
|
},
|
|
"webscraping": {
|
|
"timeout": config.get('WebScraping', 'TIMEOUT', fallback=default_config["webscraping"]["timeout"]),
|
|
"max_urls": config.get('WebScraping', 'MAX_URLS', fallback=default_config["webscraping"]["max_urls"]),
|
|
"max_content_length": config.get('WebScraping', 'MAX_CONTENT_LENGTH', fallback=default_config["webscraping"]["max_content_length"]),
|
|
"user_agent": config.get('WebScraping', 'USER_AGENT', fallback=default_config["webscraping"]["user_agent"])
|
|
}
|
|
}
|
|
|
|
# Debug-Modus einstellen
|
|
if result["application"]["debug"].lower() == "true":
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
logger.setLevel(logging.DEBUG)
|
|
logger.debug("Debug-Modus aktiviert")
|
|
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Laden der Konfiguration: {e}")
|
|
return default_config
|
|
|
|
|
|
class AgentService:
|
|
"""
|
|
Service für die Verwaltung und Ausführung von Multi-Agent-Workflows mit OpenAI GPT-4o.
|
|
"""
|
|
|
|
def __init__(self, mandate_id: int = None, user_id: int = None):
|
|
"""
|
|
Initialisiert den AgentService.
|
|
|
|
Args:
|
|
mandate_id: ID des aktuellen Mandanten (optional)
|
|
user_id: ID des aktuellen Benutzers (optional)
|
|
"""
|
|
# Mandanten- und Benutzerkontext
|
|
self.mandate_id = mandate_id
|
|
self.user_id = user_id
|
|
|
|
# Konfiguration laden
|
|
self.config = load_config()
|
|
|
|
# Verzeichnisse aus der Konfiguration übernehmen
|
|
self.results_dir = self.config["application"]["results_dir"]
|
|
self.upload_dir = self.config["application"]["upload_dir"]
|
|
|
|
# Web-Scraping-Konfiguration
|
|
self.scraping_timeout = int(self.config["webscraping"]["timeout"])
|
|
self.scraping_max_urls = int(self.config["webscraping"]["max_urls"])
|
|
self.scraping_max_content_length = int(self.config["webscraping"]["max_content_length"])
|
|
self.scraping_user_agent = self.config["webscraping"]["user_agent"]
|
|
|
|
# Verzeichnisse erstellen
|
|
os.makedirs(self.results_dir, exist_ok=True)
|
|
os.makedirs(self.upload_dir, exist_ok=True)
|
|
|
|
logger.info(f"AgentService initialisiert mit:")
|
|
logger.info(f" - Modell: {self.config['openai']['model_name']}")
|
|
logger.info(f" - Ergebnisverzeichnis: {self.results_dir}")
|
|
logger.info(f" - Upload-Verzeichnis: {self.upload_dir}")
|
|
|
|
# Workflow-Speicher
|
|
self.workflows = {}
|
|
|
|
# HttpClient für API-Aufrufe
|
|
self.http_client = httpx.AsyncClient(
|
|
timeout=120.0, # Längeres Timeout für komplexe Anfragen
|
|
headers={
|
|
"Authorization": f"Bearer {self.config['openai']['api_key']}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
)
|
|
|
|
async def execute_workflow(
|
|
self,
|
|
workflow_id: str,
|
|
prompt: str,
|
|
agents: List[Dict[str, Any]],
|
|
files: List[Dict[str, Any]]
|
|
) -> str:
|
|
"""
|
|
Führt einen Workflow mit den angegebenen Agenten und Dateien aus.
|
|
Verwendet OpenAI GPT-4o für die Verarbeitung der Anfragen.
|
|
"""
|
|
logger.info(f"Starte Workflow {workflow_id} mit {len(agents)} Agenten und {len(files)} Dateien")
|
|
|
|
# Mandanten- und Benutzerkontext in die Workflow-Daten aufnehmen
|
|
self.workflows[workflow_id] = {
|
|
"id": workflow_id,
|
|
"mandate_id": self.mandate_id,
|
|
"user_id": self.user_id,
|
|
"status": "running",
|
|
"progress": 0.0,
|
|
"started_at": datetime.now().isoformat(),
|
|
"completed_at": None,
|
|
"agent_statuses": {},
|
|
"logs": [],
|
|
"results": []
|
|
}
|
|
|
|
# Log-Eintrag für den Start des Workflows
|
|
self._add_log(workflow_id, "Workflow gestartet", "info")
|
|
self._add_log(workflow_id, f"Verarbeite {len(files)} Dateien...", "info")
|
|
|
|
# Dateikontexte und Inhalte vorbereiten
|
|
file_contexts = []
|
|
file_contents = {}
|
|
|
|
for file in files:
|
|
file_id = file["id"]
|
|
file_name = file["name"]
|
|
file_type = file["type"]
|
|
file_path = file.get("path", "")
|
|
|
|
# Wenn kein Pfad angegeben ist, versuche, ihn aus dem Upload-Verzeichnis abzuleiten
|
|
if not file_path and file_name:
|
|
possible_path = os.path.join(self.upload_dir, file_name)
|
|
if os.path.exists(possible_path):
|
|
file_path = possible_path
|
|
logger.debug(f"Pfad für Datei {file_name} gefunden: {file_path}")
|
|
|
|
file_contexts.append({
|
|
"id": file_id,
|
|
"name": file_name,
|
|
"type": file_type,
|
|
"size": file.get("size", "Unbekannt"),
|
|
"path": file_path
|
|
})
|
|
|
|
# Dateiinhalt lesen, wenn der Pfad verfügbar ist
|
|
if file_path and os.path.exists(file_path):
|
|
try:
|
|
# Text-basierte Dateien direkt lesen
|
|
if file_type == "document":
|
|
# Einfache Textdateien
|
|
if file_name.endswith(('.txt', '.csv', '.md', '.json')):
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
file_contents[file_id] = f.read()
|
|
self._add_log(workflow_id, f"Datei {file_name} gelesen", "info")
|
|
|
|
# Excel-Dateien
|
|
elif file_name.endswith(('.xlsx', '.xls')):
|
|
import pandas as pd
|
|
try:
|
|
df = pd.read_excel(file_path)
|
|
file_contents[file_id] = f"Excel-Datei mit {len(df)} Zeilen und {len(df.columns)} Spalten.\n"
|
|
file_contents[file_id] += f"Spalten: {', '.join(df.columns.tolist())}\n"
|
|
file_contents[file_id] += "Erste 5 Zeilen:\n"
|
|
file_contents[file_id] += df.head(5).to_string()
|
|
self._add_log(workflow_id, f"Excel-Datei {file_name} gelesen", "info")
|
|
except Exception as e:
|
|
self._add_log(workflow_id, f"Fehler beim Lesen der Excel-Datei {file_name}: {str(e)}", "error")
|
|
|
|
# CSV-Dateien
|
|
elif file_name.endswith('.csv'):
|
|
import pandas as pd
|
|
try:
|
|
df = pd.read_csv(file_path)
|
|
file_contents[file_id] = f"CSV-Datei mit {len(df)} Zeilen und {len(df.columns)} Spalten.\n"
|
|
file_contents[file_id] += f"Spalten: {', '.join(df.columns.tolist())}\n"
|
|
file_contents[file_id] += "Erste 5 Zeilen:\n"
|
|
file_contents[file_id] += df.head(5).to_string()
|
|
self._add_log(workflow_id, f"CSV-Datei {file_name} gelesen", "info")
|
|
except Exception as e:
|
|
self._add_log(workflow_id, f"Fehler beim Lesen der CSV-Datei {file_name}: {str(e)}", "error")
|
|
|
|
# PDF-Dateien
|
|
elif file_name.endswith('.pdf'):
|
|
try:
|
|
# Falls PyPDF2 installiert ist
|
|
try:
|
|
from PyPDF2 import PdfReader
|
|
reader = PdfReader(file_path)
|
|
text = ""
|
|
for page in reader.pages:
|
|
text += page.extract_text() + "\n\n"
|
|
file_contents[file_id] = f"PDF mit {len(reader.pages)} Seiten.\nInhalt:\n{text[:2000]}..."
|
|
self._add_log(workflow_id, f"PDF-Datei {file_name} gelesen", "info")
|
|
except ImportError:
|
|
self._add_log(workflow_id, "PyPDF2 nicht installiert. PDF-Inhalt kann nicht extrahiert werden.", "warning")
|
|
file_contents[file_id] = f"PDF-Datei (Inhalt nicht verfügbar, PyPDF2 fehlt)"
|
|
except Exception as e:
|
|
self._add_log(workflow_id, f"Fehler beim Lesen der PDF-Datei {file_name}: {str(e)}", "error")
|
|
|
|
# Andere Dokumenttypen
|
|
else:
|
|
self._add_log(workflow_id, f"Nicht unterstütztes Dokumentformat: {file_name}", "warning")
|
|
file_contents[file_id] = f"Dateiinhalt nicht verfügbar (Nicht unterstütztes Format)"
|
|
|
|
# Bilddateien werden nicht direkt gelesen, nur Metadaten gespeichert
|
|
elif file_type == "image":
|
|
file_contents[file_id] = f"Bilddatei: {file_name} (Inhalt nicht als Text verfügbar)"
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Lesen der Datei {file_name}: {str(e)}")
|
|
self._add_log(workflow_id, f"Fehler beim Lesen der Datei {file_name}: {str(e)}", "error")
|
|
else:
|
|
if file_path:
|
|
self._add_log(workflow_id, f"Datei {file_name} nicht gefunden: {file_path}", "warning")
|
|
else:
|
|
self._add_log(workflow_id, f"Kein Pfad für Datei {file_name} verfügbar", "warning")
|
|
file_contents[file_id] = f"Dateiinhalt nicht verfügbar"
|
|
|
|
# Erstelle einen Kontext mit Dateiliste und Inhalten für leichteren Zugriff
|
|
file_context_text = "Verfügbare Dateien:\n" + "\n".join([f"- {file['name']} ({file['type']}, {file['size']})" for file in file_contexts])
|
|
|
|
# Füge Dateiinhalte hinzu (mit Längenbegrenzung)
|
|
for file_id, content in file_contents.items():
|
|
file_name = next((f['name'] for f in file_contexts if f['id'] == file_id), "Unbekannte Datei")
|
|
file_context_text += f"\n\n==== DATEIINHALT: {file_name} ====\n"
|
|
|
|
# Begrenze den Inhalt, um Token-Limits zu respektieren
|
|
max_content_length = 5000 # Anpassen je nach Anzahl der Dateien und Umfang
|
|
if len(content) > max_content_length:
|
|
file_context_text += content[:max_content_length] + "...\n[Dateiinhalt gekürzt aus Platzgründen]"
|
|
else:
|
|
file_context_text += content
|
|
|
|
self.workflows[workflow_id]["progress"] = 0.1
|
|
|
|
# Verarbeitung pro Agent ausführen
|
|
for i, agent in enumerate(agents):
|
|
agent_id = agent["id"]
|
|
agent_name = agent["name"]
|
|
agent_type = agent["type"]
|
|
|
|
self.workflows[workflow_id]["agent_statuses"][agent_id] = "running"
|
|
self._add_log(
|
|
workflow_id,
|
|
f"Agent '{agent_name}' beginnt mit der Verarbeitung...",
|
|
"start",
|
|
agent_id,
|
|
agent_name
|
|
)
|
|
|
|
# Fortschritt aktualisieren
|
|
self.workflows[workflow_id]["progress"] = 0.1 + (i + 1) * (0.8 / len(agents)) * 0.5
|
|
|
|
try:
|
|
# Agent-spezifische Anweisungen erstellen
|
|
agent_instructions = self._get_agent_instructions(agent_type)
|
|
|
|
# Vollständige Anfrage an OpenAI erstellen
|
|
full_prompt = f"""
|
|
# Aufgabe
|
|
{prompt}
|
|
|
|
# Dateikontexte
|
|
{file_context_text}
|
|
|
|
# Agent-Rolle
|
|
Du bist ein {agent_name} ({agent_type}).
|
|
|
|
{agent_instructions}
|
|
|
|
Bitte analysiere die Daten und beantworte die Anfrage gemäß deiner Rolle.
|
|
"""
|
|
|
|
# Für Web-Scraper: Führe Web-Scraping durch
|
|
if agent_type == "scraper":
|
|
self._add_log(workflow_id, "Führe Web-Scraping durch...", "info", agent_id, agent_name)
|
|
web_data = await self._scrape_web_data(workflow_id, prompt)
|
|
if web_data:
|
|
full_prompt += f"\n\n# Gescrapte Web-Daten\n{web_data}"
|
|
self._add_log(workflow_id, "Web-Scraping abgeschlossen", "info", agent_id, agent_name)
|
|
|
|
# API-Anfrage an OpenAI senden
|
|
response = await self._call_openai_api(full_prompt)
|
|
|
|
# Ergebnis extrahieren und speichern
|
|
result_content = response.get("choices", [{}])[0].get("message", {}).get("content", "Keine Antwort erhalten")
|
|
|
|
# Agent-Ergebnis erstellen
|
|
result = self._create_agent_result(workflow_id, agent, i, prompt, file_contexts, result_content)
|
|
self.workflows[workflow_id]["results"].append(result)
|
|
|
|
self._add_log(
|
|
workflow_id,
|
|
f"Agent '{agent_name}' hat die Verarbeitung abgeschlossen",
|
|
"complete",
|
|
agent_id,
|
|
agent_name
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der Ausführung von Agent '{agent_name}': {str(e)}")
|
|
self._add_log(
|
|
workflow_id,
|
|
f"Fehler bei der Ausführung: {str(e)}",
|
|
"error",
|
|
agent_id,
|
|
agent_name
|
|
)
|
|
self.workflows[workflow_id]["agent_statuses"][agent_id] = "failed"
|
|
continue
|
|
|
|
self.workflows[workflow_id]["agent_statuses"][agent_id] = "completed"
|
|
self.workflows[workflow_id]["progress"] = 0.1 + (i + 1) * (0.8 / len(agents))
|
|
|
|
# Kurze Pause zwischen Agent-Aufrufen
|
|
await asyncio.sleep(1)
|
|
|
|
# Workflow-Status prüfen
|
|
failed_agents = [a for a, s in self.workflows[workflow_id]["agent_statuses"].items() if s == "failed"]
|
|
|
|
if failed_agents:
|
|
self._add_log(workflow_id, f"{len(failed_agents)} Agent(s) sind fehlgeschlagen", "error")
|
|
self._add_log(workflow_id, "Workflow mit Fehlern beendet", "error")
|
|
self.workflows[workflow_id]["status"] = "failed"
|
|
else:
|
|
self._add_log(workflow_id, "Alle Agenten haben ihre Aufgaben abgeschlossen", "info")
|
|
self._add_log(workflow_id, "Workflow erfolgreich beendet", "success")
|
|
self.workflows[workflow_id]["status"] = "completed"
|
|
|
|
self.workflows[workflow_id]["progress"] = 1.0
|
|
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
|
|
|
|
# Speichere Ergebnisse in Datei für spätere Verwendung
|
|
self._save_workflow_results(workflow_id)
|
|
|
|
return workflow_id
|
|
|
|
# Die übrigen Methoden bleiben unverändert
|
|
# ...
|
|
|
|
async def _scrape_web_data(self, workflow_id: str, prompt: str) -> str:
|
|
"""
|
|
Führt Web-Scraping basierend auf dem Prompt durch
|
|
"""
|
|
try:
|
|
# Extrahiere mögliche Schlüsselwörter oder URLs aus dem Prompt
|
|
keywords = self._extract_keywords(prompt)
|
|
urls = self._extract_urls(prompt)
|
|
|
|
results = []
|
|
|
|
# Falls direkte URLs im Prompt enthalten sind
|
|
if urls:
|
|
self._add_log(workflow_id, f"Gefundene URLs: {', '.join(urls[:self.scraping_max_urls])}", "info")
|
|
for url in urls[:self.scraping_max_urls]: # Begrenze auf max_urls (Standard: 3)
|
|
try:
|
|
self._add_log(workflow_id, f"Scrape URL: {url}", "info")
|
|
content = self._scrape_url(url)
|
|
if content:
|
|
results.append(f"## Inhalt von {url}\n{content}")
|
|
self._add_log(workflow_id, f"Scraping von {url} erfolgreich", "info")
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Scrapen von {url}: {e}")
|
|
self._add_log(workflow_id, f"Fehler beim Scrapen von {url}: {e}", "error")
|
|
|
|
# Falls keine URLs, versuche Suche mit Schlüsselwörtern
|
|
elif keywords:
|
|
self._add_log(workflow_id, f"Verwende Keywords für Suche: {keywords}", "info")
|
|
search_results = self._search_web(keywords)
|
|
if search_results:
|
|
results.append(f"## Suchergebnisse für: {keywords}\n{search_results}")
|
|
self._add_log(workflow_id, "Suche abgeschlossen", "info")
|
|
|
|
if results:
|
|
return "\n\n".join(results)
|
|
|
|
self._add_log(workflow_id, "Keine relevanten Web-Daten gefunden", "warning")
|
|
return "Keine relevanten Web-Daten gefunden."
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Web-Scraping: {e}")
|
|
self._add_log(workflow_id, f"Fehler beim Web-Scraping: {e}", "error")
|
|
return f"Web-Scraping konnte nicht durchgeführt werden: {str(e)}"
|
|
|
|
def _extract_keywords(self, text: str) -> str:
|
|
"""Extrahiert Schlüsselwörter aus dem Text"""
|
|
# Einfache Implementierung - in der Praxis könntest du NLP verwenden
|
|
words = text.split()
|
|
# Filtere kurze Wörter und häufige Stopwörter
|
|
stopwords = ["einen", "einer", "eines", "keine", "nicht", "diese", "dieses", "zwischen",
|
|
"und", "oder", "aber", "denn", "wenn", "weil", "obwohl", "während", "für",
|
|
"mit", "von", "aus", "nach", "bei", "über", "unter", "durch", "gegen"]
|
|
keywords = [w for w in words if len(w) > 4 and w.lower() not in stopwords]
|
|
return " ".join(keywords[:5]) # Begrenze auf 5 Keywords
|
|
|
|
def _extract_urls(self, text: str) -> List[str]:
|
|
"""Extrahiert URLs aus dem Text"""
|
|
# Einfacher URL-Extraktions-Regex
|
|
url_pattern = re.compile(r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[/\w\.-]*(?:\?\S+)?')
|
|
return url_pattern.findall(text)
|
|
|
|
def _scrape_url(self, url: str) -> str:
|
|
"""Scrapt den Inhalt einer URL"""
|
|
headers = {
|
|
'User-Agent': self.scraping_user_agent
|
|
}
|
|
|
|
response = requests.get(url, headers=headers, timeout=self.scraping_timeout)
|
|
response.raise_for_status()
|
|
|
|
soup = BeautifulSoup(response.text, 'html.parser')
|
|
|
|
# Entferne Skripte, Styles und andere unwichtige Elemente
|
|
for script in soup(["script", "style", "meta", "noscript", "iframe"]):
|
|
script.extract()
|
|
|
|
# Extrahiere den Hauptinhalt
|
|
main_content = ""
|
|
|
|
# Versuche, Hauptcontainer zu finden (häufige IDs und Klassen)
|
|
main_elements = soup.select('main, #main, .main, #content, .content, article, .article, .post, #post')
|
|
|
|
if main_elements:
|
|
# Nehme den ersten gefundenen Hauptcontainer
|
|
main_content = main_elements[0].get_text(separator='\n', strip=True)
|
|
else:
|
|
# Falls kein Hauptcontainer gefunden, nehme den Body-Text
|
|
main_content = soup.body.get_text(separator='\n', strip=True)
|
|
|
|
# Bereinige den Text (entferne mehrfache Leerzeilen etc.)
|
|
lines = [line.strip() for line in main_content.split('\n') if line.strip()]
|
|
main_content = '\n'.join(lines)
|
|
|
|
# Begrenze die Länge
|
|
if len(main_content) > self.scraping_max_content_length:
|
|
main_content = main_content[:self.scraping_max_content_length] + "...\n[Inhalt gekürzt]"
|
|
|
|
return main_content
|
|
|
|
def _search_web(self, query: str) -> str:
|
|
"""
|
|
Simuliert eine Websuche (in einer vollständigen Implementierung
|
|
würdest du hier eine echte Suchmaschinen-API verwenden)
|
|
"""
|
|
# HINWEIS: Dies ist eine Simulation! In einer echten Anwendung
|
|
# würdest du Google Custom Search API, SerpAPI oder ähnliches verwenden
|
|
|
|
# Für eine echte Implementierung:
|
|
# - Google Custom Search API: https://developers.google.com/custom-search/v1/overview
|
|
# - SerpAPI: https://serpapi.com/
|
|
# - Oder ähnliche Dienste
|
|
|
|
return f"Hinweis: Dies ist eine Demo-Implementierung ohne echte Websuche. In der Produktion würde hier der Agent tatsächlich nach '{query}' suchen."
|
|
|
|
async def _call_openai_api(self, prompt: str) -> Dict[str, Any]:
|
|
"""Ruft die OpenAI API auf und gibt die Antwort zurück"""
|
|
try:
|
|
payload = {
|
|
"model": self.config["openai"]["model_name"],
|
|
"messages": [
|
|
{"role": "system", "content": "Du bist ein spezialisierter Agent in einem Multi-Agent-System zur Datenanalyse und -verarbeitung."},
|
|
{"role": "user", "content": prompt}
|
|
],
|
|
"temperature": 0.2, # Niedrige Temperatur für konsistentere Ergebnisse
|
|
"max_tokens": 2000
|
|
}
|
|
|
|
response = await self.http_client.post(
|
|
self.config["openai"]["api_url"],
|
|
json=payload
|
|
)
|
|
|
|
if response.status_code != 200:
|
|
logger.error(f"OpenAI API-Fehler: {response.status_code} - {response.text}")
|
|
raise HTTPException(status_code=500, detail="Fehler bei der Kommunikation mit OpenAI API")
|
|
|
|
return response.json()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Aufruf der OpenAI API: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=f"Fehler beim Aufruf der OpenAI API: {str(e)}")
|
|
|
|
def _get_agent_instructions(self, agent_type: str) -> str:
|
|
"""
|
|
Gibt agententypspezifische Anweisungen zurück, die aus der agents.json geladen werden.
|
|
Falls die Datei nicht existiert oder der Agententyp nicht gefunden wird,
|
|
werden Standard-Anweisungen zurückgegeben.
|
|
"""
|
|
try:
|
|
# Pfad zur agents.json-Datei
|
|
agents_file = os.path.join(os.path.dirname(__file__), 'data', 'agents.json')
|
|
|
|
# Überprüfen, ob die Datei existiert
|
|
if not os.path.exists(agents_file):
|
|
logger.warning(f"Agents-Definitionen nicht gefunden: {agents_file}")
|
|
return self._get_default_agent_instructions(agent_type)
|
|
|
|
# Datei lesen
|
|
with open(agents_file, 'r', encoding='utf-8') as f:
|
|
agents_data = json.load(f)
|
|
|
|
# Nach dem Agententyp suchen
|
|
for agent in agents_data:
|
|
if agent.get("type") == agent_type:
|
|
# Anweisungen zurückgeben, wenn vorhanden
|
|
instructions = agent.get("instructions")
|
|
if instructions:
|
|
logger.debug(f"Anweisungen für Agent-Typ '{agent_type}' aus agents.json geladen")
|
|
return instructions
|
|
|
|
# Wenn kein passender Agent gefunden wurde, Standardanweisungen verwenden
|
|
logger.warning(f"Keine Anweisungen für Agent-Typ '{agent_type}' in agents.json gefunden")
|
|
return self._get_default_agent_instructions(agent_type)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Laden der Agent-Anweisungen aus agents.json: {e}")
|
|
return self._get_default_agent_instructions(agent_type)
|
|
|
|
def _get_default_agent_instructions(self, agent_type: str) -> str:
|
|
"""
|
|
Gibt Standard-Anweisungen für einen Agententyp zurück,
|
|
wenn keine spezifischen Anweisungen in der agents.json gefunden wurden.
|
|
"""
|
|
default_instructions = {
|
|
"analyzer": """
|
|
Als Datenanalyse-Agent ist es deine Aufgabe, die bereitgestellten Daten zu analysieren und wichtige Erkenntnisse zu extrahieren.
|
|
|
|
Folge diesen Anweisungen zur Analyse der Dateien:
|
|
1. Lese und verstehe den Inhalt der bereitgestellten Dateien gründlich
|
|
2. Identifiziere welchen Datentyp jede Datei enthält (z.B. Zeitreihendaten, kategorische Daten, Text)
|
|
3. Wenn es sich um tabellarische Daten handelt:
|
|
- Identifiziere Muster, Trends und Anomalien
|
|
- Berechne relevante statistische Kennzahlen (Mittelwerte, Mediane, Standardabweichungen)
|
|
- Suche nach Korrelationen zwischen verschiedenen Spalten
|
|
- Identifiziere Ausreißer und ungewöhnliche Datenpunkte
|
|
4. Wenn es sich um Textdaten handelt:
|
|
- Analysiere Schlüsselthemen und -begriffe
|
|
- Identifiziere Stimmung und Tonalität, wenn relevant
|
|
- Extrahiere zentrale Aussagen und Schlussfolgerungen
|
|
5. Erstelle eine strukturierte Zusammenfassung deiner Erkenntnisse
|
|
6. Gib konkrete, datengestützte Empfehlungen, wenn möglich
|
|
|
|
In deiner Antwort:
|
|
- Beginne mit einer kurzen Übersicht der analysierten Daten
|
|
- Struktur
|
|
- Strukturiere deine Erkenntnisse klar mit Überschriften und Aufzählungen
|
|
- Füge quantitative Erkenntnisse ein, wo immer möglich
|
|
- Schließe mit einer Zusammenfassung der wichtigsten Punkte ab
|
|
""",
|
|
"visualizer": """
|
|
Als Visualisierungs-Agent ist es deine Aufgabe, die Daten visuell zu beschreiben.
|
|
- Beschreibe, welche Art von Visualisierungen für die Daten sinnvoll wären
|
|
- Erkläre das Layout und die Komponenten der Visualisierung
|
|
- Beschreibe, wie die Daten grafisch dargestellt werden sollten
|
|
- Erkläre, welche Erkenntnisse aus dieser Visualisierung gewonnen werden können
|
|
""",
|
|
"writer": """
|
|
Als Text-Generator ist es deine Aufgabe, verständliche Berichte und Zusammenfassungen zu erstellen.
|
|
- Fasse die wichtigsten Erkenntnisse aus den Daten zusammen
|
|
- Strukturiere den Bericht klar und prägnant
|
|
- Verwende eine sachliche und professionelle Sprache
|
|
- Formuliere konkrete Empfehlungen basierend auf den Daten
|
|
- Nutze Markdown für bessere Formatierung
|
|
""",
|
|
"scraper": """
|
|
Als Web-Scraper-Agent ist es deine Aufgabe, Webseiten zu durchsuchen und relevante Informationen zu extrahieren.
|
|
|
|
Folge diesen Anweisungen:
|
|
1. Analysiere die bereitgestellten Web-Daten sorgfältig
|
|
2. Extrahiere die wichtigsten Informationen und Fakten aus den Webinhalten
|
|
3. Organisiere die Informationen in klare Kategorien
|
|
4. Identifiziere Trends, Muster und wichtige Erkenntnisse
|
|
5. Vergleiche die Informationen aus verschiedenen Quellen, wenn verfügbar
|
|
6. Fasse die gefundenen Informationen prägnant zusammen
|
|
7. Erkenne mögliche Einschränkungen oder Verzerrungen in den Quellen
|
|
|
|
In deiner Antwort:
|
|
- Beginne mit einer Zusammenfassung der gefundenen Informationen
|
|
- Strukturiere die extrahierten Daten in logische Abschnitte
|
|
- Hebe wichtige Fakten und Zahlen hervor
|
|
- Gib Quellenhinweise an
|
|
- Formuliere Schlussfolgerungen basierend auf den gesammelten Daten
|
|
"""
|
|
}
|
|
|
|
return default_instructions.get(agent_type, "Du bist ein Datenverarbeitungs-Agent. Analysiere die gegebenen Informationen und liefere relevante Erkenntnisse.")
|
|
|
|
def _add_log(
|
|
self,
|
|
workflow_id: str,
|
|
message: str,
|
|
log_type: str,
|
|
agent_id: Optional[str] = None,
|
|
agent_name: Optional[str] = None
|
|
) -> None:
|
|
"""Fügt einen Protokolleintrag zum Workflow hinzu"""
|
|
log_entry = {
|
|
"id": f"log_{uuid.uuid4()}",
|
|
"mandate_id": self.mandate_id,
|
|
"user_id": self.user_id,
|
|
"message": message,
|
|
"type": log_type,
|
|
"timestamp": datetime.now().isoformat(),
|
|
"agent_id": agent_id,
|
|
"agent_name": agent_name
|
|
}
|
|
|
|
workflow = self.workflows.get(workflow_id)
|
|
if workflow:
|
|
workflow["logs"].append(log_entry)
|
|
logger.info(f"Workflow {workflow_id}: {message}")
|
|
|
|
def _create_agent_result(
|
|
self,
|
|
workflow_id: str,
|
|
agent: Dict[str, Any],
|
|
index: int,
|
|
prompt: str,
|
|
file_contexts: List[Dict[str, Any]],
|
|
content: str
|
|
) -> Dict[str, Any]:
|
|
"""Erstellt ein Ergebnisobjekt basierend auf dem Agententyp und der API-Antwort"""
|
|
agent_type = agent["type"]
|
|
agent_id = agent["id"]
|
|
agent_name = agent["name"]
|
|
|
|
# Grundlegende Ergebnisstruktur
|
|
result = {
|
|
"id": f"result_{workflow_id}_{index}",
|
|
"mandate_id": self.mandate_id,
|
|
"user_id": self.user_id,
|
|
"agent_id": agent_id,
|
|
"agent_name": agent_name,
|
|
"timestamp": datetime.now().isoformat(),
|
|
"type": "text", # Standardtyp
|
|
"metadata": {
|
|
"files_processed": [file["name"] for file in file_contexts],
|
|
"prompt": prompt
|
|
}
|
|
}
|
|
|
|
# Titel und Inhalt basierend auf dem Agententyp anpassen
|
|
if agent_type == "analyzer":
|
|
result.update({
|
|
"title": "Datenanalyse-Ergebnis",
|
|
"content": content,
|
|
})
|
|
elif agent_type == "visualizer":
|
|
result.update({
|
|
"title": "Visualisierungsvorschlag",
|
|
"content": content,
|
|
"type": "chart" # Auch wenn kein echtes Diagramm, markieren wir es als solches
|
|
})
|
|
elif agent_type == "writer":
|
|
result.update({
|
|
"title": "Zusammenfassung und Empfehlungen",
|
|
"content": content,
|
|
})
|
|
elif agent_type == "scraper":
|
|
result.update({
|
|
"title": "Web-Recherche Ergebnisse",
|
|
"content": content,
|
|
})
|
|
else:
|
|
result.update({
|
|
"title": f"Ergebnis von {agent_name}",
|
|
"content": content,
|
|
})
|
|
|
|
return result
|
|
|
|
def _save_workflow_results(self, workflow_id: str) -> None:
|
|
"""Speichert die Workflow-Ergebnisse in einer Datei"""
|
|
workflow = self.workflows.get(workflow_id)
|
|
if workflow:
|
|
try:
|
|
file_path = os.path.join(self.results_dir, f"workflow_{workflow_id}.json")
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
json.dump(workflow, f, indent=2, ensure_ascii=False)
|
|
logger.info(f"Workflow-Ergebnisse gespeichert: {file_path}")
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Speichern der Workflow-Ergebnisse: {e}")
|
|
|
|
def get_workflow_status(self, workflow_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Gibt den Status eines Workflows zurück"""
|
|
workflow = self.workflows.get(workflow_id)
|
|
if not workflow:
|
|
return None
|
|
|
|
return {
|
|
"id": workflow["id"],
|
|
"mandate_id": workflow.get("mandate_id"),
|
|
"user_id": workflow.get("user_id"),
|
|
"status": workflow["status"],
|
|
"progress": workflow["progress"],
|
|
"started_at": workflow["started_at"],
|
|
"completed_at": workflow["completed_at"],
|
|
"agent_statuses": workflow["agent_statuses"]
|
|
}
|
|
|
|
def get_workflow_logs(self, workflow_id: str) -> Optional[List[Dict[str, Any]]]:
|
|
"""Gibt die Protokolle eines Workflows zurück"""
|
|
workflow = self.workflows.get(workflow_id)
|
|
if not workflow:
|
|
return None
|
|
|
|
return workflow["logs"]
|
|
|
|
def get_workflow_results(self, workflow_id: str) -> Optional[List[Dict[str, Any]]]:
|
|
"""Gibt die Ergebnisse eines Workflows zurück"""
|
|
workflow = self.workflows.get(workflow_id)
|
|
if not workflow:
|
|
return None
|
|
|
|
return workflow["results"]
|
|
|
|
async def close(self):
|
|
"""Schließt den HTTP-Client beim Beenden der Anwendung"""
|
|
await self.http_client.aclose()
|
|
|
|
|
|
# Singleton-Factory für AgentService-Instanzen pro Kontext
|
|
_agent_service_instances = {}
|
|
|
|
def get_agent_service(mandate_id: int = None, user_id: int = None) -> AgentService:
|
|
"""
|
|
Gibt eine AgentService-Instanz für den angegebenen Kontext zurück.
|
|
Wiederverwendet bestehende Instanzen.
|
|
"""
|
|
context_key = f"{mandate_id}_{user_id}"
|
|
if context_key not in _agent_service_instances:
|
|
_agent_service_instances[context_key] = AgentService(mandate_id, user_id)
|
|
return _agent_service_instances[context_key] |