dtlpy 1.90.39__py3-none-any.whl → 1.92.18__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.
- dtlpy/__init__.py +5 -2
- dtlpy/__version__.py +1 -1
- dtlpy/assets/lock_open.png +0 -0
- dtlpy/entities/__init__.py +1 -1
- dtlpy/entities/analytic.py +118 -98
- dtlpy/entities/annotation.py +22 -31
- dtlpy/entities/annotation_collection.py +19 -21
- dtlpy/entities/app.py +13 -3
- dtlpy/entities/assignment.py +6 -0
- dtlpy/entities/base_entity.py +0 -23
- dtlpy/entities/command.py +3 -2
- dtlpy/entities/dataset.py +53 -3
- dtlpy/entities/dpk.py +15 -0
- dtlpy/entities/execution.py +13 -1
- dtlpy/entities/feature_set.py +3 -0
- dtlpy/entities/filters.py +87 -8
- dtlpy/entities/integration.py +1 -1
- dtlpy/entities/item.py +41 -1
- dtlpy/entities/node.py +49 -3
- dtlpy/entities/ontology.py +62 -5
- dtlpy/entities/package_function.py +2 -0
- dtlpy/entities/package_module.py +13 -0
- dtlpy/entities/pipeline.py +20 -1
- dtlpy/entities/pipeline_execution.py +37 -6
- dtlpy/entities/prompt_item.py +240 -27
- dtlpy/entities/recipe.py +37 -0
- dtlpy/entities/service.py +33 -4
- dtlpy/ml/base_model_adapter.py +166 -18
- dtlpy/new_instance.py +80 -9
- dtlpy/repositories/apps.py +68 -22
- dtlpy/repositories/assignments.py +1 -1
- dtlpy/repositories/commands.py +10 -2
- dtlpy/repositories/datasets.py +143 -13
- dtlpy/repositories/dpks.py +34 -1
- dtlpy/repositories/executions.py +27 -30
- dtlpy/repositories/feature_sets.py +23 -3
- dtlpy/repositories/features.py +4 -1
- dtlpy/repositories/models.py +1 -1
- dtlpy/repositories/packages.py +6 -3
- dtlpy/repositories/pipeline_executions.py +58 -5
- dtlpy/repositories/services.py +28 -7
- dtlpy/repositories/tasks.py +8 -2
- dtlpy/repositories/uploader.py +5 -2
- dtlpy/services/api_client.py +74 -12
- {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/METADATA +2 -2
- {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/RECORD +54 -57
- tests/features/environment.py +67 -1
- dtlpy/callbacks/__init__.py +0 -16
- dtlpy/callbacks/piper_progress_reporter.py +0 -29
- dtlpy/callbacks/progress_viewer.py +0 -54
- {dtlpy-1.90.39.data → dtlpy-1.92.18.data}/scripts/dlp +0 -0
- {dtlpy-1.90.39.data → dtlpy-1.92.18.data}/scripts/dlp.bat +0 -0
- {dtlpy-1.90.39.data → dtlpy-1.92.18.data}/scripts/dlp.py +0 -0
- {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/LICENSE +0 -0
- {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/WHEEL +0 -0
- {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/entry_points.txt +0 -0
- {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/top_level.txt +0 -0
dtlpy/entities/package_module.py
CHANGED
|
@@ -75,7 +75,20 @@ class PackageModule(entities.DlEntity):
|
|
|
75
75
|
for cls_name, cls_inst in inspect.getmembers(file_module, predicate=inspect.isclass):
|
|
76
76
|
spec = getattr(cls_inst, '__dtlpy__', None)
|
|
77
77
|
if spec is not None:
|
|
78
|
+
functions = spec['functions']
|
|
79
|
+
available_methods = [name for name in ['train', 'predict']
|
|
80
|
+
if 'BaseModelAdapter' not in getattr(cls_inst, name).__qualname__]
|
|
81
|
+
if "train" not in available_methods:
|
|
82
|
+
# remove train_model from functions list if train is not available
|
|
83
|
+
functions[:] = [d for d in functions if d.get('name') != "train_model"]
|
|
84
|
+
if "predict" not in available_methods:
|
|
85
|
+
# remove predict_items from functions list if predict is not available
|
|
86
|
+
functions[:] = [d for d in functions if d.get('name') != "predict_items"]
|
|
87
|
+
if "extract_features" not in available_methods:
|
|
88
|
+
# remove extract_item_features from functions list if extract_features is not available
|
|
89
|
+
functions[:] = [d for d in functions if d.get('name') != "extract_item_features"]
|
|
78
90
|
spec['entryPoint'] = entry_point
|
|
91
|
+
spec['functions'] = functions
|
|
79
92
|
module = cls.from_json(spec)
|
|
80
93
|
break
|
|
81
94
|
if module is None:
|
dtlpy/entities/pipeline.py
CHANGED
|
@@ -456,7 +456,6 @@ class Pipeline(entities.BaseEntity):
|
|
|
456
456
|
"""
|
|
457
457
|
return self.pipelines.update(pipeline=self)
|
|
458
458
|
|
|
459
|
-
|
|
460
459
|
def delete(self):
|
|
461
460
|
"""
|
|
462
461
|
Delete pipeline object
|
|
@@ -560,3 +559,23 @@ class Pipeline(entities.BaseEntity):
|
|
|
560
559
|
else:
|
|
561
560
|
self.start_nodes = [{"nodeId": node.node_id,
|
|
562
561
|
"type": "root", }]
|
|
562
|
+
|
|
563
|
+
def update_variables_values(self, **kwargs):
|
|
564
|
+
"""
|
|
565
|
+
Update pipeline variables values for the given keyword arguments.
|
|
566
|
+
|
|
567
|
+
**Example**:
|
|
568
|
+
|
|
569
|
+
.. code-block:: python
|
|
570
|
+
pipeline.update_variables_values(
|
|
571
|
+
dataset=dataset.id,
|
|
572
|
+
model=model.id,
|
|
573
|
+
threshold=0.9
|
|
574
|
+
)
|
|
575
|
+
pipeline.update()
|
|
576
|
+
"""
|
|
577
|
+
keys = kwargs.keys()
|
|
578
|
+
for variable in self.variables:
|
|
579
|
+
if variable.name in keys:
|
|
580
|
+
variable.value = kwargs[variable.name]
|
|
581
|
+
|
|
@@ -11,6 +11,16 @@ from ..services.api_client import ApiClient
|
|
|
11
11
|
logger = logging.getLogger(name='dtlpy')
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
class PipelineExecutionStatus(str, Enum):
|
|
15
|
+
PENDING = "pending"
|
|
16
|
+
IN_PROGRESS = "in-progress"
|
|
17
|
+
FAILED = "failed"
|
|
18
|
+
SUCCESS = "success"
|
|
19
|
+
QUEUE = "queue"
|
|
20
|
+
TERMINATED = "terminated"
|
|
21
|
+
RERUN = "rerun"
|
|
22
|
+
|
|
23
|
+
|
|
14
24
|
class CycleRerunMethod(str, Enum):
|
|
15
25
|
START_FROM_NODES = 'startFromNodes',
|
|
16
26
|
START_FROM_FAILED_EXECUTIONS = 'startFromFailedExecutions',
|
|
@@ -76,6 +86,7 @@ class PipelineExecution(entities.BaseEntity):
|
|
|
76
86
|
|
|
77
87
|
# sdk
|
|
78
88
|
_pipeline = attr.ib(repr=False)
|
|
89
|
+
_project = attr.ib(repr=False)
|
|
79
90
|
_client_api = attr.ib(type=ApiClient, repr=False)
|
|
80
91
|
_repositories = attr.ib(repr=False)
|
|
81
92
|
|
|
@@ -103,7 +114,7 @@ class PipelineExecution(entities.BaseEntity):
|
|
|
103
114
|
return status, pipeline
|
|
104
115
|
|
|
105
116
|
@classmethod
|
|
106
|
-
def from_json(cls, _json, client_api, pipeline, is_fetched=True):
|
|
117
|
+
def from_json(cls, _json, client_api, pipeline, is_fetched=True) -> 'PipelineExecution':
|
|
107
118
|
"""
|
|
108
119
|
Turn platform representation of pipeline_execution into a pipeline_execution entity
|
|
109
120
|
|
|
@@ -112,9 +123,11 @@ class PipelineExecution(entities.BaseEntity):
|
|
|
112
123
|
:param dtlpy.entities.pipeline.Pipeline pipeline: Pipeline entity
|
|
113
124
|
:param bool is_fetched: is Entity fetched from Platform
|
|
114
125
|
:return: Pipeline entity
|
|
115
|
-
:rtype: dtlpy.entities.
|
|
126
|
+
:rtype: dtlpy.entities.PipelineExecution
|
|
116
127
|
"""
|
|
128
|
+
project = None
|
|
117
129
|
if pipeline is not None:
|
|
130
|
+
project = pipeline._project
|
|
118
131
|
if pipeline.id != _json.get('pipelineId', None):
|
|
119
132
|
logger.warning('Pipeline has been fetched from a project that is not belong to it')
|
|
120
133
|
pipeline = None
|
|
@@ -132,6 +145,7 @@ class PipelineExecution(entities.BaseEntity):
|
|
|
132
145
|
nodes=nodes,
|
|
133
146
|
executions=_json.get('executions', dict()),
|
|
134
147
|
pipeline=pipeline,
|
|
148
|
+
project=project,
|
|
135
149
|
client_api=client_api,
|
|
136
150
|
)
|
|
137
151
|
|
|
@@ -183,7 +197,10 @@ class PipelineExecution(entities.BaseEntity):
|
|
|
183
197
|
|
|
184
198
|
@property
|
|
185
199
|
def project(self):
|
|
186
|
-
|
|
200
|
+
if self._project is None:
|
|
201
|
+
self._project = self.pipeline.project
|
|
202
|
+
assert isinstance(self._pipeline.project, entities.Project)
|
|
203
|
+
return self._pipeline.project
|
|
187
204
|
|
|
188
205
|
################
|
|
189
206
|
# repositories #
|
|
@@ -195,9 +212,10 @@ class PipelineExecution(entities.BaseEntity):
|
|
|
195
212
|
|
|
196
213
|
r = reps(
|
|
197
214
|
projects=repositories.Projects(client_api=self._client_api),
|
|
198
|
-
pipelines=repositories.Pipelines(client_api=self._client_api, project=self.
|
|
199
|
-
pipeline_executions=repositories.PipelineExecutions(client_api=self._client_api,
|
|
200
|
-
|
|
215
|
+
pipelines=repositories.Pipelines(client_api=self._client_api, project=self._project),
|
|
216
|
+
pipeline_executions=repositories.PipelineExecutions(client_api=self._client_api,
|
|
217
|
+
project=self._project,
|
|
218
|
+
pipeline=self._pipeline)
|
|
201
219
|
)
|
|
202
220
|
return r
|
|
203
221
|
|
|
@@ -246,3 +264,16 @@ class PipelineExecution(entities.BaseEntity):
|
|
|
246
264
|
filters=filters,
|
|
247
265
|
wait=wait
|
|
248
266
|
)
|
|
267
|
+
|
|
268
|
+
def wait(self):
|
|
269
|
+
"""
|
|
270
|
+
Wait for pipeline execution
|
|
271
|
+
|
|
272
|
+
:return: Pipeline execution object
|
|
273
|
+
"""
|
|
274
|
+
return self.pipeline_executions.wait(pipeline_execution_id=self.id)
|
|
275
|
+
|
|
276
|
+
def in_progress(self):
|
|
277
|
+
return self.status not in [PipelineExecutionStatus.FAILED,
|
|
278
|
+
PipelineExecutionStatus.SUCCESS,
|
|
279
|
+
PipelineExecutionStatus.TERMINATED]
|
dtlpy/entities/prompt_item.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import io
|
|
3
2
|
import enum
|
|
4
3
|
import json
|
|
4
|
+
import os.path
|
|
5
|
+
from dtlpy import entities, repositories
|
|
6
|
+
from dtlpy.services.api_client import client as client_api
|
|
7
|
+
import base64
|
|
8
|
+
import requests
|
|
5
9
|
|
|
6
10
|
logger = logging.getLogger(name='dtlpy')
|
|
7
11
|
|
|
@@ -11,27 +15,37 @@ class PromptType(str, enum.Enum):
|
|
|
11
15
|
IMAGE = 'image/*'
|
|
12
16
|
AUDIO = 'audio/*'
|
|
13
17
|
VIDEO = 'video/*'
|
|
18
|
+
METADATA = 'metadata'
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class Prompt:
|
|
17
|
-
def __init__(self, key):
|
|
22
|
+
def __init__(self, key, role='user'):
|
|
18
23
|
"""
|
|
19
24
|
Create a single Prompt. Prompt can contain multiple mimetype elements, e.g. text sentence and an image.
|
|
20
|
-
|
|
21
25
|
:param key: unique identifier of the prompt in the item
|
|
22
26
|
"""
|
|
23
27
|
self.key = key
|
|
24
28
|
self.elements = list()
|
|
29
|
+
self._items = repositories.Items(client_api=client_api)
|
|
30
|
+
self.metadata = {'role': role}
|
|
25
31
|
|
|
26
|
-
def
|
|
32
|
+
def add_element(self, value, mimetype='application/text'):
|
|
27
33
|
"""
|
|
28
34
|
|
|
29
35
|
:param value: url or string of the input
|
|
30
36
|
:param mimetype: mimetype of the input. options: `text`, `image/*`, `video/*`, `audio/*`
|
|
31
37
|
:return:
|
|
32
38
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
allowed_prompt_types = [prompt_type for prompt_type in PromptType]
|
|
40
|
+
if mimetype not in allowed_prompt_types:
|
|
41
|
+
raise ValueError(f'Invalid mimetype: {mimetype}. Allowed values: {allowed_prompt_types}')
|
|
42
|
+
if not isinstance(value, str) and mimetype != PromptType.METADATA:
|
|
43
|
+
raise ValueError(f'Expected str for Prompt element value, got {type(value)} instead')
|
|
44
|
+
if mimetype == PromptType.METADATA and isinstance(value, dict):
|
|
45
|
+
self.metadata.update(value)
|
|
46
|
+
else:
|
|
47
|
+
self.elements.append({'mimetype': mimetype,
|
|
48
|
+
'value': value})
|
|
35
49
|
|
|
36
50
|
def to_json(self):
|
|
37
51
|
"""
|
|
@@ -39,26 +53,169 @@ class Prompt:
|
|
|
39
53
|
|
|
40
54
|
:return:
|
|
41
55
|
"""
|
|
56
|
+
elements_json = [
|
|
57
|
+
{
|
|
58
|
+
"mimetype": e['mimetype'],
|
|
59
|
+
"value": e['value'],
|
|
60
|
+
} for e in self.elements
|
|
61
|
+
]
|
|
62
|
+
elements_json.append({
|
|
63
|
+
"mimetype": PromptType.METADATA,
|
|
64
|
+
"value": self.metadata
|
|
65
|
+
})
|
|
42
66
|
return {
|
|
43
|
-
self.key:
|
|
44
|
-
{
|
|
45
|
-
"mimetype": e['mimetype'],
|
|
46
|
-
"value": e['value']
|
|
47
|
-
} for e in self.elements
|
|
48
|
-
]
|
|
67
|
+
self.key: elements_json
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
class PromptItem:
|
|
53
|
-
def __init__(self, name):
|
|
70
|
+
def _convert_stream_to_binary(self, image_url: str):
|
|
54
71
|
"""
|
|
55
|
-
|
|
72
|
+
Convert a stream to binary
|
|
73
|
+
:param image_url: dataloop image stream url
|
|
74
|
+
:return: binary object
|
|
75
|
+
"""
|
|
76
|
+
image_buffer = None
|
|
77
|
+
if '.' in image_url and 'dataloop.ai' not in image_url:
|
|
78
|
+
# URL and not DL item stream
|
|
79
|
+
try:
|
|
80
|
+
response = requests.get(image_url, stream=True)
|
|
81
|
+
response.raise_for_status() # Raise an exception for bad status codes
|
|
82
|
+
|
|
83
|
+
# Check for valid image content type
|
|
84
|
+
if response.headers["Content-Type"].startswith("image/"):
|
|
85
|
+
# Read the image data in chunks to avoid loading large images in memory
|
|
86
|
+
image_buffer = b"".join(chunk for chunk in response.iter_content(1024))
|
|
87
|
+
except requests.exceptions.RequestException as e:
|
|
88
|
+
logger.error(f"Failed to download image from URL: {image_url}, error: {e}")
|
|
56
89
|
|
|
57
|
-
|
|
90
|
+
elif '.' in image_url and 'stream' in image_url:
|
|
91
|
+
# DL Stream URL
|
|
92
|
+
item_id = image_url.split("/stream")[0].split("/items/")[-1]
|
|
93
|
+
image_buffer = self._items.get(item_id=item_id).download(save_locally=False).getvalue()
|
|
94
|
+
else:
|
|
95
|
+
# DL item ID
|
|
96
|
+
image_buffer = self._items.get(item_id=image_url).download(save_locally=False).getvalue()
|
|
97
|
+
|
|
98
|
+
if image_buffer is not None:
|
|
99
|
+
encoded_image = base64.b64encode(image_buffer).decode()
|
|
100
|
+
else:
|
|
101
|
+
logger.error(f'Invalid image url: {image_url}')
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
return f'data:image/jpeg;base64,{encoded_image}'
|
|
105
|
+
|
|
106
|
+
def messages(self):
|
|
107
|
+
"""
|
|
108
|
+
return a list of messages in the prompt item,
|
|
109
|
+
messages are returned following the openai SDK format https://platform.openai.com/docs/guides/vision
|
|
58
110
|
"""
|
|
111
|
+
messages = []
|
|
112
|
+
for element in self.elements:
|
|
113
|
+
if element['mimetype'] == PromptType.TEXT:
|
|
114
|
+
data = {
|
|
115
|
+
"type": "text",
|
|
116
|
+
"text": element['value']
|
|
117
|
+
}
|
|
118
|
+
messages.append(data)
|
|
119
|
+
elif element['mimetype'] == PromptType.IMAGE:
|
|
120
|
+
image_url = self._convert_stream_to_binary(element['value'])
|
|
121
|
+
data = {
|
|
122
|
+
"type": "image_url",
|
|
123
|
+
"image_url": {
|
|
124
|
+
"url": image_url
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
messages.append(data)
|
|
128
|
+
elif element['mimetype'] == PromptType.AUDIO:
|
|
129
|
+
raise NotImplementedError('Audio prompt is not supported yet')
|
|
130
|
+
elif element['mimetype'] == PromptType.VIDEO:
|
|
131
|
+
raise NotImplementedError('Video prompt is not supported yet')
|
|
132
|
+
else:
|
|
133
|
+
raise ValueError(f'Invalid mimetype: {element["mimetype"]}')
|
|
134
|
+
return messages, self.key
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class PromptItem:
|
|
138
|
+
def __init__(self, name, item: entities.Item = None):
|
|
139
|
+
# prompt item name
|
|
59
140
|
self.name = name
|
|
60
|
-
|
|
141
|
+
# list of user prompts in the prompt item
|
|
61
142
|
self.prompts = list()
|
|
143
|
+
# list of assistant (annotations) prompts in the prompt item
|
|
144
|
+
self.assistant_prompts = dict()
|
|
145
|
+
# Dataloop Item
|
|
146
|
+
self._item = None
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def from_item(cls, item: entities.Item):
|
|
150
|
+
"""
|
|
151
|
+
Load a prompt item from the platform
|
|
152
|
+
:param item : Item object
|
|
153
|
+
:return: PromptItem object
|
|
154
|
+
"""
|
|
155
|
+
if 'json' not in item.mimetype or item.system.get('shebang', dict()).get('dltype') != 'prompt':
|
|
156
|
+
raise ValueError('Expecting a json item with system.shebang.dltype = prompt')
|
|
157
|
+
# Not using `save_locally=False` to use the from_local_file method
|
|
158
|
+
item_file_path = item.download()
|
|
159
|
+
prompt_item = cls.from_local_file(file_path=item_file_path)
|
|
160
|
+
if os.path.exists(item_file_path):
|
|
161
|
+
os.remove(item_file_path)
|
|
162
|
+
prompt_item._item = item
|
|
163
|
+
return prompt_item
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def from_local_file(cls, file_path):
|
|
167
|
+
"""
|
|
168
|
+
Create a new prompt item from a file
|
|
169
|
+
:param file_path: path to the file
|
|
170
|
+
:return: PromptItem object
|
|
171
|
+
"""
|
|
172
|
+
if os.path.exists(file_path) is False:
|
|
173
|
+
raise FileNotFoundError(f'File does not exists: {file_path}')
|
|
174
|
+
if 'json' not in os.path.splitext(file_path)[-1]:
|
|
175
|
+
raise ValueError(f'Expected path to json item, got {os.path.splitext(file_path)[-1]}')
|
|
176
|
+
prompt_item = cls(name=file_path)
|
|
177
|
+
with open(file_path, 'r') as f:
|
|
178
|
+
data = json.load(f)
|
|
179
|
+
for prompt_key, prompt_values in data.get('prompts', dict()).items():
|
|
180
|
+
prompt = Prompt(key=prompt_key)
|
|
181
|
+
for val in prompt_values:
|
|
182
|
+
if val['mimetype'] == PromptType.METADATA:
|
|
183
|
+
_ = val.pop('mimetype')
|
|
184
|
+
prompt.add_element(value=val, mimetype=PromptType.METADATA)
|
|
185
|
+
else:
|
|
186
|
+
prompt.add_element(mimetype=val['mimetype'], value=val['value'])
|
|
187
|
+
prompt_item.add_prompt(prompt=prompt, update_item=False)
|
|
188
|
+
return prompt_item
|
|
189
|
+
|
|
190
|
+
def get_assistant_messages(self, annotations: entities.AnnotationCollection):
|
|
191
|
+
"""
|
|
192
|
+
Get all the annotations in the item for the assistant messages
|
|
193
|
+
"""
|
|
194
|
+
# clearing the assistant prompts from previous annotations that might not belong
|
|
195
|
+
self.assistant_prompts = dict()
|
|
196
|
+
for annotation in annotations:
|
|
197
|
+
prompt_id = annotation.metadata.get('system', dict()).get('promptId', None)
|
|
198
|
+
if annotation.type == 'ref_image':
|
|
199
|
+
prompt = Prompt(key=prompt_id)
|
|
200
|
+
prompt.add_element(value=annotation.coordinates.get('ref'), mimetype=PromptType.IMAGE)
|
|
201
|
+
self.assistant_prompts[annotation.id] = prompt
|
|
202
|
+
elif annotation.type == 'text':
|
|
203
|
+
prompt = Prompt(key=prompt_id)
|
|
204
|
+
prompt.add_element(value=annotation.coordinates, mimetype=PromptType.TEXT)
|
|
205
|
+
self.assistant_prompts[annotation.id] = prompt
|
|
206
|
+
|
|
207
|
+
def get_assistant_prompts(self, model_name):
|
|
208
|
+
"""
|
|
209
|
+
Get assistant prompts
|
|
210
|
+
:return:
|
|
211
|
+
"""
|
|
212
|
+
if self._item is None:
|
|
213
|
+
logger.warning('Item is not loaded, skipping annotations context')
|
|
214
|
+
return
|
|
215
|
+
filters = entities.Filters(resource=entities.FiltersResource.ANNOTATION)
|
|
216
|
+
filters.add(field='metadata.user.model.name', values=model_name)
|
|
217
|
+
annotations = self._item.annotations.list(filters=filters)
|
|
218
|
+
self.get_assistant_messages(annotations=annotations)
|
|
62
219
|
|
|
63
220
|
def to_json(self):
|
|
64
221
|
"""
|
|
@@ -69,26 +226,82 @@ class PromptItem:
|
|
|
69
226
|
prompts_json = {
|
|
70
227
|
"shebang": "dataloop",
|
|
71
228
|
"metadata": {
|
|
72
|
-
"dltype":
|
|
229
|
+
"dltype": 'prompt'
|
|
73
230
|
},
|
|
74
231
|
"prompts": {}
|
|
75
232
|
}
|
|
76
233
|
for prompt in self.prompts:
|
|
77
234
|
for prompt_key, prompt_values in prompt.to_json().items():
|
|
78
235
|
prompts_json["prompts"][prompt_key] = prompt_values
|
|
79
|
-
|
|
236
|
+
prompts_json["prompts"][prompt_key].append({'metadata'})
|
|
80
237
|
|
|
81
|
-
|
|
82
|
-
byte_io = io.BytesIO()
|
|
83
|
-
byte_io.name = self.name
|
|
84
|
-
byte_io.write(json.dumps(self.to_json()).encode())
|
|
85
|
-
byte_io.seek(0)
|
|
86
|
-
return byte_io
|
|
238
|
+
return prompts_json
|
|
87
239
|
|
|
88
|
-
def
|
|
240
|
+
def add_prompt(self, prompt: Prompt, update_item=True):
|
|
89
241
|
"""
|
|
90
242
|
add a prompt to the prompt item
|
|
91
243
|
prompt: a dictionary. keys are prompt message id, values are prompt messages
|
|
92
244
|
responses: a list of annotations representing responses to the prompt
|
|
93
245
|
"""
|
|
94
246
|
self.prompts.append(prompt)
|
|
247
|
+
if update_item is True:
|
|
248
|
+
if self._item is not None:
|
|
249
|
+
self._item._Item__update_item_binary(_json=self.to_json())
|
|
250
|
+
else:
|
|
251
|
+
logger.warning('Item is not loaded, skipping upload')
|
|
252
|
+
|
|
253
|
+
def messages(self, model_name=None):
|
|
254
|
+
"""
|
|
255
|
+
return a list of messages in the prompt item
|
|
256
|
+
messages are returned following the openai SDK format
|
|
257
|
+
"""
|
|
258
|
+
if model_name is not None:
|
|
259
|
+
self.get_assistant_prompts(model_name=model_name)
|
|
260
|
+
else:
|
|
261
|
+
logger.warning('Model name is not provided, skipping assistant prompts')
|
|
262
|
+
|
|
263
|
+
all_prompts_messages = dict()
|
|
264
|
+
for prompt in self.prompts:
|
|
265
|
+
if prompt.key not in all_prompts_messages:
|
|
266
|
+
all_prompts_messages[prompt.key] = list()
|
|
267
|
+
prompt_messages, prompt_key = prompt.messages()
|
|
268
|
+
messages = {
|
|
269
|
+
'role': prompt.metadata.get('role', 'user'),
|
|
270
|
+
'content': prompt_messages
|
|
271
|
+
}
|
|
272
|
+
all_prompts_messages[prompt.key].append(messages)
|
|
273
|
+
|
|
274
|
+
for ann_id, prompt in self.assistant_prompts.items():
|
|
275
|
+
if prompt.key not in all_prompts_messages:
|
|
276
|
+
logger.warning(f'Prompt key {prompt.key} is not found in the user prompts, skipping Assistant prompt')
|
|
277
|
+
continue
|
|
278
|
+
prompt_messages, prompt_key = prompt.messages()
|
|
279
|
+
assistant_messages = {
|
|
280
|
+
'role': 'assistant',
|
|
281
|
+
'content': prompt_messages
|
|
282
|
+
}
|
|
283
|
+
all_prompts_messages[prompt.key].append(assistant_messages)
|
|
284
|
+
res = list()
|
|
285
|
+
for prompts in all_prompts_messages.values():
|
|
286
|
+
for prompt in prompts:
|
|
287
|
+
res.append(prompt)
|
|
288
|
+
return res
|
|
289
|
+
|
|
290
|
+
def add_responses(self, annotation: entities.BaseAnnotationDefinition, model: entities.Model):
|
|
291
|
+
"""
|
|
292
|
+
Add an annotation to the prompt item
|
|
293
|
+
:param annotation: Annotation object
|
|
294
|
+
:param model: Model object
|
|
295
|
+
"""
|
|
296
|
+
if self._item is None:
|
|
297
|
+
raise ValueError('Item is not loaded, cannot add annotation')
|
|
298
|
+
annotation_collection = entities.AnnotationCollection()
|
|
299
|
+
annotation_collection.add(annotation_definition=annotation,
|
|
300
|
+
prompt_id=self.prompts[-1].key,
|
|
301
|
+
model_info={
|
|
302
|
+
'name': model.name,
|
|
303
|
+
'model_id': model.id,
|
|
304
|
+
'confidence': 1.0
|
|
305
|
+
})
|
|
306
|
+
annotations = self._item.annotations.upload(annotations=annotation_collection)
|
|
307
|
+
self.get_assistant_messages(annotations=annotations)
|
dtlpy/entities/recipe.py
CHANGED
|
@@ -262,3 +262,40 @@ class Recipe(entities.BaseEntity):
|
|
|
262
262
|
'datasetId': dataset.id,
|
|
263
263
|
'name': instruction_item.name}
|
|
264
264
|
self.update(True)
|
|
265
|
+
|
|
266
|
+
def upload_annotations_verification_file(self, local_path: str, overwrite: bool = False) -> entities.Item:
|
|
267
|
+
"""
|
|
268
|
+
Add Annotations Verification js file to the recipe.
|
|
269
|
+
|
|
270
|
+
:param str local_path: file path of the annotations verification js file.
|
|
271
|
+
:param bool overwrite: overwrite exiting file if the local and the remote names are matching
|
|
272
|
+
:return: annotations verification js item.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
validation_file_metadata = self.metadata.get("system", dict()).get("validationFile", None)
|
|
276
|
+
if validation_file_metadata is None:
|
|
277
|
+
validation_file_metadata = dict()
|
|
278
|
+
|
|
279
|
+
remote_name = validation_file_metadata.get("name", None)
|
|
280
|
+
local_name = os.path.basename(local_path)
|
|
281
|
+
binaries_dataset = self._project.datasets._get_binaries_dataset()
|
|
282
|
+
remote_path = f"/.dataloop/recipes/{self.id}/verification/"
|
|
283
|
+
|
|
284
|
+
if remote_name is None or overwrite or remote_name != local_name:
|
|
285
|
+
validation_item = binaries_dataset.items.upload(
|
|
286
|
+
local_path=local_path,
|
|
287
|
+
remote_path=remote_path,
|
|
288
|
+
remote_name=local_name,
|
|
289
|
+
overwrite=True
|
|
290
|
+
)
|
|
291
|
+
self.metadata["system"]["validationFile"] = {
|
|
292
|
+
"itemId": validation_item.id,
|
|
293
|
+
"datasetId": binaries_dataset.id,
|
|
294
|
+
"name": local_name
|
|
295
|
+
}
|
|
296
|
+
self.update(system_metadata=True)
|
|
297
|
+
else:
|
|
298
|
+
logger.debug(f"Existing Annotations Validation Script was found.")
|
|
299
|
+
validation_item_id = self.metadata["system"]["validationFile"]["itemId"]
|
|
300
|
+
validation_item = binaries_dataset.items.get(item_id=validation_item_id)
|
|
301
|
+
return validation_item
|
dtlpy/entities/service.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import warnings
|
|
1
2
|
from collections import namedtuple
|
|
2
3
|
from enum import Enum
|
|
3
4
|
import traceback
|
|
@@ -219,6 +220,7 @@ class Service(entities.BaseEntity):
|
|
|
219
220
|
on_reset = attr.ib(type=OnResetAction)
|
|
220
221
|
_type = attr.ib(type=ServiceType)
|
|
221
222
|
project_id = attr.ib()
|
|
223
|
+
org_id = attr.ib()
|
|
222
224
|
is_global = attr.ib()
|
|
223
225
|
max_attempts = attr.ib()
|
|
224
226
|
mode = attr.ib(repr=False)
|
|
@@ -235,6 +237,8 @@ class Service(entities.BaseEntity):
|
|
|
235
237
|
_project = attr.ib(default=None, repr=False)
|
|
236
238
|
_repositories = attr.ib(repr=False)
|
|
237
239
|
updated_by = attr.ib(default=None)
|
|
240
|
+
app = attr.ib(default=None)
|
|
241
|
+
integrations = attr.ib(default=None)
|
|
238
242
|
|
|
239
243
|
@property
|
|
240
244
|
def createdAt(self):
|
|
@@ -334,6 +338,9 @@ class Service(entities.BaseEntity):
|
|
|
334
338
|
updated_by=_json.get('updatedBy', None),
|
|
335
339
|
config=_json.get('config', None),
|
|
336
340
|
settings=_json.get('settings', None),
|
|
341
|
+
app=_json.get('app', None),
|
|
342
|
+
integrations=_json.get('integrations', None),
|
|
343
|
+
org_id=_json.get('orgId', None)
|
|
337
344
|
)
|
|
338
345
|
inst.is_fetched = is_fetched
|
|
339
346
|
return inst
|
|
@@ -363,13 +370,25 @@ class Service(entities.BaseEntity):
|
|
|
363
370
|
def package(self):
|
|
364
371
|
if self._package is None:
|
|
365
372
|
try:
|
|
373
|
+
dpk_id = None
|
|
374
|
+
dpk_version = None
|
|
375
|
+
if self.app and isinstance(self.app, dict):
|
|
376
|
+
dpk_id = self.app.get('dpkId', None)
|
|
377
|
+
dpk_version = self.app.get('dpkVersion', None)
|
|
378
|
+
if dpk_id is None:
|
|
379
|
+
self._package = repositories.Dpks(client_api=self._client_api, project=self.project).get(
|
|
380
|
+
dpk_id=self.package_id)
|
|
381
|
+
else:
|
|
382
|
+
self._package = repositories.Dpks(client_api=self._client_api, project=self.project).get_revisions(
|
|
383
|
+
dpk_id=dpk_id,
|
|
384
|
+
version=dpk_version)
|
|
385
|
+
|
|
386
|
+
assert isinstance(self._package, entities.Dpk)
|
|
387
|
+
except:
|
|
366
388
|
self._package = repositories.Packages(client_api=self._client_api).get(package_id=self.package_id,
|
|
367
389
|
fetch=None,
|
|
368
390
|
log_error=False)
|
|
369
391
|
assert isinstance(self._package, entities.Package)
|
|
370
|
-
except:
|
|
371
|
-
self._package = repositories.Dpks(client_api=self._client_api).get(dpk_id=self.package_id)
|
|
372
|
-
assert isinstance(self._package, entities.Dpk)
|
|
373
392
|
return self._package
|
|
374
393
|
|
|
375
394
|
@property
|
|
@@ -462,11 +481,15 @@ class Service(entities.BaseEntity):
|
|
|
462
481
|
attr.fields(Service).archive,
|
|
463
482
|
attr.fields(Service).updated_by,
|
|
464
483
|
attr.fields(Service).config,
|
|
465
|
-
attr.fields(Service).settings
|
|
484
|
+
attr.fields(Service).settings,
|
|
485
|
+
attr.fields(Service).app,
|
|
486
|
+
attr.fields(Service).integrations,
|
|
487
|
+
attr.fields(Service).org_id
|
|
466
488
|
)
|
|
467
489
|
)
|
|
468
490
|
|
|
469
491
|
_json['projectId'] = self.project_id
|
|
492
|
+
_json['orgId'] = self.org_id
|
|
470
493
|
_json['packageId'] = self.package_id
|
|
471
494
|
_json['initParams'] = self.init_input
|
|
472
495
|
_json['moduleName'] = self.module_name
|
|
@@ -518,6 +541,12 @@ class Service(entities.BaseEntity):
|
|
|
518
541
|
if self.settings is not None:
|
|
519
542
|
_json['settings'] = self.settings
|
|
520
543
|
|
|
544
|
+
if self.app is not None:
|
|
545
|
+
_json['app'] = self.app
|
|
546
|
+
|
|
547
|
+
if self.integrations is not None:
|
|
548
|
+
_json['integrations'] = self.integrations
|
|
549
|
+
|
|
521
550
|
return _json
|
|
522
551
|
|
|
523
552
|
def update(self, force=False):
|