From a958defd42a2f5917d46101117d2bde7ed1bd101 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Mon, 29 Dec 2025 23:54:27 +0100
Subject: [PATCH] fixed workflow run with chatlog
---
.../services/serviceAi/subStructureFilling.py | 102 ++++++++++++------
modules/shared/progressLogger.py | 4 +
.../methods/methodAi/actions/process.py | 5 +
modules/workflows/methods/methodBase.py | 12 ++-
4 files changed, 87 insertions(+), 36 deletions(-)
diff --git a/modules/services/serviceAi/subStructureFilling.py b/modules/services/serviceAi/subStructureFilling.py
index 7089103c..6e3377a1 100644
--- a/modules/services/serviceAi/subStructureFilling.py
+++ b/modules/services/serviceAi/subStructureFilling.py
@@ -437,19 +437,26 @@ class StructureFiller:
if isinstance(jsonContent, dict) and jsonContent.get("type") == "image":
elements.append(jsonContent)
logger.debug("AI returned proper JSON image structure")
- continue
+ # Skip remaining image processing, but continue with progress updates
+ base64Data = None # Signal that image was already processed
elif isinstance(jsonContent, list) and len(jsonContent) > 0:
# Check if first element is an image
if isinstance(jsonContent[0], dict) and jsonContent[0].get("type") == "image":
elements.extend(jsonContent)
logger.debug("AI returned proper JSON image structure in list")
- continue
+ # Skip remaining image processing, but continue with progress updates
+ base64Data = None # Signal that image was already processed
+ else:
+ base64Data = "" # Continue with normal processing
except (json.JSONDecodeError, ValueError, AttributeError):
# Not JSON, treat as base64 string or data URI
- pass
+ base64Data = "" # Will be processed below
- # Already base64 string or data URI
- if aiResponse.content.startswith("data:image/"):
+ # Process base64 if not already handled above
+ if base64Data is None:
+ # Already processed as JSON, skip base64 processing
+ pass
+ elif aiResponse.content.startswith("data:image/"):
# Extract base64 from data URI
base64Data = aiResponse.content.split(",", 1)[1]
else:
@@ -463,8 +470,11 @@ class StructureFiller:
else:
base64Data = ""
- # Always create proper JSON structure for images
- if base64Data:
+ # Always create proper JSON structure for images (if not already processed)
+ if base64Data is None:
+ # Image already processed as JSON, skip
+ pass
+ elif base64Data:
elements.append({
"type": "image",
"content": {
@@ -527,20 +537,20 @@ class StructureFiller:
except Exception as e:
# Fehlerhafte Section mit Fehlermeldung rendern (kein Abbruch!)
self.services.chat.progressLogFinish(sectionOperationId, False)
- elements.append({
- "type": "error",
- "message": f"Error generating section {sectionId}: {str(e)}",
- "sectionId": sectionId
- })
- logger.error(f"Error generating section {sectionId}: {str(e)}")
- # Still update chapter progress even on error
- chapterProgress = (sectionIndex + 1) / totalSections if totalSections > 0 else 1.0
- self.services.chat.progressLogUpdate(
- chapterOperationId,
- chapterProgress,
- f"Section {sectionIndex + 1}/{totalSections} completed (with errors)"
- )
- # NICHT raise - Section wird mit Fehlermeldung gerendert
+ elements.append({
+ "type": "error",
+ "message": f"Error generating section {sectionId}: {str(e)}",
+ "sectionId": sectionId
+ })
+ logger.error(f"Error generating section {sectionId}: {str(e)}")
+ # Still update chapter progress even on error
+ chapterProgress = (sectionIndex + 1) / totalSections if totalSections > 0 else 1.0
+ self.services.chat.progressLogUpdate(
+ chapterOperationId,
+ chapterProgress,
+ f"Section {sectionIndex + 1}/{totalSections} completed (with errors)"
+ )
+ # NICHT raise - Section wird mit Fehlermeldung gerendert
else:
# Einzelverarbeitung: Jeder Part einzeln ODER Generation ohne ContentParts
@@ -634,17 +644,25 @@ class StructureFiller:
if isinstance(jsonContent, dict) and jsonContent.get("type") == "image":
elements.append(jsonContent)
logger.debug("AI returned proper JSON image structure")
- continue
+ # Skip remaining image processing, but continue with progress updates
+ base64Data = None # Signal that image was already processed
elif isinstance(jsonContent, list) and len(jsonContent) > 0:
if isinstance(jsonContent[0], dict) and jsonContent[0].get("type") == "image":
elements.extend(jsonContent)
logger.debug("AI returned proper JSON image structure in list")
- continue
+ # Skip remaining image processing, but continue with progress updates
+ base64Data = None # Signal that image was already processed
+ else:
+ base64Data = "" # Continue with normal processing
except (json.JSONDecodeError, ValueError, AttributeError):
- pass
+ base64Data = "" # Will be processed below
- # Already base64 string or data URI
- if aiResponse.content.startswith("data:image/"):
+ # Process base64 if not already handled above
+ if base64Data is None:
+ # Already processed as JSON, skip base64 processing
+ pass
+ elif aiResponse.content.startswith("data:image/"):
+ # Extract base64 from data URI
base64Data = aiResponse.content.split(",", 1)[1]
else:
content_stripped = aiResponse.content.strip()
@@ -655,8 +673,11 @@ class StructureFiller:
else:
base64Data = ""
- # Always create proper JSON structure for images
- if base64Data:
+ # Always create proper JSON structure for images (if not already processed)
+ if base64Data is None:
+ # Image already processed as JSON, skip
+ pass
+ elif base64Data:
elements.append({
"type": "image",
"content": {
@@ -853,17 +874,25 @@ class StructureFiller:
if isinstance(jsonContent, dict) and jsonContent.get("type") == "image":
elements.append(jsonContent)
logger.debug("AI returned proper JSON image structure")
- continue
+ # Skip remaining image processing, but continue with progress updates
+ base64Data = None # Signal that image was already processed
elif isinstance(jsonContent, list) and len(jsonContent) > 0:
if isinstance(jsonContent[0], dict) and jsonContent[0].get("type") == "image":
elements.extend(jsonContent)
logger.debug("AI returned proper JSON image structure in list")
- continue
+ # Skip remaining image processing, but continue with progress updates
+ base64Data = None # Signal that image was already processed
+ else:
+ base64Data = "" # Continue with normal processing
except (json.JSONDecodeError, ValueError, AttributeError):
- pass
+ base64Data = "" # Will be processed below
- # Already base64 string or data URI
- if aiResponse.content.startswith("data:image/"):
+ # Process base64 if not already handled above
+ if base64Data is None:
+ # Already processed as JSON, skip base64 processing
+ pass
+ elif aiResponse.content.startswith("data:image/"):
+ # Extract base64 from data URI
base64Data = aiResponse.content.split(",", 1)[1]
else:
content_stripped = aiResponse.content.strip()
@@ -874,8 +903,11 @@ class StructureFiller:
else:
base64Data = ""
- # Always create proper JSON structure for images
- if base64Data:
+ # Always create proper JSON structure for images (if not already processed)
+ if base64Data is None:
+ # Image already processed as JSON, skip
+ pass
+ elif base64Data:
elements.append({
"type": "image",
"content": {
diff --git a/modules/shared/progressLogger.py b/modules/shared/progressLogger.py
index f7dcecac..8c6e56f8 100644
--- a/modules/shared/progressLogger.py
+++ b/modules/shared/progressLogger.py
@@ -139,6 +139,10 @@ class ProgressLogger:
logger.warning(f"Cannot log progress: no workflow available")
return None
+ # Validate parentOperationId exists in activeOperations (for debugging)
+ if parentOperationId and parentOperationId not in self.activeOperations:
+ logger.debug(f"WARNING: Parent operation '{parentOperationId}' not found in activeOperations when creating log for '{operationId}'. Available operations: {list(self.activeOperations.keys())}. Child operation may appear at root level.")
+
# parentId in ChatLog should be the operationId of the parent operation, not the log entry ID
logData = {
"workflowId": workflow.id,
diff --git a/modules/workflows/methods/methodAi/actions/process.py b/modules/workflows/methods/methodAi/actions/process.py
index 5abc57cd..807c1a64 100644
--- a/modules/workflows/methods/methodAi/actions/process.py
+++ b/modules/workflows/methods/methodAi/actions/process.py
@@ -38,6 +38,11 @@ async def process(self, parameters: Dict[str, Any]) -> ActionResult:
# Start progress tracking
parentOperationId = parameters.get('parentOperationId')
+ if not parentOperationId:
+ logger.warning(f"ai.process: No parentOperationId provided in parameters. Operation '{operationId}' will appear at root level. Available parameters: {list(parameters.keys())}")
+ else:
+ logger.debug(f"ai.process: Using parentOperationId '{parentOperationId}' for operation '{operationId}'")
+
self.services.chat.progressLogStart(
operationId,
"Generate",
diff --git a/modules/workflows/methods/methodBase.py b/modules/workflows/methods/methodBase.py
index a20f5ec1..7934ea19 100644
--- a/modules/workflows/methods/methodBase.py
+++ b/modules/workflows/methods/methodBase.py
@@ -188,9 +188,19 @@ class MethodBase:
return wrapper
def _validateParameters(self, parameters: Dict[str, Any], paramDefs: Dict[str, WorkflowActionParameter]) -> Dict[str, Any]:
- """Validate parameters against definitions"""
+ """Validate parameters against definitions
+
+ IMPORTANT: System parameters (like parentOperationId, expectedDocumentFormats) are preserved
+ even if they're not in the parameter definitions, as they're used internally by the framework.
+ """
validated = {}
+ # System parameters that should always be preserved, even if not in paramDefs
+ systemParams = ['parentOperationId', 'expectedDocumentFormats']
+ for sysParam in systemParams:
+ if sysParam in parameters:
+ validated[sysParam] = parameters[sysParam]
+
for paramName, paramDef in paramDefs.items():
value = parameters.get(paramName)