import asyncio import uuid import os import json import logging import re from typing import List, Dict, Any, Optional from datetime import datetime import httpx from fastapi import HTTPException import requests from bs4 import BeautifulSoup import configload as configload # Logger konfigurieren logger = logging.getLogger(__name__) # Konfigurationsdaten laden def load_config_data(): config=configload.load_config() result = { "openai": { "api_key": config.get('OpenAI', 'API_KEY'), "api_url": config.get('OpenAI', 'API_URL'), "model_name": config.get('OpenAI', 'MODEL_NAME') }, "application": { "debug": config.get('Application', 'DEBUG'), "upload_dir": config.get('Application', 'UPLOAD_DIR'), "results_dir": config.get('Application', 'RESULTS_DIR') }, "webscraping": { "timeout": config.get('WebScraping', 'TIMEOUT'), "max_urls": config.get('WebScraping', 'MAX_URLS'), "max_content_length": config.get('WebScraping', 'MAX_CONTENT_LENGTH'), "user_agent": config.get('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 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_data() # 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]