| from copy import deepcopy |
|
|
| from app.models.workflow import ( |
| OptimizationResponse, |
| OptimizationSuggestion, |
| WorkflowDocument, |
| ) |
|
|
|
|
| class WorkflowOptimizer: |
| def optimize(self, workflow: WorkflowDocument) -> OptimizationResponse: |
| optimized = deepcopy(workflow) |
| suggestions: list[OptimizationSuggestion] = [] |
|
|
| http_nodes = [ |
| node for node in optimized.nodes if node.data.type == "n8n-nodes-base.httpRequest" |
| ] |
| for node in http_nodes: |
| options = node.data.parameters.setdefault("options", {}) |
| if isinstance(options, dict) and "retry" not in options: |
| options.update({"retry": {"maxTries": 3, "waitBetweenTries": 1000}}) |
| suggestions.append( |
| OptimizationSuggestion( |
| title="Add API retries", |
| description=f"Added bounded retry behavior to {node.data.label}.", |
| impact="high", |
| nodeIds=[node.id], |
| ) |
| ) |
|
|
| if not optimized.settings.get("saveExecutionProgress"): |
| optimized.settings["saveExecutionProgress"] = True |
| suggestions.append( |
| OptimizationSuggestion( |
| title="Enable execution recovery", |
| description="Save execution progress so long-running workflows can recover.", |
| impact="medium", |
| ) |
| ) |
|
|
| linear_pairs = [] |
| for edge in optimized.edges: |
| source = next((node for node in optimized.nodes if node.id == edge.source), None) |
| target = next((node for node in optimized.nodes if node.id == edge.target), None) |
| if source and target and source.data.type == target.data.type == "n8n-nodes-base.set": |
| linear_pairs.append((source, target)) |
| if linear_pairs: |
| suggestions.append( |
| OptimizationSuggestion( |
| title="Combine adjacent Edit Fields nodes", |
| description="Adjacent field transformations can be handled by one node.", |
| impact="medium", |
| nodeIds=[node.id for pair in linear_pairs for node in pair], |
| ) |
| ) |
|
|
| branches = {} |
| for edge in optimized.edges: |
| branches[edge.source] = branches.get(edge.source, 0) + 1 |
| parallel = [node_id for node_id, count in branches.items() if count > 1] |
| if parallel: |
| suggestions.append( |
| OptimizationSuggestion( |
| title="Review parallel branches", |
| description="Independent branches can execute concurrently; merge only when required.", |
| impact="medium", |
| nodeIds=parallel, |
| ) |
| ) |
|
|
| if not suggestions: |
| suggestions.append( |
| OptimizationSuggestion( |
| title="Workflow is already compact", |
| description="No deterministic optimization could be applied safely.", |
| impact="low", |
| ) |
| ) |
|
|
| return OptimizationResponse(workflow=optimized, suggestions=suggestions) |
|
|
|
|
| optimizer = WorkflowOptimizer() |
|
|