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

475 lines
No EOL
19 KiB
Python

"""
Hilfsfunktion für die intelligente Extraktion von Dateninhalten (Fortsetzung).
"""
from datetime import datetime
import logging
import json
from typing import List, Dict, Any, Optional, Tuple
import asyncio
import copy
# Import erweiterte Dateiverarbeitung
from gateway.gwserver.modules.agentservice_filemanager import extract_text_from_file_content
logger = logging.getLogger(__name__)
async def data_extraction(
prompt: str,
files: List[Dict[str, Any]],
messages: List[Dict[str, Any]],
ai_service,
lucydom_interface = None,
workflow_id: str = None,
add_log_func = None
) -> Dict[str, Any]:
"""
Führt einen AI Call durch, um zu bestimmen, welche Inhalte aus welchen Dateiobjekten extrahiert werden sollen,
und führt dann die notwendigen Extraktionen durch.
Args:
prompt: Spezifizierung, welche Daten extrahiert werden sollen
files: Liste aller verfügbaren Dateien mit Metadaten
messages: Liste aller Nachrichten im Workflow
ai_service: Service für KI-Anfragen
lucydom_interface: Interface für Datenbankzugriffe (optional)
workflow_id: Optionale ID des Workflows für Logging
add_log_func: Optionale Funktion für das Hinzufügen von Logs
Returns:
Strukturiertes Text-Objekt mit extrahierten Daten und Kontext-Informationen
"""
try:
# 1. AI Call zur Bestimmung der notwendigen Extraktionen
extraction_plan = await _create_extraction_plan(prompt, files, messages, ai_service, workflow_id, add_log_func)
# 2. Extraktionen durchführen
extracted_data = await _execute_extractions(
extraction_plan,
files,
lucydom_interface,
ai_service,
workflow_id,
add_log_func
)
# 3. Extrahierte Daten strukturieren
structured_result = _structure_extracted_data(extracted_data, files, prompt)
return structured_result
except Exception as e:
logger.error(f"Fehler bei der Datenextraktion: {str(e)}", exc_info=True)
# Fehler-Log hinzufügen
if add_log_func and workflow_id:
add_log_func(workflow_id, f"Fehler bei der Datenextraktion: {str(e)}", "error")
# Fehler-Ergebnis zurückgeben
return {
"error": str(e),
"status": "error",
"files_processed": len(files),
"message": f"Die Datenextraktion konnte nicht durchgeführt werden: {str(e)}"
}
async def _create_extraction_plan(
prompt: str,
files: List[Dict[str, Any]],
messages: List[Dict[str, Any]],
ai_service,
workflow_id: str = None,
add_log_func = None
) -> List[Dict[str, Any]]:
"""
Erstellt einen Extraktionsplan mit AI-Unterstützung.
Args:
prompt: Spezifizierung, welche Daten extrahiert werden sollen
files: Liste aller verfügbaren Dateien mit Metadaten
messages: Liste aller Nachrichten im Workflow
ai_service: Service für KI-Anfragen
workflow_id: Optionale ID des Workflows für Logging
add_log_func: Optionale Funktion für das Hinzufügen von Logs
Returns:
Extraktionsplan (Liste von Extraktionsanweisungen pro Datei)
"""
# Erstelle Kontext-Informationen für den AI Call
file_infos = []
for file in files:
# Basis-Metadaten
file_info = {
"id": file.get("id", ""),
"name": file.get("name", ""),
"type": file.get("type", ""),
"content_type": file.get("content_type", ""),
"size": file.get("size", "")
}
# Extraktionsstatus prüfen (falls vorhanden)
doc_contents = _extract_document_contents_from_messages(file.get("id", ""), messages)
if doc_contents:
# Prüfen, ob mindestens ein Content mit is_extracted=True existiert
already_extracted = any(
content.get("is_extracted", False) for content in doc_contents
)
file_info["already_extracted"] = already_extracted
# Eine kurze Vorschau des Inhalts hinzufügen (falls verfügbar)
for content in doc_contents:
if content.get("type") == "text" and content.get("text"):
preview_text = content.get("text", "")[:200] + "..." if len(content.get("text", "")) > 200 else content.get("text", "")
file_info["content_preview"] = preview_text
break
else:
file_info["already_extracted"] = False
file_infos.append(file_info)
# AI-Prompt erstellen
extraction_prompt = f"""
Du bist ein Datenextraktionsexperte, der mithilfe von KI-Analyse entscheidet, welche Dateien
und Inhalte für eine bestimmte Aufgabe extrahiert werden müssen.
AUFGABE:
{prompt}
VERFÜGBARE DATEIEN:
{json.dumps(file_infos, indent=2)}
Für jede Datei, die für die Aufgabe relevant ist, erstelle eine Extraktionsanweisung mit den folgenden Informationen:
1. file_id: Die ID der zu extrahierenden Datei
2. extract_needed: Boolean, ob eine Extraktion erforderlich ist (True, wenn die Datei noch nicht extrahiert wurde und für die Aufgabe benötigt wird)
3. extraction_prompt: Ein spezifischer Prompt für die Extraktion der Datei (besonders wichtig für Bilder und nicht-textbasierte Dateien)
4. importance: Priorität/Wichtigkeit für die Aufgabe (1-5, wobei 5 am wichtigsten ist)
Format:
[
{{
"file_id": "1234",
"extract_needed": true,
"extraction_prompt": "Extrahiere die Tabellendaten mit Fokus auf die Umsatzzahlen",
"importance": 5
}},
...
]
Gib nur das JSON-Array zurück, ohne weitere Erklärungen.
"""
# Log hinzufügen
if add_log_func and workflow_id:
add_log_func(workflow_id, "Extraktionsplan wird erstellt...", "info")
try:
# AI-Call durchführen
extraction_plan_response = await ai_service.call_api([{"role": "user", "content": extraction_prompt}])
# JSON aus der Antwort extrahieren
import re
json_match = re.search(r'\[.*\]', extraction_plan_response, re.DOTALL)
if json_match:
extraction_plan = json.loads(json_match.group(0))
# Log hinzufügen
if add_log_func and workflow_id:
add_log_func(
workflow_id,
f"Extraktionsplan erstellt für {len(extraction_plan)} Dateien",
"info"
)
return extraction_plan
else:
# Fallback bei Parsing-Problemen
if add_log_func and workflow_id:
add_log_func(
workflow_id,
"Parsing-Fehler beim Extraktionsplan, erstelle Standard-Plan",
"warning"
)
# Standard-Plan: Alle nicht extrahierten Dateien extrahieren
default_plan = []
for file in files:
doc_contents = _extract_document_contents_from_messages(file.get("id", ""), messages)
already_extracted = any(
content.get("is_extracted", False) for content in doc_contents
) if doc_contents else False
default_plan.append({
"file_id": file.get("id", ""),
"extract_needed": not already_extracted,
"extraction_prompt": f"Extrahiere alle relevanten Informationen aus {file.get('name', '')}",
"importance": 3
})
return default_plan
except Exception as e:
logger.error(f"Fehler bei der Erstellung des Extraktionsplans: {str(e)}", exc_info=True)
if add_log_func and workflow_id:
add_log_func(
workflow_id,
f"Fehler bei der Erstellung des Extraktionsplans: {str(e)}",
"error"
)
# Leerer Plan bei Fehlern
return []
async def _execute_extractions(
extraction_plan: List[Dict[str, Any]],
files: List[Dict[str, Any]],
lucydom_interface,
ai_service,
workflow_id: str = None,
add_log_func = None
) -> List[Dict[str, Any]]:
"""
Führt die geplanten Extraktionen durch.
Args:
extraction_plan: Liste von Extraktionsanweisungen
files: Liste aller verfügbaren Dateien
lucydom_interface: Interface für Datenbankzugriffe
ai_service: Service für KI-Anfragen
workflow_id: Optionale ID des Workflows für Logging
add_log_func: Optionale Funktion für das Hinzufügen von Logs
Returns:
Liste mit extrahierten Daten pro Datei
"""
extracted_data = []
# Nach Wichtigkeit sortieren
sorted_plan = sorted(extraction_plan, key=lambda x: x.get("importance", 0), reverse=True)
for extraction_item in sorted_plan:
file_id = extraction_item.get("file_id")
extract_needed = extraction_item.get("extract_needed", False)
extraction_prompt = extraction_item.get("extraction_prompt", "")
# Dateimetadaten finden
file_metadata = next((f for f in files if f.get("id") == file_id), None)
if not file_metadata:
logger.warning(f"Datei mit ID {file_id} nicht gefunden")
continue
file_name = file_metadata.get("name", "")
file_type = file_metadata.get("type", "")
content_type = file_metadata.get("content_type", "")
# Log hinzufügen
if add_log_func and workflow_id:
add_log_func(
workflow_id,
f"Verarbeite Datei: {file_name} (Extraktion notwendig: {extract_needed})",
"info"
)
# Extraktion nur durchführen, wenn notwendig
if extract_needed:
# Dateiinhalt über LucyDOM-Interface abrufen
if lucydom_interface:
try:
file_content = await lucydom_interface.read_file_content(file_id)
if not file_content:
if add_log_func and workflow_id:
add_log_func(workflow_id, f"Datei {file_name} nicht gefunden", "warning")
continue
# Extraktion basierend auf Dateityp durchführen
if file_type == "image" or file_name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')):
# Bildanalyse mit AI-Service
if ai_service and hasattr(ai_service, "analyze_image"):
try:
image_analysis = await ai_service.analyze_image(
image_data=file_content,
prompt=extraction_prompt,
mime_type=content_type
)
extracted_data.append({
"file_id": file_id,
"name": file_name,
"type": file_type,
"content": image_analysis,
"is_extracted": True,
"extraction_method": "image_analysis"
})
if add_log_func and workflow_id:
add_log_func(workflow_id, f"Bild {file_name} erfolgreich analysiert", "info")
except Exception as e:
logger.error(f"Fehler bei der Bildanalyse {file_name}: {str(e)}")
if add_log_func and workflow_id:
add_log_func(workflow_id, f"Fehler bei der Bildanalyse {file_name}: {str(e)}", "error")
else:
# Fallback, wenn keine Bildanalyse verfügbar
extracted_data.append({
"file_id": file_id,
"name": file_name,
"type": file_type,
"content": f"Bild: {file_name} (Analyse nicht verfügbar)",
"is_extracted": False,
"extraction_method": "none"
})
else:
# Text-basierte Extraktion für alle anderen Dateitypen
try:
content, is_extracted = extract_text_from_file_content(
file_content, file_name, content_type
)
extracted_data.append({
"file_id": file_id,
"name": file_name,
"type": file_type,
"content": content,
"is_extracted": is_extracted,
"extraction_method": "text_extraction"
})
if add_log_func and workflow_id:
add_log_func(
workflow_id,
f"Datei {file_name} extrahiert (Status: {is_extracted})",
"info"
)
except Exception as e:
logger.error(f"Fehler bei der Textextraktion {file_name}: {str(e)}")
if add_log_func and workflow_id:
add_log_func(workflow_id, f"Fehler bei der Textextraktion {file_name}: {str(e)}", "error")
except Exception as e:
logger.error(f"Fehler beim Lesen der Datei {file_name}: {str(e)}")
if add_log_func and workflow_id:
add_log_func(workflow_id, f"Fehler beim Lesen der Datei {file_name}: {str(e)}", "error")
else:
logger.warning(f"Kein LucyDOM-Interface verfügbar für Datei {file_name}")
if add_log_func and workflow_id:
add_log_func(workflow_id, f"Kein LucyDOM-Interface verfügbar für Datei {file_name}", "warning")
else:
# Keine Extraktion notwendig, vorhandene Inhalte verwenden
doc_contents = _extract_document_contents_from_messages(file_id, messages)
if doc_contents:
# Ersten Textinhalt verwenden
for content in doc_contents:
if content.get("type") == "text":
extracted_data.append({
"file_id": file_id,
"name": file_name,
"type": file_type,
"content": content.get("text", ""),
"is_extracted": content.get("is_extracted", False),
"extraction_method": "existing_content"
})
break
else:
# Keine vorhandenen Inhalte gefunden
extracted_data.append({
"file_id": file_id,
"name": file_name,
"type": file_type,
"content": f"Keine Inhalte verfügbar für {file_name}",
"is_extracted": False,
"extraction_method": "none"
})
return extracted_data
def _structure_extracted_data(
extracted_data: List[Dict[str, Any]],
files: List[Dict[str, Any]],
prompt: str
) -> Dict[str, Any]:
"""
Strukturiert die extrahierten Daten in ein formatiertes Ergebnis.
Args:
extracted_data: Liste der extrahierten Daten pro Datei
files: Liste aller verfügbaren Dateien
prompt: Ursprünglicher Extraktionsprompt
Returns:
Strukturiertes Ergebnisobjekt
"""
# Basis-Struktur erstellen
result = {
"prompt": prompt,
"files_processed": len(extracted_data),
"total_files": len(files),
"extraction_timestamp": datetime.now().isoformat(),
"status": "success",
"extracted_content": []
}
# Extrahierte Inhalte hinzufügen
for data_item in extracted_data:
# Datei Metadaten anreichern
file_id = data_item.get("file_id", "")
file_metadata = next((f for f in files if f.get("id") == file_id), {})
content_item = {
"file_id": file_id,
"name": data_item.get("name", file_metadata.get("name", "")),
"type": data_item.get("type", file_metadata.get("type", "")),
"content_type": file_metadata.get("content_type", ""),
"size": file_metadata.get("size", ""),
"is_extracted": data_item.get("is_extracted", False),
"extraction_method": data_item.get("extraction_method", ""),
"content": data_item.get("content", "")
}
result["extracted_content"].append(content_item)
return result
def _extract_document_contents_from_messages(file_id: str, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Extrahiert Document-Contents für eine bestimmte Datei aus den Workflow-Nachrichten.
Args:
file_id: ID der Datei
messages: Liste aller Nachrichten im Workflow
Returns:
Liste der Document-Contents für die angegebene Datei
"""
contents = []
for message in messages:
# Dokumente in der Nachricht durchsuchen
for document in message.get("documents", []):
source = document.get("source", {})
# Prüfen, ob die Datei-ID übereinstimmt
if source.get("id") == file_id or source.get("type") == "file" and source.get("id") == file_id:
# Contents der Datei hinzufügen
doc_contents = document.get("contents", [])
if doc_contents:
contents.extend(doc_contents)
return contents
def _log(add_log_func, workflow_id, message, log_type, agent_id=None, agent_name=None):
"""Hilfsfunktion zum Loggen mit unterschiedlichen Log-Funktionen"""
# Log über die Logger-Instanz
if log_type == "error":
logger.error(message)
elif log_type == "warning":
logger.warning(message)
else:
logger.info(message)
# Log über die bereitgestellte Log-Funktion (falls vorhanden)
if add_log_func and workflow_id:
add_log_func(workflow_id, message, log_type, agent_id, agent_name)