858 lines
No EOL
33 KiB
Python
858 lines
No EOL
33 KiB
Python
"""
|
|
ChatManager Modul zur Verwaltung von AI-Chat-Workflows.
|
|
Implementiert eine kompakte und modulare Architektur für die Verarbeitung
|
|
von Benutzeranfragen, Agentenausführung und Ergebnisformatierung.
|
|
"""
|
|
|
|
import logging
|
|
import json
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import Dict, Any, List, Optional, Union
|
|
|
|
# Notwendige Importe
|
|
from connectors.connector_aichat_openai import ChatService
|
|
from modules.chat_registry import get_agent_registry
|
|
from modules.lucydom_interface import get_lucydom_interface
|
|
|
|
# Logger konfigurieren
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class ChatManager:
|
|
"""
|
|
Verwaltet die Verarbeitung von Chat-Anfragen, Agentenausführung und
|
|
die Integration von Ergebnissen in den Workflow.
|
|
"""
|
|
|
|
def __init__(self, mandate_id: int, user_id: int):
|
|
"""
|
|
Initialisiert den ChatManager mit Mandanten- und Benutzerkontext.
|
|
|
|
Args:
|
|
mandate_id: ID des aktuellen Mandanten
|
|
user_id: ID des aktuellen Benutzers
|
|
"""
|
|
self.mandate_id = mandate_id
|
|
self.user_id = user_id
|
|
self.ai_service = ChatService()
|
|
self.lucy_interface = get_lucydom_interface(mandate_id, user_id)
|
|
self.agent_registry = get_agent_registry()
|
|
|
|
|
|
### Chat Management
|
|
|
|
async def chat_run(self, user_input: Dict[str, Any], workflow_id: Optional[str] = None) -> Dict[str, Any]:
|
|
"""
|
|
Hauptfunktion zur Integration von Benutzeranfragen in den Workflow.
|
|
|
|
Args:
|
|
user_input, which will be parsed to message_user: Message-Objekt mit Benutzeranfrage und Dokumenten
|
|
workflow_id: Optional - ID des Workflows (None für neue Workflows)
|
|
|
|
Returns:
|
|
Workflow-Objekt mit aktualisiertem Zustand
|
|
"""
|
|
logger.info(f"User message object: {self.parse_json2text(message_user)}")
|
|
|
|
# 0. User-Input mit file id's in Message User als message object transformieren und alle contents vorbereiten
|
|
message_user = self.chat_user_message_integration(user_input)
|
|
|
|
# 1. Workflow initialisieren oder bestehenden laden
|
|
workflow = self.workflow_init(workflow_id)
|
|
|
|
# 2. Benutzer-Message im Workflow speichern
|
|
self.message_add(workflow, message_user)
|
|
|
|
# 3. Projektleiter-Prompt erstellen und Antwort analysieren
|
|
project_manager_response = await self.chat_prompt(message_user, workflow)
|
|
|
|
# 3.1. Extrahiere die benötigten Informationen aus der Antwort
|
|
obj_answer = project_manager_response.get("obj_answer", [])
|
|
obj_workplan = project_manager_response.get("obj_workplan", [])
|
|
user_response = project_manager_response.get("user_response", "")
|
|
|
|
# 3.2. Speichere die Antwort als Message im Workflow und füge Log-Einträge hinzu
|
|
response_message = {
|
|
"role": "assistant",
|
|
"agent_type": "project_manager",
|
|
"content": user_response
|
|
}
|
|
self.message_add(workflow, response_message)
|
|
|
|
# 3.3. Log-Eintrag für den Workplan und die geplanten Ergebnisse
|
|
self.log_add(workflow, f"Arbeitsplan: {self.parse_json2text(obj_workplan)}")
|
|
self.log_add(workflow, f"Geplante Ergebnisse: {self.parse_json2text(obj_answer)}")
|
|
|
|
# 4. Agenten gemäss Workplan ausführen
|
|
obj_results = []
|
|
if obj_workplan:
|
|
for task in obj_workplan:
|
|
# Informiere Benutzer über aktuellen Schritt
|
|
agent_name = task.get("agent")
|
|
step_info = f"Führe Agent '{agent_name}' aus um {', '.join([d.get('label') for d in task.get('doc_output', [])])} zu erstellen"
|
|
self.log_add(workflow, step_info)
|
|
|
|
# Bereite Eingabedokumente für den Agenten vor
|
|
input_docs = self.agent_input_documents(task.get('doc_input', []), workflow)
|
|
|
|
# Führe den Agenten aus
|
|
agent_results = await self.agent_execute(
|
|
agent_name=agent_name,
|
|
prompt=task.get("prompt", ""),
|
|
input_docs=input_docs,
|
|
output_format=task.get("doc_output", [])
|
|
)
|
|
|
|
# Sammle Ergebnisse
|
|
obj_results.extend(agent_results)
|
|
|
|
# Speichere Zwischenergebnisse
|
|
for result in agent_results:
|
|
self.log_add(workflow, f"Ergebnis erstellt: {result.get('label')}")
|
|
|
|
# 5. Erstelle die finale Antwort mit den gesammelten Dokumenten
|
|
final_message = self.chat_final_message(user_response, obj_results, obj_answer)
|
|
self.message_add(workflow, final_message)
|
|
|
|
# 6. Finalisiere den Workflow
|
|
self.workflow_finish(workflow)
|
|
|
|
return workflow
|
|
|
|
async def chat_prompt(self, message_user: Dict[str, Any], workflow: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Erstellt den Prompt für den Projektleiter und verarbeitet seine Antwort.
|
|
|
|
Args:
|
|
message_user: Message-Objekt mit Benutzeranfrage
|
|
workflow: Aktuelles Workflow-Objekt
|
|
|
|
Returns:
|
|
Antwort des Projektleiters mit obj_answer, obj_workplan und user_response
|
|
"""
|
|
# Verfügbare Dokumenttypen aus der Funktion holen
|
|
doc_types = self.document_types_accepted()
|
|
doc_types_str = ", ".join(doc_types)
|
|
|
|
# Verfügbare Agenten mit ihren Fähigkeiten abrufen
|
|
available_agents = self.agent_profiles()
|
|
|
|
# Erstelle eine Zusammenfassung des Workflows
|
|
workflow_summary = await self.workflow_summarize(workflow, "Fasse den bisherigen Verlauf kurz und prägnant zusammen")
|
|
|
|
# Erstelle eine Zusammenfassung der vom Benutzer bereitgestellten Dokumente
|
|
user_docs_summary = await self.message_summarize_documents(message_user, "Fasse den Inhalt des Dokuments kurz zusammen")
|
|
|
|
# Liste der aktuell verfügbaren Dokumente aus User-Input oder bereits generierten Dokumenten erstellen
|
|
available_documents = self.available_documents_get(message_user, workflow)
|
|
available_docs_str = self.available_documents_format(available_documents)
|
|
|
|
# Erstelle den Prompt für den Projektleiter
|
|
prompt = f"""
|
|
Basierend auf der Benutzeranfrage: "{message_user.get('content')}" und den bereitgestellten Dokumenten,
|
|
analysiere bitte die Anforderungen und erstelle einen Plan zur Bearbeitung.
|
|
|
|
# Bisheriger Konversationsverlauf:
|
|
{workflow_summary}
|
|
|
|
# Vom Benutzer bereitgestellte Dokumente:
|
|
{user_docs_summary}
|
|
|
|
# Verfügbare Dokumente (aktuell im Workflow):
|
|
{available_docs_str}
|
|
|
|
# Verfügbare Dokumenttypen:
|
|
{doc_types_str}
|
|
|
|
# Verfügbare Agenten und ihre Fähigkeiten:
|
|
{self.parse_json2text(available_agents)}
|
|
|
|
Bitte analysiere die Anfrage und erstelle:
|
|
|
|
1. Eine Liste benötigter Ergebnisdokumente (obj_answer)
|
|
2. Einen Plan für die Ausführung von Agenten (obj_workplan)
|
|
3. Eine verständliche Antwort an den Benutzer
|
|
|
|
## WICHTIGE REGELN FÜR DEN ARBEITSPLAN:
|
|
1. Jedes Eingabedokument muss entweder bereits vorhanden sein (vom Benutzer bereitgestellt oder vorher von einem Agenten erzeugt) oder von einem Agenten erstellt werden, bevor es verwendet wird.
|
|
2. Wenn nötig, konvertiere Eingabedokumente durch Agenten in ein passendes Format, wenn der Typ nicht übereinstimmt.
|
|
3. Definiere keine Dokument-Inputs, die nicht existieren oder nicht vorab generiert werden.
|
|
4. Erstelle eine logische Reihenfolge - frühere Agenten können Dokumente erzeugen, die später als Eingaben verwendet werden.
|
|
5. Wenn der Benutzer Dokumente bereitgestellt hat, nutze diese kreativ, auch wenn sie nicht exakt dem gewünschten Typ entsprechen.
|
|
|
|
Antworte in folgendem JSON-Format:
|
|
{{
|
|
"obj_answer": [
|
|
{{
|
|
"label": "eindeutiger_dokumentname",
|
|
"doc_type": "{doc_types[0]}", # Einer der verfügbaren Dokumenttypen: {doc_types_str}
|
|
"summary": "Beschreibung des Dokumentinhalts"
|
|
}}
|
|
],
|
|
"obj_workplan": [
|
|
{{
|
|
"agent": "agent_name", # Name eines verfügbaren Agenten
|
|
"doc_output": [
|
|
{{
|
|
"label": "eindeutiger_dokumentname",
|
|
"doc_type": "{doc_types[0]}" # Einer der verfügbaren Dokumenttypen: {doc_types_str}
|
|
}}
|
|
],
|
|
"prompt": "Anweisungen für den Agenten",
|
|
"doc_input": [
|
|
{{
|
|
"label": "eindeutiger_dokumentname",
|
|
"doc_type": "{doc_types[0]}" # Einer der verfügbaren Dokumenttypen: {doc_types_str}
|
|
}}
|
|
]
|
|
# Falls keine Eingabedokumente benötigt werden, kann "doc_input" leer bleiben oder weggelassen werden
|
|
}}
|
|
# Mehrere Agent-Tasks können hier hinzugefügt werden und sollten logisch aufeinander aufbauen
|
|
],
|
|
"user_response": "Klare Erklärung für den Benutzer, was als nächstes passiert"
|
|
}}
|
|
"""
|
|
|
|
# Rufe den AI-Service auf, um die Antwort des Projektleiters zu erhalten
|
|
project_manager_output = await self.ai_service.call_api([
|
|
{"role": "system", "content": "Du bist ein erfahrener Projektleiter, der Benutzeranfragen analysiert und Arbeitspläne erstellt. Du achtest sehr sorgfältig darauf, dass alle Dokument-Abhängigkeiten korrekt sind und keine nicht existierenden Dokumente als Eingaben definiert werden."},
|
|
{"role": "user", "content": prompt}
|
|
])
|
|
|
|
# Parsen der JSON-Antwort
|
|
return self.parse_json_response(project_manager_output)
|
|
|
|
def chat_user_message_integration(self, user_input: Dict[str, Any]) -> Dict[str, Any]:
|
|
# Nachrichteninhalt überprüfen
|
|
message_content = user_input.get("message", "")
|
|
if isinstance(message_content, dict) and "content" in message_content:
|
|
message_content = message_content["content"]
|
|
|
|
# Wenn Nachrichteninhalt leer ist, kein Chat
|
|
if message_content is None or message_content.strip() == "":
|
|
logger.warning(f"Leere Nachricht, kein Chat")
|
|
message_content = "(No user input received)"
|
|
|
|
# Zusätzliche Dateien verarbeiten
|
|
additional_fileids = user_input.get("additional_fileids", [])
|
|
additional_files = self.process_file_ids(additional_fileids)
|
|
|
|
# Nachrichtenobjekt erstellen
|
|
message_object = {
|
|
"role": "user",
|
|
"content": message_content,
|
|
"documents": additional_files
|
|
}
|
|
return message_object
|
|
|
|
def chat_final_message(self, user_response: str, obj_results: List[Dict[str, Any]],
|
|
obj_answer: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""
|
|
Erstellt die finale Antwortnachricht mit Dokumenten.
|
|
|
|
Args:
|
|
user_response: Textantwort an den Benutzer
|
|
obj_results: Liste der erzeugten Ergebnisdokumente
|
|
obj_answer: Liste der erwarteten Antwortdokumente
|
|
|
|
Returns:
|
|
Vollständiges Message-Objekt mit Inhalt und Dokumenten
|
|
"""
|
|
# Grundlegende Nachrichtenstruktur erstellen
|
|
final_message = {
|
|
"role": "assistant",
|
|
"agent_type": "final_responder",
|
|
"content": user_response,
|
|
"documents": []
|
|
}
|
|
|
|
# Dokumente vom Typ "text" in die Antwort integrieren
|
|
text_parts = [user_response]
|
|
|
|
for doc in obj_results:
|
|
doc_label = None
|
|
doc_contents = []
|
|
|
|
for content in doc.get("contents", []):
|
|
if content.get("label"):
|
|
doc_label = content.get("label")
|
|
|
|
# Suche nach dem passenden doc_type in obj_answer
|
|
target_type = None
|
|
for answer_spec in obj_answer:
|
|
if answer_spec.get("label") == doc_label:
|
|
target_type = answer_spec.get("doc_type")
|
|
break
|
|
|
|
# Wenn der Content vom Typ "text" ist und integriert werden soll
|
|
if content.get("type") == "text" and content.get("text"):
|
|
text = content.get("text")
|
|
text_parts.append(f"\n\n--- {doc_label} ---\n{text}")
|
|
|
|
doc_contents.append(content)
|
|
|
|
# Füge das Dokument zur finalen Nachricht hinzu
|
|
if doc_contents:
|
|
final_message["documents"].append({
|
|
"id": doc.get("id", f"doc_{str(uuid.uuid4())}"),
|
|
"source": doc.get("source", {"type": "agent", "name": "Response Generator"}),
|
|
"contents": doc_contents
|
|
})
|
|
|
|
# Aktualisiere den Nachrichteninhalt mit integrierten Texten
|
|
final_message["content"] = "\n".join(text_parts)
|
|
|
|
return final_message
|
|
|
|
|
|
### Workflow
|
|
|
|
def workflow_init(self, workflow_id: Optional[str] = None) -> Dict[str, Any]:
|
|
"""
|
|
Initialisiert einen Workflow oder lädt einen bestehenden.
|
|
|
|
Args:
|
|
workflow_id: Optional - ID des zu ladenden Workflows
|
|
|
|
Returns:
|
|
Initialisiertes Workflow-Objekt
|
|
"""
|
|
current_time = datetime.now().isoformat()
|
|
|
|
if workflow_id is None or not self.lucy_interface.get_workflow(workflow_id):
|
|
# Neuen Workflow erstellen
|
|
new_workflow_id = str(uuid.uuid4()) if workflow_id is None else workflow_id
|
|
workflow = {
|
|
"id": new_workflow_id,
|
|
"mandate_id": self.mandate_id,
|
|
"user_id": self.user_id,
|
|
"name": f"Workflow {new_workflow_id[:8]}",
|
|
"status": "running",
|
|
"started_at": current_time,
|
|
"last_activity": current_time,
|
|
"current_round": 1,
|
|
"waiting_for_user": False,
|
|
"messages": [],
|
|
"logs": [],
|
|
"data_stats": {}
|
|
}
|
|
|
|
# In Datenbank speichern
|
|
self.lucy_interface.create_workflow(workflow)
|
|
return workflow
|
|
else:
|
|
# Bestehenden Workflow laden
|
|
workflow = self.lucy_interface.load_workflow_state(workflow_id)
|
|
|
|
# Status aktualisieren
|
|
workflow["status"] = "running"
|
|
workflow["last_activity"] = current_time
|
|
workflow["waiting_for_user"] = False
|
|
|
|
# In Datenbank aktualisieren
|
|
self.lucy_interface.save_workflow_state(workflow)
|
|
return workflow
|
|
|
|
async def workflow_summarize(self, workflow: Dict[str, Any], prompt: str) -> str:
|
|
"""
|
|
Erstellt eine Zusammenfassung des Workflows.
|
|
|
|
Args:
|
|
workflow: Workflow-Objekt
|
|
prompt: Anweisungen zur Erstellung der Zusammenfassung
|
|
|
|
Returns:
|
|
Zusammenfassung des Workflows
|
|
"""
|
|
if not workflow or "messages" not in workflow or not workflow["messages"]:
|
|
return "Keine vorherigen Nachrichten im Workflow vorhanden."
|
|
|
|
# Nachrichten in umgekehrter Reihenfolge durchgehen (neueste zuerst)
|
|
messages = sorted(workflow["messages"], key=lambda m: m.get("sequence_no", 0), reverse=True)
|
|
|
|
summary_parts = []
|
|
for message in messages:
|
|
message_summary = await self.message_summarize(message, prompt)
|
|
summary_parts.append(message_summary)
|
|
|
|
return "\n\n".join(summary_parts)
|
|
|
|
def workflow_finish(self, workflow: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Finalisiert einen Workflow und setzt den Status auf 'stopped'.
|
|
|
|
Args:
|
|
workflow: Workflow-Objekt
|
|
|
|
Returns:
|
|
Aktualisiertes Workflow-Objekt
|
|
"""
|
|
workflow["status"] = "completed"
|
|
workflow["last_activity"] = datetime.now().isoformat()
|
|
workflow["waiting_for_user"] = True
|
|
|
|
# In Datenbank speichern
|
|
self.lucy_interface.save_workflow_state(workflow)
|
|
return workflow
|
|
|
|
|
|
### Agents
|
|
|
|
def agent_profiles(self) -> List[Dict[str, Any]]:
|
|
"""
|
|
Ruft Informationen über alle verfügbaren Agenten ab.
|
|
|
|
Returns:
|
|
Liste mit Informationen über alle verfügbaren Agenten
|
|
"""
|
|
return self.agent_registry.get_agent_infos()
|
|
|
|
def agent_input_documents(self, doc_input_list: List[Dict[str, Any]], workflow: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
"""
|
|
Bereitet Eingabedokumente für einen Agenten vor.
|
|
|
|
Args:
|
|
doc_input_list: Liste der benötigten Eingabedokumente
|
|
workflow: Workflow-Objekt
|
|
|
|
Returns:
|
|
Aufbereitete Eingabedokumente für den Agenten
|
|
"""
|
|
prepared_inputs = []
|
|
|
|
for doc_spec in doc_input_list:
|
|
doc_label = doc_spec.get("label")
|
|
doc_type = doc_spec.get("doc_type")
|
|
|
|
found_doc = None
|
|
|
|
# Durchsuche alle Nachrichten nach dem gesuchten Dokument
|
|
for message in workflow.get("messages", []):
|
|
for doc in message.get("documents", []):
|
|
# Dokument anhand des Labels identifizieren
|
|
if any(content.get("label") == doc_label for content in doc.get("contents", [])):
|
|
found_doc = doc
|
|
break
|
|
|
|
if found_doc:
|
|
break
|
|
|
|
if found_doc:
|
|
prepared_inputs.append(found_doc)
|
|
|
|
return prepared_inputs
|
|
|
|
async def agent_execute(self, agent_name: str, prompt: str, input_docs: List[Dict[str, Any]],
|
|
output_format: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
"""
|
|
Führt einen Agenten mit den angegebenen Parametern aus.
|
|
|
|
Args:
|
|
agent_name: Name des auszuführenden Agenten
|
|
prompt: Prompt für den Agenten
|
|
input_docs: Eingabedokumente
|
|
output_format: Erwartetes Ausgabeformat
|
|
|
|
Returns:
|
|
Liste der vom Agenten erzeugten Ergebnisdokumente
|
|
"""
|
|
# Hole den Agenten aus der Registry
|
|
agent = self.agent_registry.get_agent(agent_name)
|
|
|
|
if not agent:
|
|
logger.error(f"Agent '{agent_name}' nicht gefunden")
|
|
return []
|
|
|
|
try:
|
|
# Erstelle die Agenten-Anfrage
|
|
agent_request = {
|
|
"role": "user",
|
|
"content": prompt,
|
|
"documents": input_docs
|
|
}
|
|
|
|
# Führe den Agenten aus
|
|
agent_response = await agent.process_message(agent_request, {"expected_format": output_format})
|
|
|
|
# Extrahiere die erzeugten Dokumente
|
|
results = agent_response.get("documents", [])
|
|
|
|
# Benenne die Dokumente gemäß des angegebenen Ausgabeformats
|
|
for i, format_spec in enumerate(output_format):
|
|
if i < len(results):
|
|
for content in results[i].get("contents", []):
|
|
content["label"] = format_spec.get("label")
|
|
content["type"] = format_spec.get("doc_type")
|
|
|
|
return results
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei Ausführung von Agent '{agent_name}': {str(e)}")
|
|
return []
|
|
|
|
|
|
### Messages
|
|
|
|
def message_add(self, workflow: Dict[str, Any], message: Dict[str, Any]) -> str:
|
|
"""
|
|
Fügt eine Nachricht zum Workflow hinzu und aktualisiert last_activity.
|
|
|
|
Args:
|
|
workflow: Workflow-Objekt
|
|
message: Zu speichernde Nachricht
|
|
|
|
Returns:
|
|
ID der hinzugefügten Nachricht
|
|
"""
|
|
current_time = datetime.now().isoformat()
|
|
|
|
# Sicherstellen, dass Messages-Liste existiert
|
|
if "messages" not in workflow:
|
|
workflow["messages"] = []
|
|
|
|
# Neue Nachrichten-ID generieren, falls nicht vorhanden
|
|
if "id" not in message:
|
|
message["id"] = f"msg_{str(uuid.uuid4())}"
|
|
|
|
# Workflow-ID und Zeitstempel hinzufügen
|
|
message["workflow_id"] = workflow["id"]
|
|
message["started_at"] = current_time
|
|
message["finished_at"] = current_time
|
|
|
|
# Sequenznummer setzen
|
|
message["sequence_no"] = len(workflow["messages"]) + 1
|
|
|
|
# Status setzen
|
|
message["status"] = "completed"
|
|
|
|
# Message zum Workflow hinzufügen
|
|
workflow["messages"].append(message)
|
|
|
|
# Workflow-Status aktualisieren
|
|
workflow["last_activity"] = current_time
|
|
workflow["last_message_id"] = message["id"]
|
|
|
|
# In Datenbank speichern
|
|
self.lucy_interface.create_workflow_message(message)
|
|
|
|
return message["id"]
|
|
|
|
async def message_summarize(self, message: Dict[str, Any], prompt: str) -> str:
|
|
"""
|
|
Erstellt eine Zusammenfassung einer Nachricht.
|
|
|
|
Args:
|
|
message: Zu summarisierende Nachricht
|
|
prompt: Anweisungen zur Erstellung der Zusammenfassung
|
|
|
|
Returns:
|
|
Zusammenfassung der Nachricht
|
|
"""
|
|
agent_type = message.get("agent_type", "Unbekannt")
|
|
role = message.get("role", "Unbekannt")
|
|
content = message.get("content", "")
|
|
|
|
# Kurze Nachrichten direkt übernehmen
|
|
if len(content) < 200:
|
|
content_summary = content
|
|
else:
|
|
# Für längere Nachrichten AI verwenden
|
|
content_summary = await self.ai_service.call_api([
|
|
{"role": "system", "content": f"Fasse den folgenden Text kurz zusammen. {prompt}"},
|
|
{"role": "user", "content": content}
|
|
])
|
|
|
|
# Dokumente zusammenfassen
|
|
docs_summary = ""
|
|
if "documents" in message and message["documents"]:
|
|
docs_list = []
|
|
for i, doc in enumerate(message["documents"]):
|
|
doc_source = doc.get("source", {})
|
|
doc_name = doc_source.get("name", f"Dokument {i+1}")
|
|
docs_list.append(f"- {doc_name}")
|
|
|
|
if docs_list:
|
|
docs_summary = f"\nDokumente: {', '.join(docs_list)}"
|
|
|
|
return f"[{role}/{agent_type}]: {content_summary}{docs_summary}"
|
|
|
|
async def message_summarize_documents(self, message: Dict[str, Any], prompt: str) -> str:
|
|
"""
|
|
Erstellt eine Zusammenfassung der Dokumente in einer Nachricht.
|
|
|
|
Args:
|
|
message: Nachricht mit Dokumenten
|
|
prompt: Anweisungen zur Erstellung der Zusammenfassung
|
|
|
|
Returns:
|
|
Zusammenfassung der Dokumente
|
|
"""
|
|
if "documents" not in message or not message["documents"]:
|
|
return "Keine Dokumente vorhanden."
|
|
|
|
summaries = []
|
|
for i, doc in enumerate(message["documents"]):
|
|
doc_source = doc.get("source", {})
|
|
doc_name = doc_source.get("name", f"Dokument {i+1}")
|
|
|
|
content_summary = "Keine Inhalte verfügbar"
|
|
|
|
if "contents" in doc and doc["contents"]:
|
|
text_contents = []
|
|
for content in doc["contents"]:
|
|
if content.get("is_text", False) and "text" in content:
|
|
text = content["text"]
|
|
# Für kurze Texte keine Zusammenfassung notwendig
|
|
if len(text) < 200:
|
|
text_contents.append(text)
|
|
else:
|
|
# AI für Zusammenfassung verwenden
|
|
summary = self.ai_service.call_api([
|
|
{"role": "system", "content": f"Fasse den folgenden Text kurz zusammen. {prompt}"},
|
|
{"role": "user", "content": text}
|
|
])
|
|
text_contents.append(summary)
|
|
|
|
if text_contents:
|
|
content_summary = "\n".join(text_contents)
|
|
|
|
summaries.append(f"Dokument: {doc_name}\n{content_summary}")
|
|
|
|
return "\n\n".join(summaries)
|
|
|
|
|
|
### Documents
|
|
|
|
def process_file_ids(self, file_ids: List[int]) -> List[Dict[str, Any]]:
|
|
"""
|
|
Verarbeitet eine Liste von File-IDs und gibt die entsprechenden Dateiobjekte als List of Document zurück
|
|
|
|
Args:
|
|
file_ids: Liste von Datei-IDs
|
|
|
|
Returns:
|
|
Liste von Dateiobjekten
|
|
"""
|
|
files = []
|
|
logger.info(f"Verarbeite {len(file_ids)} Dateien")
|
|
|
|
for file_id in file_ids:
|
|
try:
|
|
# Existiert die Datei?
|
|
file = self.lucy_interface.get_file(file_id)
|
|
if not file:
|
|
logger.warning(f"Datei mit ID {file_id} nicht gefunden")
|
|
continue
|
|
|
|
# Prüfen, ob Datei zum aktuellen Mandanten gehört
|
|
if file.get("mandate_id") != self.mandate_id:
|
|
logger.warning(f"Datei {file_id} gehört nicht zum Mandanten {self.mandate_id}")
|
|
continue
|
|
|
|
document = {}
|
|
|
|
files.append(document)
|
|
logger.info(f"Datei {file.get('name', 'unbenannt')} (ID: {file_id}) hinzugefügt")
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der Verarbeitung der Datei {file_id}: {str(e)}")
|
|
# Mit restlichen Dateien fortfahren statt zu scheitern
|
|
continue
|
|
|
|
return files
|
|
|
|
def available_documents_get(self, message_user: Dict[str, Any], workflow: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
"""
|
|
Ermittelt alle aktuell verfügbaren Dokumente aus User-Input und bereits generierten Dokumenten.
|
|
|
|
Args:
|
|
message_user: Aktuelle Nachricht vom Benutzer
|
|
workflow: Aktuelles Workflow-Objekt
|
|
|
|
Returns:
|
|
Liste mit Informationen über alle verfügbaren Dokumente
|
|
"""
|
|
available_docs = []
|
|
|
|
# TODO ****** user input object --> spearate routine to read content from user documents (files: to store mime-type or extension? - to analyse) // separate routine to get content from messages
|
|
|
|
# Dokumente aus der aktuellen Benutzer-Nachricht
|
|
if "documents" in message_user and message_user["documents"]:
|
|
for doc in message_user["documents"]:
|
|
source = doc.get("source", {})
|
|
doc_info = {
|
|
"label": source.get("name", "unbenannt"),
|
|
"doc_type": source.get("content_type", "text"),
|
|
"source": "user",
|
|
"message_id": message_user.get("id", "current")
|
|
}
|
|
available_docs.append(doc_info)
|
|
|
|
# Dokumente aus vorherigen Nachrichten im Workflow
|
|
if "messages" in workflow and workflow["messages"]:
|
|
for message in workflow["messages"]:
|
|
if "documents" in message and message["documents"]:
|
|
for doc in message["documents"]:
|
|
# Dokumente aus Inhalten extrahieren
|
|
for content in doc.get("contents", []):
|
|
if "label" in content:
|
|
doc_info = {
|
|
"label": content.get("label"),
|
|
"doc_type": content.get("type", "text"),
|
|
"source": "agent" if message.get("role") == "assistant" else "user",
|
|
"message_id": message.get("id", "unknown")
|
|
}
|
|
available_docs.append(doc_info)
|
|
|
|
logger.info(f"Available documents: {available_docs}")
|
|
return available_docs
|
|
|
|
def available_documents_format(self, documents: List[Dict[str, Any]]) -> str:
|
|
"""
|
|
Formatiert die Liste der verfügbaren Dokumente als lesbaren Text.
|
|
|
|
Args:
|
|
documents: Liste mit Dokumentinformationen
|
|
|
|
Returns:
|
|
Formatierter Text mit Dokumentinformationen
|
|
"""
|
|
if not documents:
|
|
return "Keine Dokumente verfügbar."
|
|
|
|
formatted_text = ""
|
|
for i, doc in enumerate(documents, 1):
|
|
source_info = f"vom Benutzer" if doc.get("source") == "user" else "von einem Agenten"
|
|
formatted_text += f"{i}. '{doc.get('label')}' (Typ: {doc.get('doc_type')}, Quelle: {source_info})\n"
|
|
|
|
return formatted_text
|
|
|
|
def document_types_accepted(self) -> List[str]:
|
|
"""
|
|
Gibt eine Liste aller verfügbaren Dokumenttypen zurück.
|
|
|
|
Returns:
|
|
Liste der Dokumenttypen
|
|
"""
|
|
return ['text', 'csv', 'png', 'html']
|
|
|
|
|
|
### Tools
|
|
|
|
def log_add(self, workflow: Dict[str, Any], message: str, level: str = "info",
|
|
agent_id: Optional[str] = None, agent_name: Optional[str] = None) -> str:
|
|
"""
|
|
Fügt einen Log-Eintrag zum Workflow hinzu und loggt diesen auch im Logger.
|
|
|
|
Args:
|
|
workflow: Workflow-Objekt
|
|
message: Log-Nachricht
|
|
level: Log-Level (info, warning, error)
|
|
agent_id: Optional - ID des Agenten
|
|
agent_name: Optional - Name des Agenten
|
|
|
|
Returns:
|
|
ID des erstellten Log-Eintrags
|
|
"""
|
|
# Sicherstellen, dass Logs-Liste existiert
|
|
if "logs" not in workflow:
|
|
workflow["logs"] = []
|
|
|
|
# Log-ID generieren
|
|
log_id = f"log_{str(uuid.uuid4())}"
|
|
|
|
# Log-Eintrag erstellen
|
|
log_entry = {
|
|
"id": log_id,
|
|
"workflow_id": workflow["id"],
|
|
"message": message,
|
|
"type": level,
|
|
"timestamp": datetime.now().isoformat(),
|
|
"agent_id": agent_id,
|
|
"agent_name": agent_name
|
|
}
|
|
|
|
# Log zum Workflow hinzufügen
|
|
workflow["logs"].append(log_entry)
|
|
|
|
# In Datenbank speichern
|
|
self.lucy_interface.create_workflow_log(log_entry)
|
|
|
|
# Auch im Logger loggen
|
|
if level == "info":
|
|
logger.info(f"Workflow {workflow['id']}: {message}")
|
|
elif level == "warning":
|
|
logger.warning(f"Workflow {workflow['id']}: {message}")
|
|
elif level == "error":
|
|
logger.error(f"Workflow {workflow['id']}: {message}")
|
|
|
|
return log_id
|
|
|
|
def parse_json2text(self, json_obj: Any) -> str:
|
|
"""
|
|
Konvertiert ein JSON-Objekt in eine lesbare Textdarstellung.
|
|
|
|
Args:
|
|
json_obj: Zu konvertierendes JSON-Objekt
|
|
|
|
Returns:
|
|
Formatierte Textdarstellung
|
|
"""
|
|
if not json_obj:
|
|
return "Keine Daten vorhanden"
|
|
|
|
try:
|
|
# Formatieren mit Einrückung für bessere Lesbarkeit
|
|
return json.dumps(json_obj, indent=2, ensure_ascii=False)
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei JSON-Konvertierung: {str(e)}")
|
|
return str(json_obj)
|
|
|
|
def parse_json_response(self, response_text: str) -> Dict[str, Any]:
|
|
"""
|
|
Parst die JSON-Antwort aus einem Text.
|
|
|
|
Args:
|
|
response_text: Text mit JSON-Inhalt
|
|
|
|
Returns:
|
|
Geparste JSON-Daten
|
|
"""
|
|
try:
|
|
# Extrahiere JSON aus dem Text (falls mit anderen Inhalten vermischt)
|
|
json_start = response_text.find('{')
|
|
json_end = response_text.rfind('}') + 1
|
|
|
|
if json_start >= 0 and json_end > json_start:
|
|
json_str = response_text[json_start:json_end]
|
|
return json.loads(json_str)
|
|
else:
|
|
# Versuche den gesamten Text zu parsen
|
|
return json.loads(response_text)
|
|
except json.JSONDecodeError as e:
|
|
logger.error(f"JSON-Parse-Fehler: {str(e)}")
|
|
# Fallback: Leere Struktur zurückgeben
|
|
return {
|
|
"obj_answer": [],
|
|
"obj_workplan": [],
|
|
"user_response": "Entschuldigung, ich konnte Ihre Anfrage nicht verarbeiten. Bitte versuchen Sie es erneut."
|
|
}
|
|
|
|
|
|
# Singleton-Factory für den ChatManager
|
|
_chat_managers = {}
|
|
|
|
def get_chat_manager(mandate_id: int = 0, user_id: int = 0) -> ChatManager:
|
|
"""
|
|
Gibt einen ChatManager für den angegebenen Kontext zurück.
|
|
Wiederverwendet bestehende Instanzen.
|
|
|
|
Args:
|
|
mandate_id: ID des Mandanten
|
|
user_id: ID des Benutzers
|
|
|
|
Returns:
|
|
ChatManager-Instanz
|
|
"""
|
|
context_key = f"{mandate_id}_{user_id}"
|
|
if context_key not in _chat_managers:
|
|
_chat_managers[context_key] = ChatManager(mandate_id, user_id)
|
|
return _chat_managers[context_key] |