gateway/gwserver/modules/agentservice_interface.py
2025-03-26 01:42:28 +01:00

950 lines
41 KiB
Python

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]