From 82f7ef61562a1566c61953b37484333c5a07dda9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:21:34 +0000 Subject: [PATCH 1/5] Initial plan From decda999ea054e67fec955ae51921aa1c6cc8ab8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:26:23 +0000 Subject: [PATCH 2/5] Add 'current' mode to local-runner and make it default Co-authored-by: mogith-pn <143642606+mogith-pn@users.noreply.github.com> --- clarifai/cli/model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clarifai/cli/model.py b/clarifai/cli/model.py index 4d63d4a3..7d5c7fce 100644 --- a/clarifai/cli/model.py +++ b/clarifai/cli/model.py @@ -1153,10 +1153,10 @@ def run_locally(ctx, model_path, port, mode, keep_env, keep_image, skip_dockerfi ) @click.option( "--mode", - type=click.Choice(['env', 'container'], case_sensitive=False), - default='env', + type=click.Choice(['current', 'env', 'container'], case_sensitive=False), + default='current', show_default=True, - help='Specifies how to run the model: "env" for virtual environment or "container" for Docker container. Defaults to "env".', + help='Specifies how to run the model: "current" for current user environment, "env" for virtual environment, or "container" for Docker container. Defaults to "current".', ) @click.option( '--keep_image', @@ -1198,7 +1198,7 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i that starts up in the local runner is the same as the one you intend to call in the API. MODEL_PATH: Path to the model directory. If not specified, the current directory is used by default. - MODE: Specifies how to run the model: "env" for virtual environment or "container" for Docker container. Defaults to "env". + MODE: Specifies how to run the model: "current" for current user environment, "env" for virtual environment, or "container" for Docker container. Defaults to "current". KEEP_IMAGE: Keep the Docker image after testing the model locally (applicable for container mode). Defaults to False. """ from clarifai.client.user import User From d1db954795912d14686822061b2a37d75e2706a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:30:23 +0000 Subject: [PATCH 3/5] Add tests for local-runner mode options Co-authored-by: mogith-pn <143642606+mogith-pn@users.noreply.github.com> --- tests/cli/test_local_runner_cli.py | 133 ++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/tests/cli/test_local_runner_cli.py b/tests/cli/test_local_runner_cli.py index aed5aece..cb8170a1 100644 --- a/tests/cli/test_local_runner_cli.py +++ b/tests/cli/test_local_runner_cli.py @@ -6,7 +6,7 @@ import os from pathlib import Path -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, Mock, patch import pytest import yaml @@ -643,3 +643,134 @@ def validate_ctx_mock(ctx): assert "num_threads" in serve_kwargs # grpc defaults to False for local runner (not always passed as kwarg) assert serve_kwargs.get("grpc", False) is False + + @patch("clarifai.runners.server.ModelServer") + @patch("clarifai.client.user.User") + @patch("clarifai.runners.models.model_run_locally.ModelRunLocally") + @patch("clarifai.runners.models.model_builder.ModelBuilder") + @patch("clarifai.cli.model.check_requirements_installed") + @patch("clarifai.cli.model.parse_requirements") + @patch("clarifai.cli.model.validate_context") + @patch("builtins.input") + def test_local_runner_mode_options( + self, + mock_input, + mock_validate_context, + mock_parse_requirements, + mock_check_requirements, + mock_builder_class, + mock_manager_class, + mock_user_class, + mock_server_class, + dummy_model_dir, + ): + """Test that local-runner supports all three modes: current, env, and container.""" + # Setup common mocks + mock_input.return_value = "y" + mock_check_requirements.return_value = True + mock_parse_requirements.return_value = {} + + # Mock ModelBuilder + mock_builder = MagicMock() + mock_builder.config = {"model": {"model_type_id": "multimodal-to-text"}, "toolkit": {}} + mock_builder.get_method_signatures.return_value = {} + mock_builder.get_model_version_proto.return_value = MagicMock() + mock_builder_class.return_value = mock_builder + + # Mock ModelRunLocally + mock_manager = MagicMock() + mock_manager.config = mock_builder.config + mock_manager._get_method_signatures.return_value = {} + mock_manager_class.return_value = mock_manager + + # Mock User and resources + mock_user = MagicMock() + + # Mock compute cluster + mock_compute_cluster = MagicMock() + mock_compute_cluster.id = "local-dev-cluster" + mock_compute_cluster.cluster_type = "local-dev" + mock_user.compute_cluster.return_value = mock_compute_cluster + + # Mock nodepool + mock_nodepool = MagicMock() + mock_nodepool.id = "local-dev-nodepool" + mock_compute_cluster.nodepool.return_value = mock_nodepool + + # Mock deployment + mock_deployment = MagicMock() + mock_deployment.id = "deployment-123" + mock_nodepool.deployment.return_value = mock_deployment + + # Mock app and model + mock_app = MagicMock() + mock_model = MagicMock() + mock_model.list_versions.return_value = [MagicMock(id="v1")] + mock_model.create_version.return_value = MagicMock(id="v1") + mock_app.model.return_value = mock_model + mock_user.app.return_value = mock_app + mock_user_class.return_value = mock_user + + # Mock ModelServer + mock_server = MagicMock() + mock_server_class.return_value = mock_server + + # Mock context + mock_ctx = MagicMock() + mock_ctx.obj.current.user_id = "test-user" + mock_ctx.obj.current.pat = "test-pat" + mock_ctx.obj.current.api_base = "https://api.clarifai.com" + mock_ctx.obj.current.name = "default" + mock_ctx.obj.current.compute_cluster_id = "local-dev-cluster" + mock_ctx.obj.current.nodepool_id = "local-dev-nodepool" + mock_ctx.obj.current.app_id = "local-dev-app" + mock_ctx.obj.current.model_id = "local-dev-model" + mock_ctx.obj.current.runner_id = "runner-123" + mock_ctx.obj.current.deployment_id = "deployment-123" + mock_ctx.obj.current.model_version_id = "v1" + mock_ctx.obj.current.ui = "https://clarifai.com" + mock_ctx.obj.to_yaml = Mock() + + def validate_ctx_mock(ctx): + ctx.obj = mock_ctx.obj + + mock_validate_context.side_effect = validate_ctx_mock + + runner = CliRunner() + + # Test 'current' mode (default) + result = runner.invoke( + cli, + ["model", "local-runner", str(dummy_model_dir)], + catch_exceptions=False, + ) + assert result.exit_code == 0, f"Command failed with: {result.output}" + # Verify venv creation methods were not called for 'current' mode + mock_manager.create_temp_venv.assert_not_called() + mock_manager.install_requirements.assert_not_called() + mock_manager_class.reset_mock() + mock_server_class.reset_mock() + + # Test 'env' mode + result = runner.invoke( + cli, + ["model", "local-runner", "--mode", "env", str(dummy_model_dir)], + catch_exceptions=False, + ) + assert result.exit_code == 0, f"Command failed with: {result.output}" + # Verify venv creation methods were called for 'env' mode + mock_manager.create_temp_venv.assert_called_once() + mock_manager.install_requirements.assert_called_once() + mock_manager_class.reset_mock() + mock_server_class.reset_mock() + + # Test 'current' mode explicitly + result = runner.invoke( + cli, + ["model", "local-runner", "--mode", "current", str(dummy_model_dir)], + catch_exceptions=False, + ) + assert result.exit_code == 0, f"Command failed with: {result.output}" + # Verify venv creation methods were not called for 'current' mode + mock_manager.create_temp_venv.assert_not_called() + mock_manager.install_requirements.assert_not_called() From b031b07be78f8fb3927be33b8ca0b4e31723f389 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:32:37 +0000 Subject: [PATCH 4/5] Add explicit handling and comments for mode logic Co-authored-by: mogith-pn <143642606+mogith-pn@users.noreply.github.com> --- clarifai/cli/model.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clarifai/cli/model.py b/clarifai/cli/model.py index 7d5c7fce..a7f56aa4 100644 --- a/clarifai/cli/model.py +++ b/clarifai/cli/model.py @@ -1212,9 +1212,15 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i manager = ModelRunLocally(model_path) port = 8080 + # Handle mode-specific environment setup if mode == "env": + # Create a temporary virtual environment and install requirements manager.create_temp_venv() manager.install_requirements() + elif mode == "current": + # Use current user environment (no action needed) + pass + # For 'container' mode, environment setup is handled later via Docker dependencies = parse_requirements(model_path) if mode != "container": From 36888e5505508aec51741ce5b6f76d31259ee554 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:33:59 +0000 Subject: [PATCH 5/5] Add explicit elif branch for container mode for consistency Co-authored-by: mogith-pn <143642606+mogith-pn@users.noreply.github.com> --- clarifai/cli/model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clarifai/cli/model.py b/clarifai/cli/model.py index a7f56aa4..1827105e 100644 --- a/clarifai/cli/model.py +++ b/clarifai/cli/model.py @@ -1220,7 +1220,9 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i elif mode == "current": # Use current user environment (no action needed) pass - # For 'container' mode, environment setup is handled later via Docker + elif mode == "container": + # Environment setup is handled later via Docker (no action needed here) + pass dependencies = parse_requirements(model_path) if mode != "container":