|
|
@@ -3,6 +3,7 @@ import os
|
|
|
import time
|
|
|
from typing import Any
|
|
|
|
|
|
+from app.config import settings
|
|
|
from app.service.engine.execution_context import ExecutionContext
|
|
|
from app.service.engine.parameter_resolver import ParameterResolver
|
|
|
from app.service.engine.workflow_result import WorkflowResult
|
|
|
@@ -32,6 +33,7 @@ class WorkflowEngine:
|
|
|
logger.info("========== Workflow start ========== version=%s, total_steps=%d",
|
|
|
workflow.version, total_steps)
|
|
|
wf_t0 = time.time()
|
|
|
+ workspace: str | None = None
|
|
|
|
|
|
ctx = ExecutionContext(inputs)
|
|
|
|
|
|
@@ -42,70 +44,75 @@ class WorkflowEngine:
|
|
|
logger.error("Failed to create workspace: %s", e)
|
|
|
return WorkflowResult.failure(f"failed: {e}")
|
|
|
|
|
|
- for idx, step_id in enumerate(workflow.order):
|
|
|
- step = workflow.steps.get(step_id)
|
|
|
- if step is None:
|
|
|
- logger.warning("Step '%s' not found in steps definition, skipping", step_id)
|
|
|
- continue
|
|
|
-
|
|
|
- step_no = idx + 1
|
|
|
- logger.info("------ Step %d/%d [%s] type=%s start ------",
|
|
|
- step_no, total_steps, step.id, step.type)
|
|
|
- logger.info("[%s] raw_inputs=%s", step.id, step.inputs)
|
|
|
-
|
|
|
- step_t0 = time.time()
|
|
|
- try:
|
|
|
- step_dir = os.path.join(workspace, step.id)
|
|
|
- os.makedirs(step_dir, exist_ok=True)
|
|
|
- result = self._execute_step_with_retry(task_id, workspace, step, ctx)
|
|
|
- step_elapsed = time.time() - step_t0
|
|
|
-
|
|
|
- if result.is_failure():
|
|
|
- recovered_result = self._recover_base64_by_rerunning_previous_step(
|
|
|
- task_id=task_id,
|
|
|
- workspace=workspace,
|
|
|
- workflow=workflow,
|
|
|
- current_step_index=idx,
|
|
|
- current_step=step,
|
|
|
- ctx=ctx,
|
|
|
- failed_result=result,
|
|
|
- )
|
|
|
- if recovered_result is not None:
|
|
|
- result = recovered_result
|
|
|
- step_elapsed = time.time() - step_t0
|
|
|
-
|
|
|
- if result.is_failure():
|
|
|
- logger.error("[%s] FAILED in %.2fs after retries. Error: %s",
|
|
|
- step_id, step_elapsed, result.error_message)
|
|
|
- return WorkflowResult.failure(
|
|
|
- f"Step {step_id} failed: {result.error_message}"
|
|
|
- )
|
|
|
- except Exception as e:
|
|
|
- step_elapsed = time.time() - step_t0
|
|
|
- logger.error("[%s] EXCEPTION in %.2fs: %s", step_id, step_elapsed, e, exc_info=True)
|
|
|
- return WorkflowResult.failure(f"Step {step_id} failed: {e}")
|
|
|
-
|
|
|
- logger.info("[%s] SUCCESS in %.2fs outputs=%s",
|
|
|
- step.id, step_elapsed, _shorten(result.outputs))
|
|
|
- ctx.put_step_result(step_id, result.outputs)
|
|
|
-
|
|
|
- logger.info("All steps completed, resolving final outputs ...")
|
|
|
- outputs: dict[str, Any] = {}
|
|
|
- if workflow.outputs:
|
|
|
- for key, value in workflow.outputs.items():
|
|
|
- outputs[key] = self._resolver.resolve(value, ctx)
|
|
|
- logger.info("Final output_keys=%s", list(outputs.keys()))
|
|
|
-
|
|
|
- cleanup_workspace(workspace, task_id)
|
|
|
- total_elapsed = time.time() - wf_t0
|
|
|
- logger.info("========== Workflow end ========== total_elapsed=%.2fs, success=True",
|
|
|
- total_elapsed)
|
|
|
- return WorkflowResult.ok(outputs)
|
|
|
+ try:
|
|
|
+ for idx, step_id in enumerate(workflow.order):
|
|
|
+ step = workflow.steps.get(step_id)
|
|
|
+ if step is None:
|
|
|
+ logger.warning("Step '%s' not found in steps definition, skipping", step_id)
|
|
|
+ continue
|
|
|
+
|
|
|
+ step_no = idx + 1
|
|
|
+ logger.info("------ Step %d/%d [%s] type=%s start ------",
|
|
|
+ step_no, total_steps, step.id, step.type)
|
|
|
+ logger.info("[%s] raw_inputs=%s", step.id, step.inputs)
|
|
|
+
|
|
|
+ step_t0 = time.time()
|
|
|
+ try:
|
|
|
+ step_dir = os.path.join(workspace, step.id)
|
|
|
+ os.makedirs(step_dir, exist_ok=True)
|
|
|
+ result = self._execute_step_with_retry(task_id, workspace, step, ctx)
|
|
|
+ step_elapsed = time.time() - step_t0
|
|
|
+
|
|
|
+ if result.is_failure():
|
|
|
+ recovered_result = self._recover_base64_by_rerunning_previous_step(
|
|
|
+ task_id=task_id,
|
|
|
+ workspace=workspace,
|
|
|
+ workflow=workflow,
|
|
|
+ current_step_index=idx,
|
|
|
+ current_step=step,
|
|
|
+ ctx=ctx,
|
|
|
+ failed_result=result,
|
|
|
+ )
|
|
|
+ if recovered_result is not None:
|
|
|
+ result = recovered_result
|
|
|
+ step_elapsed = time.time() - step_t0
|
|
|
+
|
|
|
+ if result.is_failure():
|
|
|
+ logger.error("[%s] FAILED in %.2fs after retries. Error: %s",
|
|
|
+ step_id, step_elapsed, result.error_message)
|
|
|
+ return WorkflowResult.failure(
|
|
|
+ f"Step {step_id} failed: {result.error_message}"
|
|
|
+ )
|
|
|
+ except Exception as e:
|
|
|
+ step_elapsed = time.time() - step_t0
|
|
|
+ logger.error("[%s] EXCEPTION in %.2fs: %s", step_id, step_elapsed, e, exc_info=True)
|
|
|
+ return WorkflowResult.failure(f"Step {step_id} failed: {e}")
|
|
|
+
|
|
|
+ logger.info("[%s] SUCCESS in %.2fs outputs=%s",
|
|
|
+ step.id, step_elapsed, _shorten(result.outputs))
|
|
|
+ ctx.put_step_result(step_id, result.outputs)
|
|
|
+
|
|
|
+ logger.info("All steps completed, resolving final outputs ...")
|
|
|
+ outputs: dict[str, Any] = {}
|
|
|
+ if workflow.outputs:
|
|
|
+ for key, value in workflow.outputs.items():
|
|
|
+ outputs[key] = self._resolver.resolve(value, ctx)
|
|
|
+ logger.info("Final output_keys=%s", list(outputs.keys()))
|
|
|
+
|
|
|
+ total_elapsed = time.time() - wf_t0
|
|
|
+ logger.info("========== Workflow end ========== total_elapsed=%.2fs, success=True",
|
|
|
+ total_elapsed)
|
|
|
+ return WorkflowResult.ok(outputs)
|
|
|
+ finally:
|
|
|
+ if workspace:
|
|
|
+ cleanup_workspace(workspace, task_id)
|
|
|
|
|
|
def _execute_step_with_retry(
|
|
|
self, task_id: str, workspace: str, step: Step, ctx: ExecutionContext
|
|
|
) -> TaskResult:
|
|
|
- max_retries = step.retry_count
|
|
|
+ max_retries = (
|
|
|
+ settings.step_retry_count if step.retry_count is None else step.retry_count
|
|
|
+ )
|
|
|
last_result: TaskResult | None = None
|
|
|
|
|
|
for attempt in range(max_retries + 1):
|