cli.py
1 import logging 2 3 import click 4 5 from mlflow.mcp.decorator import mlflow_mcp 6 from mlflow.models import python_api 7 from mlflow.models.flavor_backend_registry import get_flavor_backend 8 from mlflow.models.model import update_model_requirements 9 from mlflow.utils import cli_args 10 from mlflow.utils import env_manager as _EnvManager 11 12 _logger = logging.getLogger(__name__) 13 14 15 @click.group("models") 16 def commands(): 17 """ 18 Deploy MLflow models locally. 19 20 To deploy a model associated with a run on a tracking server, set the MLFLOW_TRACKING_URI 21 environment variable to the URL of the desired server. 22 """ 23 24 25 @commands.command("serve") 26 @mlflow_mcp(tool_name="serve_model") 27 @cli_args.MODEL_URI 28 @cli_args.PORT 29 @cli_args.HOST 30 @cli_args.TIMEOUT 31 @cli_args.MODELS_WORKERS 32 @cli_args.ENV_MANAGER 33 @cli_args.NO_CONDA 34 @cli_args.INSTALL_MLFLOW 35 @cli_args.ENABLE_MLSERVER 36 def serve( 37 model_uri, 38 port, 39 host, 40 timeout, 41 workers, 42 env_manager=None, 43 no_conda=False, 44 install_mlflow=False, 45 enable_mlserver=False, 46 ): 47 """ 48 Serve a model saved with MLflow by launching a webserver on the specified host and port. 49 The command supports models with the ``python_function`` or ``crate`` (R Function) flavor. 50 For information about the input data formats accepted by the webserver, see the following 51 documentation: https://www.mlflow.org/docs/latest/models.html#built-in-deployment-tools. 52 53 .. warning:: 54 55 Models built using MLflow 1.x will require adjustments to the endpoint request payload 56 if executed in an environment that has MLflow 2.x installed. In 1.x, a request payload 57 was in the format: ``{'columns': [str], 'data': [[...]]}``. 2.x models require 58 payloads that are defined by the structural-defining keys of either ``dataframe_split``, 59 ``instances``, ``inputs`` or ``dataframe_records``. See the examples below for 60 demonstrations of the changes to the invocation API endpoint in 2.0. 61 62 .. note:: 63 64 Requests made in pandas DataFrame structures can be made in either `split` or `records` 65 oriented formats. 66 See https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_json.html for 67 detailed information on orientation formats for converting a pandas DataFrame to json. 68 69 Example: 70 71 .. code-block:: bash 72 73 $ mlflow models serve -m runs:/my-run-id/model-path & 74 75 # records orientation input format for serializing a pandas DataFrame 76 $ curl http://127.0.0.1:5000/invocations -H 'Content-Type: application/json' -d '{ 77 "dataframe_records": [{"a":1, "b":2}, {"a":3, "b":4}, {"a":5, "b":6}] 78 }' 79 80 # split orientation input format for serializing a pandas DataFrame 81 $ curl http://127.0.0.1:5000/invocations -H 'Content-Type: application/json' -d '{ 82 "dataframe_split": {"columns": ["a", "b"], 83 "index": [0, 1, 2], 84 "data": [[1, 2], [3, 4], [5, 6]]} 85 }' 86 87 # inputs format for List submission of array, tensor, or DataFrame data 88 $ curl http://127.0.0.1:5000/invocations -H 'Content-Type: application/json' -d '{ 89 "inputs": [[1, 2], [3, 4], [5, 6]] 90 }' 91 92 # instances format for submission of Tensor data 93 curl http://127.0.0.1:5000/invocations -H 'Content-Type: application/json' -d '{ 94 "instances": [ 95 {"a": "t1", "b": [1, 2, 3]}, 96 {"a": "t2", "b": [4, 5, 6]}, 97 {"a": "t3", "b": [7, 8, 9]} 98 ] 99 }' 100 101 """ 102 env_manager = _EnvManager.LOCAL if no_conda else env_manager 103 104 return get_flavor_backend( 105 model_uri, env_manager=env_manager, workers=workers, install_mlflow=install_mlflow 106 ).serve( 107 model_uri=model_uri, port=port, host=host, timeout=timeout, enable_mlserver=enable_mlserver 108 ) 109 110 111 class KeyValueType(click.ParamType): 112 name = "key=value" 113 114 def convert(self, value, param, ctx): 115 if "=" not in value: 116 self.fail(f"{value!r} is not a valid key value pair, expecting `key=value`", param, ctx) 117 return value.split("=", 1) 118 119 120 @commands.command("predict") 121 @mlflow_mcp(tool_name="predict_with_model") 122 @cli_args.MODEL_URI 123 @click.option( 124 "--input-path", "-i", default=None, help="CSV containing pandas DataFrame to predict against." 125 ) 126 @click.option( 127 "--output-path", 128 "-o", 129 default=None, 130 help="File to output results to as json file. If not provided, output to stdout.", 131 ) 132 @click.option( 133 "--content-type", 134 "-t", 135 default="json", 136 help="Content type of the input file. Can be one of {'json', 'csv'}.", 137 ) 138 @cli_args.ENV_MANAGER 139 @cli_args.INSTALL_MLFLOW 140 @click.option( 141 "--pip-requirements-override", 142 "-r", 143 default=None, 144 help="Specify packages and versions to override the dependencies defined " 145 "in the model. Must be a comma-separated string like x==y,z==a.", 146 ) 147 @click.option( 148 "--env", 149 default=None, 150 type=KeyValueType(), 151 multiple=True, 152 help="Extra environment variables to set when running the model. Must be " 153 "key value pairs, e.g. `--env key=value`.", 154 ) 155 def predict( 156 model_uri, 157 input_data=None, 158 input_path=None, 159 content_type=python_api._CONTENT_TYPE_JSON, 160 output_path=None, 161 env_manager=_EnvManager.VIRTUALENV, 162 install_mlflow=False, 163 pip_requirements_override=None, 164 env=None, 165 ): 166 """ 167 Generate predictions in json format using a saved MLflow model. For information about the input 168 data formats accepted by this function, see the following documentation: 169 https://www.mlflow.org/docs/latest/models.html#built-in-deployment-tools. 170 """ 171 return python_api.predict( 172 model_uri=model_uri, 173 input_data=input_data, 174 input_path=input_path, 175 content_type=content_type, 176 output_path=output_path, 177 env_manager=env_manager, 178 install_mlflow=install_mlflow, 179 pip_requirements_override=pip_requirements_override, 180 extra_envs=dict(env), 181 ) 182 183 184 @commands.command("prepare-env") 185 @mlflow_mcp(tool_name="prepare_model_env") 186 @cli_args.MODEL_URI 187 @cli_args.ENV_MANAGER 188 @cli_args.INSTALL_MLFLOW 189 def prepare_env( 190 model_uri, 191 env_manager, 192 install_mlflow, 193 ): 194 """ 195 Performs any preparation necessary to predict or serve the model, for example 196 downloading dependencies or initializing a conda environment. After preparation, 197 calling predict or serve should be fast. 198 """ 199 return get_flavor_backend( 200 model_uri, env_manager=env_manager, install_mlflow=install_mlflow 201 ).prepare_env(model_uri=model_uri) 202 203 204 @commands.command("generate-dockerfile") 205 @mlflow_mcp(tool_name="generate_model_dockerfile") 206 @cli_args.MODEL_URI_BUILD_DOCKER 207 @click.option( 208 "--output-directory", 209 "-d", 210 default="mlflow-dockerfile", 211 help="Output directory where the generated Dockerfile is stored.", 212 ) 213 @cli_args.ENV_MANAGER_DOCKERFILE 214 @cli_args.MLFLOW_HOME 215 @cli_args.INSTALL_JAVA 216 @cli_args.INSTALL_MLFLOW 217 @cli_args.ENABLE_MLSERVER 218 def generate_dockerfile( 219 model_uri, 220 output_directory, 221 env_manager, 222 mlflow_home, 223 install_java, 224 install_mlflow, 225 enable_mlserver, 226 ): 227 """ 228 Generates a directory with Dockerfile whose default entrypoint serves an MLflow model at port 229 8080 using the python_function flavor. The generated Dockerfile is written to the specified 230 output directory, along with the model (if specified). This Dockerfile defines an image that 231 is equivalent to the one produced by ``mlflow models build-docker``. 232 """ 233 if model_uri: 234 _logger.info("Generating Dockerfile for model %s", model_uri) 235 else: 236 _logger.info("Generating Dockerfile") 237 backend = get_flavor_backend(model_uri, docker_build=True, env_manager=env_manager) 238 if backend.can_build_image(): 239 backend.generate_dockerfile( 240 model_uri, 241 output_directory, 242 mlflow_home=mlflow_home, 243 install_java=install_java, 244 install_mlflow=install_mlflow, 245 enable_mlserver=enable_mlserver, 246 ) 247 _logger.info("Generated Dockerfile in directory %s", output_directory) 248 else: 249 _logger.error( 250 "Cannot build docker image for selected backend", 251 extra={"backend": backend.__class__.__name__}, 252 ) 253 raise NotImplementedError("Cannot build docker image for selected backend") 254 255 256 @commands.command("build-docker") 257 @mlflow_mcp(tool_name="build_model_docker") 258 @cli_args.MODEL_URI_BUILD_DOCKER 259 @click.option("--name", "-n", default="mlflow-pyfunc-servable", help="Name to use for built image") 260 @cli_args.ENV_MANAGER 261 @cli_args.MLFLOW_HOME 262 @cli_args.INSTALL_JAVA 263 @cli_args.INSTALL_MLFLOW 264 @cli_args.ENABLE_MLSERVER 265 def build_docker(**kwargs): 266 """ 267 Builds a Docker image whose default entrypoint serves an MLflow model at port 8080, using the 268 python_function flavor. The container serves the model referenced by ``--model-uri``, if 269 specified when ``build-docker`` is called. If ``--model-uri`` is not specified when build_docker 270 is called, an MLflow Model directory must be mounted as a volume into the /opt/ml/model 271 directory in the container. 272 273 Building a Docker image with ``--model-uri``: 274 275 .. code:: bash 276 277 # Build a Docker image named 'my-image-name' that serves the model from run 'some-run-uuid' 278 # at run-relative artifact path 'my-model' 279 mlflow models build-docker --model-uri "runs:/some-run-uuid/my-model" --name "my-image-name" 280 # Serve the model 281 docker run -p 5001:8080 "my-image-name" 282 283 Building a Docker image without ``--model-uri``: 284 285 .. code:: bash 286 287 # Build a generic Docker image named 'my-image-name' 288 mlflow models build-docker --name "my-image-name" 289 # Mount the model stored in '/local/path/to/artifacts/model' and serve it 290 docker run --rm -p 5001:8080 -v /local/path/to/artifacts/model:/opt/ml/model "my-image-name" 291 292 .. important:: 293 294 Since MLflow 2.10.1, the Docker image built with ``--model-uri`` does **not install Java** 295 for improved performance, unless the model flavor is one of ``["johnsnowlabs", "h2o", 296 "spark"]``. If you need to install Java for other flavors, e.g. custom Python model 297 that uses SparkML, please specify the ``--install-java`` flag to enforce Java installation. 298 299 NB: by default, the container will start nginx and uvicorn processes. If you don't need the 300 nginx process to be started (for instance if you deploy your container to Google Cloud Run), 301 you can disable it via the DISABLE_NGINX environment variable: 302 303 .. code:: bash 304 305 docker run -p 5001:8080 -e DISABLE_NGINX=true "my-image-name" 306 307 By default, the number of uvicorn workers is set to CPU count. If you want to set a custom 308 number of workers, you can set the MLFLOW_MODELS_WORKERS environment variable: 309 310 .. code:: bash 311 312 docker run -p 5001:8080 -e MLFLOW_MODELS_WORKERS=4 "my-image-name" 313 314 See https://www.mlflow.org/docs/latest/python_api/mlflow.pyfunc.html for more information on the 315 'python_function' flavor. 316 """ 317 python_api.build_docker(**kwargs) 318 319 320 @commands.command("update-pip-requirements") 321 @mlflow_mcp(tool_name="update_model_pip_requirements") 322 @cli_args.MODEL_URI 323 @click.argument("operation", type=click.Choice(["add", "remove"])) 324 @click.argument("requirement_strings", type=str, nargs=-1) 325 def update_pip_requirements(model_uri, operation, requirement_strings): 326 """ 327 Add or remove requirements from a model's conda.yaml and requirements.txt files. 328 If using a remote tracking server, please make sure to set the MLFLOW_TRACKING_URI 329 environment variable to the URL of the desired server. 330 331 REQUIREMENT_STRINGS is a list of pip requirements specifiers. 332 See below for examples. 333 334 Sample usage: 335 336 .. code:: 337 338 # Add requirements using the model's "runs:/" URI 339 340 mlflow models update-pip-requirements -m runs:/<run_id>/<model_path> \\ 341 add "pandas==1.0.0" "scikit-learn" "mlflow >= 2.8, != 2.9.0" 342 343 # Remove requirements from a local model 344 345 mlflow models update-pip-requirements -m /path/to/local/model \\ 346 remove "torchvision" "pydantic" 347 348 Note that model registry URIs (i.e. URIs in the form ``models:/``) are not 349 supported, as artifacts in the model registry are intended to be read-only. 350 Editing requirements is read-only artifact repositories is also not supported. 351 352 If adding requirements, the function will overwrite any existing requirements 353 that overlap, or else append the new requirements to the existing list. 354 355 If removing requirements, the function will ignore any version specifiers, 356 and remove all the specified package names. Any requirements that are not 357 found in the existing files will be ignored. 358 """ 359 update_model_requirements(model_uri, operation, requirement_strings) 360 361 _logger.info(f"Successfully updated the requirements for the model at {model_uri}!")