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 CHANGED
@@ -1 +1 @@
1
- __version__ = "11.6.8"
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. Apply for `--mode conatainer`.',
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. Apply for `--mode conatainer`.',
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=1, # default to 1 thread for local runner to avoid rapid depletion of compute time.
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=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
- # when: "build" # or "runtime", "upload"
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
 
@@ -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) -> tuple[service_pb2_grpc.V2Stub, grpc.Channel]:
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 create_dockerfile(self):
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
- # Write Dockerfile
889
- with open(os.path.join(self.folder, 'Dockerfile'), 'w') as dockerfile:
890
- dockerfile.write(dockerfile_content)
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 create a Dockerfile.
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
- deploy_model = input("Do you want to deploy the model? (y/n): ")
1276
- if deploy_model.lower() != 'y':
1277
- logger.info("Model uploaded successfully. Skipping deployment setup.")
1278
- return
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
- # Setup deployment for the uploaded model
1281
- setup_deployment_for_model(builder)
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}/settings/compute/deployments/new?computeClusterId={compute_cluster.id}&nodePoolId={nodepool.id}"
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 = json.loads(msg)
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 json.dumps(response.model_dump())
164
+ return response.model_dump_json()
134
165
  except Exception as e:
135
- return f"Error: {e}"
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 = json.loads(msg)
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 json.dumps(chunk.model_dump())
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 json.dumps(chunk.model_dump())
203
+ yield chunk.model_dump_json()
166
204
 
167
205
  except Exception as e:
168
- yield f"Error: {e}"
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.6.8
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.6.6
23
- Requires-Dist: clarifai-protocol>=0.0.25
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/python-sdk/api-reference) | [Examples](https://github.com/Clarifai/examples) | [Colab Notebooks](https://github.com/Clarifai/colab-notebooks) | [Discord](https://discord.gg/XAPE3Vtg)
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 -r requirements.txt
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=X6Ae2cXGrameIerv46p54-fULe3UApanwCJ4-JaaI_M,23
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=UwlHHOfXc-Yd55TfBSOe1YgsqJNrnLPN7gAj8VgEG74,42814
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=q1dKF5jHhX_1g4qtiYcEbJdWQaxCv2BSEAXI4AR2JOo,9709
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=DeB-es6rp0FJQVY46oO2eR_-Ux5tTmkM_qYsN1X6WcE,90793
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=eitq1uQBoJ1TPjsTtbJ9g7eqT3mFm9vgc4HQR1s-zNg,16283
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=pcmAVbqTTGG4J3BLVjKfvM_SQ-GET_XexIUdLcr9Zvo,8373
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=6DEwMo-Aq5CSWxTgMNgQISq2H37HXlkPauOgnmSWhZk,64704
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=OVYe4dWJPhskyZn53X9yJorzKZA7mjRCqYbu1rzRTFU,6715
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.6.8.dist-info/licenses/LICENSE,sha256=mUqF_d12-qE2n41g7C5_sq-BMLOcj6CNN-jevr15YHU,555
124
- clarifai-11.6.8.dist-info/METADATA,sha256=KPfsSo5hJ7EUSgWidOaX7PV7-JZNjtp3Qglw_doUc5E,22737
125
- clarifai-11.6.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
126
- clarifai-11.6.8.dist-info/entry_points.txt,sha256=X9FZ4Z-i_r2Ud1RpZ9sNIFYuu_-9fogzCMCRUD9hyX0,51
127
- clarifai-11.6.8.dist-info/top_level.txt,sha256=wUMdCQGjkxaynZ6nZ9FAnvBUCgp5RJUVFSy2j-KYo0s,9
128
- clarifai-11.6.8.dist-info/RECORD,,
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,,