426 lines
No EOL
14 KiB
Python
426 lines
No EOL
14 KiB
Python
"""
|
|
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 |