Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clarifai/cli/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ def local_dev(ctx, model_path):
logger.info(f"Current deployment_id: {deployment_id}")

logger.info(
f"Full url for the model: /users/{user_id}/apps/{app_id}/models/{model.id}/versions/{version.id}"
f"Full url for the model: {ctx.obj.current.ui}/users/{user_id}/apps/{app_id}/models/{model.id}/versions/{version.id}"
)

# Now that we have all the context in ctx.obj, we need to update the config.yaml in
Expand Down
4 changes: 3 additions & 1 deletion clarifai/client/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,16 @@ def __init__(

@classmethod
def from_current_context(cls, **kwargs) -> 'Model':
from clarifai.urls.helper import ClarifaiUrlHelper, auth_obj
from clarifai.utils.config import Config

current = Config.from_yaml().current

# set the current context to env vars.
current.set_to_env()

url = f"https://clarifai.com/{current.user_id}/{current.app_id}/models/{current.model_id}"
url_helper = ClarifaiUrlHelper(auth_obj(ui=current.ui, base=current.api_base))
url = url_helper.clarifai_url(current.user_id, current.app_id, "models", current.model_id)

# construct the Model object.
kwargs = {}
Expand Down
63 changes: 55 additions & 8 deletions clarifai/runners/utils/code_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,22 @@
from clarifai_grpc.grpc.api import resources_pb2

from clarifai.runners.utils import data_utils
from clarifai.urls.helper import ClarifaiUrlHelper
from clarifai.urls.helper import ClarifaiUrlHelper, auth_obj
from clarifai.utils.config import Config
from clarifai.utils.constants import MCP_TRANSPORT_NAME, OPENAI_TRANSPORT_NAME


def has_signature_method(
name: str, method_signatures: List[resources_pb2.MethodSignature]
) -> bool:
"""
Check if a method signature with the given name exists in the list of method signatures.

:param name: The name of the method to check.
:param method_signatures: List of MethodSignature objects to search in.
:return: True if a method with the given name exists, False otherwise.
"""
return any(method_signature.name == name for method_signature in method_signatures)


def generate_client_script(
Expand All @@ -16,14 +31,18 @@ def generate_client_script(
deployment_id: str = None,
use_ctx: bool = False,
) -> str:
url_helper = ClarifaiUrlHelper()
if use_ctx:
# use the config values.
current = Config.from_yaml().current
url_helper = ClarifaiUrlHelper(auth_obj(ui=current.ui, base=current.api_base))
else:
url_helper = ClarifaiUrlHelper()

# Provide an mcp client config
if len(method_signatures) == 1 and method_signatures[0].name == "mcp_transport":
api_url = url_helper.api_url(
# Provide an mcp client config if there is a method named "mcp_transport"
if has_signature_method(MCP_TRANSPORT_NAME, method_signatures):
mcp_url = url_helper.mcp_api_url(
user_id,
app_id,
"models",
model_id,
)

Expand All @@ -33,20 +52,48 @@ def generate_client_script(
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

transport = StreamableHttpTransport(url="%s/mcp",
transport = StreamableHttpTransport(url="%s",
headers={"Authorization": "Bearer " + os.environ["CLARIFAI_PAT"]})

async def main():
async with Client(transport) as client:
tools = await client.list_tools()
print(f"Available tools: {tools}")
# TODO: update the dictionary of arguments passed to call_tool to make sense for your MCP.
result = await client.call_tool(tools[0].name, {"a": 5, "b": 3})
print(f"Result: {result[0].text}")

if __name__ == "__main__":
asyncio.run(main())
"""
return _CLIENT_TEMPLATE % api_url
return _CLIENT_TEMPLATE % mcp_url

if has_signature_method(OPENAI_TRANSPORT_NAME, method_signatures):
openai_api_base = url_helper.openai_api_url()
model_ui_url = url_helper.clarifai_url(user_id, app_id, "models", model_id)
_CLIENT_TEMPLATE = """
import os
from openai import OpenAI

client = OpenAI(
base_url="%s",
api_key=os.environ['CLARIFAI_PAT'],
)
response = client.chat.completions.create(
model="%s",
messages=[
{"role": "developer", "content": "Talk like a pirate."},
{
"role": "user",
"content": "How do I check if a Python object is an instance of a class?",
},
],
temperature=0.7,
stream=False, # stream=True also works, just iterator over the response
)
print(response)
"""
return _CLIENT_TEMPLATE % (openai_api_base, model_ui_url)

_CLIENT_TEMPLATE = """\
import os
Expand Down
41 changes: 41 additions & 0 deletions clarifai/urls/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,47 @@ def imv_ui_url(self, dest_user_id, dest_app_id, imv_id):
imv_id,
)

def mcp_api_url(self, user_id, app_id, model_id, version_id: str = None):
"""We have a special endpoint for MCP hosted models.

Example:
https://api.clarifai.com/v2/ext/mcp/v1/users/{user_id}/apps/{app_id}/models/{model_id}/versions/{version_id}

Args:
user_id: the author of the resource.
app_id: the author's app the resource was created in.
model_id: the resource ID
version_id: the version of the resource.
"""
if version_id is None:
return "%s/v2/ext/mcp/v1/users/%s/apps/%s/models/%s" % (
self.base,
user_id,
app_id,
model_id,
)
return "%s/v2/ext/mcp/v1/users/%s/apps/%s/models/%s/versions/%s" % (
self.base,
user_id,
app_id,
model_id,
version_id,
)

def openai_api_url(self):
"""We have a special endpoint for openAI compatible models.

This doesn't include the /chat/completions suffix which the openAI client automatically
adds.

It also doesn't incldue the model which you an set as the model arg in an openAI client call
using the clarifai_url() method below.

Example:
https://api.clarifai.com/v2/ext/openai/v1
"""
return "%s/v2/ext/openai/v1" % self.base

def api_url(self, user_id, app_id, resource_type, resource_id, version_id: str = None):
"""This is the path to the resource in the API.

Expand Down
23 changes: 20 additions & 3 deletions clarifai/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import yaml

from clarifai.utils.constants import DEFAULT_CONFIG
from clarifai.utils.constants import DEFAULT_BASE, DEFAULT_CONFIG, DEFAULT_UI


class Context(OrderedDict):
Expand All @@ -23,6 +23,12 @@ def __init__(self, name, **kwargs):
self['env'] = kwargs

def __getattr__(self, key):
"""Get the key from the config. You can pass a lowercase key like "pat" and it will check if
the environment variable CLARIFAI_PAT set and use that first. If no env var, then it checks
if that env var name is in the config and use that. If not then checks if
"pat" is in the config, if not then it falls back to CLARIFAI_PAT in the environment
variables, else raises an AttributeError.
"""
try:
if key == 'name':
return self[key]
Expand All @@ -32,16 +38,27 @@ def __getattr__(self, key):
# Allow accessing CLARIFAI_PAT type env var names from config as .pat
envvar_name = 'CLARIFAI_' + key.upper()
env = self['env']
if envvar_name in env:
if envvar_name in os.environ: # environment variable take precedence.
value = os.environ[envvar_name]
elif envvar_name in env:
value = env[envvar_name]
if value == "ENVVAR":
if envvar_name not in os.environ:
raise AttributeError(
f"Environment variable '{envvar_name}' not set. Attempting to load it for config '{self['name']}'. Please set it in your terminal."
)
return os.environ[envvar_name]
else:
elif key in env: # check if key is in the config
value = env[key]
# below are some default fallback values for UI and API base.
elif envvar_name == 'CLARIFAI_UI':
value = DEFAULT_UI
elif envvar_name == 'CLARIFAI_API_BASE':
value = DEFAULT_BASE
else:
raise AttributeError(
f"'{type(self).__name__}' object has no attribute '{key}' or '{envvar_name}' and '{envvar_name}' is also not in os.environ:"
)

if isinstance(value, dict):
return Context(value)
Expand Down
2 changes: 2 additions & 0 deletions clarifai/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
DEFAULT_UI = os.environ.get("CLARIFAI_UI", "https://clarifai.com")
DEFAULT_BASE = os.environ.get("CLARIFAI_API_BASE", "https://api.clarifai.com")

MCP_TRANSPORT_NAME = "mcp_transport"
OPENAI_TRANSPORT_NAME = "openai_transport"

CLARIFAI_PAT_ENV_VAR = "CLARIFAI_PAT"
CLARIFAI_SESSION_TOKEN_ENV_VAR = "CLARIFAI_SESSION_TOKEN"
Expand Down