clarifai 11.6.4rc2__py3-none-any.whl → 11.6.6__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.
Files changed (200) hide show
  1. clarifai/__init__.py +1 -1
  2. clarifai/cli/README.md +39 -0
  3. clarifai/cli/base.py +119 -118
  4. clarifai/cli/model.py +116 -86
  5. clarifai/client/app.py +1 -1
  6. clarifai/client/auth/helper.py +7 -2
  7. clarifai/client/base.py +35 -8
  8. clarifai/client/compute_cluster.py +1 -1
  9. clarifai/client/model.py +3 -1
  10. clarifai/client/model_client.py +20 -1
  11. clarifai/client/nodepool.py +4 -2
  12. clarifai/client/user.py +4 -2
  13. clarifai/runners/models/model_runner.py +55 -0
  14. clarifai/runners/server.py +23 -1
  15. clarifai/runners/utils/model_utils.py +4 -4
  16. clarifai/runners/utils/url_fetcher.py +51 -12
  17. clarifai/utils/cli.py +148 -13
  18. clarifai/utils/constants.py +5 -0
  19. clarifai/utils/misc.py +47 -0
  20. {clarifai-11.6.4rc2.dist-info → clarifai-11.6.6.dist-info}/METADATA +2 -2
  21. clarifai-11.6.6.dist-info/RECORD +127 -0
  22. {clarifai-11.6.4rc2.dist-info → clarifai-11.6.6.dist-info}/WHEEL +1 -1
  23. clarifai/__pycache__/__init__.cpython-311.pyc +0 -0
  24. clarifai/__pycache__/__init__.cpython-39.pyc +0 -0
  25. clarifai/__pycache__/errors.cpython-311.pyc +0 -0
  26. clarifai/__pycache__/errors.cpython-39.pyc +0 -0
  27. clarifai/__pycache__/versions.cpython-311.pyc +0 -0
  28. clarifai/__pycache__/versions.cpython-39.pyc +0 -0
  29. clarifai/cli/__pycache__/__init__.cpython-39.pyc +0 -0
  30. clarifai/cli/__pycache__/base.cpython-39.pyc +0 -0
  31. clarifai/cli/__pycache__/compute_cluster.cpython-39.pyc +0 -0
  32. clarifai/cli/__pycache__/deployment.cpython-39.pyc +0 -0
  33. clarifai/cli/__pycache__/model.cpython-39.pyc +0 -0
  34. clarifai/cli/__pycache__/nodepool.cpython-39.pyc +0 -0
  35. clarifai/cli/model_templates.py +0 -243
  36. clarifai/cli/pipeline_step_templates.py +0 -64
  37. clarifai/client/__pycache__/__init__.cpython-311.pyc +0 -0
  38. clarifai/client/__pycache__/__init__.cpython-39.pyc +0 -0
  39. clarifai/client/__pycache__/app.cpython-311.pyc +0 -0
  40. clarifai/client/__pycache__/app.cpython-39.pyc +0 -0
  41. clarifai/client/__pycache__/base.cpython-311.pyc +0 -0
  42. clarifai/client/__pycache__/base.cpython-39.pyc +0 -0
  43. clarifai/client/__pycache__/compute_cluster.cpython-311.pyc +0 -0
  44. clarifai/client/__pycache__/dataset.cpython-311.pyc +0 -0
  45. clarifai/client/__pycache__/dataset.cpython-39.pyc +0 -0
  46. clarifai/client/__pycache__/deployment.cpython-311.pyc +0 -0
  47. clarifai/client/__pycache__/deployment.cpython-39.pyc +0 -0
  48. clarifai/client/__pycache__/input.cpython-311.pyc +0 -0
  49. clarifai/client/__pycache__/input.cpython-39.pyc +0 -0
  50. clarifai/client/__pycache__/lister.cpython-311.pyc +0 -0
  51. clarifai/client/__pycache__/lister.cpython-39.pyc +0 -0
  52. clarifai/client/__pycache__/model.cpython-311.pyc +0 -0
  53. clarifai/client/__pycache__/model.cpython-39.pyc +0 -0
  54. clarifai/client/__pycache__/model_client.cpython-311.pyc +0 -0
  55. clarifai/client/__pycache__/model_client.cpython-39.pyc +0 -0
  56. clarifai/client/__pycache__/module.cpython-311.pyc +0 -0
  57. clarifai/client/__pycache__/nodepool.cpython-311.pyc +0 -0
  58. clarifai/client/__pycache__/runner.cpython-311.pyc +0 -0
  59. clarifai/client/__pycache__/search.cpython-311.pyc +0 -0
  60. clarifai/client/__pycache__/user.cpython-311.pyc +0 -0
  61. clarifai/client/__pycache__/workflow.cpython-311.pyc +0 -0
  62. clarifai/client/auth/__pycache__/__init__.cpython-311.pyc +0 -0
  63. clarifai/client/auth/__pycache__/__init__.cpython-39.pyc +0 -0
  64. clarifai/client/auth/__pycache__/helper.cpython-311.pyc +0 -0
  65. clarifai/client/auth/__pycache__/helper.cpython-39.pyc +0 -0
  66. clarifai/client/auth/__pycache__/register.cpython-311.pyc +0 -0
  67. clarifai/client/auth/__pycache__/register.cpython-39.pyc +0 -0
  68. clarifai/client/auth/__pycache__/stub.cpython-311.pyc +0 -0
  69. clarifai/client/auth/__pycache__/stub.cpython-39.pyc +0 -0
  70. clarifai/constants/__pycache__/base.cpython-311.pyc +0 -0
  71. clarifai/constants/__pycache__/base.cpython-39.pyc +0 -0
  72. clarifai/constants/__pycache__/dataset.cpython-311.pyc +0 -0
  73. clarifai/constants/__pycache__/dataset.cpython-39.pyc +0 -0
  74. clarifai/constants/__pycache__/input.cpython-311.pyc +0 -0
  75. clarifai/constants/__pycache__/input.cpython-39.pyc +0 -0
  76. clarifai/constants/__pycache__/model.cpython-311.pyc +0 -0
  77. clarifai/constants/__pycache__/model.cpython-39.pyc +0 -0
  78. clarifai/constants/__pycache__/search.cpython-311.pyc +0 -0
  79. clarifai/constants/__pycache__/workflow.cpython-311.pyc +0 -0
  80. clarifai/datasets/__pycache__/__init__.cpython-311.pyc +0 -0
  81. clarifai/datasets/__pycache__/__init__.cpython-39.pyc +0 -0
  82. clarifai/datasets/export/__pycache__/__init__.cpython-311.pyc +0 -0
  83. clarifai/datasets/export/__pycache__/__init__.cpython-39.pyc +0 -0
  84. clarifai/datasets/export/__pycache__/inputs_annotations.cpython-311.pyc +0 -0
  85. clarifai/datasets/export/__pycache__/inputs_annotations.cpython-39.pyc +0 -0
  86. clarifai/datasets/upload/__pycache__/__init__.cpython-311.pyc +0 -0
  87. clarifai/datasets/upload/__pycache__/__init__.cpython-39.pyc +0 -0
  88. clarifai/datasets/upload/__pycache__/base.cpython-311.pyc +0 -0
  89. clarifai/datasets/upload/__pycache__/base.cpython-39.pyc +0 -0
  90. clarifai/datasets/upload/__pycache__/features.cpython-311.pyc +0 -0
  91. clarifai/datasets/upload/__pycache__/features.cpython-39.pyc +0 -0
  92. clarifai/datasets/upload/__pycache__/image.cpython-311.pyc +0 -0
  93. clarifai/datasets/upload/__pycache__/image.cpython-39.pyc +0 -0
  94. clarifai/datasets/upload/__pycache__/multimodal.cpython-311.pyc +0 -0
  95. clarifai/datasets/upload/__pycache__/multimodal.cpython-39.pyc +0 -0
  96. clarifai/datasets/upload/__pycache__/text.cpython-311.pyc +0 -0
  97. clarifai/datasets/upload/__pycache__/text.cpython-39.pyc +0 -0
  98. clarifai/datasets/upload/__pycache__/utils.cpython-311.pyc +0 -0
  99. clarifai/datasets/upload/__pycache__/utils.cpython-39.pyc +0 -0
  100. clarifai/models/model_serving/README.md +0 -158
  101. clarifai/models/model_serving/__init__.py +0 -14
  102. clarifai/models/model_serving/cli/__init__.py +0 -12
  103. clarifai/models/model_serving/cli/_utils.py +0 -53
  104. clarifai/models/model_serving/cli/base.py +0 -14
  105. clarifai/models/model_serving/cli/build.py +0 -79
  106. clarifai/models/model_serving/cli/clarifai_clis.py +0 -33
  107. clarifai/models/model_serving/cli/create.py +0 -171
  108. clarifai/models/model_serving/cli/example_cli.py +0 -34
  109. clarifai/models/model_serving/cli/login.py +0 -26
  110. clarifai/models/model_serving/cli/upload.py +0 -179
  111. clarifai/models/model_serving/constants.py +0 -21
  112. clarifai/models/model_serving/docs/cli.md +0 -161
  113. clarifai/models/model_serving/docs/concepts.md +0 -229
  114. clarifai/models/model_serving/docs/dependencies.md +0 -11
  115. clarifai/models/model_serving/docs/inference_parameters.md +0 -139
  116. clarifai/models/model_serving/docs/model_types.md +0 -19
  117. clarifai/models/model_serving/model_config/__init__.py +0 -16
  118. clarifai/models/model_serving/model_config/base.py +0 -369
  119. clarifai/models/model_serving/model_config/config.py +0 -312
  120. clarifai/models/model_serving/model_config/inference_parameter.py +0 -129
  121. clarifai/models/model_serving/model_config/model_types_config/multimodal-embedder.yaml +0 -25
  122. clarifai/models/model_serving/model_config/model_types_config/text-classifier.yaml +0 -19
  123. clarifai/models/model_serving/model_config/model_types_config/text-embedder.yaml +0 -20
  124. clarifai/models/model_serving/model_config/model_types_config/text-to-image.yaml +0 -19
  125. clarifai/models/model_serving/model_config/model_types_config/text-to-text.yaml +0 -19
  126. clarifai/models/model_serving/model_config/model_types_config/visual-classifier.yaml +0 -22
  127. clarifai/models/model_serving/model_config/model_types_config/visual-detector.yaml +0 -32
  128. clarifai/models/model_serving/model_config/model_types_config/visual-embedder.yaml +0 -19
  129. clarifai/models/model_serving/model_config/model_types_config/visual-segmenter.yaml +0 -19
  130. clarifai/models/model_serving/model_config/output.py +0 -133
  131. clarifai/models/model_serving/model_config/triton/__init__.py +0 -14
  132. clarifai/models/model_serving/model_config/triton/serializer.py +0 -136
  133. clarifai/models/model_serving/model_config/triton/triton_config.py +0 -182
  134. clarifai/models/model_serving/model_config/triton/wrappers.py +0 -281
  135. clarifai/models/model_serving/repo_build/__init__.py +0 -14
  136. clarifai/models/model_serving/repo_build/build.py +0 -198
  137. clarifai/models/model_serving/repo_build/static_files/_requirements.txt +0 -2
  138. clarifai/models/model_serving/repo_build/static_files/base_test.py +0 -169
  139. clarifai/models/model_serving/repo_build/static_files/inference.py +0 -26
  140. clarifai/models/model_serving/repo_build/static_files/sample_clarifai_config.yaml +0 -25
  141. clarifai/models/model_serving/repo_build/static_files/test.py +0 -40
  142. clarifai/models/model_serving/repo_build/static_files/triton/model.py +0 -75
  143. clarifai/models/model_serving/utils.py +0 -23
  144. clarifai/runners/__pycache__/__init__.cpython-311.pyc +0 -0
  145. clarifai/runners/__pycache__/__init__.cpython-39.pyc +0 -0
  146. clarifai/runners/models/__pycache__/__init__.cpython-311.pyc +0 -0
  147. clarifai/runners/models/__pycache__/__init__.cpython-39.pyc +0 -0
  148. clarifai/runners/models/__pycache__/mcp_class.cpython-311.pyc +0 -0
  149. clarifai/runners/models/__pycache__/model_builder.cpython-311.pyc +0 -0
  150. clarifai/runners/models/__pycache__/model_builder.cpython-39.pyc +0 -0
  151. clarifai/runners/models/__pycache__/model_class.cpython-311.pyc +0 -0
  152. clarifai/runners/models/__pycache__/model_runner.cpython-311.pyc +0 -0
  153. clarifai/runners/models/__pycache__/openai_class.cpython-311.pyc +0 -0
  154. clarifai/runners/models/base_typed_model.py +0 -238
  155. clarifai/runners/models/model_upload.py +0 -607
  156. clarifai/runners/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  157. clarifai/runners/utils/__pycache__/__init__.cpython-39.pyc +0 -0
  158. clarifai/runners/utils/__pycache__/code_script.cpython-311.pyc +0 -0
  159. clarifai/runners/utils/__pycache__/code_script.cpython-39.pyc +0 -0
  160. clarifai/runners/utils/__pycache__/const.cpython-311.pyc +0 -0
  161. clarifai/runners/utils/__pycache__/data_utils.cpython-311.pyc +0 -0
  162. clarifai/runners/utils/__pycache__/data_utils.cpython-39.pyc +0 -0
  163. clarifai/runners/utils/__pycache__/loader.cpython-311.pyc +0 -0
  164. clarifai/runners/utils/__pycache__/method_signatures.cpython-311.pyc +0 -0
  165. clarifai/runners/utils/__pycache__/model_utils.cpython-311.pyc +0 -0
  166. clarifai/runners/utils/__pycache__/openai_convertor.cpython-311.pyc +0 -0
  167. clarifai/runners/utils/__pycache__/serializers.cpython-311.pyc +0 -0
  168. clarifai/runners/utils/__pycache__/url_fetcher.cpython-311.pyc +0 -0
  169. clarifai/runners/utils/data_handler.py +0 -231
  170. clarifai/runners/utils/data_types/__pycache__/__init__.cpython-311.pyc +0 -0
  171. clarifai/runners/utils/data_types/__pycache__/__init__.cpython-39.pyc +0 -0
  172. clarifai/runners/utils/data_types/__pycache__/data_types.cpython-311.pyc +0 -0
  173. clarifai/runners/utils/data_types/__pycache__/data_types.cpython-39.pyc +0 -0
  174. clarifai/runners/utils/data_types.py +0 -471
  175. clarifai/runners/utils/temp.py +0 -59
  176. clarifai/schema/__pycache__/search.cpython-311.pyc +0 -0
  177. clarifai/urls/__pycache__/helper.cpython-311.pyc +0 -0
  178. clarifai/urls/__pycache__/helper.cpython-39.pyc +0 -0
  179. clarifai/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  180. clarifai/utils/__pycache__/__init__.cpython-39.pyc +0 -0
  181. clarifai/utils/__pycache__/cli.cpython-39.pyc +0 -0
  182. clarifai/utils/__pycache__/config.cpython-311.pyc +0 -0
  183. clarifai/utils/__pycache__/config.cpython-39.pyc +0 -0
  184. clarifai/utils/__pycache__/constants.cpython-311.pyc +0 -0
  185. clarifai/utils/__pycache__/constants.cpython-39.pyc +0 -0
  186. clarifai/utils/__pycache__/logging.cpython-311.pyc +0 -0
  187. clarifai/utils/__pycache__/logging.cpython-39.pyc +0 -0
  188. clarifai/utils/__pycache__/misc.cpython-311.pyc +0 -0
  189. clarifai/utils/__pycache__/misc.cpython-39.pyc +0 -0
  190. clarifai/utils/__pycache__/model_train.cpython-311.pyc +0 -0
  191. clarifai/utils/__pycache__/protobuf.cpython-311.pyc +0 -0
  192. clarifai/utils/__pycache__/protobuf.cpython-39.pyc +0 -0
  193. clarifai/workflows/__pycache__/__init__.cpython-311.pyc +0 -0
  194. clarifai/workflows/__pycache__/export.cpython-311.pyc +0 -0
  195. clarifai/workflows/__pycache__/utils.cpython-311.pyc +0 -0
  196. clarifai/workflows/__pycache__/validate.cpython-311.pyc +0 -0
  197. clarifai-11.6.4rc2.dist-info/RECORD +0 -301
  198. {clarifai-11.6.4rc2.dist-info → clarifai-11.6.6.dist-info}/entry_points.txt +0 -0
  199. {clarifai-11.6.4rc2.dist-info → clarifai-11.6.6.dist-info}/licenses/LICENSE +0 -0
  200. {clarifai-11.6.4rc2.dist-info → clarifai-11.6.6.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ import time
1
2
  from typing import Iterator
2
3
 
3
4
  from clarifai_grpc.grpc.api import service_pb2
@@ -6,6 +7,8 @@ from clarifai_protocol import BaseRunner
6
7
  from clarifai_protocol.utils.health import HealthProbeRequestHandler
7
8
 
8
9
  from clarifai.client.auth.helper import ClarifaiAuthHelper
10
+ from clarifai.utils.constants import STATUS_FAIL, STATUS_MIXED, STATUS_OK, STATUS_UNKNOWN
11
+ from clarifai.utils.logging import get_req_id_from_context, logger
9
12
 
10
13
  from ..utils.url_fetcher import ensure_urls_downloaded
11
14
  from .model_class import ModelClass
@@ -106,6 +109,20 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
106
109
  raise Exception("Unexpected work item type: {}".format(runner_item))
107
110
  request = runner_item.post_model_outputs_request
108
111
  ensure_urls_downloaded(request, auth_helper=self._auth_helper)
112
+ start_time = time.time()
113
+ req_id = get_req_id_from_context()
114
+ status_str = STATUS_UNKNOWN
115
+ # Endpoint is always POST /v2/.../outputs for this runner
116
+ endpoint = "POST /v2/.../outputs "
117
+
118
+ # if method_name == '_GET_SIGNATURES' then the request is for getting signatures and we don't want to log it.
119
+ # This is a workaround to avoid logging the _GET_SIGNATURES method call.
120
+ method_name = None
121
+ logging = True
122
+ if len(request.inputs) > 0 and '_method_name' in request.inputs[0].data.metadata:
123
+ method_name = request.inputs[0].data.metadata['_method_name']
124
+ if method_name == '_GET_SIGNATURES':
125
+ logging = False
109
126
 
110
127
  resp = self.model.predict_wrapper(request)
111
128
  # if we have any non-successful code already it's an error we can return.
@@ -113,6 +130,9 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
113
130
  resp.status.code != status_code_pb2.SUCCESS
114
131
  and resp.status.code != status_code_pb2.ZERO
115
132
  ):
133
+ status_str = f"{resp.status.code} ERROR"
134
+ duration_ms = (time.time() - start_time) * 1000
135
+ logger.info(f"{endpoint} | {status_str} | {duration_ms:.2f}ms | req_id={req_id}")
116
136
  return service_pb2.RunnerItemOutput(multi_output_response=resp)
117
137
  successes = []
118
138
  for output in resp.outputs:
@@ -126,18 +146,24 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
126
146
  code=status_code_pb2.SUCCESS,
127
147
  description="Success",
128
148
  )
149
+ status_str = STATUS_OK
129
150
  elif any(successes):
130
151
  status = status_pb2.Status(
131
152
  code=status_code_pb2.MIXED_STATUS,
132
153
  description="Mixed Status",
133
154
  )
155
+ status_str = STATUS_MIXED
134
156
  else:
135
157
  status = status_pb2.Status(
136
158
  code=status_code_pb2.FAILURE,
137
159
  description="Failed",
138
160
  )
161
+ status_str = STATUS_FAIL
139
162
 
140
163
  resp.status.CopyFrom(status)
164
+ if logging:
165
+ duration_ms = (time.time() - start_time) * 1000
166
+ logger.info(f"{endpoint} | {status_str} | {duration_ms:.2f}ms | req_id={req_id}")
141
167
  return service_pb2.RunnerItemOutput(multi_output_response=resp)
142
168
 
143
169
  def runner_item_generate(
@@ -150,12 +176,21 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
150
176
  request = runner_item.post_model_outputs_request
151
177
  ensure_urls_downloaded(request, auth_helper=self._auth_helper)
152
178
 
179
+ # --- Live logging additions ---
180
+ start_time = time.time()
181
+ req_id = get_req_id_from_context()
182
+ status_str = STATUS_UNKNOWN
183
+ endpoint = "POST /v2/.../outputs/generate"
184
+
153
185
  for resp in self.model.generate_wrapper(request):
154
186
  # if we have any non-successful code already it's an error we can return.
155
187
  if (
156
188
  resp.status.code != status_code_pb2.SUCCESS
157
189
  and resp.status.code != status_code_pb2.ZERO
158
190
  ):
191
+ status_str = f"{resp.status.code} ERROR"
192
+ duration_ms = (time.time() - start_time) * 1000
193
+ logger.info(f"{endpoint} | {status_str} | {duration_ms:.2f}ms | req_id={req_id}")
159
194
  yield service_pb2.RunnerItemOutput(multi_output_response=resp)
160
195
  continue
161
196
  successes = []
@@ -170,30 +205,44 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
170
205
  code=status_code_pb2.SUCCESS,
171
206
  description="Success",
172
207
  )
208
+ status_str = STATUS_OK
173
209
  elif any(successes):
174
210
  status = status_pb2.Status(
175
211
  code=status_code_pb2.MIXED_STATUS,
176
212
  description="Mixed Status",
177
213
  )
214
+ status_str = STATUS_MIXED
178
215
  else:
179
216
  status = status_pb2.Status(
180
217
  code=status_code_pb2.FAILURE,
181
218
  description="Failed",
182
219
  )
220
+ status_str = STATUS_FAIL
183
221
  resp.status.CopyFrom(status)
184
222
 
185
223
  yield service_pb2.RunnerItemOutput(multi_output_response=resp)
186
224
 
225
+ duration_ms = (time.time() - start_time) * 1000
226
+ logger.info(f"{endpoint} | {status_str} | {duration_ms:.2f}ms | req_id={req_id}")
227
+
187
228
  def runner_item_stream(
188
229
  self, runner_item_iterator: Iterator[service_pb2.RunnerItem]
189
230
  ) -> Iterator[service_pb2.RunnerItemOutput]:
190
231
  # Call the generate() method the underlying model implements.
232
+ start_time = time.time()
233
+ req_id = get_req_id_from_context()
234
+ status_str = STATUS_UNKNOWN
235
+ endpoint = "POST /v2/.../outputs/stream "
236
+
191
237
  for resp in self.model.stream_wrapper(pmo_iterator(runner_item_iterator)):
192
238
  # if we have any non-successful code already it's an error we can return.
193
239
  if (
194
240
  resp.status.code != status_code_pb2.SUCCESS
195
241
  and resp.status.code != status_code_pb2.ZERO
196
242
  ):
243
+ status_str = f"{resp.status.code} ERROR"
244
+ duration_ms = (time.time() - start_time) * 1000
245
+ logger.info(f"{endpoint} | {status_str} | {duration_ms:.2f}ms | req_id={req_id}")
197
246
  yield service_pb2.RunnerItemOutput(multi_output_response=resp)
198
247
  continue
199
248
  successes = []
@@ -208,20 +257,26 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
208
257
  code=status_code_pb2.SUCCESS,
209
258
  description="Success",
210
259
  )
260
+ status_str = STATUS_OK
211
261
  elif any(successes):
212
262
  status = status_pb2.Status(
213
263
  code=status_code_pb2.MIXED_STATUS,
214
264
  description="Mixed Status",
215
265
  )
266
+ status_str = STATUS_MIXED
216
267
  else:
217
268
  status = status_pb2.Status(
218
269
  code=status_code_pb2.FAILURE,
219
270
  description="Failed",
220
271
  )
272
+ status_str = STATUS_FAIL
221
273
  resp.status.CopyFrom(status)
222
274
 
223
275
  yield service_pb2.RunnerItemOutput(multi_output_response=resp)
224
276
 
277
+ duration_ms = (time.time() - start_time) * 1000
278
+ logger.info(f"{endpoint} | {status_str} | {duration_ms:.2f}ms | req_id={req_id}")
279
+
225
280
 
226
281
  def pmo_iterator(runner_item_iterator, auth_helper=None):
227
282
  for runner_item in runner_item_iterator:
@@ -92,6 +92,7 @@ def serve(
92
92
  runner_id: str = os.environ.get("CLARIFAI_RUNNER_ID", None),
93
93
  base_url: str = os.environ.get("CLARIFAI_API_BASE", "https://api.clarifai.com"),
94
94
  pat: str = os.environ.get("CLARIFAI_PAT", None),
95
+ context=None, # This is the current context object that contains user_id, app_id, model_id, etc.
95
96
  ):
96
97
  builder = ModelBuilder(model_path, download_validation_only=True)
97
98
 
@@ -133,7 +134,28 @@ def serve(
133
134
  pat=pat,
134
135
  num_parallel_polls=num_threads,
135
136
  )
136
- logger.info("Runner started successfully and is waiting for work from the API...")
137
+ method_signatures = builder.get_method_signatures(mocking=False)
138
+ from clarifai.runners.utils import code_script
139
+
140
+ snippet = code_script.generate_client_script(
141
+ method_signatures,
142
+ user_id=context.user_id,
143
+ app_id=context.app_id,
144
+ model_id=context.model_id,
145
+ deployment_id=context.deployment_id,
146
+ base_url=context.api_base,
147
+ )
148
+ logger.info("✅ Your model is running locally and is ready for requests from the API...\n")
149
+ logger.info(
150
+ f"> Code Snippet: To call your model via the API, use this code snippet:\n{snippet}"
151
+ )
152
+ logger.info(
153
+ f"> Playground: To chat with your model, visit: {context.ui}/playground?model={context.model_id}__{context.model_version_id}&user_id={context.user_id}&app_id={context.app_id}\n"
154
+ )
155
+ logger.info(
156
+ f"> API URL: To call your model via the API, use this model URL: {context.ui}/users/{context.user_id}/apps/{context.app_id}/models/{context.model_id}\n"
157
+ )
158
+ logger.info("Press CTRL+C to stop the runner.\n")
137
159
  runner.start() # start the runner to fetch work from the API.
138
160
 
139
161
 
@@ -68,13 +68,13 @@ def kill_process_tree(parent_pid, include_parent: bool = True, skip_pid: int = N
68
68
  logger.warning(f"Failed to kill parent process {parent_pid}: {e}")
69
69
 
70
70
 
71
- def execute_shell_command(
72
- command: str,
73
- ) -> subprocess.Popen:
71
+ def execute_shell_command(command: str, stdout=None, stderr=subprocess.STDOUT) -> subprocess.Popen:
74
72
  """Execute a shell command and return its process handle.
75
73
 
76
74
  Args:
77
75
  command (str): The shell command to execute.
76
+ stdout : Verbose logging control,
77
+ stderr : Verbose error logging control
78
78
 
79
79
  Returns:
80
80
  subprocess.Popen: Process handle for the executed command.
@@ -90,7 +90,7 @@ def execute_shell_command(
90
90
  parts = shlex.split(command)
91
91
 
92
92
  try:
93
- process = subprocess.Popen(parts, text=True, stderr=subprocess.STDOUT)
93
+ process = subprocess.Popen(parts, text=True, stdout=stdout, stderr=stderr)
94
94
 
95
95
  return process
96
96
  except subprocess.SubprocessError as e:
@@ -1,6 +1,7 @@
1
1
  import concurrent.futures
2
2
 
3
3
  import fsspec
4
+ import requests
4
5
 
5
6
  from clarifai.utils.logging import logger
6
7
 
@@ -12,6 +13,28 @@ def download_input(input, auth_helper=None):
12
13
  _download_input_data(input.data.parts[i].data, auth_helper=auth_helper)
13
14
 
14
15
 
16
+ def _download_with_handling(url, mode, auth_kwargs, setter, media_type):
17
+ fsspec_exceptions = (
18
+ getattr(fsspec.exceptions, 'FSTimeoutError', Exception),
19
+ getattr(fsspec.exceptions, 'BlocksizeMismatchError', Exception),
20
+ )
21
+ try:
22
+ with fsspec.open(url, mode, **auth_kwargs) as f:
23
+ setter(f.read())
24
+ except fsspec_exceptions as e:
25
+ logger.error(f"FSSpec error downloading {media_type} from {url}: {e}")
26
+ raise RuntimeError(f"FSSpec error downloading {media_type} from {url}: {e}") from e
27
+ except requests.RequestException as e:
28
+ logger.error(f"Requests error downloading {media_type} from {url}: {e}")
29
+ raise RuntimeError(f"Requests error downloading {media_type} from {url}: {e}") from e
30
+ except (IOError, OSError) as e:
31
+ logger.error(f"IO error downloading {media_type} from {url}: {e}")
32
+ raise RuntimeError(f"IO error downloading {media_type} from {url}: {e}") from e
33
+ except Exception as e:
34
+ logger.error(f"Unexpected error downloading {media_type} from {url}: {e}")
35
+ raise RuntimeError(f"Unexpected error downloading {media_type} from {url}: {e}") from e
36
+
37
+
15
38
  def _download_input_data(input_data, auth_helper=None):
16
39
  """
17
40
  This function will download any urls that are not already bytes.
@@ -26,21 +49,37 @@ def _download_input_data(input_data, auth_helper=None):
26
49
  auth_kwargs = _get_auth_kwargs(auth_helper)
27
50
 
28
51
  if input_data.image.url and not input_data.image.base64:
29
- # Download the image
30
- with fsspec.open(input_data.image.url, 'rb', **auth_kwargs) as f:
31
- input_data.image.base64 = f.read()
52
+ _download_with_handling(
53
+ input_data.image.url,
54
+ 'rb',
55
+ auth_kwargs,
56
+ lambda val: setattr(input_data.image, 'base64', val),
57
+ 'image',
58
+ )
32
59
  if input_data.video.url and not input_data.video.base64:
33
- # Download the video
34
- with fsspec.open(input_data.video.url, 'rb', **auth_kwargs) as f:
35
- input_data.video.base64 = f.read()
60
+ _download_with_handling(
61
+ input_data.video.url,
62
+ 'rb',
63
+ auth_kwargs,
64
+ lambda val: setattr(input_data.video, 'base64', val),
65
+ 'video',
66
+ )
36
67
  if input_data.audio.url and not input_data.audio.base64:
37
- # Download the audio
38
- with fsspec.open(input_data.audio.url, 'rb', **auth_kwargs) as f:
39
- input_data.audio.base64 = f.read()
68
+ _download_with_handling(
69
+ input_data.audio.url,
70
+ 'rb',
71
+ auth_kwargs,
72
+ lambda val: setattr(input_data.audio, 'base64', val),
73
+ 'audio',
74
+ )
40
75
  if input_data.text.url and not input_data.text.raw:
41
- # Download the text
42
- with fsspec.open(input_data.text.url, 'r', **auth_kwargs) as f:
43
- input_data.text.raw = f.read()
76
+ _download_with_handling(
77
+ input_data.text.url,
78
+ 'r',
79
+ auth_kwargs,
80
+ lambda val: setattr(input_data.text, 'raw', val),
81
+ 'text',
82
+ )
44
83
 
45
84
 
46
85
  def _get_auth_kwargs(auth_helper):
clarifai/utils/cli.py CHANGED
@@ -4,6 +4,7 @@ import pkgutil
4
4
  import sys
5
5
  import typing as t
6
6
  from collections import defaultdict
7
+ from pathlib import Path
7
8
  from typing import OrderedDict
8
9
 
9
10
  import click
@@ -205,18 +206,152 @@ def validate_context_auth(pat: str, user_id: str, api_base: str = None):
205
206
  logger.info("✅ Context is valid")
206
207
 
207
208
  except Exception as e:
208
- error_msg = str(e)
209
-
210
209
  # Check for common authentication errors and provide user-friendly messages
211
- if "PERMISSION_DENIED" in error_msg or "Unauthorized" in error_msg:
212
- logger.error(f"Invalid PAT token or incorrect user ID '{user_id}': {error_msg}")
213
- elif "UNAUTHENTICATED" in error_msg:
214
- logger.error(f"Invalid PAT token or user ID: {error_msg}")
215
- elif "SSL" in error_msg or "certificate" in error_msg:
216
- logger.error(f"SSL/Certificate error: {error_msg}")
217
- elif "Connection" in error_msg or "timeout" in error_msg:
218
- logger.error(f"Network connection error: {error_msg}")
219
- else:
220
- logger.error(f"❌ Validation failed: \n{error_msg}")
221
- logger.error("Please check your credentials and try again.")
210
+ logger.error(" Authentication failed. Please check your token and user ID.")
222
211
  raise click.Abort() # Exit without saving the configuration
212
+
213
+
214
+ def customize_ollama_model(
215
+ model_path, model_name=None, port=None, context_length=None, verbose=False
216
+ ):
217
+ """Customize the Ollama model name in the cloned template files.
218
+ Args:
219
+ model_path: Path to the cloned model directory
220
+ model_name: The model name to set (e.g., 'llama3.1', 'mistral') - optional
221
+ port: Port for Ollama server - optional
222
+ context_length: Context length for the model - optional
223
+ verbose: Whether to enable verbose logging - optional (defaults to False)
224
+
225
+ """
226
+ model_py_path = os.path.join(model_path, "1", "model.py")
227
+
228
+ if not os.path.exists(model_py_path):
229
+ logger.warning(f"Model file {model_py_path} not found, skipping model name customization")
230
+ return
231
+
232
+ try:
233
+ # Read the model.py file
234
+ with open(model_py_path, 'r') as file:
235
+ content = file.read()
236
+ if model_name:
237
+ # Replace the default model name in the load_model method
238
+ content = content.replace(
239
+ 'self.model = os.environ.get("OLLAMA_MODEL_NAME", \'llama3.2\')',
240
+ f'self.model = os.environ.get("OLLAMA_MODEL_NAME", \'{model_name}\')',
241
+ )
242
+
243
+ if port:
244
+ # Replace the default port variable in the model.py file
245
+ content = content.replace("PORT = '23333'", f"PORT = '{port}'")
246
+
247
+ if context_length:
248
+ # Replace the default context length variable in the model.py file
249
+ content = content.replace(
250
+ "context_length = '8192'", f"context_length = '{context_length}'"
251
+ )
252
+
253
+ verbose_str = str(verbose)
254
+ if "VERBOSE_OLLAMA = True" in content:
255
+ content = content.replace("VERBOSE_OLLAMA = True", f"VERBOSE_OLLAMA = {verbose_str}")
256
+ elif "VERBOSE_OLLAMA = False" in content:
257
+ content = content.replace("VERBOSE_OLLAMA = False", f"VERBOSE_OLLAMA = {verbose_str}")
258
+
259
+ # Write the modified content back to model.py
260
+ with open(model_py_path, 'w') as file:
261
+ file.write(content)
262
+
263
+ except Exception as e:
264
+ logger.error(f"Failed to customize Ollama model name in {model_py_path}: {e}")
265
+ raise
266
+
267
+
268
+ def check_ollama_installed():
269
+ """Check if the Ollama CLI is installed."""
270
+ try:
271
+ import subprocess
272
+
273
+ result = subprocess.run(
274
+ ['ollama', '--version'], capture_output=True, text=True, check=False
275
+ )
276
+ if result.returncode == 0:
277
+ return True
278
+ else:
279
+ return False
280
+ except FileNotFoundError:
281
+ return False
282
+
283
+
284
+ def _is_package_installed(package_name):
285
+ """Helper function to check if a single package in requirements.txt is installed."""
286
+ import importlib.metadata
287
+
288
+ try:
289
+ importlib.metadata.distribution(package_name)
290
+ logger.debug(f"✅ {package_name} - installed")
291
+ return True
292
+ except importlib.metadata.PackageNotFoundError:
293
+ logger.debug(f"❌ {package_name} - not installed")
294
+ return False
295
+ except Exception as e:
296
+ logger.warning(f"Error checking {package_name}: {e}")
297
+ return False
298
+
299
+
300
+ def parse_requirements(model_path: str):
301
+ """Parse requirements.txt in the model directory and return a dictionary of dependencies."""
302
+ from packaging.requirements import Requirement
303
+
304
+ requirements_path = Path(model_path) / "requirements.txt"
305
+
306
+ if not requirements_path.exists():
307
+ logger.warning(f"requirements.txt not found at {requirements_path}")
308
+ return []
309
+
310
+ deps = {}
311
+ for line in requirements_path.read_text().splitlines():
312
+ line = line.strip()
313
+ if not line or line.startswith("#"):
314
+ continue
315
+ try:
316
+ req = Requirement(line)
317
+ deps[req.name] = str(req.specifier) if req.specifier else None
318
+ except Exception as e:
319
+ logger.warning(f"⚠️ Could not parse line: {line!r} — {e}")
320
+ return deps
321
+
322
+
323
+ def check_requirements_installed(model_path):
324
+ """Check if all dependencies in requirements.txt are installed."""
325
+
326
+ try:
327
+ # Getting package name and version (for logging)
328
+ requirements = parse_requirements(model_path)
329
+
330
+ if not requirements:
331
+ logger.info("No dependencies found in requirements.txt")
332
+ return True
333
+
334
+ logger.info(f"Checking {len(requirements)} dependencies...")
335
+
336
+ missing = [
337
+ full_req
338
+ for package_name, full_req in requirements.items()
339
+ if not _is_package_installed(package_name)
340
+ ]
341
+
342
+ if not missing:
343
+ logger.info(f"✅ All {len(requirements)} dependencies are installed!")
344
+ return True
345
+
346
+ # Report missing packages
347
+ logger.error(
348
+ f"❌ {len(missing)} of {len(requirements)} required packages are missing in the current environment"
349
+ )
350
+ logger.error("\n".join(f" - {pkg}" for pkg in missing))
351
+ requirements_path = Path(model_path) / "requirements.txt"
352
+ logger.warning(f"To install: pip install -r {requirements_path}")
353
+ return False
354
+
355
+ except Exception as e:
356
+ logger.error(f"Failed to check requirements: {e}")
357
+ return False
@@ -64,3 +64,8 @@ DEFAULT_LOCAL_RUNNER_NODEPOOL_CONFIG = {
64
64
  }
65
65
  DEFAULT_OLLAMA_MODEL_REPO = "https://github.com/Clarifai/runners-examples"
66
66
  DEFAULT_OLLAMA_MODEL_REPO_BRANCH = "ollama"
67
+
68
+ STATUS_OK = "200 OK"
69
+ STATUS_MIXED = "207 MIXED"
70
+ STATUS_FAIL = "500 FAIL"
71
+ STATUS_UNKNOWN = "UNKNOWN"
clarifai/utils/misc.py CHANGED
@@ -78,6 +78,53 @@ def get_from_env(key: str, env_key: str) -> str:
78
78
  )
79
79
 
80
80
 
81
+ def get_from_dict_env_or_config(key: str, env_key: str, **data) -> str:
82
+ """Get a value from a dictionary, environment variable, or CLI config context."""
83
+ # First try the provided data/kwargs
84
+ if key in data and data[key]:
85
+ return data[key]
86
+
87
+ # Then try environment variables
88
+ if env_key in os.environ and os.environ[env_key]:
89
+ return os.environ[env_key]
90
+
91
+ # Finally try CLI config context as fallback
92
+ try:
93
+ from clarifai.utils.config import Config
94
+ from clarifai.utils.constants import DEFAULT_CONFIG
95
+
96
+ config = Config.from_yaml(filename=DEFAULT_CONFIG)
97
+ current_context = config.current
98
+
99
+ # Convert env_key to the attribute name expected by Context
100
+ # e.g., CLARIFAI_PAT -> pat, CLARIFAI_USER_ID -> user_id, CLARIFAI_API_BASE -> api_base
101
+ if env_key == "CLARIFAI_PAT":
102
+ attr_name = "pat"
103
+ elif env_key == "CLARIFAI_USER_ID":
104
+ attr_name = "user_id"
105
+ elif env_key == "CLARIFAI_API_BASE":
106
+ attr_name = "api_base"
107
+ else:
108
+ # For other cases, convert CLARIFAI_SOMETHING to something
109
+ attr_name = env_key.replace("CLARIFAI_", "").lower()
110
+
111
+ if hasattr(current_context, attr_name):
112
+ value = getattr(current_context, attr_name)
113
+ if value:
114
+ return value
115
+ except Exception:
116
+ # If CLI config loading fails, fall through to raise error
117
+ pass
118
+
119
+ # If all methods fail, raise an error suggesting clarifai login
120
+ raise UserError(
121
+ f"Configuration Required. Could not find '{key}'. Please provide it in one of the following ways:\n\n"
122
+ f"- Pass '{key}' as a named parameter to your function.\n"
123
+ f"- Set the {env_key} environment variable in your environment.\n"
124
+ f"- Run `clarifai login` in your terminal to configure CLI authentication."
125
+ )
126
+
127
+
81
128
  def concept_relations_accumulation(
82
129
  relations_dict: Dict[str, Any], subject_concept: str, object_concept: str, predicate: str
83
130
  ) -> Dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clarifai
3
- Version: 11.6.4rc2
3
+ Version: 11.6.6
4
4
  Home-page: https://github.com/Clarifai/clarifai-python
5
5
  Author: Clarifai
6
6
  Author-email: support@clarifai.com
@@ -19,7 +19,7 @@ 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.1
22
+ Requires-Dist: clarifai-grpc>=11.6.6
23
23
  Requires-Dist: clarifai-protocol>=0.0.25
24
24
  Requires-Dist: numpy>=1.22.0
25
25
  Requires-Dist: tqdm>=4.65.0