gateway/modules/chat.py
2025-04-19 01:02:46 +02:00

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]