clarifai 11.2.3rc9__py3-none-any.whl → 11.2.4rc2__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.
- clarifai/__init__.py +1 -1
- clarifai/client/model_client.py +8 -9
- clarifai/runners/models/model_builder.py +55 -0
- clarifai/runners/models/model_class.py +5 -0
- clarifai/runners/utils/data_utils.py +66 -16
- clarifai/runners/utils/method_signatures.py +8 -0
- clarifai/runners/utils/openai_convertor.py +227 -0
- clarifai/runners/utils/openai_format.py +87 -0
- {clarifai-11.2.3rc9.dist-info → clarifai-11.2.4rc2.dist-info}/METADATA +13 -2
- {clarifai-11.2.3rc9.dist-info → clarifai-11.2.4rc2.dist-info}/RECORD +14 -12
- {clarifai-11.2.3rc9.dist-info → clarifai-11.2.4rc2.dist-info}/WHEEL +1 -1
- {clarifai-11.2.3rc9.dist-info → clarifai-11.2.4rc2.dist-info}/LICENSE +0 -0
- {clarifai-11.2.3rc9.dist-info → clarifai-11.2.4rc2.dist-info}/entry_points.txt +0 -0
- {clarifai-11.2.3rc9.dist-info → clarifai-11.2.4rc2.dist-info}/top_level.txt +0 -0
clarifai/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "11.2.
|
1
|
+
__version__ = "11.2.4rc2"
|
clarifai/client/model_client.py
CHANGED
@@ -60,28 +60,28 @@ class ModelClient:
|
|
60
60
|
Dict: The method signatures.
|
61
61
|
'''
|
62
62
|
try:
|
63
|
-
response = self.
|
63
|
+
response = self.STUB.GetModelVersion(
|
64
64
|
service_pb2.GetModelVersionRequest(
|
65
65
|
user_app_id=self.request_template.user_app_id,
|
66
|
-
model_id=
|
67
|
-
version_id=self.request_template.
|
66
|
+
model_id=self.request_template.model_id,
|
67
|
+
version_id=self.request_template.version_id,
|
68
68
|
))
|
69
|
+
|
69
70
|
method_signatures = None
|
70
71
|
if response.status.code == status_code_pb2.SUCCESS:
|
71
72
|
method_signatures = response.model_version.method_signatures
|
72
73
|
if response.status.code != status_code_pb2.SUCCESS:
|
73
74
|
raise Exception(f"Model failed with response {response!r}")
|
74
|
-
self._method_signatures= {}
|
75
|
+
self._method_signatures = {}
|
75
76
|
for method_signature in method_signatures:
|
76
77
|
method_name = method_signature.name
|
77
78
|
# check for duplicate method names
|
78
79
|
if method_name in self._method_signatures:
|
79
80
|
raise ValueError(f"Duplicate method name {method_name}")
|
80
81
|
self._method_signatures[method_name] = method_signature
|
81
|
-
if not self._method_signatures:
|
82
|
+
if not self._method_signatures: # if no method signatures, try to fetch from the model
|
82
83
|
self._fetch_signatures_backup()
|
83
|
-
except Exception
|
84
|
-
logger.info(f"Failed to fetch method signatures from model: {e}")
|
84
|
+
except Exception:
|
85
85
|
# try to fetch from the model
|
86
86
|
self._fetch_signatures_backup()
|
87
87
|
if not self._method_signatures:
|
@@ -259,7 +259,6 @@ class ModelClient:
|
|
259
259
|
inputs, # TODO set up functions according to fetched signatures?
|
260
260
|
method_name: str = 'predict',
|
261
261
|
) -> Any:
|
262
|
-
|
263
262
|
input_signature = self._method_signatures[method_name].input_fields
|
264
263
|
output_signature = self._method_signatures[method_name].output_fields
|
265
264
|
|
@@ -328,7 +327,7 @@ class ModelClient:
|
|
328
327
|
response = self.STUB.PostModelOutputs(request)
|
329
328
|
if status_is_retryable(
|
330
329
|
response.status.code) and time.time() - start_time < 60 * 10: # 10 minutes
|
331
|
-
logger.info(
|
330
|
+
logger.info("Model is still deploying, please wait...")
|
332
331
|
time.sleep(next(backoff_iterator))
|
333
332
|
continue
|
334
333
|
|
@@ -23,6 +23,7 @@ from clarifai.runners.utils.const import (
|
|
23
23
|
DEFAULT_DOWNLOAD_CHECKPOINT_WHEN, DEFAULT_PYTHON_VERSION, DEFAULT_RUNTIME_DOWNLOAD_PATH,
|
24
24
|
PYTHON_BASE_IMAGE, TORCH_BASE_IMAGE)
|
25
25
|
from clarifai.runners.utils.loader import HuggingFaceLoader
|
26
|
+
from clarifai.runners.utils import data_utils
|
26
27
|
from clarifai.runners.utils.method_signatures import signatures_to_yaml
|
27
28
|
from clarifai.urls.helper import ClarifaiUrlHelper
|
28
29
|
from clarifai.utils.logging import logger
|
@@ -336,6 +337,16 @@ class ModelBuilder:
|
|
336
337
|
method_info = model_class._get_method_info()
|
337
338
|
signatures = [method.signature for method in method_info.values()]
|
338
339
|
return signatures
|
340
|
+
|
341
|
+
def get_methods_defaults(self):
|
342
|
+
"""
|
343
|
+
Returns the inference parameters for the model class.
|
344
|
+
"""
|
345
|
+
model_class = self.load_model_class(mocking=True)
|
346
|
+
method_info = model_class._get_method_info()
|
347
|
+
python_param_defaults = [method.python_param_types for method in method_info.values()]
|
348
|
+
return python_param_defaults
|
349
|
+
|
339
350
|
|
340
351
|
@property
|
341
352
|
def client(self):
|
@@ -622,14 +633,58 @@ class ModelBuilder:
|
|
622
633
|
concepts = config.get('concepts')
|
623
634
|
logger.info(f"Updated config.yaml with {len(concepts)} concepts.")
|
624
635
|
|
636
|
+
def filled_params_specs_with_inference_params(self, method_signatures: list[resources_pb2.MethodSignature], methods_defaults) -> list[resources_pb2.ModelTypeField]:
|
637
|
+
"""
|
638
|
+
Fills the params_specs with the inference params.
|
639
|
+
"""
|
640
|
+
inference_params = set()
|
641
|
+
for i, method_defaults in enumerate(methods_defaults):
|
642
|
+
for name, default in method_defaults.items():
|
643
|
+
if isinstance(default, data_utils.InputField):
|
644
|
+
if i==0:
|
645
|
+
inference_params.add(name)
|
646
|
+
else:
|
647
|
+
# if field.name not in inference_params then remove from inference_params
|
648
|
+
if name not in inference_params:
|
649
|
+
inference_params.remove(field.name)
|
650
|
+
output=[]
|
651
|
+
for signature in method_signatures:
|
652
|
+
for field in signature.input_fields:
|
653
|
+
if field.name in inference_params:
|
654
|
+
field.path = field.name
|
655
|
+
if field.type == resources_pb2.ModelTypeField.DataType.STR:
|
656
|
+
field.default_value= str(field.default)
|
657
|
+
field.field_type = resources_pb2.ModelTypeField.ModelTypeFieldType.STRING
|
658
|
+
elif field.type == resources_pb2.ModelTypeField.DataType.INT:
|
659
|
+
field.default_value= int(field.default)
|
660
|
+
field.field_type = resources_pb2.ModelTypeField.ModelTypeFieldType.NUMBER
|
661
|
+
elif field.type == resources_pb2.ModelTypeField.DataType.FLOAT:
|
662
|
+
field.default_value= float(field.default)
|
663
|
+
field.field_type = resources_pb2.ModelTypeField.ModelTypeFieldType.NUMBER
|
664
|
+
elif field.type == resources_pb2.ModelTypeField.DataType.BOOL:
|
665
|
+
field.default_value= bool(field.default)
|
666
|
+
field.field_type = resources_pb2.ModelTypeField.ModelTypeFieldType.BOOLEAN
|
667
|
+
else:
|
668
|
+
field.default_value= field.default
|
669
|
+
field.field_type = resources_pb2.ModelTypeField.ModelTypeFieldType.STRING
|
670
|
+
output.append(field)
|
671
|
+
return output
|
672
|
+
|
673
|
+
|
625
674
|
def get_model_version_proto(self):
|
626
675
|
signatures = self.get_method_signatures()
|
676
|
+
methods_defaults = self.get_methods_defaults()
|
677
|
+
|
627
678
|
model_version_proto = resources_pb2.ModelVersion(
|
628
679
|
pretrained_model_config=resources_pb2.PretrainedModelConfig(),
|
629
680
|
inference_compute_info=self.inference_compute_info,
|
630
681
|
method_signatures=signatures,
|
682
|
+
output_info= resources_pb2.OutputInfo(
|
683
|
+
params_specs=self.filled_params_specs_with_inference_params(signatures, methods_defaults),
|
684
|
+
)
|
631
685
|
)
|
632
686
|
|
687
|
+
|
633
688
|
model_type_id = self.config.get('model').get('model_type_id')
|
634
689
|
if model_type_id in CONCEPTS_REQUIRED_MODEL_TYPE:
|
635
690
|
|
@@ -354,4 +354,9 @@ class _MethodInfo:
|
|
354
354
|
for p in inspect.signature(method).parameters.values()
|
355
355
|
if p.annotation != inspect.Parameter.empty
|
356
356
|
}
|
357
|
+
self.python_param_defaults = {
|
358
|
+
p.name: p.default
|
359
|
+
for p in inspect.signature(method).parameters.values()
|
360
|
+
if p.default != inspect.Parameter.empty
|
361
|
+
}
|
357
362
|
self.python_param_types.pop('self', None)
|
@@ -115,12 +115,7 @@ class InputField(MessageData):
|
|
115
115
|
# proto.is_param = self.is_param
|
116
116
|
|
117
117
|
if self.default is not None:
|
118
|
-
|
119
|
-
self.default, (int, float)):
|
120
|
-
proto.default = str(self.default)
|
121
|
-
else:
|
122
|
-
import json
|
123
|
-
proto.default = json.dumps(self.default)
|
118
|
+
proto = self.set_default(proto, self.default)
|
124
119
|
|
125
120
|
return proto
|
126
121
|
|
@@ -169,17 +164,58 @@ class InputField(MessageData):
|
|
169
164
|
|
170
165
|
@classmethod
|
171
166
|
def set_default(cls, proto=None, default=None):
|
172
|
-
|
173
|
-
|
174
|
-
proto
|
175
|
-
|
176
|
-
if
|
177
|
-
default, (int, float)):
|
178
|
-
proto.default = str(default)
|
179
|
-
else:
|
180
|
-
import json
|
167
|
+
try:
|
168
|
+
import json
|
169
|
+
if proto is None:
|
170
|
+
proto = InputFieldProto()
|
171
|
+
if default is not None:
|
181
172
|
proto.default = json.dumps(default)
|
182
|
-
|
173
|
+
return proto
|
174
|
+
except json.JSONDecodeError:
|
175
|
+
if default is not None:
|
176
|
+
proto.default = str(default)
|
177
|
+
return proto
|
178
|
+
except Exception as e:
|
179
|
+
raise ValueError(f"Error setting default value: {e}")
|
180
|
+
|
181
|
+
@classmethod
|
182
|
+
def get_default(cls, proto):
|
183
|
+
default_str = proto.default
|
184
|
+
default = None
|
185
|
+
import json
|
186
|
+
try:
|
187
|
+
# Attempt to parse as JSON first (for complex types)
|
188
|
+
return json.loads(default_str)
|
189
|
+
except json.JSONDecodeError:
|
190
|
+
pass
|
191
|
+
# Check for boolean values stored as "True" or "False"
|
192
|
+
if proto.type == resources_pb2.ModelTypeField.DataType.BOOL:
|
193
|
+
try:
|
194
|
+
default = bool(default_str)
|
195
|
+
except ValueError:
|
196
|
+
pass
|
197
|
+
# Try to parse as integer
|
198
|
+
elif proto.type == resources_pb2.ModelTypeField.DataType.INT:
|
199
|
+
try:
|
200
|
+
default = int(default_str)
|
201
|
+
except ValueError:
|
202
|
+
pass
|
203
|
+
|
204
|
+
# Try to parse as float
|
205
|
+
elif proto.type == resources_pb2.ModelTypeField.DataType.FLOAT:
|
206
|
+
try:
|
207
|
+
default = float(default_str)
|
208
|
+
except ValueError:
|
209
|
+
pass
|
210
|
+
elif proto.type == resources_pb2.ModelTypeField.DataType.STR:
|
211
|
+
default = default_str
|
212
|
+
|
213
|
+
if default is None:
|
214
|
+
# If all parsing fails, return the string value
|
215
|
+
default = default_str
|
216
|
+
return default
|
217
|
+
|
218
|
+
|
183
219
|
|
184
220
|
|
185
221
|
class DataConverter:
|
@@ -211,54 +247,68 @@ class DataConverter:
|
|
211
247
|
if data_type == resources_pb2.ModelTypeField.DataType.STR:
|
212
248
|
if old_data.HasField('text'):
|
213
249
|
new_data.string_value = old_data.text.raw
|
250
|
+
old_data.ClearField('text')
|
214
251
|
return new_data
|
215
252
|
elif data_type == resources_pb2.ModelTypeField.DataType.IMAGE:
|
216
253
|
if old_data.HasField('image'):
|
217
254
|
new_data.image.CopyFrom(old_data.image)
|
255
|
+
# Clear the old field to avoid duplication
|
256
|
+
old_data.ClearField('image')
|
218
257
|
return new_data
|
219
258
|
elif data_type == resources_pb2.ModelTypeField.DataType.VIDEO:
|
220
259
|
if old_data.HasField('video'):
|
221
260
|
new_data.video.CopyFrom(old_data.video)
|
261
|
+
old_data.ClearField('video')
|
222
262
|
return new_data
|
223
263
|
elif data_type == resources_pb2.ModelTypeField.DataType.BOOL:
|
224
264
|
if old_data.bool_value is not False:
|
225
265
|
new_data.bool_value = old_data.bool_value
|
266
|
+
old_data.bool_value = False
|
226
267
|
return new_data
|
227
268
|
elif data_type == resources_pb2.ModelTypeField.DataType.INT:
|
228
269
|
if old_data.int_value != 0:
|
229
270
|
new_data.int_value = old_data.int_value
|
271
|
+
old_data.int_value = 0
|
230
272
|
return new_data
|
231
273
|
elif data_type == resources_pb2.ModelTypeField.DataType.FLOAT:
|
232
274
|
if old_data.float_value != 0.0:
|
233
275
|
new_data.float_value = old_data.float_value
|
276
|
+
old_data.float_value = 0.0
|
234
277
|
return new_data
|
235
278
|
elif data_type == resources_pb2.ModelTypeField.DataType.BYTES:
|
236
279
|
if old_data.bytes_value != b"":
|
237
280
|
new_data.bytes_value = old_data.bytes_value
|
281
|
+
old_data.bytes_value = b""
|
238
282
|
return new_data
|
239
283
|
elif data_type == resources_pb2.ModelTypeField.DataType.NDARRAY:
|
240
284
|
if old_data.HasField('ndarray'):
|
241
285
|
new_data.ndarray.CopyFrom(old_data.ndarray)
|
286
|
+
old_data.ClearField('ndarray')
|
242
287
|
return new_data
|
243
288
|
elif data_type == resources_pb2.ModelTypeField.DataType.TEXT:
|
244
289
|
if old_data.HasField('text'):
|
245
290
|
new_data.text.CopyFrom(old_data.text)
|
291
|
+
old_data.ClearField('text')
|
246
292
|
return new_data
|
247
293
|
elif data_type == resources_pb2.ModelTypeField.DataType.AUDIO:
|
248
294
|
if old_data.HasField('audio'):
|
249
295
|
new_data.audio.CopyFrom(old_data.audio)
|
296
|
+
old_data.ClearField('audio')
|
250
297
|
return new_data
|
251
298
|
elif data_type == resources_pb2.ModelTypeField.DataType.CONCEPT:
|
252
299
|
if old_data.concepts:
|
253
300
|
new_data.concepts.extend(old_data.concepts)
|
301
|
+
old_data.ClearField('concepts')
|
254
302
|
return new_data
|
255
303
|
elif data_type == resources_pb2.ModelTypeField.DataType.REGION:
|
256
304
|
if old_data.regions:
|
257
305
|
new_data.regions.extend(old_data.regions)
|
306
|
+
old_data.ClearField('regions')
|
258
307
|
return new_data
|
259
308
|
elif data_type == resources_pb2.ModelTypeField.DataType.FRAME:
|
260
309
|
if old_data.frames:
|
261
310
|
new_data.frames.extend(old_data.frames)
|
311
|
+
old_data.ClearField('frames')
|
262
312
|
return new_data
|
263
313
|
elif data_type == resources_pb2.ModelTypeField.DataType.LIST:
|
264
314
|
if not field.type_args:
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import collections.abc as abc
|
2
2
|
import inspect
|
3
|
+
import logging
|
3
4
|
import json
|
4
5
|
from collections import namedtuple
|
5
6
|
from typing import Dict, List, Tuple, get_args, get_origin
|
@@ -312,6 +313,13 @@ def deserialize(proto, signatures, inference_params={}, is_output=False):
|
|
312
313
|
kwargs[sig.name] = serializer.deserialize(part.data)
|
313
314
|
elif inference_params_value is not None:
|
314
315
|
kwargs[sig.name] = inference_params_value
|
316
|
+
elif sig.default and (sig.required is False) and (not is_output):
|
317
|
+
try:
|
318
|
+
kwargs[sig.name] = data_utils.InputField.get_default(sig)
|
319
|
+
except Exception as e:
|
320
|
+
# default is not set, so ignore
|
321
|
+
logging.exception('Default value not set for %s: %s', sig.name, e)
|
322
|
+
pass
|
315
323
|
else:
|
316
324
|
if sig_i == 0:
|
317
325
|
# possible inlined first value
|
@@ -0,0 +1,227 @@
|
|
1
|
+
import time
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
def generate_id():
|
5
|
+
return f"chatcmpl-{uuid.uuid4().hex}"
|
6
|
+
|
7
|
+
def _format_non_streaming_response(
|
8
|
+
generated_text,
|
9
|
+
model="custom-model",
|
10
|
+
id=None,
|
11
|
+
created=None,
|
12
|
+
usage=None,
|
13
|
+
finish_reason="stop",
|
14
|
+
):
|
15
|
+
if id is None:
|
16
|
+
id = generate_id()
|
17
|
+
if created is None:
|
18
|
+
created = int(time.time())
|
19
|
+
|
20
|
+
response = {
|
21
|
+
"id": id,
|
22
|
+
"object": "chat.completion",
|
23
|
+
"created": created,
|
24
|
+
"model": model,
|
25
|
+
"choices": [
|
26
|
+
{
|
27
|
+
"index": 0,
|
28
|
+
"message": {
|
29
|
+
"role": "assistant",
|
30
|
+
"content": generated_text,
|
31
|
+
},
|
32
|
+
"finish_reason": finish_reason,
|
33
|
+
"logprobs": None,
|
34
|
+
}
|
35
|
+
],
|
36
|
+
}
|
37
|
+
|
38
|
+
if usage is not None:
|
39
|
+
response["usage"] = usage
|
40
|
+
|
41
|
+
return response
|
42
|
+
|
43
|
+
def _format_streaming_response(
|
44
|
+
generated_chunks,
|
45
|
+
model="custom-model",
|
46
|
+
id=None,
|
47
|
+
created=None,
|
48
|
+
finish_reason="stop",
|
49
|
+
):
|
50
|
+
if id is None:
|
51
|
+
id = generate_id()
|
52
|
+
if created is None:
|
53
|
+
created = int(time.time())
|
54
|
+
|
55
|
+
for chunk in generated_chunks:
|
56
|
+
yield {
|
57
|
+
"id": id,
|
58
|
+
"object": "chat.completion.chunk",
|
59
|
+
"created": created,
|
60
|
+
"model": model,
|
61
|
+
"choices": [
|
62
|
+
{
|
63
|
+
"index": 0,
|
64
|
+
"delta": {
|
65
|
+
"content": chunk,
|
66
|
+
},
|
67
|
+
"finish_reason": None,
|
68
|
+
"logprobs": None,
|
69
|
+
}
|
70
|
+
],
|
71
|
+
}
|
72
|
+
|
73
|
+
# Final chunk indicating completion
|
74
|
+
yield {
|
75
|
+
"id": id,
|
76
|
+
"object": "chat.completion.chunk",
|
77
|
+
"created": created,
|
78
|
+
"model": model,
|
79
|
+
"choices": [
|
80
|
+
{
|
81
|
+
"index": 0,
|
82
|
+
"delta": {},
|
83
|
+
"finish_reason": finish_reason,
|
84
|
+
"logprobs": None,
|
85
|
+
}
|
86
|
+
],
|
87
|
+
}
|
88
|
+
|
89
|
+
def openai_response_format(
|
90
|
+
generated_text,
|
91
|
+
model="custom-model",
|
92
|
+
id=None,
|
93
|
+
created=None,
|
94
|
+
usage=None,
|
95
|
+
finish_reason="stop",
|
96
|
+
stream=True,
|
97
|
+
):
|
98
|
+
if stream:
|
99
|
+
return _format_streaming_response(
|
100
|
+
generated_text, model, id, created, finish_reason
|
101
|
+
)
|
102
|
+
else:
|
103
|
+
return _format_non_streaming_response(
|
104
|
+
generated_text, model, id, created, usage, finish_reason
|
105
|
+
)
|
106
|
+
from typing import List, Dict, Union, Optional
|
107
|
+
import base64
|
108
|
+
from PIL import Image
|
109
|
+
import io
|
110
|
+
|
111
|
+
def openai_to_hf_chat_messages(
|
112
|
+
messages: List[Dict[str, str]],
|
113
|
+
tokenizer: Optional[object] = None
|
114
|
+
) -> List[Dict[str, Union[str, Dict]]]:
|
115
|
+
"""
|
116
|
+
Converts OpenAI-style chat messages into Hugging Face chat template format.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
messages: List of OpenAI-style messages (e.g., [{"role": "user", "content": "Hello"}]).
|
120
|
+
model_family: Optional model family (e.g., "llava", "llama") for special handling.
|
121
|
+
tokenizer: Optional tokenizer to check for chat template support.
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
List of messages in Hugging Face chat format.
|
125
|
+
"""
|
126
|
+
hf_messages = []
|
127
|
+
|
128
|
+
for msg in messages:
|
129
|
+
role = msg["role"]
|
130
|
+
content = msg["content"]
|
131
|
+
|
132
|
+
# Handle multimodal content (e.g., images in OpenAI format)
|
133
|
+
if isinstance(content, list):
|
134
|
+
# OpenAI-style multimodal: [{"type": "text", "text": "..."}, {"type": "image_url", "image_url": "..."}]
|
135
|
+
new_content = []
|
136
|
+
for item in content:
|
137
|
+
if item["type"] == "text":
|
138
|
+
new_content.append(item["text"])
|
139
|
+
elif item["type"] == "image_url":
|
140
|
+
# Handle image (extract base64 or URL)
|
141
|
+
image_url = item["image_url"]["url"]
|
142
|
+
if image_url.startswith("data:image"):
|
143
|
+
# Base64-encoded image
|
144
|
+
image_data = image_url.split(",")[1]
|
145
|
+
image_bytes = base64.b64decode(image_data)
|
146
|
+
image = Image.open(io.BytesIO(image_bytes))
|
147
|
+
new_content.append({"image": image})
|
148
|
+
else:
|
149
|
+
# URL (model must handle downloads)
|
150
|
+
new_content.append({"url": image_url})
|
151
|
+
content = " ".join(new_content) if all(isinstance(c, str) for c in new_content) else new_content
|
152
|
+
elif not isinstance(content, str):
|
153
|
+
raise ValueError(f"Unsupported content type: {type(content)}")
|
154
|
+
|
155
|
+
# Add to HF messages
|
156
|
+
hf_messages.append({"role": role, "content": content})
|
157
|
+
|
158
|
+
# Apply model-specific adjustments
|
159
|
+
if tokenizer is not None and hasattr(tokenizer, "apply_chat_template"):
|
160
|
+
# Let Hugging Face tokenizer handle further formatting if needed
|
161
|
+
try:
|
162
|
+
return tokenizer.apply_chat_template(hf_messages, tokenize=False)
|
163
|
+
except:
|
164
|
+
pass # Fall back to manual formatting
|
165
|
+
|
166
|
+
return hf_messages
|
167
|
+
|
168
|
+
def convert_openai_to_hf_messages(openai_messages):
|
169
|
+
"""
|
170
|
+
Converts OpenAI-style chat messages into a format compatible with Hugging Face's
|
171
|
+
`tokenizer.apply_chat_template()` function, supporting all modalities (text, images, etc.).
|
172
|
+
|
173
|
+
Args:
|
174
|
+
openai_messages (list): List of OpenAI-style messages, where each message is a dict with
|
175
|
+
'role' (str) and 'content' (str or list of parts).
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
list: Hugging Face-compatible messages. Each message is a dict with 'role' and 'content'.
|
179
|
+
Content is a string (text-only) or a list of parts (multimodal).
|
180
|
+
"""
|
181
|
+
hf_messages = []
|
182
|
+
for msg in openai_messages:
|
183
|
+
role = msg['role']
|
184
|
+
content = msg['content']
|
185
|
+
|
186
|
+
if isinstance(content, list):
|
187
|
+
# Handle multimodal content (e.g., text + images)
|
188
|
+
converted_content = []
|
189
|
+
for part in content:
|
190
|
+
if part['type'] == 'text':
|
191
|
+
converted_content.append({'type': 'text', 'text': part['text']})
|
192
|
+
elif part['type'] == 'image_url':
|
193
|
+
# Handle image (extract base64 or URL)
|
194
|
+
image_url = part["image_url"]["url"]
|
195
|
+
if image_url.startswith("data:image"):
|
196
|
+
# Base64-encoded image
|
197
|
+
b64_img = image_url.split(",")[1]
|
198
|
+
converted_content.append({
|
199
|
+
'type': 'image',
|
200
|
+
'base64': b64_img
|
201
|
+
})
|
202
|
+
else:
|
203
|
+
# URL (model must handle downloads)
|
204
|
+
converted_content.append({
|
205
|
+
'type': 'image',
|
206
|
+
'url': image_url
|
207
|
+
})
|
208
|
+
elif part['type'] == 'video_url':
|
209
|
+
video_url = part["video_url"]["url"]
|
210
|
+
if video_url.startswith("data:video"):
|
211
|
+
ValueError("Base64 video data is not supported in HF format.")
|
212
|
+
else:
|
213
|
+
# URL (model must handle downloads)
|
214
|
+
converted_content.append({
|
215
|
+
'type': 'video',
|
216
|
+
'url': video_url
|
217
|
+
})
|
218
|
+
else:
|
219
|
+
raise ValueError(f"Unsupported content type: {part['type']} for conversion.")
|
220
|
+
hf_content = converted_content
|
221
|
+
else:
|
222
|
+
# Text-only content (string)
|
223
|
+
hf_content = content
|
224
|
+
|
225
|
+
hf_messages.append({'role': role, 'content': hf_content})
|
226
|
+
|
227
|
+
return hf_messages
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import time
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
def generate_id():
|
5
|
+
return f"chatcmpl-{uuid.uuid4().hex}"
|
6
|
+
|
7
|
+
def format_non_streaming_response(
|
8
|
+
generated_text,
|
9
|
+
model="custom-model",
|
10
|
+
id=None,
|
11
|
+
created=None,
|
12
|
+
usage=None,
|
13
|
+
finish_reason="stop",
|
14
|
+
):
|
15
|
+
if id is None:
|
16
|
+
id = generate_id()
|
17
|
+
if created is None:
|
18
|
+
created = int(time.time())
|
19
|
+
|
20
|
+
response = {
|
21
|
+
"id": id,
|
22
|
+
"object": "chat.completion",
|
23
|
+
"created": created,
|
24
|
+
"model": model,
|
25
|
+
"choices": [
|
26
|
+
{
|
27
|
+
"index": 0,
|
28
|
+
"message": {
|
29
|
+
"role": "assistant",
|
30
|
+
"content": generated_text,
|
31
|
+
},
|
32
|
+
"finish_reason": finish_reason,
|
33
|
+
"logprobs": None,
|
34
|
+
}
|
35
|
+
],
|
36
|
+
}
|
37
|
+
|
38
|
+
if usage is not None:
|
39
|
+
response["usage"] = usage
|
40
|
+
|
41
|
+
return response
|
42
|
+
|
43
|
+
def format_streaming_response(
|
44
|
+
generated_chunks,
|
45
|
+
model="custom-model",
|
46
|
+
id=None,
|
47
|
+
created=None,
|
48
|
+
finish_reason="stop",
|
49
|
+
):
|
50
|
+
if id is None:
|
51
|
+
id = generate_id()
|
52
|
+
if created is None:
|
53
|
+
created = int(time.time())
|
54
|
+
|
55
|
+
for chunk in generated_chunks:
|
56
|
+
yield {
|
57
|
+
"id": id,
|
58
|
+
"object": "chat.completion.chunk",
|
59
|
+
"created": created,
|
60
|
+
"model": model,
|
61
|
+
"choices": [
|
62
|
+
{
|
63
|
+
"index": 0,
|
64
|
+
"delta": {
|
65
|
+
"content": chunk,
|
66
|
+
},
|
67
|
+
"finish_reason": None,
|
68
|
+
"logprobs": None,
|
69
|
+
}
|
70
|
+
],
|
71
|
+
}
|
72
|
+
|
73
|
+
# Final chunk indicating completion
|
74
|
+
yield {
|
75
|
+
"id": id,
|
76
|
+
"object": "chat.completion.chunk",
|
77
|
+
"created": created,
|
78
|
+
"model": model,
|
79
|
+
"choices": [
|
80
|
+
{
|
81
|
+
"index": 0,
|
82
|
+
"delta": {},
|
83
|
+
"finish_reason": finish_reason,
|
84
|
+
"logprobs": None,
|
85
|
+
}
|
86
|
+
],
|
87
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: clarifai
|
3
|
-
Version: 11.2.
|
3
|
+
Version: 11.2.4rc2
|
4
4
|
Summary: Clarifai Python SDK
|
5
5
|
Home-page: https://github.com/Clarifai/clarifai-python
|
6
6
|
Author: Clarifai
|
@@ -35,6 +35,17 @@ Requires-Dist: requests>=2.32.3
|
|
35
35
|
Requires-Dist: aiohttp>=3.10.0
|
36
36
|
Provides-Extra: all
|
37
37
|
Requires-Dist: pycocotools==2.0.6; extra == "all"
|
38
|
+
Dynamic: author
|
39
|
+
Dynamic: author-email
|
40
|
+
Dynamic: classifier
|
41
|
+
Dynamic: description
|
42
|
+
Dynamic: description-content-type
|
43
|
+
Dynamic: home-page
|
44
|
+
Dynamic: license
|
45
|
+
Dynamic: provides-extra
|
46
|
+
Dynamic: requires-dist
|
47
|
+
Dynamic: requires-python
|
48
|
+
Dynamic: summary
|
38
49
|
|
39
50
|
<h1 align="center">
|
40
51
|
<a href="https://www.clarifai.com/"><img alt="Clarifai" title="Clarifai" src="https://github.com/user-attachments/assets/623b883b-7fe5-4b95-bbfa-8691f5779af4"></a>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
clarifai/__init__.py,sha256=
|
1
|
+
clarifai/__init__.py,sha256=MifEmQdTShs6F9sPHBKRleuDIhSTAInWoN8okWqMJPs,26
|
2
2
|
clarifai/cli.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
clarifai/errors.py,sha256=RwzTajwds51wLD0MVlMC5kcpBnzRpreDLlazPSBZxrg,2605
|
4
4
|
clarifai/versions.py,sha256=jctnczzfGk_S3EnVqb2FjRKfSREkNmvNEwAAa_VoKiQ,222
|
@@ -31,7 +31,7 @@ clarifai/client/deployment.py,sha256=w7Y6pA1rYG4KRK1SwusRZc2sQRXlG8wezuVdzSWpCo0
|
|
31
31
|
clarifai/client/input.py,sha256=obMAHMDU1OwfXZ8KraOnGFlWzlW-3F7Ob_2lcOQMlhY,46339
|
32
32
|
clarifai/client/lister.py,sha256=03KGMvs5RVyYqxLsSrWhNc34I8kiF1Ph0NeyEwu7nMU,2082
|
33
33
|
clarifai/client/model.py,sha256=HLTzCoGhZ5Ifm5x5nSFa4YULnLLlBpZF-29nfOcwFuY,76995
|
34
|
-
clarifai/client/model_client.py,sha256=
|
34
|
+
clarifai/client/model_client.py,sha256=X0RndVfTdfa4PZaOtSick0VKC3AkQakT-qrkPqaFKuI,19971
|
35
35
|
clarifai/client/module.py,sha256=FTkm8s9m-EaTKN7g9MnLhGJ9eETUfKG7aWZ3o1RshYs,4204
|
36
36
|
clarifai/client/nodepool.py,sha256=la3vTFrO4LX8zm2eQ5jqf2L0-kQ63Dano8FibadoZbk,10152
|
37
37
|
clarifai/client/search.py,sha256=GaPWN6JmTQGZaCHr6U1yv0zqR6wKFl7i9IVLg2ul1CI,14254
|
@@ -138,8 +138,8 @@ clarifai/runners/dockerfile_template/Dockerfile.nim,sha256=CSdUAehj3uOwminioLnT5
|
|
138
138
|
clarifai/runners/dockerfile_template/Dockerfile.template,sha256=5cjv7U8PmWa3DB_5B1CqSYh_6GE0E0np52TIAa7EIDE,2312
|
139
139
|
clarifai/runners/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
140
140
|
clarifai/runners/models/base_typed_model.py,sha256=0QCWxch8CcyJSKvE1D4PILd2RSnQZHTmx4DXlQQ6dpo,7856
|
141
|
-
clarifai/runners/models/model_builder.py,sha256=
|
142
|
-
clarifai/runners/models/model_class.py,sha256=
|
141
|
+
clarifai/runners/models/model_builder.py,sha256=6ezRfKxXYI7jwWUiXdsy2L72Q3dvarqikvQuE5ZraVQ,38849
|
142
|
+
clarifai/runners/models/model_class.py,sha256=j3oyOCmh-XfFg3UhdquMZ-DxrNSlOGnFlQMNT3fNQSI,14920
|
143
143
|
clarifai/runners/models/model_class_refract.py,sha256=HxuozxSW7ag5yWCPxjNwgLArQ6dORhyGXlnpPaZz2-c,3211
|
144
144
|
clarifai/runners/models/model_run_locally.py,sha256=H7FKUBzZ_EPPj1b6P59qbOYr3mUJjJTlD5gavH5e80o,17746
|
145
145
|
clarifai/runners/models/model_runner.py,sha256=T4Qn_x0vky7XdeS54bvipzEmKZMra1tQdAu_u01yyjc,6503
|
@@ -161,10 +161,12 @@ clarifai/runners/utils/const.py,sha256=9qnOC1Bt6SGLQ9XCQEQ6519XhW4gzcztsV1Rgej67
|
|
161
161
|
clarifai/runners/utils/data_handler.py,sha256=b7k6MWYPXSgjrfw6wsDf82xFYa0D7UjYmjE4mw5HzHM,8499
|
162
162
|
clarifai/runners/utils/data_handler_refract.py,sha256=3M-V4hkOoF-9Ix4hE6ocXWiTJPc9dewtu6FMtddd-jQ,6343
|
163
163
|
clarifai/runners/utils/data_types.py,sha256=CtcYoW4EFE1EG6JkcP2rcRast9Eac8hFoYRHHCuwl2w,12432
|
164
|
-
clarifai/runners/utils/data_utils.py,sha256=
|
164
|
+
clarifai/runners/utils/data_utils.py,sha256=BuIxcQOudFEle8V6ZvycrYd-6X0i__EQbXxKKF1n7mA,12911
|
165
165
|
clarifai/runners/utils/loader.py,sha256=Sl0m29RDtMPx2cIiSbbDFtKHQj2ktXQ5CnkvaHi-zDc,8804
|
166
166
|
clarifai/runners/utils/logger.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
167
|
-
clarifai/runners/utils/method_signatures.py,sha256=
|
167
|
+
clarifai/runners/utils/method_signatures.py,sha256=HypEj1EN7pzRPzn-KUu1oOZcKlwfarvYTZXUZ-DEEYQ,18509
|
168
|
+
clarifai/runners/utils/openai_convertor.py,sha256=tFFNkjcdsNi73DfT_JOqdZZAu8fDYQwpbToWcr62OOw,7650
|
169
|
+
clarifai/runners/utils/openai_format.py,sha256=vOrpgqVWmQZGGFANZf0hmR_ksZXsr2-S9WUxvkG5lZs,1980
|
168
170
|
clarifai/runners/utils/serializers.py,sha256=S4sRsOVvH191vAGTRTAAdwLlQwlK4T5QVRDGPptg9nQ,7191
|
169
171
|
clarifai/runners/utils/url_fetcher.py,sha256=v_8JOWmkyFAzsBulsieKX7Nfjy1Yg7wGSZeqfEvw2cg,1640
|
170
172
|
clarifai/runners/utils/__pycache__/__init__.cpython-310.pyc,sha256=0GGbXIecXlOZmQKMCkSRhEBY_a1zvoimv-mHG4pJuNA,167
|
@@ -231,9 +233,9 @@ clarifai/workflows/__pycache__/__init__.cpython-39.pyc,sha256=9nA--jULSW7OFrYOcs
|
|
231
233
|
clarifai/workflows/__pycache__/export.cpython-310.pyc,sha256=phEGwi2gAojCUhRTqjZVeTDn7Gk6LCVBeSTjAj4m9iY,2418
|
232
234
|
clarifai/workflows/__pycache__/utils.cpython-310.pyc,sha256=M9_KTM7GOOS5SPrWwAzqHDqyGvgKi3xuSGvyw6MNf-I,1925
|
233
235
|
clarifai/workflows/__pycache__/validate.cpython-310.pyc,sha256=c18Jgp_-CAm8RD_tmUpDCPoqZeexaoWELG0yBzb9rjw,2149
|
234
|
-
clarifai-11.2.
|
235
|
-
clarifai-11.2.
|
236
|
-
clarifai-11.2.
|
237
|
-
clarifai-11.2.
|
238
|
-
clarifai-11.2.
|
239
|
-
clarifai-11.2.
|
236
|
+
clarifai-11.2.4rc2.dist-info/LICENSE,sha256=mUqF_d12-qE2n41g7C5_sq-BMLOcj6CNN-jevr15YHU,555
|
237
|
+
clarifai-11.2.4rc2.dist-info/METADATA,sha256=2NQ4ajyyei0MMGHMBZimPeO7qxqoxMfMvlq1TaNzLRI,22453
|
238
|
+
clarifai-11.2.4rc2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
239
|
+
clarifai-11.2.4rc2.dist-info/entry_points.txt,sha256=X9FZ4Z-i_r2Ud1RpZ9sNIFYuu_-9fogzCMCRUD9hyX0,51
|
240
|
+
clarifai-11.2.4rc2.dist-info/top_level.txt,sha256=wUMdCQGjkxaynZ6nZ9FAnvBUCgp5RJUVFSy2j-KYo0s,9
|
241
|
+
clarifai-11.2.4rc2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|