From 949e349953bb36eb933676e6019ec30c72b58749 Mon Sep 17 00:00:00 2001 From: Isabel Zimmerman <54685329+isabelizimm@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:53:46 -0500 Subject: [PATCH] docs: add common pitfalls section to deploying custom code (#228) --- docs/custom_code.qmd | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/custom_code.qmd b/docs/custom_code.qmd index d6b9ebc..258f584 100644 --- a/docs/custom_code.qmd +++ b/docs/custom_code.qmd @@ -2,12 +2,12 @@ In some cases, you may need to create and deploy custom code as part of your MLOps workflow using vetiver. This could be necessary when you need to: -- deploy custom models in vetiver -- deploy unsupported models in vetiver -- include custom code in vetiver -- deploy a vetiver model with a custom pipeline +- deploy custom models in vetiver +- deploy unsupported models in vetiver +- include custom code in vetiver +- deploy a vetiver model with a custom pipeline - You may also have custom code in a known framework, such as a column transformer for a scikit-learn model. +You may also have custom code in a known framework, such as a column transformer for a scikit-learn model. In these cases, extra steps will be required to successfully create and deploy a `VetiverModel` object. @@ -17,12 +17,12 @@ Vetiver supports basic [scikit-learn](https://scikit-learn.org/), [torch](https: To create a model handler, you should create a subclass of vetiver's `BaseHandler` class. This handler should include the following: -- `model_type`: A static method that declares the type of your model. -- `handler_predict()`: A method that defines how predictions should be made for your model. This method is used at the /predict endpoint in the VetiverAPI. +- `model_type`: A static method that declares the type of your model. +- `handler_predict()`: A method that defines how predictions should be made for your model. This method is used at the /predict endpoint in the VetiverAPI. Here's an example of a handler for a model of `newmodeltype` type. Once you have defined your handler, you can initialize it with your model and pass it to the `VetiverModel` class. -```python +``` python from vetiver.handlers.base import BaseHandler class CustomHandler(BaseHandler): @@ -60,7 +60,7 @@ To deploy custom code, you need to include the necessary source code in your dep If your `VetiverModel` includes custom source code, you need to include that code in your deployment files to build an API in another location. The example below shows a user-created `FeatureSelector`, which is part of a scikit-learn pipeline. -```{.python filename="model.py"} +``` {.python filename="model.py"} from sklearn.base import BaseEstimator, TransformerMixin from sklearn.tree import DecisionTreeClassifier from sklearn.pipeline import Pipeline @@ -94,18 +94,18 @@ board = pins.board_connect(allow_pickle_read=True) vetiver_pin_write(board, v) ``` -::: {.panel-tabset} +::: panel-tabset ## Docker To generate files needed to start a Docker container, you can use the command `vetiver.prepare_docker`. -```{.python} +``` python vetiver.prepare_docker(board, "selected_decision_tree") ``` When you run this line, 3 files are generated: a Dockerfile, an `app.py` file, and a `vetiver_requirements.txt`. In the `app.py` file, you'll need to add an import statement that is formatted `from {name of file, excluding .py, that has custom element} import {name of custom element}`. -```{.python filename="app.py"} +``` {.python filename="app.py"} from vetiver import VetiverModel import vetiver import pins @@ -121,7 +121,7 @@ api = vetiver_api.app Add a line to your Dockerfile to copy your source file(s) into your Docker container. The format will be `COPY path/to/your/filename.py /vetiver/app/filename.py`, where the destination is always in the `/vetiver/app/` directory. -```{.bash filename="Dockerfile"} +``` {.bash filename="Dockerfile"} # # Generated by the vetiver package; edit with care # start with python base image FROM python:3.10 @@ -152,13 +152,13 @@ CMD ["uvicorn", "app.app:api", "--host", "0.0.0.0", "--port", "8080"] To deploy custom code to Posit Connect, you'll first start with the command `vetiver.write_app`. -```{.python} +``` python vetiver.write_app(board, 'selected_decision_tree') ``` This will generate an `app.py` file, where you'll need to add an import statement that is formatted `from {name of file, excluding .py, that has custom element} import {name of custom element}`. -```{.python filename=="app.py"} +``` {.python filename="=\"app.py\""} from vetiver import VetiverModel import vetiver import pins @@ -174,7 +174,7 @@ api = vetiver_api.app After editing the `app.py` file, you can deploy it to Posit Connect using the `rsconnect` package. Use the `rsconnect.api.actions.deploy_python_fastapi()` function to deploy the API, specifying the Connect server URL, API key, directory containing the `app.py` and `model.py` files, and the entry point of the API. -```{.python} +``` python from rsconnect.api.actions import deploy_python_fastapi import rsconnect @@ -191,9 +191,15 @@ rsconnect.actions.deploy_python_fastapi( directory = "./", # path to the directory containing the app.py and model.py files entry_point = "app:api" # the API is the app.py file, in a variable named api ) - ``` - ::: +## Common Pitfalls + +When deploying custom code, the most common error is something similar to `AttributeError: Can't get attribute 'ExampleModel' on `. There are a few possible causes for this error: + +1. The original `ExampleModel` may have been pinned from inside a Jupyter Notebook. Because pickling only saves a reference for how to read a class, not the source code, a custom model transformer pinned from a Jupyter Notebook cannot be imported and resolved later. To fix this, repin your model/transformer from inside a Python script. + +2. You may not be uploading the custom code to be used later. When deploying, you'll want to add the files containing your custom code to the `extra_files` argument so that it can be imported, eg, `vetiver.deploy_rsconnect(connect_server, board, model_name, extra_files=['custom_model.py', 'requirements.txt'])` . + Please note that the above steps are a general guide, and you may need to adapt them to your specific use case and deployment environment. If you have any questions, please consider [opening an issue](https://github.com/rstudio/vetiver-python/issues/new).