Skip to content

Commit

Permalink
docs: add common pitfalls section to deploying custom code (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
isabelizimm authored Dec 16, 2024
1 parent 2f01464 commit 949e349
Showing 1 changed file with 24 additions and 18 deletions.
42 changes: 24 additions & 18 deletions docs/custom_code.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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 <module '__main__' (built-in)>`. 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).

0 comments on commit 949e349

Please sign in to comment.