clarifai 11.4.3rc1__py3-none-any.whl → 11.4.5__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/base.py +1 -2
- clarifai/cli/model.py +0 -2
- clarifai/client/app.py +2 -1
- clarifai/client/auth/helper.py +6 -4
- clarifai/client/compute_cluster.py +2 -1
- clarifai/client/dataset.py +2 -1
- clarifai/client/deployment.py +2 -1
- clarifai/client/input.py +2 -1
- clarifai/client/model.py +2 -1
- clarifai/client/model_client.py +2 -2
- clarifai/client/module.py +2 -1
- clarifai/client/nodepool.py +2 -1
- clarifai/client/runner.py +2 -1
- clarifai/client/search.py +2 -1
- clarifai/client/user.py +2 -1
- clarifai/client/workflow.py +2 -1
- clarifai/runners/__init__.py +2 -0
- clarifai/runners/models/dummy_openai_model.py +197 -0
- clarifai/runners/models/mcp_class.py +19 -39
- clarifai/runners/models/model_builder.py +46 -42
- clarifai/runners/models/openai_class.py +219 -0
- clarifai/runners/utils/code_script.py +41 -15
- clarifai/runners/utils/data_types/data_types.py +48 -0
- clarifai/runners/utils/data_utils.py +67 -43
- clarifai/runners/utils/method_signatures.py +0 -20
- clarifai/runners/utils/openai_convertor.py +103 -0
- clarifai/urls/helper.py +80 -12
- clarifai/utils/config.py +1 -1
- clarifai/utils/constants.py +4 -0
- {clarifai-11.4.3rc1.dist-info → clarifai-11.4.5.dist-info}/METADATA +13 -2
- clarifai-11.4.5.dist-info/RECORD +114 -0
- {clarifai-11.4.3rc1.dist-info → clarifai-11.4.5.dist-info}/WHEEL +1 -1
- clarifai/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/__pycache__/errors.cpython-312.pyc +0 -0
- clarifai/__pycache__/errors.cpython-39.pyc +0 -0
- clarifai/__pycache__/versions.cpython-312.pyc +0 -0
- clarifai/__pycache__/versions.cpython-39.pyc +0 -0
- clarifai/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/cli/__pycache__/base.cpython-312.pyc +0 -0
- clarifai/cli/__pycache__/compute_cluster.cpython-312.pyc +0 -0
- clarifai/cli/__pycache__/deployment.cpython-312.pyc +0 -0
- clarifai/cli/__pycache__/model.cpython-312.pyc +0 -0
- clarifai/cli/__pycache__/nodepool.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/client/__pycache__/app.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/app.cpython-39.pyc +0 -0
- clarifai/client/__pycache__/base.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/base.cpython-39.pyc +0 -0
- clarifai/client/__pycache__/compute_cluster.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/dataset.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/deployment.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/input.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/lister.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/model.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/model_client.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/module.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/nodepool.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/runner.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/search.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/user.cpython-312.pyc +0 -0
- clarifai/client/__pycache__/workflow.cpython-312.pyc +0 -0
- clarifai/client/auth/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/client/auth/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/client/auth/__pycache__/helper.cpython-312.pyc +0 -0
- clarifai/client/auth/__pycache__/helper.cpython-39.pyc +0 -0
- clarifai/client/auth/__pycache__/register.cpython-312.pyc +0 -0
- clarifai/client/auth/__pycache__/register.cpython-39.pyc +0 -0
- clarifai/client/auth/__pycache__/stub.cpython-312.pyc +0 -0
- clarifai/client/auth/__pycache__/stub.cpython-39.pyc +0 -0
- clarifai/constants/__pycache__/base.cpython-312.pyc +0 -0
- clarifai/constants/__pycache__/base.cpython-39.pyc +0 -0
- clarifai/constants/__pycache__/dataset.cpython-312.pyc +0 -0
- clarifai/constants/__pycache__/input.cpython-312.pyc +0 -0
- clarifai/constants/__pycache__/model.cpython-312.pyc +0 -0
- clarifai/constants/__pycache__/rag.cpython-312.pyc +0 -0
- clarifai/constants/__pycache__/search.cpython-312.pyc +0 -0
- clarifai/constants/__pycache__/workflow.cpython-312.pyc +0 -0
- clarifai/datasets/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/datasets/export/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/datasets/export/__pycache__/inputs_annotations.cpython-312.pyc +0 -0
- clarifai/datasets/upload/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/datasets/upload/__pycache__/base.cpython-312.pyc +0 -0
- clarifai/datasets/upload/__pycache__/features.cpython-312.pyc +0 -0
- clarifai/datasets/upload/__pycache__/image.cpython-312.pyc +0 -0
- clarifai/datasets/upload/__pycache__/multimodal.cpython-312.pyc +0 -0
- clarifai/datasets/upload/__pycache__/text.cpython-312.pyc +0 -0
- clarifai/datasets/upload/__pycache__/utils.cpython-312.pyc +0 -0
- clarifai/datasets/upload/loaders/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/datasets/upload/loaders/__pycache__/coco_detection.cpython-312.pyc +0 -0
- clarifai/datasets/upload/loaders/__pycache__/imagenet_classification.cpython-312.pyc +0 -0
- clarifai/modules/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/modules/__pycache__/css.cpython-312.pyc +0 -0
- clarifai/rag/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/rag/__pycache__/rag.cpython-312.pyc +0 -0
- clarifai/rag/__pycache__/utils.cpython-312.pyc +0 -0
- clarifai/runners/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/runners/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/runners/__pycache__/server.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/runners/models/__pycache__/base_typed_model.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/mcp_class.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/model_builder.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/model_builder.cpython-39.pyc +0 -0
- clarifai/runners/models/__pycache__/model_class.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/model_run_locally.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/model_runner.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/model_servicer.cpython-312.pyc +0 -0
- clarifai/runners/models/__pycache__/test_model_builder.cpython-312-pytest-8.3.5.pyc +0 -0
- clarifai/runners/models/base_typed_model.py +0 -238
- clarifai/runners/models/example_mcp_server.py +0 -44
- clarifai/runners/models/mcp_class.py~ +0 -149
- clarifai/runners/models/test_model_builder.py +0 -89
- clarifai/runners/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/code_script.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/const.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_handler.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_types.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_utils.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/loader.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/method_signatures.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/serializers.cpython-312.pyc +0 -0
- clarifai/runners/utils/__pycache__/url_fetcher.cpython-312.pyc +0 -0
- clarifai/runners/utils/data_handler.py +0 -231
- clarifai/runners/utils/data_types/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/runners/utils/data_types/__pycache__/data_types.cpython-312.pyc +0 -0
- clarifai/schema/__pycache__/search.cpython-312.pyc +0 -0
- clarifai/urls/__pycache__/helper.cpython-312.pyc +0 -0
- clarifai/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/utils/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/utils/__pycache__/cli.cpython-312.pyc +0 -0
- clarifai/utils/__pycache__/config.cpython-312.pyc +0 -0
- clarifai/utils/__pycache__/constants.cpython-312.pyc +0 -0
- clarifai/utils/__pycache__/constants.cpython-39.pyc +0 -0
- clarifai/utils/__pycache__/logging.cpython-312.pyc +0 -0
- clarifai/utils/__pycache__/logging.cpython-39.pyc +0 -0
- clarifai/utils/__pycache__/misc.cpython-312.pyc +0 -0
- clarifai/utils/__pycache__/misc.cpython-39.pyc +0 -0
- clarifai/utils/__pycache__/model_train.cpython-312.pyc +0 -0
- clarifai/utils/__pycache__/protobuf.cpython-312.pyc +0 -0
- clarifai/utils/config.py~ +0 -145
- clarifai/utils/evaluation/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/utils/evaluation/__pycache__/helpers.cpython-312.pyc +0 -0
- clarifai/utils/evaluation/__pycache__/main.cpython-312.pyc +0 -0
- clarifai/workflows/__pycache__/__init__.cpython-312.pyc +0 -0
- clarifai/workflows/__pycache__/export.cpython-312.pyc +0 -0
- clarifai/workflows/__pycache__/utils.cpython-312.pyc +0 -0
- clarifai/workflows/__pycache__/validate.cpython-312.pyc +0 -0
- clarifai-11.4.3rc1.dist-info/RECORD +0 -230
- {clarifai-11.4.3rc1.dist-info → clarifai-11.4.5.dist-info}/entry_points.txt +0 -0
- {clarifai-11.4.3rc1.dist-info → clarifai-11.4.5.dist-info/licenses}/LICENSE +0 -0
- {clarifai-11.4.3rc1.dist-info → clarifai-11.4.5.dist-info}/top_level.txt +0 -0
@@ -413,6 +413,12 @@ class ModelBuilder:
|
|
413
413
|
def get_method_signatures(self, mocking=True):
|
414
414
|
"""
|
415
415
|
Returns the method signatures for the model class.
|
416
|
+
|
417
|
+
Args:
|
418
|
+
mocking (bool): Whether to mock the model class or not. Defaults to False.
|
419
|
+
|
420
|
+
Returns:
|
421
|
+
list: A list of method signatures for the model class.
|
416
422
|
"""
|
417
423
|
model_class = self.load_model_class(mocking=mocking)
|
418
424
|
method_info = model_class._get_method_info()
|
@@ -436,22 +442,42 @@ class ModelBuilder:
|
|
436
442
|
return self._client
|
437
443
|
|
438
444
|
@property
|
439
|
-
def
|
445
|
+
def model_ui_url(self):
|
446
|
+
url_helper = ClarifaiUrlHelper(self._client.auth_helper)
|
447
|
+
# Note(zeiler): the UI experience isn't the best when including version id right now.
|
448
|
+
# if self.model_version_id is not None:
|
449
|
+
# return url_helper.clarifai_url(
|
450
|
+
# self.client.user_app_id.user_id,
|
451
|
+
# self.client.user_app_id.app_id,
|
452
|
+
# "models",
|
453
|
+
# self.model_id,
|
454
|
+
# self.model_version_id,
|
455
|
+
# )
|
456
|
+
# else:
|
457
|
+
return url_helper.clarifai_url(
|
458
|
+
self.client.user_app_id.user_id,
|
459
|
+
self.client.user_app_id.app_id,
|
460
|
+
"models",
|
461
|
+
self.model_id,
|
462
|
+
)
|
463
|
+
|
464
|
+
@property
|
465
|
+
def model_api_url(self):
|
440
466
|
url_helper = ClarifaiUrlHelper(self._client.auth_helper)
|
441
467
|
if self.model_version_id is not None:
|
442
|
-
return url_helper.
|
468
|
+
return url_helper.api_url(
|
443
469
|
self.client.user_app_id.user_id,
|
444
470
|
self.client.user_app_id.app_id,
|
445
471
|
"models",
|
446
472
|
self.model_id,
|
473
|
+
self.model_version_id,
|
447
474
|
)
|
448
475
|
else:
|
449
|
-
return url_helper.
|
476
|
+
return url_helper.api_url(
|
450
477
|
self.client.user_app_id.user_id,
|
451
478
|
self.client.user_app_id.app_id,
|
452
479
|
"models",
|
453
480
|
self.model_id,
|
454
|
-
self.model_version_id,
|
455
481
|
)
|
456
482
|
|
457
483
|
def _get_model_proto(self):
|
@@ -930,44 +956,20 @@ class ModelBuilder:
|
|
930
956
|
return
|
931
957
|
self.model_version_id = response.model_version_id
|
932
958
|
logger.info(f"Created Model Version ID: {self.model_version_id}")
|
933
|
-
logger.info(f"Full url to that version is: {self.
|
959
|
+
logger.info(f"Full url to that version is: {self.model_ui_url}")
|
934
960
|
try:
|
935
961
|
is_uploaded = self.monitor_model_build()
|
936
962
|
if is_uploaded:
|
937
|
-
#
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
headers={"Authorization": "Bearer " + os.environ["CLARIFAI_PAT"]})
|
948
|
-
|
949
|
-
async def main():
|
950
|
-
async with Client(transport) as client:
|
951
|
-
tools = await client.list_tools()
|
952
|
-
print(f"Available tools: {tools}")
|
953
|
-
result = await client.call_tool(tools[0].name, {"a": 5, "b": 3})
|
954
|
-
print(f"Result: {result[0].text}")
|
955
|
-
|
956
|
-
if __name__ == "__main__":
|
957
|
-
asyncio.run(main())
|
958
|
-
"""
|
959
|
-
% self.model_url
|
960
|
-
)
|
961
|
-
else: # python code to run the model.
|
962
|
-
from clarifai.runners.utils import code_script
|
963
|
-
|
964
|
-
method_signatures = self.get_method_signatures()
|
965
|
-
snippet = code_script.generate_client_script(
|
966
|
-
method_signatures,
|
967
|
-
user_id=self.client.user_app_id.user_id,
|
968
|
-
app_id=self.client.user_app_id.app_id,
|
969
|
-
model_id=self.model_proto.id,
|
970
|
-
)
|
963
|
+
# python code to run the model.
|
964
|
+
from clarifai.runners.utils import code_script
|
965
|
+
|
966
|
+
method_signatures = self.get_method_signatures()
|
967
|
+
snippet = code_script.generate_client_script(
|
968
|
+
method_signatures,
|
969
|
+
user_id=self.client.user_app_id.user_id,
|
970
|
+
app_id=self.client.user_app_id.app_id,
|
971
|
+
model_id=self.model_proto.id,
|
972
|
+
)
|
971
973
|
logger.info("""\n
|
972
974
|
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
973
975
|
# Here is a code snippet to use this model:
|
@@ -1074,7 +1076,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
1074
1076
|
logger.info("Model build complete!")
|
1075
1077
|
logger.info(f"Build time elapsed {time.time() - st:.1f}s)")
|
1076
1078
|
logger.info(
|
1077
|
-
f"Check out the model at {self.
|
1079
|
+
f"Check out the model at {self.model_ui_url} version: {self.model_version_id}"
|
1078
1080
|
)
|
1079
1081
|
return True
|
1080
1082
|
else:
|
@@ -1099,10 +1101,12 @@ def upload_model(folder, stage, skip_dockerfile):
|
|
1099
1101
|
exists = builder.check_model_exists()
|
1100
1102
|
if exists:
|
1101
1103
|
logger.info(
|
1102
|
-
f"Model already exists at {builder.
|
1104
|
+
f"Model already exists at {builder.model_ui_url}, this upload will create a new version for it."
|
1103
1105
|
)
|
1104
1106
|
else:
|
1105
|
-
logger.info(
|
1107
|
+
logger.info(
|
1108
|
+
f"New model will be created at {builder.model_ui_url} with it's first version."
|
1109
|
+
)
|
1106
1110
|
|
1107
1111
|
input("Press Enter to continue...")
|
1108
1112
|
builder.upload_model_version()
|
@@ -0,0 +1,219 @@
|
|
1
|
+
"""Base class for creating OpenAI-compatible API server."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
from typing import Any, Dict, Iterator
|
5
|
+
|
6
|
+
from clarifai.runners.models.model_class import ModelClass
|
7
|
+
|
8
|
+
|
9
|
+
class OpenAIModelClass(ModelClass):
|
10
|
+
"""Base class for wrapping OpenAI-compatible servers as a model running in Clarifai.
|
11
|
+
This handles all the transport between the API and the OpenAI-compatible server.
|
12
|
+
|
13
|
+
To use this class, create a subclass and set the following class attributes:
|
14
|
+
- openai_client: The OpenAI-compatible client instance
|
15
|
+
- model_name: The name of the model to use with the client
|
16
|
+
|
17
|
+
Example:
|
18
|
+
class MyOpenAIModel(OpenAIModelClass):
|
19
|
+
openai_client = OpenAI(api_key="your-key")
|
20
|
+
model_name = "gpt-4"
|
21
|
+
"""
|
22
|
+
|
23
|
+
# These should be overridden in subclasses
|
24
|
+
openai_client = None
|
25
|
+
model_name = None
|
26
|
+
|
27
|
+
def __init__(self) -> None:
|
28
|
+
if self.openai_client is None:
|
29
|
+
raise NotImplementedError("Subclasses must set the 'openai_client' class attribute")
|
30
|
+
if self.model_name is None:
|
31
|
+
self.model_name = self.openai_client.models.list().data[0].id
|
32
|
+
|
33
|
+
self.client = self.openai_client
|
34
|
+
self.model = self.model_name
|
35
|
+
|
36
|
+
def _extract_request_params(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
37
|
+
"""Extract and validate common openai arguments parameters from the request data.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
request_data: The parsed JSON request data
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
Dict containing the extracted parameters
|
44
|
+
"""
|
45
|
+
return {
|
46
|
+
"messages": request_data.get("messages", []),
|
47
|
+
"temperature": request_data.get("temperature", 1.0),
|
48
|
+
"max_tokens": request_data.get("max_tokens"),
|
49
|
+
"max_completion_tokens": request_data.get("max_completion_tokens"),
|
50
|
+
"n": request_data.get("n", 1),
|
51
|
+
"frequency_penalty": request_data.get("frequency_penalty"),
|
52
|
+
"presence_penalty": request_data.get("presence_penalty"),
|
53
|
+
"top_p": request_data.get("top_p", 1.0),
|
54
|
+
"reasoning_effort": request_data.get("reasoning_effort"),
|
55
|
+
"response_format": request_data.get("response_format"),
|
56
|
+
"stop": request_data.get("stop"),
|
57
|
+
"tools": request_data.get("tools"),
|
58
|
+
"tool_choice": request_data.get("tool_choice"),
|
59
|
+
"tool_resources": request_data.get("tool_resources"),
|
60
|
+
"modalities": request_data.get("modalities"),
|
61
|
+
"stream_options": request_data.get("stream_options", {"include_usage": True}),
|
62
|
+
}
|
63
|
+
|
64
|
+
def _create_completion_args(
|
65
|
+
self, params: Dict[str, Any], stream: bool = False
|
66
|
+
) -> Dict[str, Any]:
|
67
|
+
"""Create the completion arguments dictionary from parameters.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
params: Dictionary of parameters extracted from request
|
71
|
+
stream: Whether this is a streaming request
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
Dict containing the completion arguments
|
75
|
+
"""
|
76
|
+
completion_args = {
|
77
|
+
"model": self.model,
|
78
|
+
"messages": params["messages"],
|
79
|
+
"temperature": params["temperature"],
|
80
|
+
}
|
81
|
+
|
82
|
+
if stream:
|
83
|
+
completion_args["stream"] = True
|
84
|
+
if params.get("stream_options"):
|
85
|
+
completion_args["stream_options"] = params["stream_options"]
|
86
|
+
|
87
|
+
# Add optional parameters if they exist
|
88
|
+
optional_params = [
|
89
|
+
"max_tokens",
|
90
|
+
"max_completion_tokens",
|
91
|
+
"n",
|
92
|
+
"frequency_penalty",
|
93
|
+
"presence_penalty",
|
94
|
+
"top_p",
|
95
|
+
"reasoning_effort",
|
96
|
+
"response_format",
|
97
|
+
"stop",
|
98
|
+
"tools",
|
99
|
+
"tool_choice",
|
100
|
+
"tool_resources",
|
101
|
+
"modalities",
|
102
|
+
]
|
103
|
+
|
104
|
+
for param in optional_params:
|
105
|
+
if params.get(param) is not None:
|
106
|
+
completion_args[param] = params[param]
|
107
|
+
|
108
|
+
return completion_args
|
109
|
+
|
110
|
+
def _format_error_response(self, error: Exception) -> str:
|
111
|
+
"""Format an error response in OpenAI-compatible format.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
error: The exception that occurred
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
JSON string containing the error response
|
118
|
+
"""
|
119
|
+
error_response = {
|
120
|
+
"error": {
|
121
|
+
"message": str(error),
|
122
|
+
"type": "InvalidRequestError",
|
123
|
+
"code": "invalid_request_error",
|
124
|
+
}
|
125
|
+
}
|
126
|
+
return json.dumps(error_response)
|
127
|
+
|
128
|
+
@ModelClass.method
|
129
|
+
def openai_transport(self, msg: str) -> str:
|
130
|
+
"""The single model method to get the OpenAI-compatible request and send it to the OpenAI server
|
131
|
+
then return its response.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
msg: JSON string containing the request parameters
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
JSON string containing the response or error
|
138
|
+
"""
|
139
|
+
try:
|
140
|
+
request_data = json.loads(msg)
|
141
|
+
params = self._extract_request_params(request_data)
|
142
|
+
stream = request_data.get("stream", False)
|
143
|
+
|
144
|
+
if stream:
|
145
|
+
chunks = self._process_streaming_request(**params)
|
146
|
+
response_list = []
|
147
|
+
for chunk in chunks:
|
148
|
+
response_list.append(chunk)
|
149
|
+
return json.dumps(response_list)
|
150
|
+
else:
|
151
|
+
completion = self._process_request(**params)
|
152
|
+
if completion.get('usage'):
|
153
|
+
if completion['usage'].get('prompt_tokens') and completion['usage'].get(
|
154
|
+
'completion_tokens'
|
155
|
+
):
|
156
|
+
self.set_output_context(
|
157
|
+
prompt_tokens=completion['usage']['prompt_tokens'],
|
158
|
+
completion_tokens=completion['usage']['completion_tokens'],
|
159
|
+
)
|
160
|
+
|
161
|
+
return json.dumps(completion)
|
162
|
+
|
163
|
+
except Exception as e:
|
164
|
+
return self._format_error_response(e)
|
165
|
+
|
166
|
+
@ModelClass.method
|
167
|
+
def openai_stream_transport(self, msg: str) -> Iterator[str]:
|
168
|
+
"""Process an OpenAI-compatible request and return a streaming response iterator.
|
169
|
+
This method is used when stream=True and returns an iterator of strings directly,
|
170
|
+
without converting to a list or JSON serializing.
|
171
|
+
|
172
|
+
Args:
|
173
|
+
msg: The request as a JSON string.
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
Iterator[str]: An iterator yielding text chunks from the streaming response.
|
177
|
+
"""
|
178
|
+
try:
|
179
|
+
request_data = json.loads(msg)
|
180
|
+
params = self._extract_request_params(request_data)
|
181
|
+
for chunk in self._process_streaming_request(**params):
|
182
|
+
if chunk.get('usage'):
|
183
|
+
if chunk['usage'].get('prompt_tokens') and chunk['usage'].get(
|
184
|
+
'completion_tokens'
|
185
|
+
):
|
186
|
+
self.set_output_context(
|
187
|
+
prompt_tokens=chunk['usage']['prompt_tokens'],
|
188
|
+
completion_tokens=chunk['usage']['completion_tokens'],
|
189
|
+
)
|
190
|
+
yield json.dumps(chunk)
|
191
|
+
except Exception as e:
|
192
|
+
yield f"Error: {str(e)}"
|
193
|
+
|
194
|
+
def _process_request(self, **kwargs) -> Any:
|
195
|
+
"""Process a standard (non-streaming) request using the OpenAI client.
|
196
|
+
|
197
|
+
Args:
|
198
|
+
**kwargs: Request parameters
|
199
|
+
|
200
|
+
Returns:
|
201
|
+
The completion response from the OpenAI client
|
202
|
+
"""
|
203
|
+
completion_args = self._create_completion_args(kwargs)
|
204
|
+
return self.client.chat.completions.create(**completion_args).to_dict()
|
205
|
+
|
206
|
+
def _process_streaming_request(self, **kwargs) -> Iterator[str]:
|
207
|
+
"""Process a streaming request using the OpenAI client.
|
208
|
+
|
209
|
+
Args:
|
210
|
+
**kwargs: Request parameters
|
211
|
+
|
212
|
+
Returns:
|
213
|
+
Iterator yielding response chunks
|
214
|
+
"""
|
215
|
+
completion_args = self._create_completion_args(kwargs, stream=True)
|
216
|
+
completion_stream = self.client.chat.completions.create(**completion_args)
|
217
|
+
|
218
|
+
for chunk in completion_stream:
|
219
|
+
yield chunk.to_dict()
|
@@ -4,6 +4,7 @@ from typing import List
|
|
4
4
|
from clarifai_grpc.grpc.api import resources_pb2
|
5
5
|
|
6
6
|
from clarifai.runners.utils import data_utils
|
7
|
+
from clarifai.urls.helper import ClarifaiUrlHelper
|
7
8
|
|
8
9
|
|
9
10
|
def generate_client_script(
|
@@ -15,6 +16,38 @@ def generate_client_script(
|
|
15
16
|
deployment_id: str = None,
|
16
17
|
use_ctx: bool = False,
|
17
18
|
) -> str:
|
19
|
+
url_helper = ClarifaiUrlHelper()
|
20
|
+
|
21
|
+
# Provide an mcp client config
|
22
|
+
if len(method_signatures) == 1 and method_signatures[0].name == "mcp_transport":
|
23
|
+
api_url = url_helper.api_url(
|
24
|
+
user_id,
|
25
|
+
app_id,
|
26
|
+
"models",
|
27
|
+
model_id,
|
28
|
+
)
|
29
|
+
|
30
|
+
_CLIENT_TEMPLATE = """
|
31
|
+
import asyncio
|
32
|
+
import os
|
33
|
+
from fastmcp import Client
|
34
|
+
from fastmcp.client.transports import StreamableHttpTransport
|
35
|
+
|
36
|
+
transport = StreamableHttpTransport(url="%s/mcp",
|
37
|
+
headers={"Authorization": "Bearer " + os.environ["CLARIFAI_PAT"]})
|
38
|
+
|
39
|
+
async def main():
|
40
|
+
async with Client(transport) as client:
|
41
|
+
tools = await client.list_tools()
|
42
|
+
print(f"Available tools: {tools}")
|
43
|
+
result = await client.call_tool(tools[0].name, {"a": 5, "b": 3})
|
44
|
+
print(f"Result: {result[0].text}")
|
45
|
+
|
46
|
+
if __name__ == "__main__":
|
47
|
+
asyncio.run(main())
|
48
|
+
"""
|
49
|
+
return _CLIENT_TEMPLATE % api_url
|
50
|
+
|
18
51
|
_CLIENT_TEMPLATE = """\
|
19
52
|
import os
|
20
53
|
|
@@ -35,8 +68,9 @@ from clarifai.runners.utils import data_types
|
|
35
68
|
model_section = """
|
36
69
|
model = Model.from_current_context()"""
|
37
70
|
else:
|
71
|
+
model_ui_url = url_helper.clarifai_url(user_id, app_id, "models", model_id)
|
38
72
|
model_section = f"""
|
39
|
-
model = Model(
|
73
|
+
model = Model({model_ui_url},
|
40
74
|
deployment_id = {deployment_id}, # Only needed for dedicated deployed models
|
41
75
|
{base_url_str}
|
42
76
|
)
|
@@ -53,13 +87,10 @@ model = Model("https://clarifai.com/{user_id}/{app_id}/models/{model_id}",
|
|
53
87
|
method_name = method_signature.name
|
54
88
|
client_script_str = f'response = model.{method_name}('
|
55
89
|
annotations = _get_annotations_source(method_signature)
|
56
|
-
for param_name, (param_type, default_value) in annotations.items():
|
57
|
-
print(
|
58
|
-
f"param_name: {param_name}, param_type: {param_type}, default_value: {default_value}"
|
59
|
-
)
|
90
|
+
for param_name, (param_type, default_value, required) in annotations.items():
|
60
91
|
if param_name == "return":
|
61
92
|
continue
|
62
|
-
if default_value is None:
|
93
|
+
if default_value is None and required:
|
63
94
|
default_value = _set_default_value(param_type)
|
64
95
|
client_script_str += f"{param_name}={default_value}, "
|
65
96
|
client_script_str = client_script_str.rstrip(", ") + ")"
|
@@ -98,7 +129,7 @@ def _get_annotations_source(method_signature: resources_pb2.MethodSignature) ->
|
|
98
129
|
default_value = None
|
99
130
|
if data_utils.Param.get_default(input_field):
|
100
131
|
default_value = _parse_default_value(input_field)
|
101
|
-
annotations[param_name] = (param_type, default_value)
|
132
|
+
annotations[param_name] = (param_type, default_value, input_field.required)
|
102
133
|
if not method_signature.output_fields:
|
103
134
|
raise ValueError("MethodSignature must have at least one output field")
|
104
135
|
for output_field in method_signature.output_fields:
|
@@ -106,7 +137,7 @@ def _get_annotations_source(method_signature: resources_pb2.MethodSignature) ->
|
|
106
137
|
param_type = _get_base_type(output_field)
|
107
138
|
if output_field.iterator:
|
108
139
|
param_type = f"Iterator[{param_type}]"
|
109
|
-
annotations[param_name] = (param_type, None)
|
140
|
+
annotations[param_name] = (param_type, None, output_field.required)
|
110
141
|
return annotations
|
111
142
|
|
112
143
|
|
@@ -155,7 +186,7 @@ def _map_default_value(field_type):
|
|
155
186
|
default_value = None
|
156
187
|
|
157
188
|
if field_type == "str":
|
158
|
-
default_value = 'What is the future of AI?'
|
189
|
+
default_value = repr('What is the future of AI?')
|
159
190
|
elif field_type == "bytes":
|
160
191
|
default_value = b""
|
161
192
|
elif field_type == "int":
|
@@ -190,11 +221,9 @@ def _set_default_value(field_type):
|
|
190
221
|
Set the default value of a field if it is not set.
|
191
222
|
"""
|
192
223
|
is_iterator = False
|
193
|
-
print(f"before field_type: {field_type}")
|
194
224
|
if field_type.startswith("Iterator["):
|
195
225
|
is_iterator = True
|
196
226
|
field_type = field_type[9:-1]
|
197
|
-
print(f"after field_type: {field_type}")
|
198
227
|
default_value = None
|
199
228
|
default_value = _map_default_value(field_type)
|
200
229
|
if field_type.startswith("List["):
|
@@ -211,11 +240,8 @@ def _set_default_value(field_type):
|
|
211
240
|
element_type_defaults = [_map_default_value(et) for et in element_types]
|
212
241
|
default_value = f"{{{', '.join([str(et) for et in element_type_defaults])}}}"
|
213
242
|
|
214
|
-
if field_type == 'str':
|
215
|
-
default_value = repr(default_value)
|
216
243
|
if is_iterator:
|
217
244
|
default_value = f'iter([{default_value}])'
|
218
|
-
print(f"after default_value: {default_value}")
|
219
245
|
return default_value
|
220
246
|
|
221
247
|
|
@@ -233,7 +259,7 @@ def _parse_default_value(field: resources_pb2.ModelTypeField):
|
|
233
259
|
elif data_type == resources_pb2.ModelTypeField.DataType.BOOL:
|
234
260
|
return 'True' if default_str.lower() == 'true' else 'False'
|
235
261
|
elif data_type == resources_pb2.ModelTypeField.DataType.STR:
|
236
|
-
return
|
262
|
+
return default_str
|
237
263
|
elif data_type == resources_pb2.ModelTypeField.DataType.BYTES:
|
238
264
|
return f"b{repr(default_str.encode('utf-8'))}"
|
239
265
|
elif data_type == resources_pb2.ModelTypeField.DataType.JSON_DATA:
|
@@ -395,6 +395,22 @@ class Image(MessageData):
|
|
395
395
|
raise ValueError("Image has no bytes")
|
396
396
|
return PILImage.open(io.BytesIO(self.proto.base64))
|
397
397
|
|
398
|
+
def to_base64_str(self) -> str:
|
399
|
+
if not self.proto.base64:
|
400
|
+
raise ValueError("Image has no bytes")
|
401
|
+
if isinstance(self.proto.base64, str):
|
402
|
+
return self.proto.base64
|
403
|
+
if isinstance(self.proto.base64, bytes):
|
404
|
+
try:
|
405
|
+
# trying direct decode (if already a base64 bytes)
|
406
|
+
return self.proto.base64.decode('utf-8')
|
407
|
+
except UnicodeDecodeError:
|
408
|
+
import base64
|
409
|
+
|
410
|
+
return base64.b64encode(self.proto.base64).decode('utf-8')
|
411
|
+
else:
|
412
|
+
raise TypeError("Expected str or bytes for Image.base64")
|
413
|
+
|
398
414
|
def to_numpy(self) -> np.ndarray:
|
399
415
|
return np.asarray(self.to_pil())
|
400
416
|
|
@@ -466,6 +482,22 @@ class Audio(MessageData):
|
|
466
482
|
def to_proto(self) -> AudioProto:
|
467
483
|
return self.proto
|
468
484
|
|
485
|
+
def to_base64_str(self) -> str:
|
486
|
+
if not self.proto.base64:
|
487
|
+
raise ValueError("Audio has no bytes")
|
488
|
+
if isinstance(self.proto.base64, str):
|
489
|
+
return self.proto.base64
|
490
|
+
if isinstance(self.proto.base64, bytes):
|
491
|
+
try:
|
492
|
+
# trying direct decode (if already a base64 bytes)
|
493
|
+
return self.proto.base64.decode('utf-8')
|
494
|
+
except UnicodeDecodeError:
|
495
|
+
import base64
|
496
|
+
|
497
|
+
return base64.b64encode(self.proto.base64).decode('utf-8')
|
498
|
+
else:
|
499
|
+
raise TypeError("Expected str or bytes for Audio.base64")
|
500
|
+
|
469
501
|
@classmethod
|
470
502
|
def from_proto(cls, proto: AudioProto) -> "Audio":
|
471
503
|
return cls(proto)
|
@@ -578,6 +610,22 @@ class Video(MessageData):
|
|
578
610
|
def to_proto(self) -> VideoProto:
|
579
611
|
return self.proto
|
580
612
|
|
613
|
+
def to_base64_str(self) -> str:
|
614
|
+
if not self.proto.base64:
|
615
|
+
raise ValueError("Video has no bytes")
|
616
|
+
if isinstance(self.proto.base64, str):
|
617
|
+
return self.proto.base64
|
618
|
+
if isinstance(self.proto.base64, bytes):
|
619
|
+
try:
|
620
|
+
# trying direct decode (if already a base64 bytes)
|
621
|
+
return self.proto.base64.decode('utf-8')
|
622
|
+
except UnicodeDecodeError:
|
623
|
+
import base64
|
624
|
+
|
625
|
+
return base64.b64encode(self.proto.base64).decode('utf-8')
|
626
|
+
else:
|
627
|
+
raise TypeError("Expected str or bytes for Video.base64")
|
628
|
+
|
581
629
|
@classmethod
|
582
630
|
def from_proto(cls, proto: VideoProto) -> "Video":
|
583
631
|
return cls(proto)
|