clarifai 11.6.8__py3-none-any.whl → 11.7.0__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/cli/model.py +13 -8
- clarifai/cli/templates/model_templates.py +3 -2
- clarifai/client/auth/helper.py +2 -2
- clarifai/client/model.py +30 -0
- clarifai/runners/models/dummy_openai_model.py +8 -0
- clarifai/runners/models/model_builder.py +161 -15
- clarifai/runners/models/openai_class.py +52 -8
- {clarifai-11.6.8.dist-info → clarifai-11.7.0.dist-info}/METADATA +24 -6
- {clarifai-11.6.8.dist-info → clarifai-11.7.0.dist-info}/RECORD +14 -14
- {clarifai-11.6.8.dist-info → clarifai-11.7.0.dist-info}/WHEEL +0 -0
- {clarifai-11.6.8.dist-info → clarifai-11.7.0.dist-info}/entry_points.txt +0 -0
- {clarifai-11.6.8.dist-info → clarifai-11.7.0.dist-info}/licenses/LICENSE +0 -0
- {clarifai-11.6.8.dist-info → clarifai-11.7.0.dist-info}/top_level.txt +0 -0
clarifai/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "11.
|
1
|
+
__version__ = "11.7.0"
|
clarifai/cli/model.py
CHANGED
@@ -298,7 +298,7 @@ def init(
|
|
298
298
|
@click.option(
|
299
299
|
'--skip_dockerfile',
|
300
300
|
is_flag=True,
|
301
|
-
help='Flag to skip generating a dockerfile so that you can manually edit an already created dockerfile.',
|
301
|
+
help='Flag to skip generating a dockerfile so that you can manually edit an already created dockerfile. If not provided, intelligently handle existing Dockerfiles with user confirmation.',
|
302
302
|
)
|
303
303
|
@click.pass_context
|
304
304
|
def upload(ctx, model_path, stage, skip_dockerfile):
|
@@ -410,7 +410,7 @@ def signatures(model_path, out_path):
|
|
410
410
|
@click.option(
|
411
411
|
'--skip_dockerfile',
|
412
412
|
is_flag=True,
|
413
|
-
help='Flag to skip generating a dockerfile so that you can manually edit an already created dockerfile.
|
413
|
+
help='Flag to skip generating a dockerfile so that you can manually edit an already created dockerfile. If not provided, intelligently handle existing Dockerfiles with user confirmation.',
|
414
414
|
)
|
415
415
|
def test_locally(model_path, keep_env=False, keep_image=False, mode='env', skip_dockerfile=False):
|
416
416
|
"""Test model locally.
|
@@ -477,7 +477,7 @@ def test_locally(model_path, keep_env=False, keep_image=False, mode='env', skip_
|
|
477
477
|
@click.option(
|
478
478
|
'--skip_dockerfile',
|
479
479
|
is_flag=True,
|
480
|
-
help='Flag to skip generating a dockerfile so that you can manually edit an already created dockerfile.
|
480
|
+
help='Flag to skip generating a dockerfile so that you can manually edit an already created dockerfile. If not provided, intelligently handle existing Dockerfiles with user confirmation.',
|
481
481
|
)
|
482
482
|
def run_locally(model_path, port, mode, keep_env, keep_image, skip_dockerfile=False):
|
483
483
|
"""Run the model locally and start a gRPC server to serve the model.
|
@@ -520,7 +520,7 @@ def run_locally(model_path, port, mode, keep_env, keep_image, skip_dockerfile=Fa
|
|
520
520
|
@click.option(
|
521
521
|
"--pool_size",
|
522
522
|
type=int,
|
523
|
-
default=
|
523
|
+
default=32,
|
524
524
|
show_default=True,
|
525
525
|
help="The number of threads to use. On community plan, the compute time allocation is drained at a rate proportional to the number of threads.",
|
526
526
|
) # pylint: disable=range-builtin-not-iterating
|
@@ -571,6 +571,8 @@ def local_runner(ctx, model_path, pool_size, verbose):
|
|
571
571
|
from clarifai.runners.models.model_builder import ModelBuilder
|
572
572
|
from clarifai.runners.server import serve
|
573
573
|
|
574
|
+
builder = ModelBuilder(model_path, download_validation_only=True)
|
575
|
+
|
574
576
|
validate_context(ctx)
|
575
577
|
builder = ModelBuilder(model_path, download_validation_only=True)
|
576
578
|
logger.info("> Checking local runner requirements...")
|
@@ -726,16 +728,20 @@ def local_runner(ctx, model_path, pool_size, verbose):
|
|
726
728
|
# Now we need to create a version for the model if no version exists. Only need one version that
|
727
729
|
# mentions it's a local runner.
|
728
730
|
model_versions = [v for v in model.list_versions()]
|
731
|
+
method_signatures = builder.get_method_signatures(mocking=False)
|
729
732
|
if len(model_versions) == 0:
|
730
733
|
logger.warning("No model versions found. Creating a new version for local runner.")
|
731
|
-
# add the signatures for local runner on how to call it.
|
732
|
-
signatures = builder.get_method_signatures(mocking=True)
|
733
734
|
version = model.create_version(
|
734
|
-
pretrained_model_config={"local_dev": True}, method_signatures=
|
735
|
+
pretrained_model_config={"local_dev": True}, method_signatures=method_signatures
|
735
736
|
).model_version
|
736
737
|
ctx.obj.current.CLARIFAI_MODEL_VERSION_ID = version.id
|
737
738
|
ctx.obj.to_yaml()
|
738
739
|
else:
|
740
|
+
model.patch_version(
|
741
|
+
version_id=model_versions[0].model_version.id,
|
742
|
+
pretrained_model_config={"local_dev": True},
|
743
|
+
method_signatures=method_signatures,
|
744
|
+
)
|
739
745
|
version = model_versions[0].model_version
|
740
746
|
ctx.obj.current.CLARIFAI_MODEL_VERSION_ID = version.id
|
741
747
|
ctx.obj.to_yaml() # save to yaml file.
|
@@ -864,7 +870,6 @@ def local_runner(ctx, model_path, pool_size, verbose):
|
|
864
870
|
ModelBuilder._backup_config(config_file)
|
865
871
|
ModelBuilder._save_config(config_file, config)
|
866
872
|
|
867
|
-
builder = ModelBuilder(model_path, download_validation_only=True)
|
868
873
|
if not check_requirements_installed(model_path):
|
869
874
|
logger.error(f"Requirements not installed for model at {model_path}.")
|
870
875
|
raise click.Abort()
|
@@ -204,8 +204,9 @@ inference_compute_info:
|
|
204
204
|
# TODO: please fill in (optional) - add checkpoints section if needed
|
205
205
|
# checkpoints:
|
206
206
|
# type: "huggingface" # supported type
|
207
|
-
# repo_id: "your-model-repo" # for huggingface
|
208
|
-
#
|
207
|
+
# repo_id: "your-model-repo" # for huggingface like openai/gpt-oss-20b
|
208
|
+
# # hf_token: "your-huggingface-token" # if private repo
|
209
|
+
# when: "runtime" # or "build", "upload"
|
209
210
|
'''
|
210
211
|
|
211
212
|
|
clarifai/client/auth/helper.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
import urllib.request
|
3
|
-
from typing import Any, Dict
|
3
|
+
from typing import Any, Dict, Tuple
|
4
4
|
|
5
5
|
import grpc
|
6
6
|
from clarifai_grpc.channel.clarifai_channel import ClarifaiChannel
|
@@ -293,7 +293,7 @@ class ClarifaiAuthHelper:
|
|
293
293
|
stub, channel = self.get_stub_and_channel()
|
294
294
|
return stub
|
295
295
|
|
296
|
-
def get_stub_and_channel(self) ->
|
296
|
+
def get_stub_and_channel(self) -> Tuple[service_pb2_grpc.V2Stub, grpc.Channel]:
|
297
297
|
"""Get the API gRPC stub and channel based on the API endpoint base.
|
298
298
|
|
299
299
|
Returns:
|
clarifai/client/model.py
CHANGED
@@ -2101,3 +2101,33 @@ class Model(Lister, BaseClient):
|
|
2101
2101
|
model_id=self.id,
|
2102
2102
|
model_version=dict(id=response.model.model_version.id),
|
2103
2103
|
)
|
2104
|
+
|
2105
|
+
def patch_version(self, version_id: str, **kwargs) -> 'Model':
|
2106
|
+
"""Patch the model version with the given version ID.
|
2107
|
+
Args:
|
2108
|
+
version_id (str): The version ID to patch.
|
2109
|
+
**kwargs: Additional keyword arguments to update the model version.
|
2110
|
+
Example:
|
2111
|
+
>>> from clarifai.client.model import Model
|
2112
|
+
>>> model = Model(model_id='model_id', user_id='user_id', app_id='app_id')
|
2113
|
+
>>> model.patch_version(version_id='version_id', method_signatures=signatures)
|
2114
|
+
"""
|
2115
|
+
request = service_pb2.PatchModelVersionsRequest(
|
2116
|
+
user_app_id=self.user_app_id,
|
2117
|
+
model_id=self.id,
|
2118
|
+
action='merge',
|
2119
|
+
model_versions=[
|
2120
|
+
resources_pb2.ModelVersion(
|
2121
|
+
id=version_id,
|
2122
|
+
**kwargs,
|
2123
|
+
)
|
2124
|
+
],
|
2125
|
+
)
|
2126
|
+
response = self._grpc_request(self.STUB.PatchModelVersions, request)
|
2127
|
+
if response.status.code != status_code_pb2.SUCCESS:
|
2128
|
+
raise Exception(response.status)
|
2129
|
+
return Model.from_auth_helper(
|
2130
|
+
auth=self.auth_helper,
|
2131
|
+
model_id=self.id,
|
2132
|
+
model_version=dict(id=version_id),
|
2133
|
+
)
|
@@ -88,6 +88,10 @@ class MockCompletion:
|
|
88
88
|
def model_dump(self):
|
89
89
|
return self.to_dict()
|
90
90
|
|
91
|
+
def model_dump_json(self):
|
92
|
+
"""Return the completion as a JSON string."""
|
93
|
+
return json.dumps(self.to_dict())
|
94
|
+
|
91
95
|
|
92
96
|
class MockCompletionStream:
|
93
97
|
"""Mock completion stream that mimics the OpenAI streaming response structure."""
|
@@ -153,6 +157,10 @@ class MockCompletionStream:
|
|
153
157
|
def model_dump(self):
|
154
158
|
return self.to_dict()
|
155
159
|
|
160
|
+
def model_dump_json(self):
|
161
|
+
"""Return the chunk as a JSON string."""
|
162
|
+
return json.dumps(self.to_dict())
|
163
|
+
|
156
164
|
def __init__(self, **kwargs):
|
157
165
|
# Generate a simple response based on the last message
|
158
166
|
messages = kwargs.get("messages")
|
@@ -21,6 +21,7 @@ from google.protobuf import json_format
|
|
21
21
|
from clarifai.client.base import BaseClient
|
22
22
|
from clarifai.client.user import User
|
23
23
|
from clarifai.runners.models.model_class import ModelClass
|
24
|
+
from clarifai.runners.utils import code_script
|
24
25
|
from clarifai.runners.utils.const import (
|
25
26
|
AMD_PYTHON_BASE_IMAGE,
|
26
27
|
AMD_TORCH_BASE_IMAGE,
|
@@ -101,6 +102,7 @@ class ModelBuilder:
|
|
101
102
|
self.folder = self._validate_folder(folder)
|
102
103
|
self.config = self._load_config(os.path.join(self.folder, 'config.yaml'))
|
103
104
|
self._validate_config()
|
105
|
+
self._validate_stream_options()
|
104
106
|
self.model_proto = self._get_model_proto()
|
105
107
|
self.model_id = self.model_proto.id
|
106
108
|
self.model_version_id = None
|
@@ -440,6 +442,89 @@ class ModelBuilder:
|
|
440
442
|
num_threads = int(os.environ.get("CLARIFAI_NUM_THREADS", 16))
|
441
443
|
self.config["num_threads"] = num_threads
|
442
444
|
|
445
|
+
def _validate_stream_options(self):
|
446
|
+
"""
|
447
|
+
Validate OpenAI streaming configuration for Clarifai models.
|
448
|
+
"""
|
449
|
+
if not self._is_clarifai_internal():
|
450
|
+
return # Skip validation for non-clarifai models
|
451
|
+
|
452
|
+
# Parse all Python files once
|
453
|
+
all_python_content = self._get_all_python_content()
|
454
|
+
|
455
|
+
if self._uses_openai_streaming(all_python_content):
|
456
|
+
logger.info(
|
457
|
+
"Detected OpenAI chat completions for Clarifai model streaming - validating stream_options..."
|
458
|
+
)
|
459
|
+
|
460
|
+
if not self.has_proper_usage_tracking(all_python_content):
|
461
|
+
logger.error(
|
462
|
+
"Missing configuration to track usage for OpenAI chat completion calls. "
|
463
|
+
"Go to your model scripts and make sure to set both: "
|
464
|
+
"1) stream_options={'include_usage': True}"
|
465
|
+
"2) set_output_context"
|
466
|
+
)
|
467
|
+
|
468
|
+
def _is_clarifai_internal(self):
|
469
|
+
"""
|
470
|
+
Check if the current user is a Clarifai internal user based on email domain.
|
471
|
+
|
472
|
+
Returns:
|
473
|
+
bool: True if user is a Clarifai internal user, False otherwise
|
474
|
+
"""
|
475
|
+
try:
|
476
|
+
# Get user info from Clarifai API
|
477
|
+
user_client = User(
|
478
|
+
pat=self.client.pat, user_id=self.config.get('model').get('user_id')
|
479
|
+
)
|
480
|
+
user_response = user_client.get_user_info()
|
481
|
+
|
482
|
+
if user_response.status.code != status_code_pb2.SUCCESS:
|
483
|
+
logger.debug("Could not retrieve user info for Clarifai internal user validation")
|
484
|
+
return False
|
485
|
+
|
486
|
+
user = user_response.user
|
487
|
+
|
488
|
+
# Check primary email domain
|
489
|
+
if hasattr(user, 'primary_email') and user.primary_email:
|
490
|
+
return user.primary_email.endswith('@clarifai.com')
|
491
|
+
|
492
|
+
return False
|
493
|
+
|
494
|
+
except Exception as e:
|
495
|
+
logger.debug(f"Employee validation failed: {e}")
|
496
|
+
return False
|
497
|
+
|
498
|
+
def _get_all_python_content(self):
|
499
|
+
"""
|
500
|
+
Parse and concatenate all Python files in the model's 1/ subfolder.
|
501
|
+
"""
|
502
|
+
model_folder = os.path.join(self.folder, '1')
|
503
|
+
if not os.path.exists(model_folder):
|
504
|
+
return ""
|
505
|
+
|
506
|
+
all_content = []
|
507
|
+
for root, _, files in os.walk(model_folder):
|
508
|
+
for file in files:
|
509
|
+
if file.endswith('.py'):
|
510
|
+
file_path = os.path.join(root, file)
|
511
|
+
try:
|
512
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
513
|
+
all_content.append(f.read())
|
514
|
+
except Exception:
|
515
|
+
continue
|
516
|
+
return "\n".join(all_content)
|
517
|
+
|
518
|
+
def _uses_openai_streaming(self, python_content):
|
519
|
+
return 'chat.completions.create' in python_content and 'generate(' in python_content
|
520
|
+
|
521
|
+
def has_proper_usage_tracking(self, python_content):
|
522
|
+
include_usage_patterns = ["'include_usage': True", '"include_usage": True']
|
523
|
+
has_include_usage = any(pattern in python_content for pattern in include_usage_patterns)
|
524
|
+
has_set_output_context = 'set_output_context' in python_content
|
525
|
+
|
526
|
+
return has_include_usage and has_set_output_context
|
527
|
+
|
443
528
|
@staticmethod
|
444
529
|
def _get_tar_file_content_size(tar_file_path):
|
445
530
|
"""
|
@@ -736,7 +821,26 @@ class ModelBuilder:
|
|
736
821
|
else:
|
737
822
|
logger.info("Setup: Python code linted successfully, no errors found.")
|
738
823
|
|
739
|
-
def
|
824
|
+
def _normalize_dockerfile_content(self, content):
|
825
|
+
"""
|
826
|
+
Normalize Dockerfile content for comparison by standardizing whitespace and indentation.
|
827
|
+
This handles differences in spacing, indentation, and line endings.
|
828
|
+
"""
|
829
|
+
lines = []
|
830
|
+
for line in content.splitlines():
|
831
|
+
# Strip leading/trailing whitespace from each line
|
832
|
+
normalized_line = line.strip()
|
833
|
+
# Skip empty lines for comparison
|
834
|
+
if normalized_line:
|
835
|
+
lines.append(normalized_line)
|
836
|
+
# Join with consistent line endings
|
837
|
+
return '\n'.join(lines)
|
838
|
+
|
839
|
+
def _generate_dockerfile_content(self):
|
840
|
+
"""
|
841
|
+
Generate the Dockerfile content based on the model configuration.
|
842
|
+
This is a helper method that returns the content without writing to file.
|
843
|
+
"""
|
740
844
|
dockerfile_template = os.path.join(
|
741
845
|
os.path.dirname(os.path.dirname(__file__)),
|
742
846
|
'dockerfile_template',
|
@@ -885,9 +989,51 @@ class ModelBuilder:
|
|
885
989
|
CLARIFAI_VERSION=clarifai_version, # for clarifai
|
886
990
|
)
|
887
991
|
|
888
|
-
|
889
|
-
|
890
|
-
|
992
|
+
return dockerfile_content
|
993
|
+
|
994
|
+
def create_dockerfile(self, generate_dockerfile=False):
|
995
|
+
"""
|
996
|
+
Create a Dockerfile for the model based on its configuration.
|
997
|
+
"""
|
998
|
+
generated_content = self._generate_dockerfile_content()
|
999
|
+
|
1000
|
+
if generate_dockerfile:
|
1001
|
+
should_create_dockerfile = True
|
1002
|
+
else:
|
1003
|
+
# Always handle Dockerfile creation with user interaction when content differs
|
1004
|
+
dockerfile_path = os.path.join(self.folder, 'Dockerfile')
|
1005
|
+
should_create_dockerfile = True
|
1006
|
+
|
1007
|
+
if os.path.exists(dockerfile_path):
|
1008
|
+
# Read existing Dockerfile content
|
1009
|
+
with open(dockerfile_path, 'r') as existing_dockerfile:
|
1010
|
+
existing_content = existing_dockerfile.read()
|
1011
|
+
|
1012
|
+
# Compare content (normalize for robust comparison that handles indentation differences)
|
1013
|
+
if self._normalize_dockerfile_content(
|
1014
|
+
existing_content
|
1015
|
+
) == self._normalize_dockerfile_content(generated_content):
|
1016
|
+
logger.info(
|
1017
|
+
"Dockerfile already exists with identical content, skipping creation."
|
1018
|
+
)
|
1019
|
+
should_create_dockerfile = False
|
1020
|
+
else:
|
1021
|
+
logger.info("Dockerfile already exists with different content.")
|
1022
|
+
response = input(
|
1023
|
+
"A different Dockerfile already exists. Do you want to overwrite it with the generated one? "
|
1024
|
+
"Type 'y' to overwrite, 'n' to keep your custom Dockerfile: "
|
1025
|
+
)
|
1026
|
+
if response.lower() != 'y':
|
1027
|
+
logger.info("Keeping existing custom Dockerfile.")
|
1028
|
+
should_create_dockerfile = False
|
1029
|
+
else:
|
1030
|
+
logger.info("Overwriting existing Dockerfile with generated content.")
|
1031
|
+
|
1032
|
+
if should_create_dockerfile:
|
1033
|
+
# Write Dockerfile
|
1034
|
+
dockerfile_path = os.path.join(self.folder, 'Dockerfile')
|
1035
|
+
with open(dockerfile_path, 'w') as dockerfile:
|
1036
|
+
dockerfile.write(generated_content)
|
891
1037
|
|
892
1038
|
@property
|
893
1039
|
def checkpoint_path(self):
|
@@ -1119,7 +1265,6 @@ class ModelBuilder:
|
|
1119
1265
|
is_uploaded = self.monitor_model_build()
|
1120
1266
|
if is_uploaded:
|
1121
1267
|
# python code to run the model.
|
1122
|
-
from clarifai.runners.utils import code_script
|
1123
1268
|
|
1124
1269
|
method_signatures = self.get_method_signatures()
|
1125
1270
|
snippet = code_script.generate_client_script(
|
@@ -1197,7 +1342,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
1197
1342
|
per_page=50,
|
1198
1343
|
)
|
1199
1344
|
response = self.client.STUB.ListLogEntries(logs_request)
|
1200
|
-
|
1201
1345
|
return response
|
1202
1346
|
|
1203
1347
|
def monitor_model_build(self):
|
@@ -1250,14 +1394,16 @@ def upload_model(folder, stage, skip_dockerfile, pat=None, base_url=None):
|
|
1250
1394
|
|
1251
1395
|
:param folder: The folder containing the model files.
|
1252
1396
|
:param stage: The stage we are calling download checkpoints from. Typically this would "upload" and will download checkpoints if config.yaml checkpoints section has when set to "upload". Other options include "runtime" to be used in load_model or "upload" to be used during model upload. Set this stage to whatever you have in config.yaml to force downloading now.
|
1253
|
-
:param skip_dockerfile: If True, will not
|
1397
|
+
:param skip_dockerfile: If True, will skip Dockerfile generation entirely. If False or not provided, intelligently handle existing Dockerfiles with user confirmation.
|
1254
1398
|
:param pat: Personal access token for authentication. If None, will use environment variables.
|
1255
1399
|
:param base_url: Base URL for the API. If None, will use environment variables.
|
1256
1400
|
"""
|
1257
1401
|
builder = ModelBuilder(folder, app_not_found_action="prompt", pat=pat, base_url=base_url)
|
1258
1402
|
builder.download_checkpoints(stage=stage)
|
1403
|
+
|
1259
1404
|
if not skip_dockerfile:
|
1260
1405
|
builder.create_dockerfile()
|
1406
|
+
|
1261
1407
|
exists = builder.check_model_exists()
|
1262
1408
|
if exists:
|
1263
1409
|
logger.info(
|
@@ -1272,13 +1418,14 @@ def upload_model(folder, stage, skip_dockerfile, pat=None, base_url=None):
|
|
1272
1418
|
model_version = builder.upload_model_version()
|
1273
1419
|
|
1274
1420
|
# Ask user if they want to deploy the model
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1421
|
+
if model_version is not None: # if it comes back None then it failed.
|
1422
|
+
deploy_model = input("Do you want to deploy the model? (y/n): ")
|
1423
|
+
if deploy_model.lower() != 'y':
|
1424
|
+
logger.info("Model uploaded successfully. Skipping deployment setup.")
|
1425
|
+
return
|
1279
1426
|
|
1280
|
-
|
1281
|
-
|
1427
|
+
# Setup deployment for the uploaded model
|
1428
|
+
setup_deployment_for_model(builder)
|
1282
1429
|
|
1283
1430
|
|
1284
1431
|
def setup_deployment_for_model(builder):
|
@@ -1459,7 +1606,7 @@ def setup_deployment_for_model(builder):
|
|
1459
1606
|
# Step 3: Help create a new deployment by providing URL
|
1460
1607
|
# Provide URL to create a new deployment
|
1461
1608
|
url_helper = ClarifaiUrlHelper()
|
1462
|
-
deployment_url = f"{url_helper.ui}/
|
1609
|
+
deployment_url = f"{url_helper.ui}/compute/deployments/create?computeClusterId={compute_cluster.id}&nodePoolId={nodepool.id}"
|
1463
1610
|
logger.info(f"Please create a new deployment by visiting: {deployment_url}")
|
1464
1611
|
|
1465
1612
|
# Ask if they want to open the URL in browser
|
@@ -1473,4 +1620,3 @@ def setup_deployment_for_model(builder):
|
|
1473
1620
|
logger.error(f"Failed to open browser: {e}")
|
1474
1621
|
|
1475
1622
|
logger.info("After creating the deployment, your model will be ready for inference!")
|
1476
|
-
logger.info(f"You can always return to view your deployments at: {deployment_url}")
|
@@ -1,9 +1,12 @@
|
|
1
1
|
"""Base class for creating OpenAI-compatible API server."""
|
2
2
|
|
3
|
-
import json
|
4
3
|
from typing import Any, Dict, Iterator
|
5
4
|
|
5
|
+
from clarifai_grpc.grpc.api.status import status_code_pb2
|
6
|
+
from pydantic_core import from_json, to_json
|
7
|
+
|
6
8
|
from clarifai.runners.models.model_class import ModelClass
|
9
|
+
from clarifai.utils.logging import logger
|
7
10
|
|
8
11
|
|
9
12
|
class OpenAIModelClass(ModelClass):
|
@@ -116,6 +119,33 @@ class OpenAIModelClass(ModelClass):
|
|
116
119
|
|
117
120
|
return handler(request_data)
|
118
121
|
|
122
|
+
def _update_old_fields(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
123
|
+
"""Update old fields in the request data to match current API expectations.
|
124
|
+
|
125
|
+
This is needed because API callers may have an old openAI client sending old fields
|
126
|
+
compared to the client within the model.
|
127
|
+
|
128
|
+
Note: this updates the request data in place and returns it.
|
129
|
+
"""
|
130
|
+
if 'max_tokens' in request_data:
|
131
|
+
request_data['max_completion_tokens'] = request_data.pop('max_tokens')
|
132
|
+
if 'top_p' in request_data:
|
133
|
+
request_data['top_p'] = float(request_data['top_p'])
|
134
|
+
# Note(zeiler): temporary fix for our playground sending additional fields.
|
135
|
+
# FIXME: remove this once the playground is updated.
|
136
|
+
for m in request_data['messages']:
|
137
|
+
m.pop('id', None)
|
138
|
+
m.pop('file', None)
|
139
|
+
m.pop('panelId', None)
|
140
|
+
|
141
|
+
# Handle the "Currently only named tools are supported." error we see from trt-llm
|
142
|
+
if 'tools' in request_data and request_data['tools'] is None:
|
143
|
+
request_data.pop('tools', None)
|
144
|
+
if 'tool_choice' in request_data and request_data['tool_choice'] is None:
|
145
|
+
request_data.pop('tool_choice', None)
|
146
|
+
|
147
|
+
return request_data
|
148
|
+
|
119
149
|
@ModelClass.method
|
120
150
|
def openai_transport(self, msg: str) -> str:
|
121
151
|
"""Process an OpenAI-compatible request and send it to the appropriate OpenAI endpoint.
|
@@ -127,12 +157,19 @@ class OpenAIModelClass(ModelClass):
|
|
127
157
|
JSON string containing the response or error
|
128
158
|
"""
|
129
159
|
try:
|
130
|
-
request_data =
|
160
|
+
request_data = from_json(msg)
|
161
|
+
request_data = self._update_old_fields(request_data)
|
131
162
|
endpoint = request_data.pop("openai_endpoint", self.DEFAULT_ENDPOINT)
|
132
163
|
response = self._route_request(endpoint, request_data)
|
133
|
-
return
|
164
|
+
return response.model_dump_json()
|
134
165
|
except Exception as e:
|
135
|
-
|
166
|
+
logger.exception(e)
|
167
|
+
error_obj = {
|
168
|
+
"code": status_code_pb2.MODEL_PREDICTION_FAILED,
|
169
|
+
"description": "Model prediction failed",
|
170
|
+
"details": str(e),
|
171
|
+
}
|
172
|
+
return to_json(error_obj)
|
136
173
|
|
137
174
|
@ModelClass.method
|
138
175
|
def openai_stream_transport(self, msg: str) -> Iterator[str]:
|
@@ -147,7 +184,8 @@ class OpenAIModelClass(ModelClass):
|
|
147
184
|
Iterator[str]: An iterator yielding text chunks from the streaming response.
|
148
185
|
"""
|
149
186
|
try:
|
150
|
-
request_data =
|
187
|
+
request_data = from_json(msg)
|
188
|
+
request_data = self._update_old_fields(request_data)
|
151
189
|
endpoint = request_data.pop("openai_endpoint", self.DEFAULT_ENDPOINT)
|
152
190
|
if endpoint not in [self.ENDPOINT_CHAT_COMPLETIONS, self.ENDPOINT_RESPONSES]:
|
153
191
|
raise ValueError("Streaming is only supported for chat completions and responses.")
|
@@ -156,13 +194,19 @@ class OpenAIModelClass(ModelClass):
|
|
156
194
|
# Handle responses endpoint
|
157
195
|
stream_response = self._route_request(endpoint, request_data)
|
158
196
|
for chunk in stream_response:
|
159
|
-
yield
|
197
|
+
yield chunk.model_dump_json()
|
160
198
|
else:
|
161
199
|
completion_args = self._create_completion_args(request_data)
|
162
200
|
stream_completion = self.client.chat.completions.create(**completion_args)
|
163
201
|
for chunk in stream_completion:
|
164
202
|
self._set_usage(chunk)
|
165
|
-
yield
|
203
|
+
yield chunk.model_dump_json()
|
166
204
|
|
167
205
|
except Exception as e:
|
168
|
-
|
206
|
+
logger.exception(e)
|
207
|
+
error_obj = {
|
208
|
+
"code": status_code_pb2.MODEL_PREDICTION_FAILED,
|
209
|
+
"description": "Model prediction failed",
|
210
|
+
"details": str(e),
|
211
|
+
}
|
212
|
+
yield to_json(error_obj)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: clarifai
|
3
|
-
Version: 11.
|
3
|
+
Version: 11.7.0
|
4
4
|
Home-page: https://github.com/Clarifai/clarifai-python
|
5
5
|
Author: Clarifai
|
6
6
|
Author-email: support@clarifai.com
|
@@ -19,8 +19,8 @@ Classifier: Operating System :: OS Independent
|
|
19
19
|
Requires-Python: >=3.8
|
20
20
|
Description-Content-Type: text/markdown
|
21
21
|
License-File: LICENSE
|
22
|
-
Requires-Dist: clarifai-grpc>=11.
|
23
|
-
Requires-Dist: clarifai-protocol>=0.0.
|
22
|
+
Requires-Dist: clarifai-grpc>=11.7.5
|
23
|
+
Requires-Dist: clarifai-protocol>=0.0.27
|
24
24
|
Requires-Dist: numpy>=1.22.0
|
25
25
|
Requires-Dist: tqdm>=4.65.0
|
26
26
|
Requires-Dist: PyYAML>=6.0.1
|
@@ -32,7 +32,10 @@ Requires-Dist: click>=8.1.7
|
|
32
32
|
Requires-Dist: requests>=2.32.3
|
33
33
|
Requires-Dist: aiohttp>=3.10.0
|
34
34
|
Requires-Dist: uv==0.7.12
|
35
|
+
Requires-Dist: ruff==0.11.4
|
35
36
|
Requires-Dist: psutil==7.0.0
|
37
|
+
Requires-Dist: pydantic_core==2.33.2
|
38
|
+
Requires-Dist: packaging==25.0
|
36
39
|
Provides-Extra: all
|
37
40
|
Requires-Dist: pycocotools>=2.0.7; extra == "all"
|
38
41
|
Dynamic: author
|
@@ -72,7 +75,7 @@ Clarifai Python SDK
|
|
72
75
|
|
73
76
|
This is the official Python client for interacting with our powerful [API](https://docs.clarifai.com). The Clarifai Python SDK offers a comprehensive set of tools to integrate Clarifai's AI platform to leverage computer vision capabilities like classification , detection ,segementation and natural language capabilities like classification , summarisation , generation , Q&A ,etc into your applications. With just a few lines of code, you can leverage cutting-edge artificial intelligence to unlock valuable insights from visual and textual content.
|
74
77
|
|
75
|
-
[Website](https://www.clarifai.com/) | [Schedule Demo](https://www.clarifai.com/company/schedule-demo) | [Signup for a Free Account](https://clarifai.com/signup) | [API Docs](https://docs.clarifai.com/) | [Clarifai Community](https://clarifai.com/explore) | [Python SDK Docs](https://docs.clarifai.com/
|
78
|
+
[Website](https://www.clarifai.com/) | [Schedule Demo](https://www.clarifai.com/company/schedule-demo) | [Signup for a Free Account](https://clarifai.com/signup) | [API Docs](https://docs.clarifai.com/) | [Clarifai Community](https://clarifai.com/explore) | [Python SDK Docs](https://docs.clarifai.com/resources/api-references/python) | [Examples](https://github.com/Clarifai/examples) | [Colab Notebooks](https://github.com/Clarifai/colab-notebooks) | [Discord](https://discord.gg/XAPE3Vtg)
|
76
79
|
|
77
80
|
Give the repo a star ⭐
|
78
81
|
---
|
@@ -130,10 +133,25 @@ git clone https://github.com/Clarifai/clarifai-python.git
|
|
130
133
|
cd clarifai-python
|
131
134
|
python3 -m venv .venv
|
132
135
|
source .venv/bin/activate
|
133
|
-
pip install -
|
134
|
-
python setup.py install
|
136
|
+
pip install -e .
|
135
137
|
```
|
136
138
|
|
139
|
+
#### Linting
|
140
|
+
|
141
|
+
For developers, use the precommit hook `.pre-commit-config.yaml` to automate linting.
|
142
|
+
|
143
|
+
```bash
|
144
|
+
pip install -r requirements-dev.txt
|
145
|
+
pre-commit install
|
146
|
+
```
|
147
|
+
|
148
|
+
Now every time you run `git commit` your code will be automatically linted and won't commit if it fails.
|
149
|
+
|
150
|
+
You can also manually trigger linting using:
|
151
|
+
|
152
|
+
```bash
|
153
|
+
pre-commit run --all-files
|
154
|
+
```
|
137
155
|
|
138
156
|
|
139
157
|
## :memo: Getting started
|
@@ -1,4 +1,4 @@
|
|
1
|
-
clarifai/__init__.py,sha256=
|
1
|
+
clarifai/__init__.py,sha256=rSjRBQs9AcakQYnfj7YZFHQKbSsnq8BzM_MJeZ3UZ1M,23
|
2
2
|
clarifai/cli.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
clarifai/errors.py,sha256=GXa6D4v_L404J83jnRNFPH7s-1V9lk7w6Ws99f1g-AY,2772
|
4
4
|
clarifai/versions.py,sha256=ecSuEB_nOL2XSoYHDw2n23XUbm_KPOGjudMXmQrGdS8,224
|
@@ -8,12 +8,12 @@ clarifai/cli/__main__.py,sha256=7nPbLW7Jr2shkgMPvnxpn4xYGMvIcnqluJ69t9w4H_k,74
|
|
8
8
|
clarifai/cli/base.py,sha256=l1WpCM9URk7mkYmHNpnZYtihbTTmMpBB5oBo9IwgeEo,10677
|
9
9
|
clarifai/cli/compute_cluster.py,sha256=8Xss0Obrp6l1XuxJe0luOqU_pf8vXGDRi6jyIe8qR6k,2282
|
10
10
|
clarifai/cli/deployment.py,sha256=9C4I6_kyMxRkWl6h681wc79-3mAtDHtTUaxRv05OZMs,4262
|
11
|
-
clarifai/cli/model.py,sha256=
|
11
|
+
clarifai/cli/model.py,sha256=G-LcUEE0pcRVqFmz0l8n2wPRzLIx4cJcy5n8NjffoYo,43071
|
12
12
|
clarifai/cli/nodepool.py,sha256=H6OIdUW_EiyDUwZogzEDoYmVwEjLMsgoDlPyE7gjIuU,4245
|
13
13
|
clarifai/cli/pipeline.py,sha256=9ZWVLokXHxqXy0g1_1ACuRX20ckrzyKwnsFfuJZm6Nw,13130
|
14
14
|
clarifai/cli/pipeline_step.py,sha256=dvoC2vAsDcxOCy88VV0X42PG22_7JSu9sfBVsk-Cix4,6133
|
15
15
|
clarifai/cli/templates/__init__.py,sha256=HbMlZuYOMyVJde73ijNAevmSRUpIttGlHdwyO4W-JOs,44
|
16
|
-
clarifai/cli/templates/model_templates.py,sha256=
|
16
|
+
clarifai/cli/templates/model_templates.py,sha256=f3fvXTwHLBExjSOpdcEgp7X1CH28jjzpLFKljWi5arQ,9793
|
17
17
|
clarifai/cli/templates/pipeline_step_templates.py,sha256=w1IJghF_4wWyEmHR1925N0hpGKocy3G7ezhxTH-0XCc,1716
|
18
18
|
clarifai/cli/templates/pipeline_templates.py,sha256=l7-0yj1aw4jVZb0xjuhc9sW9LJfG3qaGqgwt7bLURCc,4462
|
19
19
|
clarifai/client/__init__.py,sha256=MWEG_jTGn6UWbGCznsZxObJ5h65k2igD1462qID2pgI,840
|
@@ -24,7 +24,7 @@ clarifai/client/dataset.py,sha256=OgdpZkQ_vYmRxL8-qphcNozpvPV1bWTlte9Jv6UkKb8,35
|
|
24
24
|
clarifai/client/deployment.py,sha256=QBf0tzkKBEpzNgmOEmWUJMOlVWdFEFc70Y44o8y75Gs,2875
|
25
25
|
clarifai/client/input.py,sha256=jpX47qwn7aUBBIEuSSLHF5jk70XaWEh0prD065c9b-E,51205
|
26
26
|
clarifai/client/lister.py,sha256=1YEm2suNxPaJO4x9V5szgD_YX6N_00vgSO-7m0HagY8,2208
|
27
|
-
clarifai/client/model.py,sha256=
|
27
|
+
clarifai/client/model.py,sha256=CQP5UiHaLR2hHcG1nU-ac_utN9ZXalOzbOqaICa0VyQ,92016
|
28
28
|
clarifai/client/model_client.py,sha256=pyPg1C3gb5Q3_DXmMmOB5AeORR1IDaaM7n0LGc3ciLk,38217
|
29
29
|
clarifai/client/module.py,sha256=jLViQYvVV3FmRN_ivvbk83uwsx7CgYGeEx4dYAr6yD4,4537
|
30
30
|
clarifai/client/nodepool.py,sha256=0E8-VgNMpdx2sAGotS3ZwfeUc9k3DBa3QJEJ1wKroVA,16700
|
@@ -35,7 +35,7 @@ clarifai/client/search.py,sha256=3LLfATrdU43a0mRNITmJV-E53bhfafZkYsbwkTtlnyU,156
|
|
35
35
|
clarifai/client/user.py,sha256=-D7itPC_XCMtFnsWUOMCPU5PQd8s-ER8k-RIG2V673Y,24798
|
36
36
|
clarifai/client/workflow.py,sha256=Bqh8lAmJcSbviJebckchTxucYlU11UQEhFSov7elpsk,13417
|
37
37
|
clarifai/client/auth/__init__.py,sha256=7EwR0NrozkAUwpUnCsqXvE_p0wqx_SelXlSpKShKJK0,136
|
38
|
-
clarifai/client/auth/helper.py,sha256=
|
38
|
+
clarifai/client/auth/helper.py,sha256=R_k094V79ZBDvujRhWV3kiV5EttuAsVtRQFNvOsSI8A,16290
|
39
39
|
clarifai/client/auth/register.py,sha256=xIg1OoZ_GGnc0KQKII-R6q_cKN1JXW3BGcU2eyS-Kkc,871
|
40
40
|
clarifai/client/auth/stub.py,sha256=nKod8eNWaBnmsaNZ59dEt42TTcwC9mMgCzEMYPZggxc,9537
|
41
41
|
clarifai/constants/base.py,sha256=ogmFSZYoF0YhGjHg5aiOc3MLqPr_poKAls6xaD0_C3U,89
|
@@ -75,14 +75,14 @@ clarifai/runners/__init__.py,sha256=wXLaSljH7qLeJCrZdKEnlQh2tNqTQAIZKWOu2rZ6wGs,
|
|
75
75
|
clarifai/runners/server.py,sha256=YgF-ZInjQ1DZeZ9UmK77rW35IUIXrJYa3SSm2xoXGtc,6252
|
76
76
|
clarifai/runners/dockerfile_template/Dockerfile.template,sha256=nEnIMqzhAXDbd0Ht7QQm0U7AVPIHWQD6kYnFZ0TKJUM,2428
|
77
77
|
clarifai/runners/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
|
-
clarifai/runners/models/dummy_openai_model.py,sha256=
|
78
|
+
clarifai/runners/models/dummy_openai_model.py,sha256=LfAJu173uTEYsN6xpaJ6TyimohyTooX16H6YYk9qVLs,8636
|
79
79
|
clarifai/runners/models/mcp_class.py,sha256=RdKn7rW4vYol0VRDZiLTSMfkqjLhO1ijXAQ0Rq0Jfnw,6647
|
80
|
-
clarifai/runners/models/model_builder.py,sha256=
|
80
|
+
clarifai/runners/models/model_builder.py,sha256=N7CyOt-jP5FGJGNvarHtlmEd_O6kNXEyaRJVmSdH3MU,70929
|
81
81
|
clarifai/runners/models/model_class.py,sha256=Ndh437BNMkpFBo6B108GuKL8sGYaGnSplZ6FxOgd_v8,20010
|
82
82
|
clarifai/runners/models/model_run_locally.py,sha256=6-6WjEKc0ba3gAv4wOLdMs2XOzS3b-2bZHJS0wdVqJY,20088
|
83
83
|
clarifai/runners/models/model_runner.py,sha256=RxhYawEX6vJKTZZ_AmcHwo2ONwT7V7e3W-vXuunY7eI,11874
|
84
84
|
clarifai/runners/models/model_servicer.py,sha256=415RqiXCPH-1WgFHrtZMDH1nb8gaQDpVlrQ_tvtbjOg,4523
|
85
|
-
clarifai/runners/models/openai_class.py,sha256=
|
85
|
+
clarifai/runners/models/openai_class.py,sha256=KWKZKNcsGDYX6Ro2Y0kK7xcMi4YDziKRZHr_3v_g7ns,8694
|
86
86
|
clarifai/runners/models/visual_classifier_class.py,sha256=1ZoLfCT2crrgRbejjTMAIwpTRgQMiH9N9yflOVpFxSg,2721
|
87
87
|
clarifai/runners/models/visual_detector_class.py,sha256=ky4oFAkGCKPpGPdgaOso-n6D3HcmnbKee_8hBsNiV8U,2883
|
88
88
|
clarifai/runners/pipeline_steps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -120,9 +120,9 @@ clarifai/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
120
120
|
clarifai/workflows/export.py,sha256=HvUYG9N_-UZoRR0-_tdGbZ950_AeBqawSppgUxQebR0,1913
|
121
121
|
clarifai/workflows/utils.py,sha256=ESL3INcouNcLKCh-nMpfXX-YbtCzX7tz7hT57_RGQ3M,2079
|
122
122
|
clarifai/workflows/validate.py,sha256=UhmukyHkfxiMFrPPeBdUTiCOHQT5-shqivlBYEyKTlU,2931
|
123
|
-
clarifai-11.
|
124
|
-
clarifai-11.
|
125
|
-
clarifai-11.
|
126
|
-
clarifai-11.
|
127
|
-
clarifai-11.
|
128
|
-
clarifai-11.
|
123
|
+
clarifai-11.7.0.dist-info/licenses/LICENSE,sha256=mUqF_d12-qE2n41g7C5_sq-BMLOcj6CNN-jevr15YHU,555
|
124
|
+
clarifai-11.7.0.dist-info/METADATA,sha256=AUq4ocwllb6s3rBIAqLHoIzJiYxzIYx1UnXQ7MB4ihg,23161
|
125
|
+
clarifai-11.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
126
|
+
clarifai-11.7.0.dist-info/entry_points.txt,sha256=X9FZ4Z-i_r2Ud1RpZ9sNIFYuu_-9fogzCMCRUD9hyX0,51
|
127
|
+
clarifai-11.7.0.dist-info/top_level.txt,sha256=wUMdCQGjkxaynZ6nZ9FAnvBUCgp5RJUVFSy2j-KYo0s,9
|
128
|
+
clarifai-11.7.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|