synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synapse-sdk might be problematic. Click here for more details.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +9 -8
- synapse_sdk/cli/agent/__init__.py +25 -0
- synapse_sdk/cli/agent/config.py +104 -0
- synapse_sdk/cli/agent/select.py +197 -0
- synapse_sdk/cli/auth.py +104 -0
- synapse_sdk/cli/main.py +1025 -0
- synapse_sdk/cli/plugin/__init__.py +58 -0
- synapse_sdk/cli/plugin/create.py +566 -0
- synapse_sdk/cli/plugin/job.py +196 -0
- synapse_sdk/cli/plugin/publish.py +322 -0
- synapse_sdk/cli/plugin/run.py +131 -0
- synapse_sdk/cli/plugin/test.py +200 -0
- synapse_sdk/clients/README.md +239 -0
- synapse_sdk/clients/__init__.py +5 -0
- synapse_sdk/clients/_template.py +266 -0
- synapse_sdk/clients/agent/__init__.py +84 -29
- synapse_sdk/clients/agent/async_ray.py +289 -0
- synapse_sdk/clients/agent/container.py +83 -0
- synapse_sdk/clients/agent/plugin.py +101 -0
- synapse_sdk/clients/agent/ray.py +296 -39
- synapse_sdk/clients/backend/__init__.py +152 -12
- synapse_sdk/clients/backend/annotation.py +164 -22
- synapse_sdk/clients/backend/core.py +101 -0
- synapse_sdk/clients/backend/data_collection.py +292 -0
- synapse_sdk/clients/backend/hitl.py +87 -0
- synapse_sdk/clients/backend/integration.py +374 -46
- synapse_sdk/clients/backend/ml.py +134 -22
- synapse_sdk/clients/backend/models.py +247 -0
- synapse_sdk/clients/base.py +538 -59
- synapse_sdk/clients/exceptions.py +35 -7
- synapse_sdk/clients/pipeline/__init__.py +5 -0
- synapse_sdk/clients/pipeline/client.py +636 -0
- synapse_sdk/clients/protocols.py +178 -0
- synapse_sdk/clients/utils.py +86 -8
- synapse_sdk/clients/validation.py +58 -0
- synapse_sdk/enums.py +76 -0
- synapse_sdk/exceptions.py +168 -0
- synapse_sdk/integrations/__init__.py +74 -0
- synapse_sdk/integrations/_base.py +119 -0
- synapse_sdk/integrations/_context.py +53 -0
- synapse_sdk/integrations/ultralytics/__init__.py +78 -0
- synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
- synapse_sdk/integrations/ultralytics/_patches.py +124 -0
- synapse_sdk/loggers.py +476 -95
- synapse_sdk/mcp/MCP.md +69 -0
- synapse_sdk/mcp/__init__.py +48 -0
- synapse_sdk/mcp/__main__.py +6 -0
- synapse_sdk/mcp/config.py +349 -0
- synapse_sdk/mcp/prompts/__init__.py +4 -0
- synapse_sdk/mcp/resources/__init__.py +4 -0
- synapse_sdk/mcp/server.py +1352 -0
- synapse_sdk/mcp/tools/__init__.py +6 -0
- synapse_sdk/plugins/__init__.py +133 -9
- synapse_sdk/plugins/action.py +229 -0
- synapse_sdk/plugins/actions/__init__.py +82 -0
- synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
- synapse_sdk/plugins/actions/dataset/action.py +471 -0
- synapse_sdk/plugins/actions/export/__init__.py +55 -0
- synapse_sdk/plugins/actions/export/action.py +183 -0
- synapse_sdk/plugins/actions/export/context.py +59 -0
- synapse_sdk/plugins/actions/inference/__init__.py +84 -0
- synapse_sdk/plugins/actions/inference/action.py +285 -0
- synapse_sdk/plugins/actions/inference/context.py +81 -0
- synapse_sdk/plugins/actions/inference/deployment.py +322 -0
- synapse_sdk/plugins/actions/inference/serve.py +252 -0
- synapse_sdk/plugins/actions/train/__init__.py +54 -0
- synapse_sdk/plugins/actions/train/action.py +326 -0
- synapse_sdk/plugins/actions/train/context.py +57 -0
- synapse_sdk/plugins/actions/upload/__init__.py +49 -0
- synapse_sdk/plugins/actions/upload/action.py +165 -0
- synapse_sdk/plugins/actions/upload/context.py +61 -0
- synapse_sdk/plugins/config.py +98 -0
- synapse_sdk/plugins/context/__init__.py +109 -0
- synapse_sdk/plugins/context/env.py +113 -0
- synapse_sdk/plugins/datasets/__init__.py +113 -0
- synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
- synapse_sdk/plugins/datasets/converters/base.py +347 -0
- synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
- synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
- synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
- synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
- synapse_sdk/plugins/datasets/formats/dm.py +351 -0
- synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
- synapse_sdk/plugins/decorators.py +83 -0
- synapse_sdk/plugins/discovery.py +790 -0
- synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
- synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
- synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
- synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
- synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
- synapse_sdk/plugins/docs/README.md +513 -0
- synapse_sdk/plugins/docs/STEP.md +656 -0
- synapse_sdk/plugins/enums.py +70 -10
- synapse_sdk/plugins/errors.py +92 -0
- synapse_sdk/plugins/executors/__init__.py +43 -0
- synapse_sdk/plugins/executors/local.py +99 -0
- synapse_sdk/plugins/executors/ray/__init__.py +18 -0
- synapse_sdk/plugins/executors/ray/base.py +282 -0
- synapse_sdk/plugins/executors/ray/job.py +298 -0
- synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
- synapse_sdk/plugins/executors/ray/packaging.py +137 -0
- synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
- synapse_sdk/plugins/executors/ray/task.py +257 -0
- synapse_sdk/plugins/models/__init__.py +26 -0
- synapse_sdk/plugins/models/logger.py +173 -0
- synapse_sdk/plugins/models/pipeline.py +25 -0
- synapse_sdk/plugins/pipelines/__init__.py +81 -0
- synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
- synapse_sdk/plugins/pipelines/context.py +107 -0
- synapse_sdk/plugins/pipelines/display.py +311 -0
- synapse_sdk/plugins/runner.py +114 -0
- synapse_sdk/plugins/schemas/__init__.py +19 -0
- synapse_sdk/plugins/schemas/results.py +152 -0
- synapse_sdk/plugins/steps/__init__.py +63 -0
- synapse_sdk/plugins/steps/base.py +128 -0
- synapse_sdk/plugins/steps/context.py +90 -0
- synapse_sdk/plugins/steps/orchestrator.py +128 -0
- synapse_sdk/plugins/steps/registry.py +103 -0
- synapse_sdk/plugins/steps/utils/__init__.py +20 -0
- synapse_sdk/plugins/steps/utils/logging.py +85 -0
- synapse_sdk/plugins/steps/utils/timing.py +71 -0
- synapse_sdk/plugins/steps/utils/validation.py +68 -0
- synapse_sdk/plugins/templates/__init__.py +50 -0
- synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
- synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
- synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
- synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
- synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
- synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
- synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
- synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
- synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
- synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
- synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
- synapse_sdk/plugins/testing/__init__.py +25 -0
- synapse_sdk/plugins/testing/sample_actions.py +98 -0
- synapse_sdk/plugins/types.py +206 -0
- synapse_sdk/plugins/upload.py +595 -64
- synapse_sdk/plugins/utils.py +325 -37
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/__init__.py +1 -0
- synapse_sdk/utils/auth.py +74 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +449 -0
- synapse_sdk/utils/file/checksum.py +167 -0
- synapse_sdk/utils/file/download.py +286 -0
- synapse_sdk/utils/file/io.py +129 -0
- synapse_sdk/utils/file/requirements.py +36 -0
- synapse_sdk/utils/network.py +168 -0
- synapse_sdk/utils/storage/__init__.py +238 -0
- synapse_sdk/utils/storage/config.py +188 -0
- synapse_sdk/utils/storage/errors.py +52 -0
- synapse_sdk/utils/storage/providers/__init__.py +13 -0
- synapse_sdk/utils/storage/providers/base.py +76 -0
- synapse_sdk/utils/storage/providers/gcs.py +168 -0
- synapse_sdk/utils/storage/providers/http.py +250 -0
- synapse_sdk/utils/storage/providers/local.py +126 -0
- synapse_sdk/utils/storage/providers/s3.py +177 -0
- synapse_sdk/utils/storage/providers/sftp.py +208 -0
- synapse_sdk/utils/storage/registry.py +125 -0
- synapse_sdk/utils/websocket.py +99 -0
- synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
- synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
- synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
- locale/en/LC_MESSAGES/messages.mo +0 -0
- locale/en/LC_MESSAGES/messages.po +0 -39
- locale/ko/LC_MESSAGES/messages.mo +0 -0
- locale/ko/LC_MESSAGES/messages.po +0 -34
- synapse_sdk/cli/create_plugin.py +0 -10
- synapse_sdk/clients/agent/core.py +0 -7
- synapse_sdk/clients/agent/service.py +0 -15
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/clients/ray/__init__.py +0 -6
- synapse_sdk/clients/ray/core.py +0 -22
- synapse_sdk/clients/ray/serve.py +0 -20
- synapse_sdk/i18n.py +0 -35
- synapse_sdk/plugins/categories/__init__.py +0 -0
- synapse_sdk/plugins/categories/base.py +0 -235
- synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
- synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
- synapse_sdk/plugins/categories/decorators.py +0 -13
- synapse_sdk/plugins/categories/export/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/export.py +0 -10
- synapse_sdk/plugins/categories/import/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
- synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
- synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
- synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
- synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
- synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
- synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
- synapse_sdk/plugins/categories/registry.py +0 -16
- synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
- synapse_sdk/plugins/categories/templates.py +0 -32
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/cli/run.py +0 -67
- synapse_sdk/plugins/exceptions.py +0 -22
- synapse_sdk/plugins/models.py +0 -121
- synapse_sdk/plugins/templates/cookiecutter.json +0 -11
- synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
- synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
- synapse_sdk/shared/enums.py +0 -8
- synapse_sdk/utils/debug.py +0 -5
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/module_loading.py +0 -29
- synapse_sdk/utils/pydantic/__init__.py +0 -0
- synapse_sdk/utils/pydantic/config.py +0 -4
- synapse_sdk/utils/pydantic/errors.py +0 -33
- synapse_sdk/utils/pydantic/validators.py +0 -7
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk/utils/string.py +0 -11
- synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
- synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
"""Ray Pipeline executor for multi-action pipeline execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
11
|
+
|
|
12
|
+
from synapse_sdk.plugins.context import PluginEnvironment
|
|
13
|
+
from synapse_sdk.plugins.enums import PackageManager
|
|
14
|
+
from synapse_sdk.plugins.errors import ExecutionError
|
|
15
|
+
from synapse_sdk.plugins.executors.ray.base import BaseRayExecutor
|
|
16
|
+
from synapse_sdk.plugins.models.logger import ActionProgress, PipelineProgress
|
|
17
|
+
from synapse_sdk.plugins.models.pipeline import ActionStatus, RunStatus
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from collections.abc import AsyncIterator, Iterator
|
|
21
|
+
|
|
22
|
+
from synapse_sdk.clients.pipeline import PipelineServiceClient
|
|
23
|
+
from synapse_sdk.plugins.action import BaseAction
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _serialize_paths(obj: Any) -> Any:
|
|
29
|
+
"""Recursively convert Path objects to strings for JSON serialization."""
|
|
30
|
+
if isinstance(obj, Path):
|
|
31
|
+
return str(obj)
|
|
32
|
+
if isinstance(obj, dict):
|
|
33
|
+
return {k: _serialize_paths(v) for k, v in obj.items()}
|
|
34
|
+
if isinstance(obj, list):
|
|
35
|
+
return [_serialize_paths(item) for item in obj]
|
|
36
|
+
return obj
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class PipelineDefinition:
|
|
41
|
+
"""Definition of a pipeline to execute.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
name: Pipeline name.
|
|
45
|
+
actions: List of action classes or entrypoint strings.
|
|
46
|
+
description: Optional description.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
name: str
|
|
50
|
+
actions: list[type['BaseAction'] | str]
|
|
51
|
+
description: str | None = None
|
|
52
|
+
|
|
53
|
+
def to_api_format(self) -> list[dict[str, Any]]:
|
|
54
|
+
"""Convert actions to API format."""
|
|
55
|
+
result = []
|
|
56
|
+
for action in self.actions:
|
|
57
|
+
if isinstance(action, str):
|
|
58
|
+
entrypoint = action
|
|
59
|
+
name = action.rsplit('.', 1)[-1]
|
|
60
|
+
else:
|
|
61
|
+
entrypoint = f'{action.__module__}.{action.__name__}'
|
|
62
|
+
name = action.action_name or action.__name__
|
|
63
|
+
result.append({'name': name, 'entrypoint': entrypoint})
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RayPipelineExecutor(BaseRayExecutor):
|
|
68
|
+
"""Ray-based executor for multi-action pipelines.
|
|
69
|
+
|
|
70
|
+
Submits pipelines as a single Ray actor that executes actions sequentially.
|
|
71
|
+
Integrates with dev-api for progress tracking and checkpointing.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
>>> executor = RayPipelineExecutor(
|
|
75
|
+
... ray_address='auto',
|
|
76
|
+
... working_dir='/path/to/plugin',
|
|
77
|
+
... pipeline_service_url='http://localhost:8100',
|
|
78
|
+
... )
|
|
79
|
+
>>> pipeline = PipelineDefinition(
|
|
80
|
+
... name='YOLO Training',
|
|
81
|
+
... actions=[DownloadAction, ConvertAction, TrainAction],
|
|
82
|
+
... )
|
|
83
|
+
>>> run_id = executor.submit(pipeline, params={'dataset_id': 123})
|
|
84
|
+
>>> progress = executor.get_progress(run_id)
|
|
85
|
+
>>> result = executor.wait(run_id)
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
env: PluginEnvironment | dict[str, Any] | None = None,
|
|
91
|
+
*,
|
|
92
|
+
ray_address: str = 'auto',
|
|
93
|
+
runtime_env: dict[str, Any] | None = None,
|
|
94
|
+
working_dir: str | Path | None = None,
|
|
95
|
+
requirements_file: str | Path | None = None,
|
|
96
|
+
package_manager: PackageManager | Literal['pip', 'uv'] = PackageManager.PIP,
|
|
97
|
+
package_manager_options: list[str] | None = None,
|
|
98
|
+
wheels_dir: str = 'wheels',
|
|
99
|
+
num_cpus: int | None = None,
|
|
100
|
+
num_gpus: int | None = None,
|
|
101
|
+
include_sdk: bool = False,
|
|
102
|
+
pipeline_service_url: str = 'http://localhost:8100',
|
|
103
|
+
actor_pipeline_service_url: str | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Initialize Ray pipeline executor.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
env: Environment config for actions.
|
|
109
|
+
ray_address: Ray cluster address. Defaults to 'auto'.
|
|
110
|
+
runtime_env: Ray runtime environment config.
|
|
111
|
+
working_dir: Plugin working directory.
|
|
112
|
+
requirements_file: Path to requirements.txt.
|
|
113
|
+
package_manager: Package manager to use.
|
|
114
|
+
package_manager_options: Additional package manager options.
|
|
115
|
+
wheels_dir: Directory containing .whl files.
|
|
116
|
+
num_cpus: Number of CPUs to request.
|
|
117
|
+
num_gpus: Number of GPUs to request.
|
|
118
|
+
include_sdk: If True, bundle local SDK with upload.
|
|
119
|
+
pipeline_service_url: URL of the pipeline service API (for local SDK).
|
|
120
|
+
actor_pipeline_service_url: URL for the Ray actor to reach the pipeline
|
|
121
|
+
service. If None, uses pipeline_service_url. Use this when the actor
|
|
122
|
+
runs on a remote cluster that needs a different URL (e.g., via VPN).
|
|
123
|
+
"""
|
|
124
|
+
super().__init__(
|
|
125
|
+
env=env,
|
|
126
|
+
runtime_env=runtime_env,
|
|
127
|
+
working_dir=working_dir,
|
|
128
|
+
requirements_file=requirements_file,
|
|
129
|
+
package_manager=package_manager,
|
|
130
|
+
package_manager_options=package_manager_options,
|
|
131
|
+
wheels_dir=wheels_dir,
|
|
132
|
+
ray_address=ray_address,
|
|
133
|
+
include_sdk=include_sdk,
|
|
134
|
+
)
|
|
135
|
+
self._num_cpus = num_cpus
|
|
136
|
+
self._num_gpus = num_gpus
|
|
137
|
+
self._pipeline_service_url = pipeline_service_url
|
|
138
|
+
self._actor_pipeline_service_url = actor_pipeline_service_url or pipeline_service_url
|
|
139
|
+
self._submitted_refs: dict[str, Any] = {} # run_id -> (actor_ref, result_ref)
|
|
140
|
+
self._pipeline_client: PipelineServiceClient | None = None
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def pipeline_client(self) -> 'PipelineServiceClient':
|
|
144
|
+
"""Get or create the pipeline service client."""
|
|
145
|
+
if self._pipeline_client is None:
|
|
146
|
+
from synapse_sdk.clients.pipeline import PipelineServiceClient
|
|
147
|
+
|
|
148
|
+
self._pipeline_client = PipelineServiceClient(self._pipeline_service_url)
|
|
149
|
+
return self._pipeline_client
|
|
150
|
+
|
|
151
|
+
def submit(
|
|
152
|
+
self,
|
|
153
|
+
pipeline: PipelineDefinition | list[type['BaseAction'] | str],
|
|
154
|
+
params: dict[str, Any],
|
|
155
|
+
*,
|
|
156
|
+
name: str | None = None,
|
|
157
|
+
resume_from: str | None = None,
|
|
158
|
+
) -> str:
|
|
159
|
+
"""Submit a pipeline for execution (non-blocking).
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
pipeline: PipelineDefinition or list of action classes/entrypoints.
|
|
163
|
+
params: Initial parameters for the pipeline.
|
|
164
|
+
name: Pipeline name (required if pipeline is a list).
|
|
165
|
+
resume_from: Run ID to resume from. If provided, the pipeline will
|
|
166
|
+
skip completed actions and restore accumulated params from the
|
|
167
|
+
latest checkpoint of that run.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Run ID for tracking.
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
ValueError: If pipeline is a list and name is not provided.
|
|
174
|
+
"""
|
|
175
|
+
import ray
|
|
176
|
+
|
|
177
|
+
self._ray_init()
|
|
178
|
+
|
|
179
|
+
# Normalize pipeline definition
|
|
180
|
+
if isinstance(pipeline, list):
|
|
181
|
+
if name is None:
|
|
182
|
+
name = 'pipeline'
|
|
183
|
+
pipeline = PipelineDefinition(name=name, actions=pipeline)
|
|
184
|
+
|
|
185
|
+
# Fetch resume checkpoint if resuming
|
|
186
|
+
resume_checkpoint: dict[str, Any] | None = None
|
|
187
|
+
if resume_from:
|
|
188
|
+
resume_checkpoint = self.pipeline_client.get_latest_checkpoint(resume_from)
|
|
189
|
+
if resume_checkpoint:
|
|
190
|
+
logger.info(
|
|
191
|
+
f'Resuming from checkpoint: action={resume_checkpoint.get("action_name")}, '
|
|
192
|
+
f'index={resume_checkpoint.get("action_index")}'
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
logger.warning(f'No checkpoint found for run {resume_from}, starting fresh')
|
|
196
|
+
|
|
197
|
+
# Register pipeline with dev-api
|
|
198
|
+
api_actions = pipeline.to_api_format()
|
|
199
|
+
pipeline_data = self.pipeline_client.create_pipeline(
|
|
200
|
+
name=pipeline.name,
|
|
201
|
+
actions=api_actions,
|
|
202
|
+
description=pipeline.description,
|
|
203
|
+
)
|
|
204
|
+
pipeline_id = pipeline_data['id']
|
|
205
|
+
|
|
206
|
+
# Create run
|
|
207
|
+
run_data = self.pipeline_client.create_run(
|
|
208
|
+
pipeline_id=pipeline_id,
|
|
209
|
+
params=params,
|
|
210
|
+
)
|
|
211
|
+
run_id = run_data['id']
|
|
212
|
+
|
|
213
|
+
# Convert action classes to entrypoint strings
|
|
214
|
+
entrypoints = []
|
|
215
|
+
for action in pipeline.actions:
|
|
216
|
+
if isinstance(action, str):
|
|
217
|
+
entrypoints.append(action)
|
|
218
|
+
else:
|
|
219
|
+
entrypoints.append(f'{action.__module__}.{action.__name__}')
|
|
220
|
+
|
|
221
|
+
# Build remote options
|
|
222
|
+
remote_options: dict[str, Any] = {'runtime_env': self._build_runtime_env()}
|
|
223
|
+
if self._num_cpus is not None:
|
|
224
|
+
remote_options['num_cpus'] = self._num_cpus
|
|
225
|
+
if self._num_gpus is not None:
|
|
226
|
+
remote_options['num_gpus'] = self._num_gpus
|
|
227
|
+
|
|
228
|
+
# Create actor and submit pipeline
|
|
229
|
+
# Use actor_pipeline_service_url for the remote actor
|
|
230
|
+
PipelineActor = ray.remote(**remote_options)(_PipelineExecutorActor)
|
|
231
|
+
actor = PipelineActor.remote(
|
|
232
|
+
run_id=run_id,
|
|
233
|
+
pipeline_id=pipeline_id,
|
|
234
|
+
pipeline_service_url=self._actor_pipeline_service_url,
|
|
235
|
+
)
|
|
236
|
+
result_ref = actor.run_pipeline.remote(entrypoints, params, resume_checkpoint=resume_checkpoint)
|
|
237
|
+
|
|
238
|
+
self._submitted_refs[run_id] = (actor, result_ref)
|
|
239
|
+
logger.info(f'Submitted pipeline run {run_id}')
|
|
240
|
+
|
|
241
|
+
return run_id
|
|
242
|
+
|
|
243
|
+
def get_status(self, run_id: str) -> RunStatus:
|
|
244
|
+
"""Get run status from the pipeline service.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
run_id: Run ID from submit().
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Current run status.
|
|
251
|
+
"""
|
|
252
|
+
progress = self.pipeline_client.get_progress(run_id)
|
|
253
|
+
return progress.status
|
|
254
|
+
|
|
255
|
+
def get_progress(self, run_id: str) -> PipelineProgress:
|
|
256
|
+
"""Get detailed progress for a run.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
run_id: Run ID from submit().
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
PipelineProgress with current state.
|
|
263
|
+
"""
|
|
264
|
+
return self.pipeline_client.get_progress(run_id)
|
|
265
|
+
|
|
266
|
+
def get_result(self, run_id: str, timeout: float | None = None) -> Any:
|
|
267
|
+
"""Get pipeline result (blocks until complete).
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
run_id: Run ID from submit().
|
|
271
|
+
timeout: Optional timeout in seconds.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Final pipeline result.
|
|
275
|
+
|
|
276
|
+
Raises:
|
|
277
|
+
ExecutionError: If pipeline failed or not found.
|
|
278
|
+
"""
|
|
279
|
+
import ray
|
|
280
|
+
|
|
281
|
+
refs = self._submitted_refs.get(run_id)
|
|
282
|
+
if refs is None:
|
|
283
|
+
# Try to get from API
|
|
284
|
+
run_data = self.pipeline_client.get_run(run_id)
|
|
285
|
+
if run_data.get('status') == 'completed':
|
|
286
|
+
return run_data.get('result')
|
|
287
|
+
elif run_data.get('status') == 'failed':
|
|
288
|
+
raise ExecutionError(f'Pipeline {run_id} failed: {run_data.get("error")}')
|
|
289
|
+
raise ExecutionError(f'Run {run_id} not found in local cache')
|
|
290
|
+
|
|
291
|
+
_, result_ref = refs
|
|
292
|
+
try:
|
|
293
|
+
return ray.get(result_ref, timeout=timeout)
|
|
294
|
+
except Exception as e:
|
|
295
|
+
raise ExecutionError(f'Pipeline {run_id} failed: {e}') from e
|
|
296
|
+
|
|
297
|
+
def wait(
|
|
298
|
+
self,
|
|
299
|
+
run_id: str,
|
|
300
|
+
timeout_seconds: float = 3600,
|
|
301
|
+
poll_interval: float = 5.0,
|
|
302
|
+
) -> PipelineProgress:
|
|
303
|
+
"""Wait for pipeline to complete, polling for progress.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
run_id: Run ID from submit().
|
|
307
|
+
timeout_seconds: Maximum time to wait.
|
|
308
|
+
poll_interval: Seconds between progress polls.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Final PipelineProgress.
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
ExecutionError: If pipeline fails or times out.
|
|
315
|
+
"""
|
|
316
|
+
start_time = time.time()
|
|
317
|
+
|
|
318
|
+
while True:
|
|
319
|
+
elapsed = time.time() - start_time
|
|
320
|
+
if elapsed > timeout_seconds:
|
|
321
|
+
raise ExecutionError(f'Pipeline {run_id} timed out after {timeout_seconds}s')
|
|
322
|
+
|
|
323
|
+
progress = self.get_progress(run_id)
|
|
324
|
+
|
|
325
|
+
if progress.status == RunStatus.COMPLETED:
|
|
326
|
+
return progress
|
|
327
|
+
elif progress.status == RunStatus.FAILED:
|
|
328
|
+
raise ExecutionError(f'Pipeline {run_id} failed: {progress.error}')
|
|
329
|
+
elif progress.status == RunStatus.CANCELLED:
|
|
330
|
+
raise ExecutionError(f'Pipeline {run_id} was cancelled')
|
|
331
|
+
|
|
332
|
+
time.sleep(poll_interval)
|
|
333
|
+
|
|
334
|
+
def stream_progress(
|
|
335
|
+
self,
|
|
336
|
+
run_id: str,
|
|
337
|
+
timeout: float = 3600.0,
|
|
338
|
+
) -> 'Iterator[PipelineProgress]':
|
|
339
|
+
"""Stream progress updates via SSE.
|
|
340
|
+
|
|
341
|
+
This method connects to the pipeline service's SSE endpoint and yields
|
|
342
|
+
PipelineProgress objects as updates are received. More efficient than
|
|
343
|
+
polling for long-running pipelines.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
run_id: Run ID from submit().
|
|
347
|
+
timeout: Maximum time to stream in seconds.
|
|
348
|
+
|
|
349
|
+
Yields:
|
|
350
|
+
PipelineProgress objects with current state.
|
|
351
|
+
|
|
352
|
+
Raises:
|
|
353
|
+
ExecutionError: If streaming fails or pipeline errors.
|
|
354
|
+
|
|
355
|
+
Example:
|
|
356
|
+
>>> run_id = executor.submit(pipeline, params)
|
|
357
|
+
>>> for progress in executor.stream_progress(run_id):
|
|
358
|
+
... print(f"Action: {progress.current_action}, Status: {progress.status}")
|
|
359
|
+
... if progress.status == RunStatus.COMPLETED:
|
|
360
|
+
... break
|
|
361
|
+
"""
|
|
362
|
+
try:
|
|
363
|
+
yield from self.pipeline_client.stream_progress(run_id, timeout=timeout)
|
|
364
|
+
except Exception as e:
|
|
365
|
+
raise ExecutionError(f'Failed to stream progress for {run_id}: {e}') from e
|
|
366
|
+
|
|
367
|
+
async def stream_progress_async(
|
|
368
|
+
self,
|
|
369
|
+
run_id: str,
|
|
370
|
+
timeout: float = 3600.0,
|
|
371
|
+
) -> 'AsyncIterator[PipelineProgress]':
|
|
372
|
+
"""Stream progress updates via SSE (async version).
|
|
373
|
+
|
|
374
|
+
This method connects to the pipeline service's SSE endpoint and yields
|
|
375
|
+
PipelineProgress objects as updates are received. More efficient than
|
|
376
|
+
polling for long-running pipelines.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
run_id: Run ID from submit().
|
|
380
|
+
timeout: Maximum time to stream in seconds.
|
|
381
|
+
|
|
382
|
+
Yields:
|
|
383
|
+
PipelineProgress objects with current state.
|
|
384
|
+
|
|
385
|
+
Raises:
|
|
386
|
+
ExecutionError: If streaming fails or pipeline errors.
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
>>> run_id = executor.submit(pipeline, params)
|
|
390
|
+
>>> async for progress in executor.stream_progress_async(run_id):
|
|
391
|
+
... print(f"Action: {progress.current_action}, Status: {progress.status}")
|
|
392
|
+
"""
|
|
393
|
+
try:
|
|
394
|
+
async for progress in self.pipeline_client.stream_progress_async(run_id, timeout=timeout):
|
|
395
|
+
yield progress
|
|
396
|
+
except Exception as e:
|
|
397
|
+
raise ExecutionError(f'Failed to stream progress for {run_id}: {e}') from e
|
|
398
|
+
|
|
399
|
+
def cancel(self, run_id: str) -> None:
|
|
400
|
+
"""Cancel a running pipeline.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
run_id: Run ID to cancel.
|
|
404
|
+
"""
|
|
405
|
+
import ray
|
|
406
|
+
|
|
407
|
+
refs = self._submitted_refs.get(run_id)
|
|
408
|
+
if refs is not None:
|
|
409
|
+
actor, result_ref = refs
|
|
410
|
+
try:
|
|
411
|
+
ray.kill(actor)
|
|
412
|
+
except Exception as e:
|
|
413
|
+
logger.warning(f'Failed to kill actor for {run_id}: {e}')
|
|
414
|
+
|
|
415
|
+
# Update status in API
|
|
416
|
+
try:
|
|
417
|
+
self.pipeline_client.update_run(run_id, status='cancelled')
|
|
418
|
+
except Exception as e:
|
|
419
|
+
logger.warning(f'Failed to update cancelled status for {run_id}: {e}')
|
|
420
|
+
|
|
421
|
+
def close(self) -> None:
|
|
422
|
+
"""Close the executor and clean up resources."""
|
|
423
|
+
if self._pipeline_client is not None:
|
|
424
|
+
self._pipeline_client.close()
|
|
425
|
+
self._pipeline_client = None
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class _PipelineLogger:
|
|
429
|
+
"""Logger that wraps ConsoleLogger and reports progress to pipeline service.
|
|
430
|
+
|
|
431
|
+
Forwards progress updates to the dev-api for real-time pipeline monitoring.
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
def __init__(
|
|
435
|
+
self,
|
|
436
|
+
base_logger: Any,
|
|
437
|
+
pipeline_client: Any,
|
|
438
|
+
run_id: str,
|
|
439
|
+
action_index: int,
|
|
440
|
+
action_name: str,
|
|
441
|
+
) -> None:
|
|
442
|
+
self._base = base_logger
|
|
443
|
+
self._client = pipeline_client
|
|
444
|
+
self._run_id = run_id
|
|
445
|
+
self._action_index = action_index
|
|
446
|
+
self._action_name = action_name
|
|
447
|
+
|
|
448
|
+
def __getattr__(self, name: str) -> Any:
|
|
449
|
+
# Forward all other methods to base logger
|
|
450
|
+
return getattr(self._base, name)
|
|
451
|
+
|
|
452
|
+
def set_progress(self, current: int, total: int, category: str | None = None) -> None:
|
|
453
|
+
"""Set progress and report to pipeline service."""
|
|
454
|
+
self._base.set_progress(current, total, category)
|
|
455
|
+
|
|
456
|
+
# Report to pipeline service
|
|
457
|
+
try:
|
|
458
|
+
percent = current / total if total > 0 else 0
|
|
459
|
+
self._client.report_progress(
|
|
460
|
+
run_id=self._run_id,
|
|
461
|
+
current_action_index=self._action_index,
|
|
462
|
+
action_progress=ActionProgress(
|
|
463
|
+
name=self._action_name,
|
|
464
|
+
status=ActionStatus.RUNNING,
|
|
465
|
+
progress=percent,
|
|
466
|
+
progress_category=category,
|
|
467
|
+
message=f'{current}/{total}',
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
except Exception as e:
|
|
471
|
+
print(f'[PipelineLogger] Failed to report progress: {e}')
|
|
472
|
+
|
|
473
|
+
def info(self, message: str) -> None:
|
|
474
|
+
self._base.info(message)
|
|
475
|
+
|
|
476
|
+
def debug(self, message: str) -> None:
|
|
477
|
+
self._base.debug(message)
|
|
478
|
+
|
|
479
|
+
def warning(self, message: str) -> None:
|
|
480
|
+
self._base.warning(message)
|
|
481
|
+
|
|
482
|
+
def error(self, message: str) -> None:
|
|
483
|
+
self._base.error(message)
|
|
484
|
+
|
|
485
|
+
def log(self, event: str, data: dict, file: str | None = None) -> None:
|
|
486
|
+
self._base.log(event, data, file)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
class _PipelineExecutorActor:
|
|
490
|
+
"""Ray Actor that executes a pipeline of actions.
|
|
491
|
+
|
|
492
|
+
This actor runs within the Ray cluster and executes actions sequentially,
|
|
493
|
+
reporting progress to the pipeline service API after each action.
|
|
494
|
+
"""
|
|
495
|
+
|
|
496
|
+
def __init__(
|
|
497
|
+
self,
|
|
498
|
+
run_id: str,
|
|
499
|
+
pipeline_id: str,
|
|
500
|
+
pipeline_service_url: str,
|
|
501
|
+
) -> None:
|
|
502
|
+
"""Initialize the pipeline executor actor.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
run_id: Run identifier.
|
|
506
|
+
pipeline_id: Pipeline identifier.
|
|
507
|
+
pipeline_service_url: URL of the pipeline service.
|
|
508
|
+
"""
|
|
509
|
+
self._run_id = run_id
|
|
510
|
+
self._pipeline_id = pipeline_id
|
|
511
|
+
self._pipeline_service_url = pipeline_service_url
|
|
512
|
+
self._client: Any = None
|
|
513
|
+
|
|
514
|
+
@property
|
|
515
|
+
def client(self) -> Any:
|
|
516
|
+
"""Get or create the pipeline service client."""
|
|
517
|
+
if self._client is None:
|
|
518
|
+
from synapse_sdk.clients.pipeline import PipelineServiceClient
|
|
519
|
+
|
|
520
|
+
self._client = PipelineServiceClient(self._pipeline_service_url)
|
|
521
|
+
return self._client
|
|
522
|
+
|
|
523
|
+
def run_pipeline(
|
|
524
|
+
self,
|
|
525
|
+
entrypoints: list[str],
|
|
526
|
+
params: dict[str, Any],
|
|
527
|
+
resume_checkpoint: dict[str, Any] | None = None,
|
|
528
|
+
) -> Any:
|
|
529
|
+
"""Execute all actions in the pipeline sequentially.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
entrypoints: List of action entrypoint strings.
|
|
533
|
+
params: Initial parameters.
|
|
534
|
+
resume_checkpoint: Checkpoint data to resume from (optional).
|
|
535
|
+
If provided, actions up to and including the checkpoint's
|
|
536
|
+
action_index will be skipped, and accumulated params will
|
|
537
|
+
be restored from params_snapshot.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
Result from the final action.
|
|
541
|
+
"""
|
|
542
|
+
import importlib
|
|
543
|
+
import logging
|
|
544
|
+
import os
|
|
545
|
+
import sys
|
|
546
|
+
import traceback
|
|
547
|
+
|
|
548
|
+
from pydantic import BaseModel
|
|
549
|
+
|
|
550
|
+
# Configure logging to ensure ConsoleLogger output is visible
|
|
551
|
+
# This is needed because Ray workers don't have logging configured by default
|
|
552
|
+
logging.basicConfig(
|
|
553
|
+
level=logging.INFO,
|
|
554
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
555
|
+
force=True, # Override any existing config
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
from synapse_sdk.loggers import ConsoleLogger
|
|
559
|
+
from synapse_sdk.plugins.context import PluginEnvironment, RuntimeContext
|
|
560
|
+
from synapse_sdk.plugins.models.logger import LogEntry, LogLevel
|
|
561
|
+
from synapse_sdk.plugins.pipelines.context import PipelineContext
|
|
562
|
+
|
|
563
|
+
# Ensure working directory is in sys.path
|
|
564
|
+
cwd = os.getcwd()
|
|
565
|
+
if cwd not in sys.path:
|
|
566
|
+
sys.path.insert(0, cwd)
|
|
567
|
+
|
|
568
|
+
# Create pipeline context for shared working directory
|
|
569
|
+
# TODO: Pass pipeline_ctx to actions via RuntimeContext
|
|
570
|
+
_pipeline_ctx = PipelineContext(
|
|
571
|
+
pipeline_id=self._pipeline_id,
|
|
572
|
+
run_id=self._run_id,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# Determine resume index and restore params if resuming
|
|
576
|
+
resume_from_index = -1
|
|
577
|
+
if resume_checkpoint:
|
|
578
|
+
resume_from_index = resume_checkpoint.get('action_index', -1)
|
|
579
|
+
# Restore accumulated params from checkpoint
|
|
580
|
+
if resume_checkpoint.get('params_snapshot'):
|
|
581
|
+
params = resume_checkpoint['params_snapshot']
|
|
582
|
+
|
|
583
|
+
# Report pipeline started
|
|
584
|
+
self.client.report_progress(
|
|
585
|
+
run_id=self._run_id,
|
|
586
|
+
status='running',
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
# Try to create BackendClient from environment
|
|
590
|
+
from synapse_sdk.utils.auth import create_backend_client
|
|
591
|
+
|
|
592
|
+
backend_client = create_backend_client()
|
|
593
|
+
|
|
594
|
+
# Create base runtime context
|
|
595
|
+
base_logger = ConsoleLogger()
|
|
596
|
+
env = PluginEnvironment.from_environ()
|
|
597
|
+
|
|
598
|
+
# Accumulated params (passed between actions)
|
|
599
|
+
accumulated_params = dict(params)
|
|
600
|
+
final_result = None
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
for idx, entrypoint in enumerate(entrypoints):
|
|
604
|
+
action_name = entrypoint.rsplit('.', 1)[-1]
|
|
605
|
+
|
|
606
|
+
# Skip completed actions when resuming
|
|
607
|
+
if idx <= resume_from_index:
|
|
608
|
+
# Report action as skipped
|
|
609
|
+
self.client.report_progress(
|
|
610
|
+
run_id=self._run_id,
|
|
611
|
+
current_action=action_name,
|
|
612
|
+
current_action_index=idx,
|
|
613
|
+
action_progress=ActionProgress(
|
|
614
|
+
name=action_name,
|
|
615
|
+
status=ActionStatus.SKIPPED,
|
|
616
|
+
progress=1.0,
|
|
617
|
+
completed_at=datetime.utcnow(),
|
|
618
|
+
),
|
|
619
|
+
)
|
|
620
|
+
self.client.append_logs(
|
|
621
|
+
run_id=self._run_id,
|
|
622
|
+
entries=[
|
|
623
|
+
LogEntry(
|
|
624
|
+
message=f'Skipping action (resumed): {action_name}',
|
|
625
|
+
level=LogLevel.INFO,
|
|
626
|
+
action_name=action_name,
|
|
627
|
+
)
|
|
628
|
+
],
|
|
629
|
+
)
|
|
630
|
+
continue
|
|
631
|
+
|
|
632
|
+
# Report action started
|
|
633
|
+
self.client.report_progress(
|
|
634
|
+
run_id=self._run_id,
|
|
635
|
+
current_action=action_name,
|
|
636
|
+
current_action_index=idx,
|
|
637
|
+
action_progress=ActionProgress(
|
|
638
|
+
name=action_name,
|
|
639
|
+
status=ActionStatus.RUNNING,
|
|
640
|
+
progress=0.0,
|
|
641
|
+
started_at=datetime.utcnow(),
|
|
642
|
+
),
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
# Log action start
|
|
646
|
+
self.client.append_logs(
|
|
647
|
+
run_id=self._run_id,
|
|
648
|
+
entries=[
|
|
649
|
+
LogEntry(
|
|
650
|
+
message=f'Starting action: {action_name}',
|
|
651
|
+
level=LogLevel.INFO,
|
|
652
|
+
action_name=action_name,
|
|
653
|
+
)
|
|
654
|
+
],
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
# Import action class
|
|
658
|
+
try:
|
|
659
|
+
module_path, class_name = entrypoint.rsplit('.', 1)
|
|
660
|
+
module = importlib.import_module(module_path)
|
|
661
|
+
action_cls = getattr(module, class_name)
|
|
662
|
+
except Exception as e:
|
|
663
|
+
raise ExecutionError(f'Failed to import action {entrypoint}: {e}') from e
|
|
664
|
+
|
|
665
|
+
# Validate params
|
|
666
|
+
try:
|
|
667
|
+
validated_params = action_cls.params_model.model_validate(accumulated_params)
|
|
668
|
+
except Exception as e:
|
|
669
|
+
raise ExecutionError(f'Parameter validation failed for {action_name}: {e}') from e
|
|
670
|
+
|
|
671
|
+
# Build context with pipeline logger for progress reporting
|
|
672
|
+
action_logger = _PipelineLogger(
|
|
673
|
+
base_logger=base_logger,
|
|
674
|
+
pipeline_client=self.client,
|
|
675
|
+
run_id=self._run_id,
|
|
676
|
+
action_index=idx,
|
|
677
|
+
action_name=action_name,
|
|
678
|
+
)
|
|
679
|
+
ctx = RuntimeContext(
|
|
680
|
+
logger=action_logger,
|
|
681
|
+
env=env,
|
|
682
|
+
job_id=self._run_id,
|
|
683
|
+
client=backend_client,
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
# Execute action
|
|
687
|
+
action = action_cls(validated_params, ctx)
|
|
688
|
+
try:
|
|
689
|
+
if hasattr(action, 'run'):
|
|
690
|
+
result = action.run()
|
|
691
|
+
else:
|
|
692
|
+
result = action.execute()
|
|
693
|
+
except Exception as e:
|
|
694
|
+
# Report action failed
|
|
695
|
+
self.client.report_progress(
|
|
696
|
+
run_id=self._run_id,
|
|
697
|
+
current_action_index=idx,
|
|
698
|
+
action_progress=ActionProgress(
|
|
699
|
+
name=action_name,
|
|
700
|
+
status=ActionStatus.FAILED,
|
|
701
|
+
error=str(e),
|
|
702
|
+
completed_at=datetime.utcnow(),
|
|
703
|
+
),
|
|
704
|
+
)
|
|
705
|
+
raise ExecutionError(f'Action {action_name} failed: {e}') from e
|
|
706
|
+
|
|
707
|
+
# Merge result into accumulated params
|
|
708
|
+
# Use mode='json' for Pydantic models to serialize Path objects
|
|
709
|
+
if isinstance(result, BaseModel):
|
|
710
|
+
result_dict = result.model_dump(mode='json')
|
|
711
|
+
elif isinstance(result, dict):
|
|
712
|
+
result_dict = _serialize_paths(result)
|
|
713
|
+
else:
|
|
714
|
+
result_dict = {}
|
|
715
|
+
|
|
716
|
+
accumulated_params.update(result_dict)
|
|
717
|
+
final_result = result
|
|
718
|
+
|
|
719
|
+
# Report action completed
|
|
720
|
+
self.client.report_progress(
|
|
721
|
+
run_id=self._run_id,
|
|
722
|
+
current_action_index=idx,
|
|
723
|
+
action_progress=ActionProgress(
|
|
724
|
+
name=action_name,
|
|
725
|
+
status=ActionStatus.COMPLETED,
|
|
726
|
+
progress=1.0,
|
|
727
|
+
completed_at=datetime.utcnow(),
|
|
728
|
+
),
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
# Create checkpoint with JSON-serializable data
|
|
732
|
+
self.client.create_checkpoint(
|
|
733
|
+
run_id=self._run_id,
|
|
734
|
+
action_name=action_name,
|
|
735
|
+
action_index=idx,
|
|
736
|
+
status='completed',
|
|
737
|
+
params_snapshot=_serialize_paths(accumulated_params),
|
|
738
|
+
result=result_dict,
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
# Log action complete
|
|
742
|
+
self.client.append_logs(
|
|
743
|
+
run_id=self._run_id,
|
|
744
|
+
entries=[
|
|
745
|
+
LogEntry(
|
|
746
|
+
message=f'Completed action: {action_name}',
|
|
747
|
+
level=LogLevel.INFO,
|
|
748
|
+
action_name=action_name,
|
|
749
|
+
)
|
|
750
|
+
],
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
# Report pipeline completed
|
|
754
|
+
result_for_api = final_result
|
|
755
|
+
if isinstance(result_for_api, BaseModel):
|
|
756
|
+
result_for_api = result_for_api.model_dump(mode='json')
|
|
757
|
+
elif isinstance(result_for_api, dict):
|
|
758
|
+
result_for_api = _serialize_paths(result_for_api)
|
|
759
|
+
|
|
760
|
+
self.client.update_run(
|
|
761
|
+
run_id=self._run_id,
|
|
762
|
+
status='completed',
|
|
763
|
+
result=result_for_api if isinstance(result_for_api, dict) else None,
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
base_logger.finish()
|
|
767
|
+
return final_result
|
|
768
|
+
|
|
769
|
+
except Exception as e:
|
|
770
|
+
# Report pipeline failed
|
|
771
|
+
error_msg = f'{type(e).__name__}: {str(e)}'
|
|
772
|
+
self.client.update_run(
|
|
773
|
+
run_id=self._run_id,
|
|
774
|
+
status='failed',
|
|
775
|
+
error=error_msg,
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
self.client.append_logs(
|
|
779
|
+
run_id=self._run_id,
|
|
780
|
+
entries=[
|
|
781
|
+
LogEntry(
|
|
782
|
+
message=f'Pipeline failed: {error_msg}\n{traceback.format_exc()}',
|
|
783
|
+
level=LogLevel.ERROR,
|
|
784
|
+
)
|
|
785
|
+
],
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
base_logger.finish()
|
|
789
|
+
raise
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
__all__ = ['RayPipelineExecutor', 'PipelineDefinition']
|