import asyncio import uuid import os import logging import json from typing import List, Dict, Any, Optional, Tuple from datetime import datetime from fastapi import HTTPException import configload as configload # Import der Teilmodule import modules.agentservice_part_filehandling as file_handling import modules.agentservice_part_agents as agents import modules.agentservice_part_results as results # Logger konfigurieren logger = logging.getLogger(__name__) # Konfigurationsdaten laden def load_config_data(): config = configload.load_config() result = { "application": { "debug": config.get('Module_AgentserviceInterface', 'DEBUG'), "upload_dir": config.get('Module_AgentserviceInterface', 'UPLOAD_DIR'), "results_dir": config.get('Module_AgentserviceInterface', 'RESULTS_DIR'), "max_history": config.get('Module_AgentserviceInterface', 'MAX_HISTORY'), "ai_provider": config.get('Module_AgentserviceInterface', 'AI_PROVIDER'), } } # 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 verschiedenen Modellen. """ 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"] self.max_history = int(self.config["application"]["max_history"]) # AI Provider aus der Konfiguration übernehmen self.ai_provider = self.config["application"]["ai_provider"].lower() # Verzeichnisse erstellen os.makedirs(self.results_dir, exist_ok=True) os.makedirs(self.upload_dir, exist_ok=True) # Connector-Instanzen initialisieren if self.ai_provider == "anthropic": import connector_aichat_anthropic as service_aichat self.service_aichat = service_aichat.ChatService() logger.info("Anthropic AI Provider wird verwendet") else: import connector_aichat_openai as service_aichat self.service_aichat = service_aichat.ChatService() logger.info("OpenAI AI Provider wird verwendet") import connector_aiweb_webscraping as service_aiscrap self.service_aiscrap = service_aiscrap.WebScrapingService() logger.info(f"AgentService initialisiert mit:") logger.info(f" - AI Provider: {self.ai_provider}") logger.info(f" - Ergebnisverzeichnis: {self.results_dir}") logger.info(f" - Upload-Verzeichnis: {self.upload_dir}") # Workflow-Speicher self.workflows = {} # Statistiken für die Datenmengen self.data_stats = {} async def execute_workflow( self, workflow_id: str, prompt: str, agents_list: List[Dict[str, Any]], files: List[Dict[str, Any]] ) -> str: """ Führt einen Workflow mit den angegebenen Agenten und Dateien aus. Anstatt die Agenten der Reihe nach abzuarbeiten, wird ein AI-Moderator verwendet, der die Agenten basierend auf ihren Fähigkeiten und bisherigen Antworten steuert. Verwendet file_handling.prepare_message_for_ai für die Vorbereitung der Nachrichten, wobei alle Dateiinhalte vollständig gelesen werden. """ logger.info(f"Starte Workflow {workflow_id} mit {len(agents_list)} 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": [], # Statistik für diesen Workflow initialisieren "data_stats": { "sent_bytes": 0, "received_bytes": 0 } } # 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 vorbereiten file_contexts = file_handling.prepare_file_contexts(files, self.upload_dir) for fc in file_contexts: logger.debug(f"Dateikontext: ID={fc['id']}, Name={fc['name']}, Typ={fc.get('type', 'unbekannt')}") # Dateiinhalte lesen - EINMAL für den gesamten Workflow file_contents = file_handling.read_file_contents( file_contexts, self.upload_dir, workflow_id, self._add_log ) # Erstelle einen formatierten Kontext für die Protokollierung file_context_text = file_handling.format_file_context_text(file_contexts, file_contents) logger.debug(f"Dateikontexte erstellt: {len(file_contexts)} Dateien") self.workflows[workflow_id]["progress"] = 0.1 # Initialisiere den Chatverlauf für den Agenten-Dialog chat_history = [] # Erstelle das standardisierte Nachrichtenobjekt für die initialen Dateien und den Prompt # Verwende file_handling.prepare_message_for_ai mit dem Service initial_message = await file_handling.prepare_message_for_ai( file_contexts, prompt, file_contents, self.service_aichat ) # Initialen Prompt zum Chatverlauf hinzufügen chat_history.append(initial_message) # Datenstatistik aktualisieren - Schätzung der gesendeten Bytes message_size = len(json.dumps(initial_message)) self.workflows[workflow_id]["data_stats"]["sent_bytes"] += message_size # Initialisiere die verfügbaren Agenten mit ihren Fähigkeiten available_agents = agents.initialize_agents(agents_list) # Füge den User-Agent hinzu user_info = await self._get_user_info(self.user_id) user_name = user_info.get("full_name") or user_info.get("username") or f"Benutzer {self.user_id}" available_agents["user_agent"] = { "id": "user_agent", "name": "User Agent", "full_name": user_name, "type": "user", "capabilities": "Beantwortung von Fragen, Bereitstellung zusätzlicher Informationen, Entscheidungsfindung", "description": f"Repräsentiert den Benutzer {user_name}", "used": False } # Initialisiere den Status für jeden Agenten for agent_id in available_agents: self.workflows[workflow_id]["agent_statuses"][agent_id] = "pending" # Speichere wichtige Daten im Workflow-Objekt für die spätere Fortsetzung self.workflows[workflow_id]["chat_history"] = chat_history self.workflows[workflow_id]["available_agents"] = available_agents self.workflows[workflow_id]["file_contexts"] = file_contexts self.workflows[workflow_id]["file_contents"] = file_contents # Starte den Workflow mit dem Moderator self._add_log(workflow_id, f"Starte Agenten-Tischrunde mit Moderator und {len(available_agents)} Agenten", "info") # Maximale Anzahl der Runden zur Vermeidung endloser Schleifen max_rounds = 12 current_round = 0 workflow_complete = False waiting_for_user_input = False # Hauptschleife für den Workflow while current_round < max_rounds and not workflow_complete: current_round += 1 self.workflows[workflow_id]["current_round"] = current_round self._add_log(workflow_id, f"Starte Runde {current_round}", "info") # Falls auf eine Benutzereingabe gewartet wird, nicht fortfahren if waiting_for_user_input: self._add_log(workflow_id, "Warte auf Benutzereingabe...", "info") # Warten wir einfach kurz und beenden dann die Schleife, um im nächsten Polling weiterzumachen await asyncio.sleep(2) break # Führe einen Moderator-Zyklus durch workflow_complete, waiting_for_user_input, selected_agent_id = await self._run_moderator_cycle( workflow_id, chat_history, available_agents, file_contexts, file_contents, is_user_input_continuation=False # Initialer Aufruf, keine Fortsetzung nach Benutzereingabe ) # Fortschritt aktualisieren self.workflows[workflow_id]["progress"] = min(0.9, 0.1 + (current_round / max_rounds) * 0.8) # Workflow abschließen, wenn nicht auf Benutzereingabe wartend if not waiting_for_user_input: if workflow_complete: self.workflows[workflow_id]["status"] = "completed" self._add_log(workflow_id, "Workflow erfolgreich beendet", "success") elif current_round >= max_rounds: self.workflows[workflow_id]["status"] = "completed" self._add_log(workflow_id, f"Workflow nach {max_rounds} Runden automatisch beendet", "info") else: self.workflows[workflow_id]["status"] = "failed" self._add_log(workflow_id, "Workflow mit Fehlern beendet", "error") 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 results.save_workflow_results(self.workflows, workflow_id, self.results_dir) return workflow_id async def _run_moderator_cycle( self, workflow_id: str, chat_history: List[Dict[str, Any]], available_agents: Dict[str, Dict[str, Any]], file_contexts: List[Dict[str, Any]], file_contents: Dict[str, str], is_user_input_continuation: bool = False, user_message: str = None ) -> Tuple[bool, bool, str]: """ Führt einen Moderator-Zyklus durch: Moderator trifft Entscheidung, Agent wird ausgewählt, und der ausgewählte Agent wird ausgeführt. Args: workflow_id: ID des Workflows chat_history: Der bisherige Chat-Verlauf available_agents: Verfügbare Agenten file_contexts: Dateikontexte file_contents: Dateiinhalte is_user_input_continuation: Gibt an, ob dieser Zyklus eine Fortsetzung nach Benutzereingabe ist user_message: Die Nachricht des Benutzers (falls is_user_input_continuation=True) Returns: Tuple mit (workflow_complete, waiting_for_user_input, selected_agent_id) """ # Initialisiere Rückgabewerte workflow_complete = False waiting_for_user_input = False selected_agent_id = None # Moderator-Prompt erstellen base_prompt = agents.get_moderator_prompt(available_agents) # Ergänze mit einem Hinweis zur Benutzereingabe, falls zutreffend if is_user_input_continuation: moderator_system_prompt = base_prompt + """ Wichtig: Der User Agent hat soeben geantwortet. Berücksichtige diese Antwort in deiner Entscheidung. Wähle nun den nächsten Agenten aus oder beende den Workflow, wenn die Aufgabe erfüllt ist. """ else: moderator_system_prompt = base_prompt # Der Moderator wählt den nächsten Agenten aus moderator_prompt = { "role": "system", "content": moderator_system_prompt } # Kopie des Chatverlaufs für den Moderator erstellen moderator_chat = [moderator_prompt] + chat_history[-self.max_history:] # Füge eine Zusammenfassung der verfügbaren Agenten hinzu agent_info = "Verfügbare Agenten:\n" for agent_id, agent in available_agents.items(): status = "✓ Bereits verwendet" if agent["used"] else "✗ Noch nicht verwendet" agent_info += f"- {agent['name']} (Typ: {agent['type']}): {agent.get('capabilities', '')}\n Status: {status}\n" moderator_chat.append({ "role": "system", "content": agent_info + "\nWähle den nächsten Agenten aus oder beende den Workflow, wenn die Aufgabe erfüllt ist." }) # Moderator trifft die Entscheidung try: moderator_chat = await self._sanitize_messages(moderator_chat) moderator_decision = await self.service_aichat.call_api(moderator_chat) moderator_text = moderator_decision["choices"][0]["message"]["content"] logger.debug(f"Full moderator decision text: {moderator_text}") # Datenstatistik aktualisieren request_size = len(json.dumps(moderator_chat)) response_size = len(json.dumps(moderator_decision)) self.workflows[workflow_id]["data_stats"]["sent_bytes"] += request_size self.workflows[workflow_id]["data_stats"]["received_bytes"] += response_size # Füge die Entscheidung des Moderators zum Chatverlauf hinzu chat_history.append({ "role": "assistant", "content": f"[Moderator] {moderator_text}" }) # Log der Moderator-Entscheidung self._add_log(workflow_id, f"Moderator-Entscheidung: {moderator_text}", "info") # Finde den nächsten zu verwendenden Agenten selected_agent_id = agents.find_next_agent(moderator_text, available_agents) # Prüfe, ob der Moderator eine Anfrage an den User Agent stellt if selected_agent_id != "user_agent": # Nur prüfen, wenn nicht explizit der User Agent ausgewählt wurde is_user_agent_query = self._check_for_user_agent_query(moderator_text) if is_user_agent_query: self._add_log(workflow_id, "Moderator stellt eine Frage an den User Agent", "info") selected_agent_id = "user_agent" waiting_for_user_input = True # Prüfe, ob der Workflow beendet werden soll if selected_agent_id == "WORKFLOW_COMPLETE": self._add_log(workflow_id, "Moderator hat den Workflow beendet", "success") workflow_complete = True return workflow_complete, waiting_for_user_input, selected_agent_id # Prüfe, ob der User-Agent ausgewählt wurde if selected_agent_id == "user_agent": self._add_log(workflow_id, "Warte auf Benutzereingabe für den User Agent", "info", "user_agent", "User Agent") # Markiere, dass wir auf eine Benutzereingabe warten waiting_for_user_input = True self.workflows[workflow_id]["waiting_for_user"] = True # Benutzeranfrage zum Chatverlauf hinzufügen chat_history.append({ "role": "assistant", "content": f"[Moderator zu User Agent] {moderator_text}" }) # Chat-Verlauf im Workflow aktualisieren self.workflows[workflow_id]["chat_history"] = chat_history # Workflow-Status speichern results.save_workflow_results(self.workflows, workflow_id, self.results_dir) return workflow_complete, waiting_for_user_input, selected_agent_id # Prüfe, ob ein anderer Agent ausgewählt wurde if not selected_agent_id: self._add_log(workflow_id, "Kein Agent ausgewählt. Beende Workflow.", "warning") workflow_complete = True return workflow_complete, waiting_for_user_input, selected_agent_id # Agenten aus der Liste markieren selected_agent = available_agents[selected_agent_id] selected_agent["used"] = True # Agenten-Status aktualisieren self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "running" self._add_log( workflow_id, f"Agent '{selected_agent['name']}' beginnt mit der Verarbeitung...", "start", selected_agent_id, selected_agent['name'] ) # Agent-spezifische Anweisungen erstellen agent_instructions = agents.get_agent_instructions(selected_agent["type"], selected_agent, file_contexts) # Agent-Prompt erstellen agent_prompt = agents.create_agent_prompt(selected_agent, agent_instructions) # Kopie des Chatverlaufs für den Agenten erstellen agent_chat = [agent_prompt] + chat_history[-self.max_history:] # Falls der Agent ein Webscraper ist und Scraping notwendig ist if selected_agent["type"] == "scraper": self._add_log(workflow_id, "Führe Web-Scraping durch...", "info", selected_agent_id, selected_agent["name"]) # Verwende die Benutzereingabe als Prompt für Scraping, falls verfügbar scrape_prompt = user_message if is_user_input_continuation and user_message else \ (chat_history[0]["content"] if chat_history else "") web_data = await self.service_aiscrap.scrape_web_data(scrape_prompt) if web_data: # Ensure web_data has no trailing whitespace web_data = web_data.strip() if isinstance(web_data, str) else web_data agent_chat.append({ "role": "system", "content": f"# Gescrapte Web-Daten\n{web_data}".strip() }) self._add_log(workflow_id, "Web-Scraping abgeschlossen", "info", selected_agent_id, selected_agent["name"]) # Agent führt seinen Teil aus try: # Da wir keine partielle Dateiladung benötigen, können wir den Agenten direkt aufrufen agent_chat = await self._sanitize_messages(agent_chat) agent_response = await self.service_aichat.call_api(agent_chat) agent_text = agent_response["choices"][0]["message"]["content"] # Datenstatistik aktualisieren request_size = len(json.dumps(agent_chat)) response_size = len(json.dumps(agent_response)) self.workflows[workflow_id]["data_stats"]["sent_bytes"] += request_size self.workflows[workflow_id]["data_stats"]["received_bytes"] += response_size # Füge die endgültige Antwort des Agenten zum Chatverlauf hinzu chat_history.append({ "role": "assistant", "content": agent_text }) # Prüfe, ob die Antwort des Agenten eine Anfrage an den User Agent enthält is_user_agent_query = self._check_for_user_agent_query(agent_text) if is_user_agent_query: self._add_log( workflow_id, f"Agent '{selected_agent['name']}' stellt eine Frage an den User Agent", "info" ) # Markiere, dass wir auf eine Benutzereingabe warten waiting_for_user_input = True self.workflows[workflow_id]["waiting_for_user"] = True # Workflow-Status speichern und Chat-Verlauf aktualisieren self.workflows[workflow_id]["chat_history"] = chat_history results.save_workflow_results(self.workflows, workflow_id, self.results_dir) return workflow_complete, waiting_for_user_input, selected_agent_id # Agent-Ergebnis erstellen - Prompt basierend auf Fortsetzungsart wählen prompt_for_result = user_message if is_user_input_continuation else \ (chat_history[0]["content"] if chat_history else "") result = results.create_agent_result( workflow_id, selected_agent, len(self.workflows[workflow_id]["results"]), prompt_for_result, file_contexts, agent_text, self.mandate_id, self.user_id ) self.workflows[workflow_id]["results"].append(result) self._add_log( workflow_id, f"Agent '{selected_agent['name']}' hat die Verarbeitung abgeschlossen", "complete", selected_agent_id, selected_agent["name"] ) # Agenten-Status aktualisieren self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "completed" except Exception as e: logger.error(f"Fehler bei der Ausführung von Agent '{selected_agent['name']}': {str(e)}") self._add_log( workflow_id, f"Fehler bei der Ausführung: {str(e)}", "error", selected_agent_id, selected_agent["name"] ) self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "failed" # Füge die Fehlermeldung zum Chatverlauf hinzu chat_history.append({ "role": "assistant", "content": f"[Fehler bei Agent '{selected_agent['name']}']: {str(e)}" }) except Exception as e: logger.error(f"Fehler in der Moderator-Phase: {str(e)}") self._add_log(workflow_id, f"Fehler in der Moderator-Phase: {str(e)}", "error") # Aktualisiere den Chat-Verlauf im Workflow self.workflows[workflow_id]["chat_history"] = chat_history return workflow_complete, waiting_for_user_input, selected_agent_id 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""" results.add_log( self.workflows, workflow_id, message, log_type, agent_id, agent_name, self.mandate_id, self.user_id ) def get_workflow_status(self, workflow_id: str) -> Optional[Dict[str, Any]]: """Gibt den Status eines Workflows zurück""" workflow_status = results.get_workflow_status(self.workflows, workflow_id) # Füge Datenstatistik hinzu if workflow_status and workflow_id in self.workflows: workflow_status["data_stats"] = self.workflows[workflow_id].get("data_stats", { "sent_bytes": 0, "received_bytes": 0 }) # Füge das waiting_for_user-Flag explizit hinzu workflow_status["waiting_for_user"] = self.workflows[workflow_id].get("waiting_for_user", False) return workflow_status def get_workflow_logs(self, workflow_id: str) -> Optional[List[Dict[str, Any]]]: """Gibt die Protokolle eines Workflows zurück""" return results.get_workflow_logs(self.workflows, workflow_id) def get_workflow_results(self, workflow_id: str) -> Optional[List[Dict[str, Any]]]: """Gibt die Ergebnisse eines Workflows zurück""" return results.get_workflow_results(self.workflows, workflow_id) async def close(self): """Schließt die HTTP-Clients beim Beenden der Anwendung""" await self.service_aichat.close() await self.service_aiscrap.close() def stop_workflow(self, workflow_id: str) -> bool: """ Stoppt einen laufenden Workflow. Args: workflow_id: ID des zu stoppenden Workflows Returns: bool: True, wenn der Workflow erfolgreich gestoppt wurde, False wenn nicht gefunden """ logger.info(f"Stoppe Workflow {workflow_id}") if workflow_id not in self.workflows: logger.warning(f"Workflow {workflow_id} nicht gefunden") return False # Prüfen, ob der Workflow bereits beendet ist current_status = self.workflows[workflow_id]["status"] if current_status not in ["running", "pending"]: logger.info(f"Workflow {workflow_id} ist bereits im Status {current_status}") return False # Workflow-Status auf "stopped" setzen self.workflows[workflow_id]["status"] = "stopped" self.workflows[workflow_id]["progress"] = 1.0 # Korrigierter Zugriff auf den Workflow self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat() # Log-Eintrag für das Stoppen hinzufügen self._add_log(workflow_id, "Workflow manuell gestoppt", "warning") # Ergebnisse speichern results.save_workflow_results(self.workflows, workflow_id, self.results_dir) logger.info(f"Workflow {workflow_id} wurde gestoppt") return True async def process_user_input(self, workflow_id: str, message: str, additional_files: List[Dict[str, Any]] = None) -> bool: """ Verarbeitet eine Benutzereingabe für einen laufenden Workflow. Ergänzt um die Verfolgung von User Agent Bestätigungen. Args: workflow_id: ID des Workflows message: Nachricht des Benutzers additional_files: Liste zusätzlicher Dateien (optional) Returns: bool: True, wenn die Eingabe erfolgreich verarbeitet wurde """ if workflow_id not in self.workflows: logger.warning(f"Workflow {workflow_id} nicht gefunden") return False # Prüfen, ob der Workflow auf eine Benutzereingabe wartet if not self.workflows[workflow_id].get("waiting_for_user", False): logger.warning(f"Workflow {workflow_id} wartet nicht auf Benutzereingabe") return False logger.info(f"Verarbeite Benutzereingabe für Workflow {workflow_id}") # Benutzerinfos abrufen user_info = await self._get_user_info(self.user_id) user_name = user_info.get("full_name") or user_info.get("username") or f"Benutzer {self.user_id}" # Log-Eintrag für die Benutzereingabe self._add_log( workflow_id, f"Benutzereingabe empfangen: {message[:50]}{'...' if len(message) > 50 else ''}", "info", "user_agent", user_name ) # Verfolge die User Agent Antwort für Bestätigungsprüfung # Importiere die Hilfsfunktion aus dem agents-Modul from modules.agentservice_part_agents import track_user_agent_response available_agents = self.workflows[workflow_id].get("available_agents", {}) track_user_agent_response(available_agents, message) # Rest der Funktion bleibt unverändert... # Wenn zusätzliche Dateien vorhanden sind, diese verarbeiten und in den Workflow integrieren additional_context = "" if additional_files and len(additional_files) > 0: file_contexts = file_handling.prepare_file_contexts(additional_files, self.upload_dir) file_contents = file_handling.read_file_contents( file_contexts, self.upload_dir, workflow_id, lambda wid, msg, typ, aid=None, aname=None: self._add_log(wid, msg, typ, aid, aname) ) # Formatiere den Dateikontext file_context_text = file_handling.format_file_context_text(file_contexts, file_contents) additional_context = f"\n\n### Zusätzliche Dateien:\n{file_context_text}" # Log-Eintrag für die zusätzlichen Dateien self._add_log( workflow_id, f"{len(additional_files)} zusätzliche Dateien hinzugefügt", "info", "user_agent", user_name ) # Füge neue Dateien zu den bestehenden Dateikontexten und -inhalten hinzu existing_file_contexts = self.workflows[workflow_id].get("file_contexts", []) existing_file_contents = self.workflows[workflow_id].get("file_contents", {}) # Alte IDs speichern, um Duplikate zu vermeiden existing_ids = {fc["id"] for fc in existing_file_contexts} # Neue Dateikontexte hinzufügen (nur wenn ID noch nicht existiert) for file_context in file_contexts: if file_context["id"] not in existing_ids: existing_file_contexts.append(file_context) existing_ids.add(file_context["id"]) # Neue Dateiinhalte hinzufügen existing_file_contents.update(file_contents) # Aktualisierte Dateikontexte und -inhalte im Workflow speichern self.workflows[workflow_id]["file_contexts"] = existing_file_contexts self.workflows[workflow_id]["file_contents"] = existing_file_contents # Kombinierte Nachricht erstellen (Benutzernachricht + ggf. zusätzliche Dateien) combined_message = message + additional_context # Schätzung der gesendeten Bytes message_size = len(combined_message) self.workflows[workflow_id]["data_stats"]["sent_bytes"] += message_size # Fortsetzung des Workflows einleiten # Wir starten eine neue Aufgabe, um den Workflow fortzusetzen asyncio.create_task( self._continue_workflow_after_user_input( workflow_id, combined_message, user_name ) ) return True async def _continue_workflow_after_user_input(self, workflow_id: str, user_message: str, user_name: str) -> None: """ Setzt einen Workflow nach einer Benutzereingabe fort. Nutzt die gemeinsame _run_moderator_cycle Methode für die Fortsetzung. Args: workflow_id: ID des Workflows user_message: Nachricht des Benutzers user_name: Name des Benutzers """ if workflow_id not in self.workflows: logger.warning(f"Workflow {workflow_id} nicht gefunden") return # Workflow-Status aktualisieren self.workflows[workflow_id]["status"] = "running" # Log-Eintrag für die Fortsetzung self._add_log( workflow_id, "Workflow wird nach Benutzereingabe fortgesetzt", "info" ) # Hole die benötigten Daten aus dem Workflow chat_history = self.workflows[workflow_id].get("chat_history", []) available_agents = self.workflows[workflow_id].get("available_agents", {}) file_contexts = self.workflows[workflow_id].get("file_contexts", []) file_contents = self.workflows[workflow_id].get("file_contents", {}) # Benutzereingabe zum Chatverlauf hinzufügen chat_history.append({ "role": "user", "content": f"[User Agent: {user_name}] {user_message}" }) # Speichere den aktualisierten Chat-Verlauf im Workflow self.workflows[workflow_id]["chat_history"] = chat_history # User-Antwort als Ergebnis speichern user_result = results.create_agent_result( workflow_id, {"id": "user_agent", "name": "User Agent", "type": "user"}, len(self.workflows[workflow_id].get("results", [])), "Benutzereingabe", file_contexts, # Dateikontexte übergeben user_message, self.mandate_id, self.user_id ) self.workflows[workflow_id]["results"].append(user_result) # Markiere den User-Agent als verwendet if "user_agent" in available_agents: available_agents["user_agent"]["used"] = True # Markiere, dass wir nicht mehr auf eine Benutzereingabe warten self.workflows[workflow_id]["waiting_for_user"] = False # Hole die aktuelle Rundenzahl und maximale Rundenzahl current_round = self.workflows[workflow_id].get("current_round", 0) max_rounds = 12 # Gleicher Wert wie in execute_workflow # Prüfe, ob wir schon die maximale Anzahl von Runden erreicht haben if current_round >= max_rounds: self._add_log(workflow_id, f"Workflow nach {max_rounds} Runden und Benutzereingabe automatisch beendet", "info") self.workflows[workflow_id]["status"] = "completed" self.workflows[workflow_id]["progress"] = 1.0 self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat() # Speichere Ergebnisse results.save_workflow_results(self.workflows, workflow_id, self.results_dir) return # Nächste Runde starten current_round += 1 self.workflows[workflow_id]["current_round"] = current_round self._add_log(workflow_id, f"Starte Runde {current_round} nach Benutzereingabe", "info") # Führe einen Moderator-Zyklus durch workflow_complete, waiting_for_user_input, selected_agent_id = await self._run_moderator_cycle( workflow_id, chat_history, available_agents, file_contexts, file_contents, is_user_input_continuation=True, # Dies ist eine Fortsetzung nach Benutzereingabe user_message=user_message ) # Workflow abschließen, wenn er vollständig ist oder nicht auf Benutzereingabe wartet if workflow_complete or not waiting_for_user_input: # Prüfe, ob wir weitere Runden durchführen sollten if not workflow_complete and not waiting_for_user_input and current_round < max_rounds: # Starte eine neue Aufgabe für die nächste Moderator-Runde asyncio.create_task( self._continue_workflow_after_user_input( workflow_id, "", # Leere Nachricht für die nächste Runde user_name ) ) else: # Workflow wurde entweder abgeschlossen oder wartet auf Benutzereingabe if workflow_complete: self.workflows[workflow_id]["status"] = "completed" self._add_log(workflow_id, "Workflow nach Benutzereingabe erfolgreich beendet", "success") self.workflows[workflow_id]["progress"] = 1.0 self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat() # Fortschritt aktualisieren if not self.workflows[workflow_id]["completed_at"]: self.workflows[workflow_id]["progress"] = min(0.9, 0.1 + (current_round / max_rounds) * 0.8) # Speichere Ergebnisse results.save_workflow_results(self.workflows, workflow_id, self.results_dir) async def _get_user_info(self, user_id: int) -> Dict[str, Any]: """ Ruft Benutzerinformationen aus der Datenbank ab. Args: user_id: ID des Benutzers Returns: Dict mit Benutzerinformationen """ try: # Hier würden wir normalerweise die Benutzerinformationen aus einer Datenbank abrufen # Für diese Implementierung verwenden wir Platzhalter return { "id": user_id, "username": f"user_{user_id}", "full_name": f"Benutzer {user_id}" } except Exception as e: logger.error(f"Fehler beim Abrufen der Benutzerinformationen: {str(e)}") return {"id": user_id} async def _sanitize_messages(self, messages): """Sanitizes all messages to prevent API errors.""" if not messages: return messages sanitized = [] for message in messages: # Create a deep copy of the message sanitized_message = message.copy() if isinstance(message, dict) else message if isinstance(sanitized_message, dict) and "content" in sanitized_message: sanitized_message["content"] = self._sanitize_message_content(sanitized_message["content"]) sanitized.append(sanitized_message) return sanitized def _sanitize_message_content(self, content): """Ensures content has no trailing whitespace.""" if isinstance(content, str): return content.rstrip() # If content is a list (for multimodal messages), process each item if isinstance(content, list): return [ {**item, 'text': item['text'].rstrip() if isinstance(item.get('text'), str) else item.get('text')} if isinstance(item, dict) and item.get('type') == 'text' else item for item in content ] return content def _check_for_user_agent_query(self, message_content: str) -> bool: """ Prüft, ob ein Text eine Anfrage an den User-Agent enthält. Args: message_content: Der zu prüfende Text Returns: bool: True, wenn der Text eine Anfrage an den User-Agent enthält """ # Keine Prüfung, wenn der Text leer ist if not message_content: return False # Prüfmuster für User-Agent-Anfragen user_agent_phrases = [ 'User Agent', 'Benutzer', 'Was denken Sie', 'Was denkst du', 'Ihre Meinung', 'deine Meinung', 'Sind Sie zufrieden', 'Gibt es weitere Fragen', 'Wollen Sie', 'Möchten Sie', 'Kannst du', 'Können Sie', 'Könnten Sie', 'Bitte teilen Sie uns mit', 'Moderator zu User Agent' ] # Text in Kleinbuchstaben umwandeln für case-insensitive Suche content_lower = message_content.lower() # Prüfen, ob ein Fragezeichen enthalten ist has_question_mark = '?' in message_content # Prüfen, ob eine der Phrasen enthalten ist has_user_agent_phrase = any(phrase.lower() in content_lower for phrase in user_agent_phrases) # Spezielle Bedingungen für User-Agent-Anfragen is_user_agent_query = ( (has_question_mark and has_user_agent_phrase) or ('user agent' in content_lower and 'workflow' in content_lower) or 'moderator zu user agent' in content_lower ) return is_user_agent_query # Singleton-Factory für AgentService-Instanzen pro Kontext _agent_service_instances = {} def get_agentservice_interface(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]