gateway/gwserver/_old_bk_modules/agentservice_agent_coder.py
2025-04-11 23:39:10 +02:00

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