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)