950 lines
41 KiB
Python
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]
|
|
|