clarifai 11.4.1__py3-none-any.whl → 11.4.3__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 (40) hide show
  1. clarifai/__init__.py +1 -1
  2. clarifai/cli/base.py +7 -0
  3. clarifai/cli/model.py +6 -8
  4. clarifai/client/app.py +2 -1
  5. clarifai/client/auth/helper.py +6 -4
  6. clarifai/client/compute_cluster.py +2 -1
  7. clarifai/client/dataset.py +8 -1
  8. clarifai/client/deployment.py +2 -1
  9. clarifai/client/input.py +2 -1
  10. clarifai/client/model.py +2 -1
  11. clarifai/client/model_client.py +1 -1
  12. clarifai/client/module.py +2 -1
  13. clarifai/client/nodepool.py +2 -1
  14. clarifai/client/runner.py +2 -1
  15. clarifai/client/search.py +2 -1
  16. clarifai/client/user.py +2 -1
  17. clarifai/client/workflow.py +2 -1
  18. clarifai/runners/models/mcp_class.py +114 -0
  19. clarifai/runners/models/model_builder.py +179 -46
  20. clarifai/runners/models/model_class.py +5 -22
  21. clarifai/runners/models/model_run_locally.py +0 -4
  22. clarifai/runners/models/visual_classifier_class.py +75 -0
  23. clarifai/runners/models/visual_detector_class.py +79 -0
  24. clarifai/runners/utils/code_script.py +75 -44
  25. clarifai/runners/utils/const.py +15 -0
  26. clarifai/runners/utils/data_types/data_types.py +48 -0
  27. clarifai/runners/utils/data_utils.py +99 -45
  28. clarifai/runners/utils/loader.py +23 -2
  29. clarifai/runners/utils/method_signatures.py +4 -4
  30. clarifai/runners/utils/openai_convertor.py +103 -0
  31. clarifai/urls/helper.py +80 -12
  32. clarifai/utils/config.py +19 -0
  33. clarifai/utils/constants.py +4 -0
  34. clarifai/utils/logging.py +22 -5
  35. {clarifai-11.4.1.dist-info → clarifai-11.4.3.dist-info}/METADATA +1 -2
  36. {clarifai-11.4.1.dist-info → clarifai-11.4.3.dist-info}/RECORD +40 -37
  37. {clarifai-11.4.1.dist-info → clarifai-11.4.3.dist-info}/WHEEL +1 -1
  38. {clarifai-11.4.1.dist-info → clarifai-11.4.3.dist-info}/entry_points.txt +0 -0
  39. {clarifai-11.4.1.dist-info → clarifai-11.4.3.dist-info}/licenses/LICENSE +0 -0
  40. {clarifai-11.4.1.dist-info → clarifai-11.4.3.dist-info}/top_level.txt +0 -0
clarifai/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "11.4.1"
1
+ __version__ = "11.4.3"
clarifai/cli/base.py CHANGED
@@ -143,6 +143,13 @@ def dump(ctx_obj, output_format):
143
143
  json.dump(ctx_obj.to_dict(), sys.stdout, indent=2)
144
144
 
145
145
 
146
+ @config.command(['cat'])
147
+ @click.pass_obj
148
+ def env(ctx_obj):
149
+ """Print env vars. Use: eval "$(clarifai config env)" """
150
+ ctx_obj.current.print_env_vars()
151
+
152
+
146
153
  @cli.command()
147
154
  @click.argument('api_url', default="https://api.clarifai.com")
148
155
  @click.option('--user_id', required=False, help='User ID')
clarifai/cli/model.py CHANGED
@@ -504,6 +504,10 @@ def local_dev(ctx, model_path):
504
504
 
505
505
  logger.info(f"Current deployment_id: {deployment_id}")
506
506
 
507
+ logger.info(
508
+ f"Full url for the model: /users/{user_id}/apps/{app_id}/models/{model.id}/versions/{version.id}"
509
+ )
510
+
507
511
  # Now that we have all the context in ctx.obj, we need to update the config.yaml in
508
512
  # the model_path directory with the model object containing user_id, app_id, model_id, version_id
509
513
  config_file = os.path.join(model_path, 'config.yaml')
@@ -526,13 +530,9 @@ def local_dev(ctx, model_path):
526
530
  ModelBuilder._backup_config(config_file)
527
531
  ModelBuilder._save_config(config_file, config)
528
532
 
529
- # client_model = Model(
530
- # TODO: once we can generate_client_script from ModelBuilder or similar
531
- # we should be able to put the exact function call in place.
532
- # model_script = model.generate_client_script()
533
-
534
533
  builder = ModelBuilder(model_path, download_validation_only=True)
535
- method_signatures = builder.get_method_signatures()
534
+ # don't mock for local dev since you need the dependencies to run the code anyways.
535
+ method_signatures = builder.get_method_signatures(mocking=False)
536
536
 
537
537
  from clarifai.runners.utils import code_script
538
538
 
@@ -546,8 +546,6 @@ def local_dev(ctx, model_path):
546
546
  base_url=ctx.obj.current.api_base,
547
547
  )
548
548
 
549
- # TODO: put in the ClarifaiUrlHelper to create the model url.
550
-
551
549
  logger.info("""\n
552
550
  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
553
551
  # About to start up the local dev runner in this terminal...
clarifai/client/app.py CHANGED
@@ -19,6 +19,7 @@ from clarifai.client.workflow import Workflow
19
19
  from clarifai.constants.model import TRAINABLE_MODEL_TYPES
20
20
  from clarifai.errors import UserError
21
21
  from clarifai.urls.helper import ClarifaiUrlHelper
22
+ from clarifai.utils.constants import DEFAULT_BASE
22
23
  from clarifai.utils.logging import display_concept_relations_tree, display_workflow_tree, logger
23
24
  from clarifai.utils.misc import concept_relations_accumulation
24
25
  from clarifai.workflows.utils import get_yaml_output_info_proto, is_same_yaml_model
@@ -33,7 +34,7 @@ class App(Lister, BaseClient):
33
34
  url: str = None,
34
35
  app_id: str = None,
35
36
  user_id: str = None,
36
- base_url: str = "https://api.clarifai.com",
37
+ base_url: str = DEFAULT_BASE,
37
38
  pat: str = None,
38
39
  token: str = None,
39
40
  root_certificates_path: str = None,
@@ -7,10 +7,12 @@ from clarifai_grpc.channel.clarifai_channel import ClarifaiChannel
7
7
  from clarifai_grpc.grpc.api import resources_pb2, service_pb2_grpc
8
8
 
9
9
  from clarifai import __version__
10
- from clarifai.utils.constants import CLARIFAI_PAT_ENV_VAR, CLARIFAI_SESSION_TOKEN_ENV_VAR
11
-
12
- DEFAULT_BASE = "https://api.clarifai.com"
13
- DEFAULT_UI = "https://clarifai.com"
10
+ from clarifai.utils.constants import (
11
+ CLARIFAI_PAT_ENV_VAR,
12
+ CLARIFAI_SESSION_TOKEN_ENV_VAR,
13
+ DEFAULT_BASE,
14
+ DEFAULT_UI,
15
+ )
14
16
 
15
17
  REQUEST_ID_PREFIX_HEADER = "x-clarifai-request-id-prefix"
16
18
  REQUEST_ID_PREFIX = f"sdk-python-{__version__}"
@@ -10,6 +10,7 @@ from clarifai.client.base import BaseClient
10
10
  from clarifai.client.lister import Lister
11
11
  from clarifai.client.nodepool import Nodepool
12
12
  from clarifai.errors import UserError
13
+ from clarifai.utils.constants import DEFAULT_BASE
13
14
  from clarifai.utils.logging import logger
14
15
 
15
16
 
@@ -20,7 +21,7 @@ class ComputeCluster(Lister, BaseClient):
20
21
  self,
21
22
  compute_cluster_id: str = None,
22
23
  user_id: str = None,
23
- base_url: str = "https://api.clarifai.com",
24
+ base_url: str = DEFAULT_BASE,
24
25
  pat: str = None,
25
26
  token: str = None,
26
27
  root_certificates_path: str = None,
@@ -45,6 +45,7 @@ ClarifaiDatasetType = TypeVar(
45
45
  VisualSegmentationDataset,
46
46
  TextClassificationDataset,
47
47
  )
48
+ from clarifai.utils.constants import DEFAULT_BASE
48
49
 
49
50
 
50
51
  class Dataset(Lister, BaseClient):
@@ -55,7 +56,7 @@ class Dataset(Lister, BaseClient):
55
56
  url: str = None,
56
57
  dataset_id: str = None,
57
58
  dataset_version_id: str = None,
58
- base_url: str = "https://api.clarifai.com",
59
+ base_url: str = DEFAULT_BASE,
59
60
  pat: str = None,
60
61
  token: str = None,
61
62
  root_certificates_path: str = None,
@@ -685,6 +686,12 @@ class Dataset(Lister, BaseClient):
685
686
  Note:
686
687
  This is a beta feature and is subject to change.
687
688
  """
689
+ try:
690
+ import rich # noqa: F401
691
+ except ImportError:
692
+ raise UserError(
693
+ "Rich library is not installed. Please install it using pip install rich>=13.4.2"
694
+ )
688
695
  self.logger.info("Getting dataset upload status...")
689
696
  dataset_version_id = uuid.uuid4().hex
690
697
  _ = self.create_version(id=dataset_version_id, description="SDK Upload Status")
@@ -2,6 +2,7 @@ from clarifai_grpc.grpc.api import resources_pb2
2
2
 
3
3
  from clarifai.client.base import BaseClient
4
4
  from clarifai.client.lister import Lister
5
+ from clarifai.utils.constants import DEFAULT_BASE
5
6
  from clarifai.utils.logging import logger
6
7
  from clarifai.utils.protobuf import dict_to_protobuf
7
8
 
@@ -13,7 +14,7 @@ class Deployment(Lister, BaseClient):
13
14
  self,
14
15
  deployment_id: str = None,
15
16
  user_id: str = None,
16
- base_url: str = "https://api.clarifai.com",
17
+ base_url: str = DEFAULT_BASE,
17
18
  pat: str = None,
18
19
  token: str = None,
19
20
  root_certificates_path: str = None,
clarifai/client/input.py CHANGED
@@ -21,6 +21,7 @@ from clarifai.client.lister import Lister
21
21
  from clarifai.constants.dataset import MAX_RETRIES
22
22
  from clarifai.constants.input import MAX_UPLOAD_BATCH_SIZE
23
23
  from clarifai.errors import UserError
24
+ from clarifai.utils.constants import DEFAULT_BASE
24
25
  from clarifai.utils.logging import logger
25
26
  from clarifai.utils.misc import BackoffIterator, Chunker, clean_input_id
26
27
 
@@ -33,7 +34,7 @@ class Inputs(Lister, BaseClient):
33
34
  user_id: str = None,
34
35
  app_id: str = None,
35
36
  logger_level: str = "INFO",
36
- base_url: str = "https://api.clarifai.com",
37
+ base_url: str = DEFAULT_BASE,
37
38
  pat: str = None,
38
39
  token: str = None,
39
40
  root_certificates_path: str = None,
clarifai/client/model.py CHANGED
@@ -34,6 +34,7 @@ from clarifai.constants.model import (
34
34
  )
35
35
  from clarifai.errors import UserError
36
36
  from clarifai.urls.helper import ClarifaiUrlHelper
37
+ from clarifai.utils.constants import DEFAULT_BASE
37
38
  from clarifai.utils.logging import logger
38
39
  from clarifai.utils.misc import BackoffIterator
39
40
  from clarifai.utils.model_train import (
@@ -58,7 +59,7 @@ class Model(Lister, BaseClient):
58
59
  url: str = None,
59
60
  model_id: str = None,
60
61
  model_version: Dict = {'id': ""},
61
- base_url: str = "https://api.clarifai.com",
62
+ base_url: str = DEFAULT_BASE,
62
63
  pat: str = None,
63
64
  token: str = None,
64
65
  root_certificates_path: str = None,
@@ -8,7 +8,6 @@ from clarifai_grpc.grpc.api.status import status_code_pb2
8
8
  from clarifai.constants.model import MAX_MODEL_PREDICT_INPUTS
9
9
  from clarifai.errors import UserError
10
10
  from clarifai.runners.utils import code_script, method_signatures
11
- from clarifai.runners.utils.data_utils import is_openai_chat_format
12
11
  from clarifai.runners.utils.method_signatures import (
13
12
  CompatibilitySerializer,
14
13
  deserialize,
@@ -16,6 +15,7 @@ from clarifai.runners.utils.method_signatures import (
16
15
  serialize,
17
16
  signatures_from_json,
18
17
  )
18
+ from clarifai.runners.utils.openai_convertor import is_openai_chat_format
19
19
  from clarifai.utils.logging import logger
20
20
  from clarifai.utils.misc import BackoffIterator, status_is_retryable
21
21
 
clarifai/client/module.py CHANGED
@@ -6,6 +6,7 @@ from clarifai.client.base import BaseClient
6
6
  from clarifai.client.lister import Lister
7
7
  from clarifai.errors import UserError
8
8
  from clarifai.urls.helper import ClarifaiUrlHelper
9
+ from clarifai.utils.constants import DEFAULT_BASE
9
10
  from clarifai.utils.logging import logger
10
11
 
11
12
 
@@ -17,7 +18,7 @@ class Module(Lister, BaseClient):
17
18
  url: str = None,
18
19
  module_id: str = None,
19
20
  module_version: Dict = {'id': ""},
20
- base_url: str = "https://api.clarifai.com",
21
+ base_url: str = DEFAULT_BASE,
21
22
  pat: str = None,
22
23
  token: str = None,
23
24
  root_certificates_path: str = None,
@@ -11,6 +11,7 @@ from clarifai.client.deployment import Deployment
11
11
  from clarifai.client.lister import Lister
12
12
  from clarifai.client.runner import Runner
13
13
  from clarifai.errors import UserError
14
+ from clarifai.utils.constants import DEFAULT_BASE
14
15
  from clarifai.utils.logging import logger
15
16
 
16
17
 
@@ -21,7 +22,7 @@ class Nodepool(Lister, BaseClient):
21
22
  self,
22
23
  nodepool_id: str = None,
23
24
  user_id: str = None,
24
- base_url: str = "https://api.clarifai.com",
25
+ base_url: str = DEFAULT_BASE,
25
26
  pat: str = None,
26
27
  token: str = None,
27
28
  root_certificates_path: str = None,
clarifai/client/runner.py CHANGED
@@ -2,6 +2,7 @@ from clarifai_grpc.grpc.api import resources_pb2
2
2
 
3
3
  from clarifai.client.base import BaseClient
4
4
  from clarifai.client.lister import Lister
5
+ from clarifai.utils.constants import DEFAULT_BASE
5
6
  from clarifai.utils.logging import logger
6
7
  from clarifai.utils.protobuf import dict_to_protobuf
7
8
 
@@ -13,7 +14,7 @@ class Runner(Lister, BaseClient):
13
14
  self,
14
15
  runner_id: str = None,
15
16
  user_id: str = None,
16
- base_url: str = "https://api.clarifai.com",
17
+ base_url: str = DEFAULT_BASE,
17
18
  pat: str = None,
18
19
  token: str = None,
19
20
  root_certificates_path: str = None,
clarifai/client/search.py CHANGED
@@ -17,6 +17,7 @@ from clarifai.constants.search import (
17
17
  )
18
18
  from clarifai.errors import UserError
19
19
  from clarifai.schema.search import get_schema
20
+ from clarifai.utils.constants import DEFAULT_BASE
20
21
 
21
22
 
22
23
  class Search(Lister, BaseClient):
@@ -28,7 +29,7 @@ class Search(Lister, BaseClient):
28
29
  metric: str = DEFAULT_SEARCH_METRIC,
29
30
  algorithm: str = DEFAULT_SEARCH_ALGORITHM,
30
31
  pagination: bool = False,
31
- base_url: str = "https://api.clarifai.com",
32
+ base_url: str = DEFAULT_BASE,
32
33
  pat: str = None,
33
34
  token: str = None,
34
35
  root_certificates_path: str = None,
clarifai/client/user.py CHANGED
@@ -12,6 +12,7 @@ from clarifai.client.base import BaseClient
12
12
  from clarifai.client.compute_cluster import ComputeCluster
13
13
  from clarifai.client.lister import Lister
14
14
  from clarifai.errors import UserError
15
+ from clarifai.utils.constants import DEFAULT_BASE
15
16
  from clarifai.utils.logging import logger
16
17
 
17
18
 
@@ -21,7 +22,7 @@ class User(Lister, BaseClient):
21
22
  def __init__(
22
23
  self,
23
24
  user_id: str = None,
24
- base_url: str = "https://api.clarifai.com",
25
+ base_url: str = DEFAULT_BASE,
25
26
  pat: str = None,
26
27
  token: str = None,
27
28
  root_certificates_path: str = None,
@@ -14,6 +14,7 @@ from clarifai.client.model import Model
14
14
  from clarifai.constants.workflow import MAX_WORKFLOW_PREDICT_INPUTS
15
15
  from clarifai.errors import UserError
16
16
  from clarifai.urls.helper import ClarifaiUrlHelper
17
+ from clarifai.utils.constants import DEFAULT_BASE
17
18
  from clarifai.utils.logging import logger
18
19
  from clarifai.utils.misc import BackoffIterator, status_is_retryable
19
20
  from clarifai.workflows.export import Exporter
@@ -28,7 +29,7 @@ class Workflow(Lister, BaseClient):
28
29
  workflow_id: str = None,
29
30
  workflow_version: Dict = {'id': ""},
30
31
  output_config: Dict = {'min_value': 0},
31
- base_url: str = "https://api.clarifai.com",
32
+ base_url: str = DEFAULT_BASE,
32
33
  pat: str = None,
33
34
  token: str = None,
34
35
  root_certificates_path: str = None,
@@ -0,0 +1,114 @@
1
+ """Base class for creating Model Context Protocol (MCP) servers."""
2
+
3
+ import asyncio
4
+ import json
5
+ from typing import Any
6
+
7
+ from fastmcp import Client, FastMCP # use fastmcp v2 not the built in mcp
8
+ from mcp import types
9
+ from mcp.shared.exceptions import McpError
10
+
11
+ from clarifai.runners.models.model_class import ModelClass
12
+
13
+
14
+ class MCPModelClass(ModelClass):
15
+ """Base class for wrapping FastMCP servers as a model running in Clarfai. This handles
16
+ all the transport between the API and the MCP server here. Simply subclass this and implement
17
+ the get_server() method to return the FastMCP server instance. The server is then used to
18
+ handle all the requests and responses.
19
+ """
20
+
21
+ def load_model(self):
22
+ # in memory transport provided in fastmcp v2 so we can easily use the client functions.
23
+ self.client = Client(self.get_server())
24
+
25
+ def get_server(self) -> FastMCP:
26
+ """Required method for each subclass to implement to return the FastMCP server to use."""
27
+ raise NotImplementedError("Subclasses must implement get_server() method")
28
+
29
+ @ModelClass.method
30
+ def mcp_transport(self, msg: str) -> str:
31
+ """The single model method to get the jsonrpc message and send it to the FastMCP server then
32
+ return it's response.
33
+
34
+ """
35
+
36
+ async def send_notification(client_message: types.ClientNotification) -> None:
37
+ async with self.client:
38
+ # Strip the jsonrpc field since send_notification will also pass it in for some reason.
39
+ client_message = types.ClientNotification.model_validate(
40
+ client_message.model_dump(
41
+ by_alias=True, mode="json", exclude_none=True, exclude={"jsonrpc"}
42
+ )
43
+ )
44
+ try:
45
+ return await self.client.session.send_notification(client_message)
46
+ except McpError as e:
47
+ return types.JSONRPCError(jsonrpc="2.0", error=e.error)
48
+
49
+ async def send_request(client_message: types.ClientRequest, id: str) -> Any:
50
+ async with self.client:
51
+ # Strip the jsonrpc and id fields as send_request sets them again too.
52
+ client_message = types.ClientRequest.model_validate(
53
+ client_message.model_dump(
54
+ by_alias=True, mode="json", exclude_none=True, exclude={"jsonrpc", "id"}
55
+ )
56
+ )
57
+
58
+ result_type = None
59
+ if isinstance(client_message.root, types.PingRequest):
60
+ result_type = types.EmptyResult
61
+ elif isinstance(client_message.root, types.InitializeRequest):
62
+ return await self.client.session.initialize()
63
+ elif isinstance(client_message.root, types.SetLevelRequest):
64
+ result_type = types.EmptyResult
65
+ elif isinstance(client_message.root, types.ListResourcesRequest):
66
+ result_type = types.ListResourcesResult
67
+ elif isinstance(client_message.root, types.ListResourceTemplatesRequest):
68
+ result_type = types.ListResourceTemplatesResult
69
+ elif isinstance(client_message.root, types.ReadResourceRequest):
70
+ result_type = types.ReadResourceResult
71
+ elif isinstance(client_message.root, types.SubscribeRequest):
72
+ result_type = types.EmptyResult
73
+ elif isinstance(client_message.root, types.UnsubscribeRequest):
74
+ result_type = types.EmptyResult
75
+ elif isinstance(client_message.root, types.ListPromptsRequest):
76
+ result_type = types.ListPromptsResult
77
+ elif isinstance(client_message.root, types.GetPromptRequest):
78
+ result_type = types.GetPromptResult
79
+ elif isinstance(client_message.root, types.CompleteRequest):
80
+ result_type = types.CompleteResult
81
+ elif isinstance(client_message.root, types.ListToolsRequest):
82
+ result_type = types.ListToolsResult
83
+ elif isinstance(client_message.root, types.CallToolRequest):
84
+ result_type = types.CallToolResult
85
+ else:
86
+ # this is a special case where we need to return the list of tools.
87
+ raise NotImplementedError(f"Method {client_message.method} not implemented")
88
+ # Call the mcp server using send_request() or send_notification() depending on the method.
89
+ try:
90
+ return await self.client.session.send_request(client_message, result_type)
91
+ except McpError as e:
92
+ return types.JSONRPCError(jsonrpc="2.0", id=id, error=e.error)
93
+
94
+ # The message coming here is the generic request. We look at it's .method
95
+ # to determine which client function to call and to further subparse the params.
96
+ # Note(zeiler): unfortunately the pydantic types in mcp/types.py are not consistent.
97
+ # The JSONRPCRequest are supposed to have an id but the InitializeRequest
98
+ # does not have it.
99
+ d = json.loads(msg)
100
+ id = d.get('id', "")
101
+
102
+ # If we have an id it's a JSONRPCRequest
103
+ if not d.get('method', '').startswith("notifications/"):
104
+ client_message = types.ClientRequest.model_validate(d)
105
+ response = asyncio.run(send_request(client_message, id=id))
106
+ else: # JSONRPCRequest
107
+ client_message = types.ClientNotification.model_validate(d)
108
+ response = asyncio.run(send_notification(client_message))
109
+ if response is None:
110
+ response = types.JSONRPCError(
111
+ jsonrpc="2.0", id=id, error="Got empty response from MCP server."
112
+ )
113
+ # return as a serialized json string
114
+ return response.model_dump_json(by_alias=True, exclude_none=True)