421 lines
No EOL
17 KiB
Python
421 lines
No EOL
17 KiB
Python
import os
|
|
import json
|
|
import logging
|
|
import re
|
|
from typing import Dict, Any, List, Optional, Tuple
|
|
|
|
# Logger konfigurieren
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def get_agent_instructions(agent_type: str, agent: Dict[str, Any] = None, file_contexts: List[Dict[str, Any]] = None) -> str:
|
|
"""
|
|
Liefert Anweisungen für einen Agenten basierend auf seinen Attributen.
|
|
|
|
Diese Version fügt explizite Informationen zu Datei-IDs hinzu.
|
|
|
|
Args:
|
|
agent_type: Typ des Agenten
|
|
agent: Agent-Konfiguration mit allen Attributen
|
|
file_contexts: Liste der verfügbaren Dateien
|
|
|
|
Returns:
|
|
Formatierte Anweisungen für den Agenten
|
|
"""
|
|
# Basis-Instruktionen aus dem Agenten-Profil extrahieren
|
|
base_instructions = ""
|
|
|
|
if agent:
|
|
if agent.get("instructions"):
|
|
base_instructions += agent.get("instructions").strip() + "\n\n"
|
|
if agent.get("description"):
|
|
base_instructions += "Kontext: " + agent.get("description").strip() + "\n\n"
|
|
if agent.get("capabilities"):
|
|
base_instructions += "Deine Fähigkeiten: " + agent.get("capabilities").strip() + "\n\n"
|
|
|
|
# Wenn keine Instruktionen gefunden wurden, verwende eine generische Anweisung
|
|
if not base_instructions:
|
|
base_instructions = """
|
|
Analysiere die Anfrage gründlich und liefere ein konkretes, nützliches Ergebnis.
|
|
Strukturiere deine Antwort klar und beantworte alle Aspekte der Anfrage.
|
|
"""
|
|
|
|
# Anweisung zur Selbstdeklaration des Ergebnisstatus hinzufügen
|
|
status_declaration = """
|
|
WICHTIG: Deklariere am Ende deiner Antwort den Status deines Ergebnisses mit einem der folgenden Tags:
|
|
|
|
[STATUS: ERGEBNIS] - Wenn du ein konkretes, vollständiges Ergebnis geliefert hast
|
|
[STATUS: TEILWEISE] - Wenn du ein teilweises Ergebnis geliefert hast, das noch ergänzt werden sollte
|
|
[STATUS: PLAN] - Wenn du hauptsächlich einen Plan oder eine Vorgehensweise vorgeschlagen hast
|
|
|
|
Diese Deklaration hilft dem Moderator zu entscheiden, ob weitere Agentenarbeit erforderlich ist.
|
|
"""
|
|
|
|
# Konkrete Dateiinformationen bereitstellen
|
|
file_info = ""
|
|
if file_contexts:
|
|
file_info = "Verfügbare Dateien:\n"
|
|
for file in file_contexts:
|
|
file_info += f"- {file['name']} (Typ: {file.get('type', 'unbekannt')}, ID: {file['id']})\n"
|
|
file_info += "\n"
|
|
|
|
# Hinweise zum Dateiaufrufen
|
|
file_access = f"""
|
|
{file_info}
|
|
Um mehr Dateiinhalte anzufordern, verwende einen der folgenden Befehle:
|
|
|
|
[[FILE:load_file(file_id=DATEI_ID, complete=true)]] - für den vollständigen Dateiinhalt
|
|
[[FILE:load_file(file_id=DATEI_ID, pages=[1,2,3])]] - für spezifische Seiten einer PDF
|
|
[[FILE:load_file(file_id=DATEI_ID, start=0, end=5000)]] - für Textabschnitte
|
|
|
|
Ersetze DATEI_ID mit der tatsächlichen ID aus der Dateiliste oben (nur die ID-Nummer, keine Anführungszeichen).
|
|
"""
|
|
|
|
return base_instructions + status_declaration + file_access
|
|
|
|
def get_default_agent_instructions() -> str:
|
|
"""
|
|
Gibt Standard-Anweisungen für einen Agenten zurück,
|
|
wenn keine spezifischen Anweisungen verfügbar sind.
|
|
Diese Funktion gibt generische Anweisungen zurück, unabhängig vom Agententyp.
|
|
"""
|
|
return """
|
|
Als Agent ist es deine Aufgabe, Anfragen zu analysieren und entsprechend deinen Fähigkeiten zu bearbeiten.
|
|
|
|
Folge diesen allgemeinen Anweisungen:
|
|
1. Verstehe die Anfrage gründlich
|
|
2. Analysiere relevante Daten und Informationen
|
|
3. Liefere präzise und hilfreiche Antworten
|
|
4. Strukturiere deine Antwort klar und verständlich
|
|
|
|
In deiner Antwort:
|
|
- Beginne mit einer Zusammenfassung der Anfrage
|
|
- Gib gut begründete Antworten oder Empfehlungen
|
|
- Führe wichtige Erkenntnisse klar auf
|
|
- Schließe mit konkreten nächsten Schritten oder Empfehlungen ab
|
|
|
|
WICHTIG: Deklariere am Ende deiner Antwort den Status deines Ergebnisses:
|
|
[STATUS: ERGEBNIS], [STATUS: TEILWEISE] oder [STATUS: PLAN]
|
|
"""
|
|
|
|
|
|
def initialize_agents(agents: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
|
|
"""
|
|
Initialisiert die Agenten mit ihren Fähigkeiten und Status
|
|
|
|
Args:
|
|
agents: Liste der Agenten aus dem Workflow
|
|
|
|
Returns:
|
|
Dictionary mit Agent-IDs als Schlüssel und Agent-Informationen
|
|
"""
|
|
available_agents = {}
|
|
|
|
for agent in agents:
|
|
agent_id = agent["id"]
|
|
agent_name = agent["name"]
|
|
agent_type = agent["type"]
|
|
agent_capabilities = agent.get("capabilities", "")
|
|
|
|
# Kopiere alle Felder vom Original-Agenten und füge used-Status hinzu
|
|
agent_data = agent.copy()
|
|
agent_data["used"] = False
|
|
agent_data["last_result_status"] = None # Für die Speicherung des Ergebnisstatus
|
|
|
|
available_agents[agent_id] = agent_data
|
|
|
|
# Log agent data for debugging
|
|
logger.debug(f"Initialized agent: {agent_name} (Type: {agent_type})")
|
|
if "instructions" in agent_data:
|
|
logger.debug(f"Agent {agent_name} has instructions of length: {len(agent_data['instructions'])}")
|
|
|
|
logger.info(f"Initialized {len(available_agents)} agents for workflow")
|
|
return available_agents
|
|
|
|
|
|
def get_moderator_prompt(available_agents: Dict[str, Dict[str, Any]]) -> str:
|
|
"""
|
|
Erstellt einen Moderator-Prompt, der die Status-Deklarationen der Agenten berücksichtigt.
|
|
Mit besonderem Fokus auf die explizite Bestätigung durch den User Agent.
|
|
|
|
Args:
|
|
available_agents: Dictionary mit verfügbaren Agenten
|
|
|
|
Returns:
|
|
Formatierter Prompt für den Moderator
|
|
"""
|
|
# Prüfen, ob der User Agent bereits verwendet wurde
|
|
user_agent_used = False
|
|
user_agent_confirmed = False
|
|
|
|
if "user_agent" in available_agents:
|
|
user_agent_used = available_agents["user_agent"].get("used", False)
|
|
# Prüfe, ob der User Agent eine explizite Bestätigung gegeben hat
|
|
if user_agent_used:
|
|
for chat_entry in available_agents["user_agent"].get("chat_entries", []):
|
|
if any(confirmation in chat_entry.lower() for confirmation in ["ja", "bestätige", "stimme zu", "akzeptiere"]):
|
|
user_agent_confirmed = True
|
|
break
|
|
|
|
base = """Du bist Moderator eines Multi-Agent-Systems. Deine Aufgabe ist es, die Agenten zu koordinieren,
|
|
um die Anfrage vollständig zu erfüllen und ein konkretes Endergebnis zu liefern.
|
|
|
|
WICHTIG: Der Workflow darf erst beendet werden, wenn TATSÄCHLICHE ERGEBNISSE geliefert wurden"""
|
|
|
|
# Unterschiedliche Bedingungen für das Beenden des Workflows
|
|
if not user_agent_confirmed:
|
|
base += """ UND der User Agent explizit mit einem 'JA' bestätigt hat, dass er mit dem Ergebnis zufrieden ist.
|
|
KRITISCH WICHTIG: Bevor du den Workflow beendest, MUSST du den User Agent befragen, ob er mit den Ergebnissen zufrieden ist,
|
|
und er muss EXPLIZIT mit 'JA' oder einer eindeutigen Zustimmung antworten!"""
|
|
|
|
agents_list = "\nVerfügbare Agenten:\n"
|
|
for agent_id, agent in available_agents.items():
|
|
status = "✓ Bereits verwendet" if agent["used"] else "✗ Noch nicht verwendet"
|
|
result_status = ""
|
|
if agent["used"] and agent.get("last_result_status"):
|
|
result_status = f" (Letzte Antwort: {agent.get('last_result_status')})"
|
|
|
|
description = agent.get("description", "")
|
|
capabilities = agent.get("capabilities", "")
|
|
agents_list += f"- {agent['name']} ({agent['type']}): {capabilities}\n {description}\n Status: {status}{result_status}\n"
|
|
|
|
instructions = """
|
|
Berücksichtige die STATUS-Deklarationen der Agenten bei deiner Entscheidung:
|
|
|
|
- [STATUS: ERGEBNIS] - Der Agent hat ein vollständiges Ergebnis geliefert
|
|
- [STATUS: TEILWEISE] - Der Agent hat ein teilweises Ergebnis geliefert, weitere Arbeit ist nötig
|
|
- [STATUS: PLAN] - Der Agent hat einen Plan geliefert, keine konkreten Ergebnisse
|
|
|
|
Mögliche Entscheidungen:
|
|
- Bei Agent-Auswahl: "Ich wähle [Agentname], um [konkrete Aufgabe]"
|
|
"""
|
|
|
|
if not user_agent_confirmed:
|
|
instructions += """- Bei Abschluss (nur wenn [STATUS: ERGEBNIS] vorliegt): DU MUSST ZUERST den User Agent explizit fragen, ob er mit den Ergebnissen zufrieden ist.
|
|
Der User Agent MUSS mit "JA" oder einer eindeutigen Zustimmung antworten, bevor der Workflow beendet werden kann!
|
|
|
|
WICHTIG: Du darfst den Workflow NICHT beenden, bevor der User Agent explizit mit "JA" bestätigt hat!
|
|
Stelle dem User Agent eine KLARE, DIREKTE Frage, ob er mit dem Ergebnis zufrieden ist oder ob er weitere Informationen benötigt.
|
|
"""
|
|
else:
|
|
instructions += """- Bei Abschluss (nur wenn [STATUS: ERGEBNIS] vorliegt und der User Agent mit "JA" bestätigt hat): "Workflow beenden - vollständiges Ergebnis erreicht"
|
|
|
|
WICHTIG: Da der User Agent bereits seine Zustimmung gegeben hat, kannst du den Workflow jetzt beenden, wenn ein Agent ein konkretes [STATUS: ERGEBNIS] geliefert hat!
|
|
"""
|
|
|
|
return base + agents_list + instructions
|
|
|
|
|
|
def track_user_agent_response(available_agents: Dict[str, Dict[str, Any]], message_content: str) -> None:
|
|
"""
|
|
Verfolgt die Antworten des User Agents, um eine explizite Bestätigung zu erkennen.
|
|
|
|
Args:
|
|
available_agents: Dictionary mit verfügbaren Agenten
|
|
message_content: Inhalt der Nachricht des User Agents
|
|
"""
|
|
if "user_agent" not in available_agents:
|
|
return
|
|
|
|
# Initialisiere das chat_entries Array, falls es noch nicht existiert
|
|
if "chat_entries" not in available_agents["user_agent"]:
|
|
available_agents["user_agent"]["chat_entries"] = []
|
|
|
|
# Füge die aktuelle Nachricht hinzu
|
|
available_agents["user_agent"]["chat_entries"].append(message_content)
|
|
|
|
# Prüfe, ob die Nachricht eine explizite Bestätigung enthält
|
|
confirmation_phrases = ["ja", "bestätige", "stimme zu", "akzeptiere", "einverstanden", "passt", "okay", "ok"]
|
|
|
|
has_confirmation = any(phrase in message_content.lower() for phrase in confirmation_phrases)
|
|
|
|
# Setze das confirmed-Flag entsprechend
|
|
available_agents["user_agent"]["confirmed"] = has_confirmation
|
|
|
|
# Loggen der Erkennung
|
|
if has_confirmation:
|
|
logger.info("User Agent hat explizit mit 'Ja' bestätigt")
|
|
else:
|
|
logger.info("User Agent hat keine eindeutige Bestätigung gegeben")
|
|
|
|
|
|
def create_agent_prompt(agent: Dict[str, Any], agent_instructions: str, file_contexts: List[Dict[str, Any]] = None) -> Dict[str, str]:
|
|
"""Verbesserter Agent-Prompt mit Datei-IDs"""
|
|
# Füge Datei-ID-Infos hinzu, wenn verfügbar
|
|
file_info = ""
|
|
if file_contexts:
|
|
file_info = "\nVerfügbare Dateien:\n"
|
|
for file in file_contexts:
|
|
file_info += f"- {file['name']} (ID: {file['id']})\n"
|
|
|
|
content = f"""
|
|
Du bist Agent {agent['name']} ({agent['type']}).
|
|
{agent_instructions}
|
|
|
|
{file_info}
|
|
|
|
Format: [Agent: {agent['name']}] Deine Antwort...
|
|
""".strip()
|
|
|
|
return {"role": "system", "content": content}
|
|
|
|
|
|
def find_next_agent(moderator_text: str, available_agents: Dict[str, Dict[str, Any]]) -> Optional[str]:
|
|
"""
|
|
Findet den nächsten Agenten basierend auf der Moderator-Entscheidung.
|
|
Berücksichtigt die Anweisung zum Workflow-Abschluss nur, wenn ein Ergebnis vorliegt
|
|
und der User Agent explizit mit "JA" bestätigt hat.
|
|
|
|
Args:
|
|
moderator_text: Text der Moderator-Entscheidung
|
|
available_agents: Dictionary mit verfügbaren Agenten
|
|
|
|
Returns:
|
|
ID des nächsten Agenten oder "WORKFLOW_COMPLETE" zum Beenden
|
|
"""
|
|
text = moderator_text.lower()
|
|
|
|
# Prüfe, ob der Workflow beendet werden soll - nur wenn vollständige Ergebnisse vorliegen
|
|
workflow_complete_phrases = [
|
|
"workflow beenden - vollständiges ergebnis erreicht",
|
|
"workflow beenden - vollständiges ergebnis",
|
|
"vollständiges ergebnis erreicht"
|
|
]
|
|
|
|
# Prüfen, ob ein Agent ein Ergebnis geliefert hat
|
|
result_exists = False
|
|
for agent_id, agent in available_agents.items():
|
|
if agent.get("used") and agent.get("last_result_status") == "ERGEBNIS":
|
|
result_exists = True
|
|
break
|
|
|
|
# Prüfen, ob der User Agent bereits befragt wurde und mit "JA" bestätigt hat
|
|
user_agent_confirmed = False
|
|
if "user_agent" in available_agents and available_agents["user_agent"].get("used", False):
|
|
# Prüfe die letzte Nachricht des User Agents auf explizite Bestätigung
|
|
for chat_entry in available_agents["user_agent"].get("chat_entries", []):
|
|
if any(confirmation in chat_entry.lower() for confirmation in ["ja", "bestätige", "stimme zu", "akzeptiere"]):
|
|
user_agent_confirmed = True
|
|
break
|
|
|
|
# Suche nach exakten Phrasen für Workflow-Beendigung
|
|
if "workflow beenden" in text:
|
|
# Wenn ein eindeutiges Ergebnis vorliegt und der User Agent explizit bestätigt hat
|
|
if result_exists and user_agent_confirmed:
|
|
if any(phrase in text for phrase in workflow_complete_phrases):
|
|
return "WORKFLOW_COMPLETE"
|
|
# Sonst: In den Logs warnen, dass Bedingungen für Beendigung nicht erfüllt sind
|
|
else:
|
|
if not result_exists:
|
|
logger.warning("Moderator versuchte, Workflow ohne vollständiges Ergebnis zu beenden")
|
|
if not user_agent_confirmed:
|
|
logger.warning("Moderator versuchte, Workflow ohne explizite Bestätigung des User Agents zu beenden")
|
|
# Wähle den User Agent aus, um eine explizite Bestätigung zu erhalten
|
|
if "user_agent" in available_agents:
|
|
return "user_agent"
|
|
|
|
# Suche nach "ich wähle" Pattern für Agentenwahl
|
|
if "ich wähle" in text:
|
|
for agent_id, agent in available_agents.items():
|
|
agent_name_lower = agent["name"].lower()
|
|
if agent_name_lower in text:
|
|
return agent_id
|
|
|
|
# Fallback: Direktes Name-Matching
|
|
for agent_id, agent in available_agents.items():
|
|
agent_name_lower = agent["name"].lower()
|
|
if agent_name_lower in text or f"agent {agent_name_lower}" in text:
|
|
return agent_id
|
|
|
|
# Wenn kein Agent explizit gewählt wurde und User Agent noch nicht befragt wurde
|
|
# oder keine explizite Bestätigung gegeben hat, priorisiere den User Agent
|
|
if result_exists and (not "user_agent" in available_agents or
|
|
not available_agents["user_agent"].get("used", False) or
|
|
not user_agent_confirmed):
|
|
if "user_agent" in available_agents:
|
|
return "user_agent"
|
|
|
|
# Wenn kein Agent explizit gewählt wurde: Wähle den ersten unbenutzten Agenten
|
|
for agent_id, agent in available_agents.items():
|
|
if not agent["used"]:
|
|
return agent_id
|
|
|
|
# Letzter Ausweg: Ersten Agenten wiederverwenden
|
|
if available_agents:
|
|
return list(available_agents.keys())[0]
|
|
|
|
return None
|
|
|
|
def update_agent_results_with_status(content: str) -> Tuple[str, str]:
|
|
"""
|
|
Extrahiert den deklarierten Status aus einem Agenten-Ergebnis.
|
|
|
|
Args:
|
|
content: Der vom Agenten gelieferte Ergebnistext
|
|
|
|
Returns:
|
|
Tuple mit (bereinigter Text, Status)
|
|
"""
|
|
# Standard-Status, falls keine Deklaration gefunden wird
|
|
status = "UNBEKANNT"
|
|
|
|
# Suche nach Status-Deklaration
|
|
status_pattern = r'\[STATUS:\s*(ERGEBNIS|TEILWEISE|PLAN)\]'
|
|
match = re.search(status_pattern, content, re.IGNORECASE)
|
|
|
|
if match:
|
|
# Extrahiere den Status
|
|
status = match.group(1).upper()
|
|
|
|
# Entferne die Status-Deklaration aus dem Text
|
|
content = re.sub(status_pattern, '', content, flags=re.IGNORECASE).strip()
|
|
|
|
return content, status
|
|
|
|
|
|
def extract_summary(text: str, max_length: int = 200) -> str:
|
|
"""
|
|
Extrahiert eine kurze Zusammenfassung aus einem Text.
|
|
|
|
Args:
|
|
text: Der zu extrahierende Text
|
|
max_length: Maximale Länge der Zusammenfassung
|
|
|
|
Returns:
|
|
Eine kurze Zusammenfassung des Textes
|
|
"""
|
|
# Erste Zeilen oder Absätze extrahieren
|
|
lines = text.split('\n')
|
|
paragraphs = []
|
|
current_para = []
|
|
|
|
for line in lines:
|
|
line = line.strip()
|
|
if not line:
|
|
if current_para:
|
|
paragraphs.append(' '.join(current_para))
|
|
current_para = []
|
|
else:
|
|
current_para.append(line)
|
|
|
|
if current_para:
|
|
paragraphs.append(' '.join(current_para))
|
|
|
|
# Versuche, den ersten oder zweiten Absatz als Zusammenfassung zu verwenden
|
|
if paragraphs:
|
|
summary = paragraphs[0]
|
|
|
|
# Falls der erste Absatz zu kurz ist, versuche den zweiten hinzuzufügen
|
|
if len(summary) < 100 and len(paragraphs) > 1:
|
|
summary += " " + paragraphs[1]
|
|
|
|
# Kürze auf die maximale Länge
|
|
if len(summary) > max_length:
|
|
summary = summary[:max_length-3] + "..."
|
|
|
|
return summary
|
|
|
|
# Fallback auf die ersten Zeichen des Textes
|
|
if text:
|
|
return text[:max_length-3] + "..." if len(text) > max_length else text
|
|
|
|
return "Keine Zusammenfassung verfügbar" |