266 lines
13 KiB
Python
266 lines
13 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
|
|
import logging
|
|
from modules.workflows.methods.methodBase import MethodBase
|
|
from modules.datamodels.datamodelWorkflowActions import WorkflowActionDefinition, WorkflowActionParameter
|
|
from modules.shared.frontendTypes import FrontendType
|
|
|
|
# Import helpers
|
|
from .helpers.documentIndex import DocumentIndexHelper
|
|
from .helpers.formatting import FormattingHelper
|
|
|
|
# Import actions
|
|
from .actions.getDocumentIndex import getDocumentIndex
|
|
from .actions.extractContent import extractContent
|
|
from .actions.neutralizeData import neutralizeData
|
|
from .actions.triggerPreprocessingServer import triggerPreprocessingServer
|
|
from .actions.setContext import setContext
|
|
from .actions.mergeContext import mergeContext
|
|
from .actions.filterContext import filterContext
|
|
from .actions.transformContext import transformContext
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MethodContext(MethodBase):
|
|
"""Context and workflow information methods."""
|
|
|
|
def __init__(self, services):
|
|
super().__init__(services)
|
|
self.name = "context"
|
|
self.description = "Context and workflow information methods"
|
|
|
|
# Initialize helper modules
|
|
self.documentIndex = DocumentIndexHelper(self)
|
|
self.formatting = FormattingHelper(self)
|
|
|
|
# RBAC-Integration: Action-Definitionen mit actionId
|
|
self._actions = {
|
|
"getDocumentIndex": WorkflowActionDefinition(
|
|
actionId="context.getDocumentIndex",
|
|
description="Generate a comprehensive index of all documents available in the current workflow",
|
|
dynamicMode=True,
|
|
outputType="DocumentList",
|
|
parameters={
|
|
"resultType": WorkflowActionParameter(
|
|
name="resultType",
|
|
type="str",
|
|
frontendType=FrontendType.SELECT,
|
|
frontendOptions=["json", "txt", "md"],
|
|
required=False,
|
|
default="json",
|
|
description="Output format"
|
|
)
|
|
},
|
|
execute=getDocumentIndex.__get__(self, self.__class__)
|
|
),
|
|
"extractContent": WorkflowActionDefinition(
|
|
actionId="context.extractContent",
|
|
description=(
|
|
"Extract document content without AI. Unified handover: (1) `documents[0]` "
|
|
"JSON `context.extractContent.handover.v1` with text in `parts` and image placeholders "
|
|
"linking to sibling blobs via `handoverMediaDocumentName`; "
|
|
"(2) each extracted image as a separate binary document (`extract_media_*`); "
|
|
"(3) `data.response` / top-level `response` after normalization — concatenated plain text "
|
|
"for prompts and file.create. Pick `response`, a specific document, or deep JSON paths."
|
|
),
|
|
dynamicMode=True,
|
|
outputType="UdmDocument",
|
|
parameters={
|
|
"documentList": WorkflowActionParameter(
|
|
name="documentList",
|
|
type="DocumentList",
|
|
frontendType=FrontendType.DOCUMENT_REFERENCE,
|
|
required=True,
|
|
description="Document reference(s) to extract content from",
|
|
),
|
|
},
|
|
execute=extractContent.__get__(self, self.__class__)
|
|
),
|
|
"neutralizeData": WorkflowActionDefinition(
|
|
actionId="context.neutralizeData",
|
|
description="Neutralize extracted data from ContentExtracted documents (for use after extractContent)",
|
|
outputType="DocumentList",
|
|
parameters={
|
|
"documentList": WorkflowActionParameter(
|
|
name="documentList",
|
|
type="DocumentList",
|
|
frontendType=FrontendType.DOCUMENT_REFERENCE,
|
|
required=True,
|
|
description="Document reference(s) containing ContentExtracted objects to neutralize"
|
|
)
|
|
},
|
|
execute=neutralizeData.__get__(self, self.__class__)
|
|
),
|
|
"triggerPreprocessingServer": WorkflowActionDefinition(
|
|
actionId="context.triggerPreprocessingServer",
|
|
description="Trigger preprocessing server at customer tenant to update database with configuration",
|
|
outputType="ActionResult",
|
|
parameters={
|
|
"endpoint": WorkflowActionParameter(
|
|
name="endpoint",
|
|
type="str",
|
|
frontendType=FrontendType.TEXT,
|
|
required=True,
|
|
description="The full URL endpoint for the preprocessing server API"
|
|
),
|
|
"configJson": WorkflowActionParameter(
|
|
name="configJson",
|
|
type="str",
|
|
frontendType=FrontendType.JSON,
|
|
required=True,
|
|
description="Configuration JSON object to send to the preprocessing server. Can be provided as a dict or as a JSON string"
|
|
),
|
|
"authSecretConfigKey": WorkflowActionParameter(
|
|
name="authSecretConfigKey",
|
|
type="str",
|
|
frontendType=FrontendType.TEXT,
|
|
required=True,
|
|
description="The APP_CONFIG key name to retrieve the authorization secret from"
|
|
)
|
|
},
|
|
execute=triggerPreprocessingServer.__get__(self, self.__class__)
|
|
),
|
|
"setContext": WorkflowActionDefinition(
|
|
actionId="context.setContext",
|
|
description=(
|
|
"Set workflow context: list of assignments with target key, then upstream picker, "
|
|
"fixed literal, or human task per row."
|
|
),
|
|
outputType="Transit",
|
|
parameters={
|
|
"scope": WorkflowActionParameter(
|
|
name="scope", type="str", required=False,
|
|
frontendType=FrontendType.SELECT,
|
|
frontendOptions=["local", "global", "session"],
|
|
default="local",
|
|
description="Storage scope for keys written by this node",
|
|
),
|
|
"assignments": WorkflowActionParameter(
|
|
name="assignments", type="list", required=True,
|
|
frontendType=FrontendType.CONTEXT_ASSIGNMENTS,
|
|
default=[],
|
|
description=(
|
|
"List of rows: contextKey, valueSource (pickUpstream | literal | humanTask), "
|
|
"upstreamRef, literal, sourcePath, mode, valueType, task fields."
|
|
),
|
|
),
|
|
},
|
|
execute=setContext.__get__(self, self.__class__),
|
|
),
|
|
"mergeContext": WorkflowActionDefinition(
|
|
actionId="context.mergeContext",
|
|
description=(
|
|
"Merge data arriving from multiple parallel branches into a single "
|
|
"MergeResult. Strategies: shallow, deep, firstWins, lastWins, "
|
|
"errorOnConflict. The execution engine waits for all connected "
|
|
"predecessors before invoking this action (waitsForAllPredecessors=True)."
|
|
),
|
|
outputType="MergeResult",
|
|
parameters={
|
|
"strategy": WorkflowActionParameter(
|
|
name="strategy", type="str", required=False,
|
|
frontendType=FrontendType.SELECT,
|
|
frontendOptions=["shallow", "deep", "firstWins", "lastWins", "errorOnConflict"],
|
|
default="deep",
|
|
description="Conflict resolution strategy for keys present in several branches",
|
|
),
|
|
"waitFor": WorkflowActionParameter(
|
|
name="waitFor", type="int", required=False,
|
|
frontendType=FrontendType.NUMBER,
|
|
default=0,
|
|
description="Number of branches to consume (0 = all). Used together with timeoutMs.",
|
|
),
|
|
"timeoutMs": WorkflowActionParameter(
|
|
name="timeoutMs", type="int", required=False,
|
|
frontendType=FrontendType.NUMBER,
|
|
default=30000,
|
|
description="Maximum wait time in milliseconds before continuing with available inputs",
|
|
),
|
|
},
|
|
execute=mergeContext.__get__(self, self.__class__),
|
|
),
|
|
"filterContext": WorkflowActionDefinition(
|
|
actionId="context.filterContext",
|
|
description=(
|
|
"Allow- or block-list keys/paths from the upstream payload. "
|
|
"Supports glob patterns (user.*, *.id) and dotted paths (address.city). "
|
|
"Missing-key behaviour is configurable (skip, nullFill, error)."
|
|
),
|
|
outputType="Transit",
|
|
parameters={
|
|
"mode": WorkflowActionParameter(
|
|
name="mode", type="str", required=False,
|
|
frontendType=FrontendType.SELECT,
|
|
frontendOptions=["allow", "block"],
|
|
default="allow",
|
|
description="allow = only these keys pass; block = these keys are removed",
|
|
),
|
|
"keys": WorkflowActionParameter(
|
|
name="keys", type="list", required=True,
|
|
frontendType=FrontendType.JSON,
|
|
default=[],
|
|
description="Key paths or glob patterns",
|
|
),
|
|
"missingKeyBehavior": WorkflowActionParameter(
|
|
name="missingKeyBehavior", type="str", required=False,
|
|
frontendType=FrontendType.SELECT,
|
|
frontendOptions=["skip", "nullFill", "error"],
|
|
default="skip",
|
|
description="What to do when an allowed key is missing in the input",
|
|
),
|
|
"preserveMeta": WorkflowActionParameter(
|
|
name="preserveMeta", type="bool", required=False,
|
|
frontendType=FrontendType.CHECKBOX,
|
|
default=True,
|
|
description="Always pass through internal meta fields (_success, _error, _transit)",
|
|
),
|
|
},
|
|
execute=filterContext.__get__(self, self.__class__),
|
|
),
|
|
"transformContext": WorkflowActionDefinition(
|
|
actionId="context.transformContext",
|
|
description=(
|
|
"Transform the upstream payload via a list of {sourceField, outputField, "
|
|
"operation, type, expression} mappings. Operations: rename, cast, nest, "
|
|
"flatten, compute. compute uses {{...}} templates; nesting is implicit "
|
|
"via dotted outputField paths."
|
|
),
|
|
outputType="Transit",
|
|
parameters={
|
|
"mappings": WorkflowActionParameter(
|
|
name="mappings", type="list", required=True,
|
|
frontendType=FrontendType.MAPPING_TABLE,
|
|
default=[],
|
|
description="List of mapping entries",
|
|
),
|
|
"passthroughUnmapped": WorkflowActionParameter(
|
|
name="passthroughUnmapped", type="bool", required=False,
|
|
frontendType=FrontendType.CHECKBOX,
|
|
default=False,
|
|
description="Forward fields of the upstream payload that no mapping consumed",
|
|
),
|
|
"flattenDepth": WorkflowActionParameter(
|
|
name="flattenDepth", type="int", required=False,
|
|
frontendType=FrontendType.NUMBER,
|
|
default=1,
|
|
description="Depth for flatten operation (1 = one level, -1 = full)",
|
|
),
|
|
},
|
|
execute=transformContext.__get__(self, self.__class__),
|
|
),
|
|
}
|
|
|
|
# Validate actions after definition
|
|
self._validateActions()
|
|
|
|
# Register actions as methods (optional, für direkten Zugriff)
|
|
self.getDocumentIndex = getDocumentIndex.__get__(self, self.__class__)
|
|
self.extractContent = extractContent.__get__(self, self.__class__)
|
|
self.neutralizeData = neutralizeData.__get__(self, self.__class__)
|
|
self.triggerPreprocessingServer = triggerPreprocessingServer.__get__(self, self.__class__)
|
|
self.setContext = setContext.__get__(self, self.__class__)
|
|
self.mergeContext = mergeContext.__get__(self, self.__class__)
|
|
self.filterContext = filterContext.__get__(self, self.__class__)
|
|
self.transformContext = transformContext.__get__(self, self.__class__)
|
|
|