synapse-sdk 1.0.0b5__py3-none-any.whl → 2025.12.3__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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/code_server.py +305 -33
- synapse_sdk/clients/agent/__init__.py +2 -1
- synapse_sdk/clients/agent/container.py +143 -0
- synapse_sdk/clients/agent/ray.py +296 -38
- synapse_sdk/clients/backend/annotation.py +1 -1
- synapse_sdk/clients/backend/core.py +31 -4
- synapse_sdk/clients/backend/data_collection.py +82 -7
- synapse_sdk/clients/backend/hitl.py +1 -1
- synapse_sdk/clients/backend/ml.py +1 -1
- synapse_sdk/clients/base.py +211 -61
- synapse_sdk/loggers.py +46 -0
- synapse_sdk/plugins/README.md +1340 -0
- synapse_sdk/plugins/categories/base.py +59 -9
- synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
- synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
- synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
- synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
- synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
- synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
- synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
- synapse_sdk/plugins/categories/export/templates/config.yaml +19 -1
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +153 -177
- synapse_sdk/plugins/categories/neural_net/actions/train.py +1130 -32
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +157 -4
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +7 -4
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
- synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
- synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
- synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
- synapse_sdk/plugins/categories/upload/templates/config.yaml +28 -2
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +82 -20
- synapse_sdk/plugins/models.py +111 -9
- synapse_sdk/plugins/templates/plugin-config-schema.json +7 -0
- synapse_sdk/plugins/templates/schema.json +7 -0
- synapse_sdk/plugins/utils/__init__.py +3 -0
- synapse_sdk/plugins/utils/ray_gcs.py +66 -0
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/converters/dm/__init__.py +42 -41
- synapse_sdk/utils/converters/dm/base.py +137 -0
- synapse_sdk/utils/converters/dm/from_v1.py +208 -562
- synapse_sdk/utils/converters/dm/to_v1.py +258 -304
- synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
- synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
- synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
- synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
- synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
- synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
- synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
- synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
- synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
- synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
- synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
- synapse_sdk/utils/converters/dm/types.py +168 -0
- synapse_sdk/utils/converters/dm/utils.py +162 -0
- synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
- synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
- synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +32 -0
- synapse_sdk/utils/file/checksum.py +56 -0
- synapse_sdk/utils/file/chunking.py +31 -0
- synapse_sdk/utils/file/download.py +385 -0
- synapse_sdk/utils/file/encoding.py +40 -0
- synapse_sdk/utils/file/io.py +22 -0
- synapse_sdk/utils/file/upload.py +165 -0
- synapse_sdk/utils/file/video/__init__.py +29 -0
- synapse_sdk/utils/file/video/transcode.py +307 -0
- synapse_sdk/utils/{file.py → file.py.backup} +77 -0
- synapse_sdk/utils/network.py +272 -0
- synapse_sdk/utils/storage/__init__.py +6 -2
- synapse_sdk/utils/storage/providers/file_system.py +6 -0
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/METADATA +19 -2
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/RECORD +134 -74
- synapse_sdk/devtools/docs/.gitignore +0 -20
- synapse_sdk/devtools/docs/README.md +0 -41
- synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +0 -12
- synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +0 -44
- synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
- synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +0 -29
- synapse_sdk/devtools/docs/blog/authors.yml +0 -25
- synapse_sdk/devtools/docs/blog/tags.yml +0 -19
- synapse_sdk/devtools/docs/docusaurus.config.ts +0 -138
- synapse_sdk/devtools/docs/package-lock.json +0 -17455
- synapse_sdk/devtools/docs/package.json +0 -47
- synapse_sdk/devtools/docs/sidebars.ts +0 -44
- synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +0 -71
- synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +0 -11
- synapse_sdk/devtools/docs/src/css/custom.css +0 -30
- synapse_sdk/devtools/docs/src/pages/index.module.css +0 -23
- synapse_sdk/devtools/docs/src/pages/index.tsx +0 -21
- synapse_sdk/devtools/docs/src/pages/markdown-page.md +0 -7
- synapse_sdk/devtools/docs/static/.nojekyll +0 -0
- synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
- synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
- synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
- synapse_sdk/devtools/docs/static/img/logo.png +0 -0
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +0 -170
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +0 -40
- synapse_sdk/devtools/docs/tsconfig.json +0 -8
- synapse_sdk/plugins/categories/export/actions/export.py +0 -346
- synapse_sdk/plugins/categories/export/enums.py +0 -7
- synapse_sdk/plugins/categories/neural_net/actions/gradio.py +0 -151
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +0 -943
- synapse_sdk/plugins/categories/upload/actions/upload.py +0 -954
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +0 -0
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Abstract base classes for ToTask strategies."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from synapse_sdk.clients.backend import BackendClient
|
|
7
|
+
|
|
8
|
+
from ..enums import AnnotationMethod
|
|
9
|
+
from ..models import MetricsRecord
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ToTaskContext:
|
|
13
|
+
"""Shared context for ToTask action execution."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
params: Dict[str, Any],
|
|
18
|
+
client: BackendClient,
|
|
19
|
+
logger: Any,
|
|
20
|
+
entrypoint: Any = None,
|
|
21
|
+
config: Optional[Dict[str, Any]] = None,
|
|
22
|
+
plugin_config: Optional[Dict[str, Any]] = None,
|
|
23
|
+
job_id: Optional[str] = None,
|
|
24
|
+
progress_categories: Optional[Dict[str, Any]] = None,
|
|
25
|
+
metrics_categories: Optional[Dict[str, Any]] = None,
|
|
26
|
+
project: Optional[Dict[str, Any]] = None,
|
|
27
|
+
data_collection: Optional[Dict[str, Any]] = None,
|
|
28
|
+
task_ids: Optional[List[int]] = None,
|
|
29
|
+
metrics: Optional[MetricsRecord] = None,
|
|
30
|
+
annotation_method: Optional[AnnotationMethod] = None,
|
|
31
|
+
):
|
|
32
|
+
self.params = params
|
|
33
|
+
self.client = client
|
|
34
|
+
self.logger = logger
|
|
35
|
+
self.entrypoint = entrypoint
|
|
36
|
+
self.config = config
|
|
37
|
+
self.plugin_config = plugin_config
|
|
38
|
+
self.job_id = job_id
|
|
39
|
+
self.progress_categories = progress_categories
|
|
40
|
+
self.metrics_categories = metrics_categories
|
|
41
|
+
self.project = project
|
|
42
|
+
self.data_collection = data_collection
|
|
43
|
+
self.task_ids = task_ids or []
|
|
44
|
+
self.metrics = metrics or MetricsRecord(stand_by=0, failed=0, success=0)
|
|
45
|
+
self.annotation_method = annotation_method
|
|
46
|
+
self.temp_files: List[str] = []
|
|
47
|
+
self.rollback_actions: List[callable] = []
|
|
48
|
+
|
|
49
|
+
def add_temp_file(self, file_path: str):
|
|
50
|
+
"""Track temporary files for cleanup."""
|
|
51
|
+
self.temp_files.append(file_path)
|
|
52
|
+
|
|
53
|
+
def add_rollback_action(self, action: callable):
|
|
54
|
+
"""Add rollback action for error recovery."""
|
|
55
|
+
self.rollback_actions.append(action)
|
|
56
|
+
|
|
57
|
+
def update_metrics(self, success_count: int, failed_count: int, total_count: int):
|
|
58
|
+
"""Update execution metrics."""
|
|
59
|
+
self.metrics = MetricsRecord(
|
|
60
|
+
stand_by=total_count - success_count - failed_count, failed=failed_count, success=success_count
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ValidationStrategy(ABC):
|
|
65
|
+
"""Abstract base class for validation strategies."""
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def validate(self, context: ToTaskContext) -> Dict[str, Any]:
|
|
69
|
+
"""Validate specific aspects of the ToTask execution.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
context: Shared context for the action execution
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Dict with 'success' boolean and optional 'error' message
|
|
76
|
+
"""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AnnotationStrategy(ABC):
|
|
81
|
+
"""Abstract base class for annotation strategies."""
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def process_task(self, context: ToTaskContext, task_id: int, task_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
|
85
|
+
"""Process a single task for annotation.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
context: Shared context for the action execution
|
|
89
|
+
task_id: The task ID to process
|
|
90
|
+
task_data: The task data dictionary
|
|
91
|
+
**kwargs: Additional method-specific parameters
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Dict with 'success' boolean and optional 'error' message
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class PreProcessorStrategy(ABC):
|
|
100
|
+
"""Abstract base class for pre-processor management strategies."""
|
|
101
|
+
|
|
102
|
+
@abstractmethod
|
|
103
|
+
def get_preprocessor_info(self, context: ToTaskContext, preprocessor_id: int) -> Dict[str, Any]:
|
|
104
|
+
"""Get pre-processor information.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
context: Shared context for the action execution
|
|
108
|
+
preprocessor_id: The pre-processor ID
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dict with pre-processor info or error
|
|
112
|
+
"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
def ensure_preprocessor_running(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
|
|
117
|
+
"""Ensure pre-processor is running.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
context: Shared context for the action execution
|
|
121
|
+
preprocessor_code: The pre-processor code
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Dict indicating success or failure
|
|
125
|
+
"""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DataExtractionStrategy(ABC):
|
|
130
|
+
"""Abstract base class for data extraction strategies."""
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def extract_data(self, context: ToTaskContext, task_data: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
|
|
134
|
+
"""Extract required data from task.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
context: Shared context for the action execution
|
|
138
|
+
task_data: The task data dictionary
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Tuple of extracted data values
|
|
142
|
+
"""
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class MetricsStrategy(ABC):
|
|
147
|
+
"""Abstract base class for metrics strategies."""
|
|
148
|
+
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def update_progress(self, context: ToTaskContext, current: int, total: int):
|
|
151
|
+
"""Update progress tracking.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
context: Shared context for the action execution
|
|
155
|
+
current: Current progress count
|
|
156
|
+
total: Total items to process
|
|
157
|
+
"""
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
@abstractmethod
|
|
161
|
+
def record_task_result(self, context: ToTaskContext, task_id: int, success: bool, error: Optional[str] = None):
|
|
162
|
+
"""Record the result of processing a single task.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
context: Shared context for the action execution
|
|
166
|
+
task_id: The task ID that was processed
|
|
167
|
+
success: Whether the task processing was successful
|
|
168
|
+
error: Error message if unsuccessful
|
|
169
|
+
"""
|
|
170
|
+
pass
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Data extraction strategies for ToTask action."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
from .base import DataExtractionStrategy, ToTaskContext
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FileUrlExtractionStrategy(DataExtractionStrategy):
|
|
9
|
+
"""Strategy for extracting file URLs from task data."""
|
|
10
|
+
|
|
11
|
+
def extract_data(self, context: ToTaskContext, task_data: Dict[str, Any]) -> str | None:
|
|
12
|
+
"""Extract primary file URL from task data.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
context: Shared context for the action execution
|
|
16
|
+
task_data: The task data dictionary
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
str: The primary file URL or None if not found
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
# This implementation follows the original _extract_primary_file_url logic
|
|
23
|
+
data_unit = task_data.get('data_unit')
|
|
24
|
+
if not data_unit:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
data_unit_files = data_unit.get('files', {})
|
|
28
|
+
if not data_unit_files:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
# Find primary file URL
|
|
32
|
+
for key in data_unit_files:
|
|
33
|
+
if data_unit_files[key]['is_primary']:
|
|
34
|
+
return data_unit_files[key]['url']
|
|
35
|
+
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
except Exception as e:
|
|
39
|
+
context.logger.log_message_with_code(
|
|
40
|
+
context.logger.LogCode.DATA_EXTRACTION_FAILED
|
|
41
|
+
if hasattr(context.logger, 'LogCode')
|
|
42
|
+
else 'DATA_EXTRACTION_FAILED',
|
|
43
|
+
str(e),
|
|
44
|
+
)
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class InferenceDataExtractionStrategy(DataExtractionStrategy):
|
|
49
|
+
"""Strategy for extracting inference data from task data."""
|
|
50
|
+
|
|
51
|
+
def extract_data(self, context: ToTaskContext, task_data: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
|
|
52
|
+
"""Extract data needed for inference processing.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
context: Shared context for the action execution
|
|
56
|
+
task_data: The task data dictionary
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Tuple of (primary_file_url, inference_metadata)
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
# Reuse the file URL extraction logic
|
|
63
|
+
file_strategy = FileUrlExtractionStrategy()
|
|
64
|
+
primary_url = file_strategy.extract_data(context, task_data)
|
|
65
|
+
|
|
66
|
+
# Extract additional inference-specific metadata
|
|
67
|
+
data_unit = task_data.get('data_unit', {})
|
|
68
|
+
inference_metadata = {
|
|
69
|
+
'data_unit_id': data_unit.get('id'),
|
|
70
|
+
'task_id': task_data.get('id'),
|
|
71
|
+
'additional_params': context.params.get('inference_params', {}),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return primary_url, str(inference_metadata) if inference_metadata else None
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
context.logger.log_message_with_code(
|
|
78
|
+
context.logger.LogCode.DATA_EXTRACTION_FAILED
|
|
79
|
+
if hasattr(context.logger, 'LogCode')
|
|
80
|
+
else 'DATA_EXTRACTION_FAILED',
|
|
81
|
+
str(e),
|
|
82
|
+
)
|
|
83
|
+
return None, None
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Metrics and progress tracking strategies for ToTask action."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from ..enums import AnnotateTaskDataStatus, LogCode
|
|
6
|
+
from .base import MetricsStrategy, ToTaskContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ProgressTrackingStrategy(MetricsStrategy):
|
|
10
|
+
"""Strategy for tracking progress and metrics during task processing."""
|
|
11
|
+
|
|
12
|
+
def update_progress(self, context: ToTaskContext, current: int, total: int):
|
|
13
|
+
"""Update progress tracking.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
context: Shared context for the action execution
|
|
17
|
+
current: Current progress count
|
|
18
|
+
total: Total items to process
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
context.logger.set_progress(current, total, category='annotate_task_data')
|
|
22
|
+
except Exception as e:
|
|
23
|
+
context.logger.log_message_with_code(LogCode.PROGRESS_UPDATE_FAILED, str(e))
|
|
24
|
+
|
|
25
|
+
def record_task_result(self, context: ToTaskContext, task_id: int, success: bool, error: Optional[str] = None):
|
|
26
|
+
"""Record the result of processing a single task.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
context: Shared context for the action execution
|
|
30
|
+
task_id: The task ID that was processed
|
|
31
|
+
success: Whether the task processing was successful
|
|
32
|
+
error: Error message if unsuccessful
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
if success:
|
|
36
|
+
status = AnnotateTaskDataStatus.SUCCESS
|
|
37
|
+
log_data = {'task_id': task_id}
|
|
38
|
+
else:
|
|
39
|
+
status = AnnotateTaskDataStatus.FAILED
|
|
40
|
+
log_data = {'task_id': task_id, 'error': error or 'Unknown error'}
|
|
41
|
+
|
|
42
|
+
context.logger.log_annotate_task_data(log_data, status)
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
context.logger.log_message_with_code(LogCode.METRICS_RECORDING_FAILED, str(e))
|
|
46
|
+
|
|
47
|
+
def update_metrics(self, context: ToTaskContext, total_tasks: int, success_count: int, failed_count: int):
|
|
48
|
+
"""Update execution metrics.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
context: Shared context for the action execution
|
|
52
|
+
total_tasks: Total number of tasks
|
|
53
|
+
success_count: Number of successful tasks
|
|
54
|
+
failed_count: Number of failed tasks
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
stand_by_count = total_tasks - success_count - failed_count
|
|
58
|
+
context.update_metrics(success_count, failed_count, total_tasks)
|
|
59
|
+
|
|
60
|
+
# Update metrics in the logger
|
|
61
|
+
metrics_data = {
|
|
62
|
+
'stand_by': stand_by_count,
|
|
63
|
+
'failed': failed_count,
|
|
64
|
+
'success': success_count,
|
|
65
|
+
}
|
|
66
|
+
context.logger.log_metrics(metrics_data, 'annotate_task_data')
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
context.logger.log_message_with_code(LogCode.METRICS_UPDATE_FAILED, str(e))
|
|
70
|
+
|
|
71
|
+
def finalize_metrics(self, context: ToTaskContext):
|
|
72
|
+
"""Finalize metrics at the end of processing.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
context: Shared context for the action execution
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
metrics = context.metrics
|
|
79
|
+
total_tasks = len(context.task_ids)
|
|
80
|
+
|
|
81
|
+
context.logger.log_message_with_code(LogCode.ANNOTATION_COMPLETED, metrics.success, metrics.failed)
|
|
82
|
+
|
|
83
|
+
# Final progress update - only set completion if some tasks succeeded
|
|
84
|
+
if metrics.success > 0:
|
|
85
|
+
# Success: Set completion progress
|
|
86
|
+
self.update_progress(context, total_tasks, total_tasks)
|
|
87
|
+
else:
|
|
88
|
+
# Failure: Mark as failed (all tasks failed)
|
|
89
|
+
context.logger.set_progress_failed(category='annotate_task_data')
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
context.logger.log_message_with_code(LogCode.METRICS_FINALIZATION_FAILED, str(e))
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Pre-processor management strategies for ToTask action."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, Dict, List
|
|
5
|
+
|
|
6
|
+
from .base import PreProcessorStrategy, ToTaskContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PreProcessorManagementStrategy(PreProcessorStrategy):
|
|
10
|
+
"""Strategy for managing pre-processor lifecycle."""
|
|
11
|
+
|
|
12
|
+
def _get_preprocessor_config(self, context: ToTaskContext, preprocessor_id: int) -> Dict[str, Any]:
|
|
13
|
+
"""Retrieve pre-processor configuration from the backend.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
context: Shared context for the action execution
|
|
17
|
+
preprocessor_id: The pre-processor ID
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Dict with 'success', 'config', 'version', and optional 'error'
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
client = context.client
|
|
24
|
+
pre_processor_response = client.get_plugin_release(preprocessor_id)
|
|
25
|
+
|
|
26
|
+
if isinstance(pre_processor_response, str):
|
|
27
|
+
return {'success': False, 'error': 'Invalid pre-processor response received'}
|
|
28
|
+
|
|
29
|
+
if not isinstance(pre_processor_response, dict):
|
|
30
|
+
return {'success': False, 'error': 'Unexpected pre-processor response format'}
|
|
31
|
+
|
|
32
|
+
config = pre_processor_response.get('config', {})
|
|
33
|
+
version = pre_processor_response.get('version')
|
|
34
|
+
|
|
35
|
+
return {'success': True, 'config': config, 'version': version}
|
|
36
|
+
|
|
37
|
+
except Exception as e:
|
|
38
|
+
return {'success': False, 'error': f'Failed to get pre-processor config: {str(e)}'}
|
|
39
|
+
|
|
40
|
+
def get_preprocessor_info(self, context: ToTaskContext, preprocessor_id: int) -> Dict[str, Any]:
|
|
41
|
+
"""Get pre-processor information from the backend.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
context: Shared context for the action execution
|
|
45
|
+
preprocessor_id: The pre-processor ID
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dict with pre-processor info or error
|
|
49
|
+
"""
|
|
50
|
+
config_result = self._get_preprocessor_config(context, preprocessor_id)
|
|
51
|
+
if not config_result['success']:
|
|
52
|
+
return config_result
|
|
53
|
+
|
|
54
|
+
config = config_result['config']
|
|
55
|
+
code = config.get('code')
|
|
56
|
+
version = config_result['version']
|
|
57
|
+
|
|
58
|
+
if not code or not version:
|
|
59
|
+
return {'success': False, 'error': 'Invalid pre-processor configuration'}
|
|
60
|
+
|
|
61
|
+
return {'success': True, 'code': code, 'version': version}
|
|
62
|
+
|
|
63
|
+
def _get_running_serve_apps(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
|
|
64
|
+
"""Get list of running serve applications for a preprocessor.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
context: Shared context for the action execution
|
|
68
|
+
preprocessor_code: The pre-processor code
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Dict with 'success', 'running_apps' (list), and optional 'error'
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
client = context.client
|
|
75
|
+
list_serve_applications_params = {
|
|
76
|
+
'plugin_code': preprocessor_code,
|
|
77
|
+
'job__agent': context.params.get('agent') if context.params else None,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
serve_applications_response = client.list_serve_applications(params=list_serve_applications_params)
|
|
81
|
+
if isinstance(serve_applications_response, str):
|
|
82
|
+
return {'success': False, 'running_apps': [], 'error': 'Invalid serve applications response'}
|
|
83
|
+
|
|
84
|
+
if not isinstance(serve_applications_response, dict):
|
|
85
|
+
return {'success': False, 'running_apps': [], 'error': 'Unexpected serve applications response format'}
|
|
86
|
+
|
|
87
|
+
results = serve_applications_response.get('results', [])
|
|
88
|
+
running_apps: List[Dict[str, Any]] = [
|
|
89
|
+
app for app in results if isinstance(app, dict) and app.get('status') == 'RUNNING'
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
return {'success': True, 'running_apps': running_apps}
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
return {'success': False, 'running_apps': [], 'error': f'Failed to get running serve apps: {str(e)}'}
|
|
96
|
+
|
|
97
|
+
def ensure_preprocessor_running(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
|
|
98
|
+
"""Ensure the pre-processor is running, restart if necessary.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
context: Shared context for the action execution
|
|
102
|
+
preprocessor_code: The pre-processor code
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Dict indicating success or failure
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
# Check if pre-processor is already running
|
|
109
|
+
result = self._get_running_serve_apps(context, preprocessor_code)
|
|
110
|
+
if not result['success']:
|
|
111
|
+
return {'success': False, 'error': result.get('error', 'Failed to check running apps')}
|
|
112
|
+
|
|
113
|
+
if result['running_apps']:
|
|
114
|
+
return {'success': True}
|
|
115
|
+
|
|
116
|
+
# If not running, restart the pre-processor
|
|
117
|
+
restart_result = self._restart_preprocessor(context, preprocessor_code)
|
|
118
|
+
if not restart_result['success']:
|
|
119
|
+
return restart_result
|
|
120
|
+
|
|
121
|
+
return {'success': True}
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
return {'success': False, 'error': f'Failed to ensure pre-processor running: {str(e)}'}
|
|
125
|
+
|
|
126
|
+
def _restart_preprocessor(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
|
|
127
|
+
"""Restart the pre-processor and wait for it to be running.
|
|
128
|
+
|
|
129
|
+
Starts the pre-processor deployment and polls for up to 3 minutes
|
|
130
|
+
to verify it is running, logging progress messages during the wait.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
context: Shared context for the action execution
|
|
134
|
+
preprocessor_code: The pre-processor code
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Dict indicating success or failure
|
|
138
|
+
"""
|
|
139
|
+
MAX_WAIT_SECONDS = 180 # 3 minutes
|
|
140
|
+
POLL_INTERVAL_SECONDS = 10
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
# Start deployment
|
|
144
|
+
start_result = self._start_preprocessor_deployment(context, preprocessor_code)
|
|
145
|
+
if not start_result['success']:
|
|
146
|
+
return start_result
|
|
147
|
+
|
|
148
|
+
context.logger.log_message('Pre-processor deployment started, waiting for it to be ready...')
|
|
149
|
+
|
|
150
|
+
# Poll for running status with logging
|
|
151
|
+
return self._wait_for_preprocessor_ready(
|
|
152
|
+
context, preprocessor_code, MAX_WAIT_SECONDS, POLL_INTERVAL_SECONDS
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
return {'success': False, 'error': f'Failed to restart pre-processor: {str(e)}'}
|
|
157
|
+
|
|
158
|
+
def _start_preprocessor_deployment(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
|
|
159
|
+
"""Start the pre-processor deployment.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
context: Shared context for the action execution
|
|
163
|
+
preprocessor_code: The pre-processor code
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Dict indicating success or failure with optional job_id
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
# Retrieve Pre-Processor Configuration
|
|
170
|
+
pre_processor_id = context.params.get('pre_processor')
|
|
171
|
+
if not pre_processor_id:
|
|
172
|
+
return {'success': False, 'error': 'No pre-processor ID provided'}
|
|
173
|
+
|
|
174
|
+
config_result = self._get_preprocessor_config(context, pre_processor_id)
|
|
175
|
+
if not config_result['success']:
|
|
176
|
+
return config_result
|
|
177
|
+
|
|
178
|
+
config = config_result['config']
|
|
179
|
+
inference_config = config.get('actions', {}).get('inference', {})
|
|
180
|
+
required_resources = inference_config.get('required_resources', {})
|
|
181
|
+
|
|
182
|
+
# Build deployment payload
|
|
183
|
+
serve_application_deployment_payload = {
|
|
184
|
+
'agent': context.params.get('agent') if context.params else None,
|
|
185
|
+
'action': 'deployment',
|
|
186
|
+
'params': {
|
|
187
|
+
'num_cpus': required_resources.get('required_cpu_count', 1),
|
|
188
|
+
'num_gpus': required_resources.get('required_gpu_count', 0.1),
|
|
189
|
+
},
|
|
190
|
+
'debug': True,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
deployment_result = context.client.run_plugin(
|
|
194
|
+
preprocessor_code,
|
|
195
|
+
serve_application_deployment_payload,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
deployment_job_id = deployment_result.get('job_id')
|
|
199
|
+
if not deployment_job_id:
|
|
200
|
+
return {'success': False, 'error': 'No deployment job ID returned'}
|
|
201
|
+
|
|
202
|
+
return {'success': True, 'job_id': deployment_job_id}
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return {'success': False, 'error': f'Failed to start deployment: {str(e)}'}
|
|
206
|
+
|
|
207
|
+
def _wait_for_preprocessor_ready(
|
|
208
|
+
self,
|
|
209
|
+
context: ToTaskContext,
|
|
210
|
+
preprocessor_code: str,
|
|
211
|
+
max_wait_seconds: int,
|
|
212
|
+
poll_interval_seconds: int,
|
|
213
|
+
) -> Dict[str, Any]:
|
|
214
|
+
"""Wait for the pre-processor to be running with polling and logging.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
context: Shared context for the action execution
|
|
218
|
+
preprocessor_code: The pre-processor code
|
|
219
|
+
max_wait_seconds: Maximum time to wait in seconds
|
|
220
|
+
poll_interval_seconds: Interval between polling attempts in seconds
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Dict indicating success or failure
|
|
224
|
+
"""
|
|
225
|
+
max_attempts = max_wait_seconds // poll_interval_seconds
|
|
226
|
+
|
|
227
|
+
for attempt in range(max_attempts):
|
|
228
|
+
elapsed_seconds = attempt * poll_interval_seconds
|
|
229
|
+
context.logger.log_message(
|
|
230
|
+
f'Waiting for pre-processor to start... ({elapsed_seconds}s / {max_wait_seconds}s)'
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
time.sleep(poll_interval_seconds)
|
|
234
|
+
|
|
235
|
+
result = self._get_running_serve_apps(context, preprocessor_code)
|
|
236
|
+
if not result['success']:
|
|
237
|
+
continue # Keep trying on transient errors
|
|
238
|
+
|
|
239
|
+
if result['running_apps']:
|
|
240
|
+
context.logger.log_message('Pre-processor started successfully')
|
|
241
|
+
return {'success': True}
|
|
242
|
+
|
|
243
|
+
return {'success': False, 'error': f'Pre-processor failed to start within {max_wait_seconds} seconds'}
|