""" Erweiterter Coder-Agent für die Entwicklung und Ausführung von Python-Code (Fortsetzung). """ import logging import json import os from typing import List, Dict, Any, Optional import asyncio import re import traceback from datetime import datetime from modules.agentservice_base import BaseAgent from modules.lucydom_interface import get_lucydom_interface from modules.agentservice_code_executor import CodeExecutor logger = logging.getLogger(__name__) class CoderAgent(BaseAgent): """Erweiterter Agent für die Entwicklung und Ausführung von Python-Code""" async def _execute_code(self, code: str, lucydom_interface, context: Dict[str, Any] = None) -> Dict[str, Any]: """ Führt Python-Code mit dem CodeExecutor aus. Args: code: Der auszuführende Python-Code lucydom_interface: Interface für Datenbankzugriffe context: Zusätzlicher Kontext Returns: Ergebnis der Codeausführung """ try: # Systemfunktionen für den Code vorbereiten system_functions_code = self._prepare_system_functions(lucydom_interface) # Code mit Systemfunktionen erweitern enhanced_code = system_functions_code + "\n\n" + code # CodeExecutor initialisieren available_modules = [ "modules.lucydom_interface", "modules.lucydom_model", "modules.agentservice_filehandling" ] # Liste erlaubter Pakete allowed_packages = None # None bedeutet alle erlaubt außer explizit blockierte # Liste blockierter Pakete blocked_packages = [ "cryptography", "flask", "django", "tornado", # Sicherheitsrisiken "tensorflow", "pytorch", "scikit-learn" # Ressourcenintensiv ] executor = CodeExecutor( app_modules=available_modules, timeout=60, # 60 Sekunden Timeout max_memory_mb=512, # 512MB Speicherlimit allowed_packages=allowed_packages, blocked_packages=blocked_packages ) try: # Eingabedaten vorbereiten (falls vorhanden) input_data = { "context": context, "workflow_id": context.get("workflow_id", "") if context else "", } # Dateireferenzen hinzufügen if context and "documents" in context: file_refs = [] for doc in context.get("documents", []): source = doc.get("source", {}) if source.get("type") == "file": file_refs.append({ "id": source.get("id", ""), "name": source.get("name", ""), "type": source.get("content_type", "") }) input_data["files"] = file_refs # Code ausführen result = executor.execute_code(enhanced_code, input_data) # Log für die Ausführung if result.get("success", False): logger.info(f"Code erfolgreich ausgeführt") output = result.get("output", "") if output: logger.debug(f"Ausgabe: {output[:200]}..." if len(output) > 200 else output) else: logger.error(f"Fehler bei der Codeausführung: {result.get('error', 'Unbekannter Fehler')}") return result finally: # Ressourcen freigeben executor.cleanup() except Exception as e: logger.error(f"Fehler bei der Codeausführung: {str(e)}", exc_info=True) return { "success": False, "output": "", "error": f"Fehler bei der Ausführung: {str(e)}\n{traceback.format_exc()}", "result": None } def _prepare_system_functions(self, lucydom_interface) -> str: """ Bereitet die Systemfunktionen für den auszuführenden Code vor. Args: lucydom_interface: Interface für Datenbankzugriffe Returns: Python-Code für die Systemfunktionen """ system_functions_code = """ # Systemfunktionen für den Code async def load_file(file_id, encoding=None): \"\"\" Lädt eine Datei aus der Datenbank anhand ihrer ID. Args: file_id: ID der zu ladenden Datei encoding: Optionale Kodierung (Standard: None für binäre Daten) Returns: Binäre Daten oder dekodierter String, je nach Encoding-Parameter \"\"\" try: # lucydom_interface wird über Globals zur Verfügung gestellt global lucydom_interface if not lucydom_interface: raise ValueError("LucyDOM-Interface nicht verfügbar") # Dateiinhalt asynchron laden file_content = await lucydom_interface.read_file_content(file_id) if not file_content: raise ValueError(f"Datei mit ID {file_id} nicht gefunden") # Wenn Encoding angegeben ist, String zurückgeben if encoding: return file_content.decode(encoding) # Andernfalls binäre Daten zurückgeben return file_content except Exception as e: print(f"Fehler beim Laden der Datei {file_id}: {str(e)}") raise def save_file(content, file_name, content_type=None): \"\"\" Speichert Daten als Datei in der Datenbank. Args: content: Zu speichernde Daten (String oder Bytes) file_name: Name der Datei content_type: MIME-Typ der Datei (z.B. 'text/csv') Returns: Metadaten der gespeicherten Datei inkl. ID \"\"\" try: # lucydom_interface wird über Globals zur Verfügung gestellt global lucydom_interface if not lucydom_interface: raise ValueError("LucyDOM-Interface nicht verfügbar") # Wenn der Inhalt ein String ist, in Bytes konvertieren if isinstance(content, str): content = content.encode('utf-8') # Datei speichern file_meta = lucydom_interface.save_uploaded_file(content, file_name) # Wenn content_type angegeben ist, Datei-Metadaten aktualisieren if content_type and "id" in file_meta: update_data = {"content_type": content_type} lucydom_interface.update_file(file_meta["id"], update_data) file_meta["content_type"] = content_type return file_meta except Exception as e: print(f"Fehler beim Speichern der Datei {file_name}: {str(e)}") raise def update_file(file_id, content, update_metadata=None): \"\"\" Aktualisiert eine bestehende Datei in der Datenbank. Args: file_id: ID der zu aktualisierenden Datei content: Neue Inhalte für die Datei (String oder Bytes) update_metadata: Optionale Metadaten-Updates Returns: Aktualisierte Metadaten der Datei \"\"\" try: # lucydom_interface wird über Globals zur Verfügung gestellt global lucydom_interface if not lucydom_interface: raise ValueError("LucyDOM-Interface nicht verfügbar") # Wenn der Inhalt ein String ist, in Bytes konvertieren if isinstance(content, str): content = content.encode('utf-8') # Temporäre Datei erstellen import tempfile import os temp_file = tempfile.NamedTemporaryFile(delete=False) temp_file.write(content) temp_file.close() # Bestehende Datei abrufen file_meta = lucydom_interface.get_file(file_id) if not file_meta: raise ValueError(f"Datei mit ID {file_id} nicht gefunden") # Datei mit neuen Inhalten aktualisieren with open(temp_file.name, 'rb') as f: updated_meta = lucydom_interface.save_uploaded_file(content, file_meta.get("name", "updated_file")) # Temporäre Datei löschen os.unlink(temp_file.name) # Metadaten aktualisieren if update_metadata and "id" in updated_meta: lucydom_interface.update_file(updated_meta["id"], update_metadata) updated_meta.update(update_metadata) return updated_meta except Exception as e: print(f"Fehler beim Aktualisieren der Datei {file_id}: {str(e)}") raise def get_file_metadata(file_id): \"\"\" Ruft die Metadaten einer Datei ab. Args: file_id: ID der Datei Returns: Metadaten der Datei als Dictionary \"\"\" try: # lucydom_interface wird über Globals zur Verfügung gestellt global lucydom_interface if not lucydom_interface: raise ValueError("LucyDOM-Interface nicht verfügbar") # Datei-Metadaten abrufen file_meta = lucydom_interface.get_file(file_id) if not file_meta: raise ValueError(f"Datei mit ID {file_id} nicht gefunden") return file_meta except Exception as e: print(f"Fehler beim Abrufen der Metadaten für Datei {file_id}: {str(e)}") raise def process_csv(content, operations=None): \"\"\" Verarbeitet CSV-Daten mit Pandas. Args: content: CSV-Daten als String oder Bytes operations: Liste von Operationen, die auf den Daten ausgeführt werden sollen [{"type": "filter", "column": "Name", "value": "Max"}, {"type": "groupby", "column": "Category"}] Returns: Ergebnis der Verarbeitung als Dictionary \"\"\" try: import pandas as pd import io # Wenn der Inhalt Bytes ist, in String konvertieren if isinstance(content, bytes): content = content.decode('utf-8') # CSV in DataFrame laden df = pd.read_csv(io.StringIO(content)) # Wenn Operationen angegeben sind, diese durchführen if operations: for op in operations: op_type = op.get("type", "").lower() if op_type == "filter" and "column" in op and "value" in op: df = df[df[op["column"]] == op["value"]] elif op_type == "groupby" and "column" in op: groupby_column = op["column"] agg_column = op.get("aggregate_column") agg_func = op.get("aggregate_function", "count") if agg_column: df = df.groupby(groupby_column).agg({agg_column: agg_func}).reset_index() else: df = df.groupby(groupby_column).size().reset_index(name='count') # Ergebnis zurückgeben return { "data": df.to_dict('records'), "columns": df.columns.tolist(), "shape": df.shape } except Exception as e: print(f"Fehler bei der CSV-Verarbeitung: {str(e)}") raise def extract_text_from_pdf(pdf_data): \"\"\" Extrahiert Text aus einem PDF-Dokument. Args: pdf_data: PDF-Daten als Bytes Returns: Extrahierter Text aus dem PDF \"\"\" try: # Versuche PyPDF2 zu verwenden try: from PyPDF2 import PdfReader from io import BytesIO reader = PdfReader(BytesIO(pdf_data)) text = "" for page in reader.pages: text += page.extract_text() + "\\n\\n" return text except ImportError: # Fallback auf pymupdf, falls PyPDF2 nicht verfügbar ist try: import fitz # PyMuPDF from io import BytesIO doc = fitz.open("pdf", pdf_data) text = "" for page in doc: text += page.get_text() + "\\n\\n" return text except ImportError: return "PDF-Extraktion fehlgeschlagen: Weder PyPDF2 noch PyMuPDF sind installiert" except Exception as e: print(f"Fehler bei der PDF-Extraktion: {str(e)}") return f"Fehler bei der PDF-Extraktion: {str(e)}" def analyze_image(image_data, analysis_type="description"): \"\"\" Analysiert ein Bild (KI-basiert, falls verfügbar). Args: image_data: Bilddaten als Bytes analysis_type: Art der Analyse: 'description', 'objects', 'text' Returns: Ergebnis der Bildanalyse \"\"\" # Hinweis: Diese Funktion simuliert eine Bildanalyse, # da die echte KI-Analyse eine async-Funktion erfordern würde try: # Bildgröße ermitteln from io import BytesIO from PIL import Image image = Image.open(BytesIO(image_data)) width, height = image.size format_name = image.format # Simulierte Analyse basierend auf dem Bildtyp analysis_result = { "image_info": { "width": width, "height": height, "format": format_name, "size_bytes": len(image_data) }, "analysis_type": analysis_type, "analysis_result": f"Simulierte Bildanalyse für ein {format_name}-Bild ({width}x{height}px)" } return analysis_result except Exception as e: print(f"Fehler bei der Bildanalyse: {str(e)}") return {"error": str(e)} # lucydom_interface global verfügbar machen import asyncio """ return system_functions_code # Singleton-Instanz _coder_agent = None def get_coder_agent(): """Gibt eine Singleton-Instanz des Coder-Agenten zurück""" global _coder_agent if _coder_agent is None: _coder_agent = CoderAgent() return _coder_agent