Model Deployment with Vetiver in R
Vetiver is Posit’s official package for MLOps workflows in R. It provides a standardized way to version, deploy, and monitor machine learning models. If you’re already using tidymodels for model building, vetiver fits naturally into your pipeline for serving predictions in production.
This article walks through an end-to-end workflow: training a model, creating a model card, versioning with pins, deploying as a Plumber API, and serving predictions over HTTP.
Training and Preparing a Model
First, you need a trained model. Vetiver works with any model that follows the tidymodels prediction interface. Here’s a simple example using a linear regression on the mtcars dataset:
library(tidymodels)
library(vetiver)
library(pins)
# Train a simple model
data(mtcars)
lm_model <- linear_reg() %>%
set_engine("lm") %>%
fit(mpg ~ wt + hp + cyl, data = mtcars)
# Create a vetiver model object
v <- vetiver_model(lm_model, "mpg_predictor")
v
The vetiver_model() function wraps your trained model with metadata needed for deployment. This includes the model name, version, and required preprocessing.
Using Workflows with Custom Preprocessing
For real-world models, you’ll often need custom preprocessing steps beyond simple formula syntax. Vetiver supports tidymodels workflows that bundle preprocessing recipes with your model:
library(recipes)
# Define a preprocessing recipe
mpg_recipe <- recipe(mpg ~ wt + hp + cyl, data = mtcars) %>%
step_log(hp) %>%
step_center(all_predictors()) %>%
step_scale(all_predictors())
# Create a workflow combining recipe and model
lm_wf <- workflow() %>%
add_recipe(mpg_recipe) %>%
add_model(linear_reg() %>% set_engine("lm")) %>%
fit(data = mtcars)
# Wrap in vetiver
v <- vetiver_model(lm_wf, "mpg_predictor_workflow")
v
The workflow object preserves your preprocessing steps when deployed, ensuring predictions use the same transformations as training.
Creating Model Cards
Model cards document your model for reproducibility and governance. Vetiver generates model cards from templates using rmarkdown:
# Generate a model card from the vetiver template
rmarkdown::draft(
"model-card.Rmd",
template = "vetiver_model_card",
package = "vetiver"
)
This creates an R Markdown file you can customize with your model description, performance metrics, and training data information. Edit the generated file to add:
- Model description and purpose
- Input/output specifications
- Performance metrics (if available)
- Training data information
- Any known limitations
Render the model card to HTML or PDF for sharing:
rmarkdown::render("model-card.Rmd")
Versioning with Pins
Vetiver uses the pins package for model versioning. This stores your model in a versioned registry, making it easy to track changes over time:
# Connect to a board (local folder in this case)
board <- board_folder("models/")
# Pin the model with version tracking
board %>% vetiver_pin_write(v)
When you update your model and re-run vetiver_pin_write(), pins automatically versions the new model. You can retrieve specific versions:
# Get the latest version
latest_model <- board %>% vetiver_pin_read("mpg_predictor")
# Get a specific version
v1_model <- board %>% vetiver_pin_read("mpg_predictor", version = 1)
The pins dashboard shows your model registry:
board %>% pin_versions("mpg_predictor")
Deploying as a Plumber API
Vetiver can generate a Plumber router automatically. Plumber is R’s package for building REST APIs that call R functions:
library(plumber)
# Create the Plumber router using vetiver_api()
pr <- pr() %>% vetiver_api(v)
# Or customize the API with additional endpoints
pr <- pr() %>%
vetiver_api(v) %>%
pr_get("/health", function() {
list(status = "ok", timestamp = Sys.time())
})
You can test the API locally before deploying:
# Run locally on port 8080
pr_run(pr, host = "0.0.0.0", port = 8080)
Serving Predictions via HTTP
Once deployed, clients can send prediction requests to your API. The request body contains the input features as JSON:
library(httr)
library(jsonlite)
# Example prediction request payload
prediction_request <- list(
wt = 2.5,
hp = 120,
cyl = 6
)
# Send request to the API
httr::POST(
"http://localhost:8080/predict",
body = jsonlite::toJSON(prediction_request),
content_type_json()
)
The API returns predictions in JSON format:
{
"predictions": [
{"mpg": 23.5}
]
}
Handling Batch Predictions
For bulk prediction workloads, you can send multiple rows at once:
# Create a data frame with multiple predictions
batch_request <- data.frame(
wt = c(2.0, 2.5, 3.0),
hp = c(100, 120, 150),
cyl = c(4, 6, 8)
)
# Send batch request
response <- httr::POST(
"http://localhost:8080/predict",
body = jsonlite::toJSON(batch_request),
content_type_json()
)
# Parse response
predictions <- jsonlite::fromJSON(httr::content(response, as = "text"))
predictions
The API automatically handles the data frame and returns predictions for all input rows.
Deploying to Posit Connect
For production deployment, Posit Connect provides the smoothest integration with vetiver. After publishing your API:
library(rsconnect)
# Deploy the Plumber API to Posit Connect
rsconnect::deployAPI(
api = "app.R",
account = "your-account-name",
appName = "mpg_predictor",
appTitle = "MPG Prediction API"
)
Replace "app.R" with the file containing your Plumber router. Posit Connect handles hosting, scaling, and authentication out of the box.
Complete Workflow Example
Here’s the full pipeline from training to deployment:
library(tidymodels)
library(vetiver)
library(pins)
library(plumber)
library(recipes)
# 1. Train model with preprocessing
data(mtcars)
lm_wf <- workflow() %>%
add_recipe(recipe(mpg ~ wt + hp + cyl, data = mtcars) %>%
step_log(hp)) %>%
add_model(linear_reg() %>% set_engine("lm")) %>%
fit(data = mtcars)
# 2. Create vetiver model
v <- vetiver_model(lm_wf, "mpg_predictor")
# 3. Create model card from template
rmarkdown::draft(
"model_card.Rmd",
template = "vetiver_model_card",
package = "vetiver"
)
# 4. Version with pins
board <- board_folder("models/")
board %>% vetiver_pin_write(v)
# 5. Deploy as API
pr <- pr() %>% vetiver_api(v)
pr_run(pr, host = "0.0.0.0", port = 8080)
Deployment Considerations
For production deployment, consider these aspects:
API Hosting: You can deploy Plumber APIs on various platforms including Posit Connect, Shiny Server, or containerized with Docker. Posit Connect provides the smoothest integration with vetiver through the rsconnect package.
Model Monitoring: Track prediction drift by logging requests and responses. Vetiver doesn’t include built-in monitoring, but you can add custom logging within your Plumber endpoints.
Authentication: Plumber supports authentication via middleware. For production APIs, ensure appropriate access controls are in place.
Scaling: For high-traffic applications, consider running multiple R processes behind a load balancer. Plumber APIs are single-threaded by default.
Summary
Vetiver makes it straightforward to serve tidymodels predictions over HTTP. The key functions are:
vetiver_model()wraps your trained modelvetiver_pin_write()andvetiver_pin_read()handle version control- The
vetiver_model_cardtemplate generates documentation pr() %>% vetiver_api(v)creates a deployable API
The workflow integrates with tidymodels for training, recipes for preprocessing, and pins for versioning, giving you the tools to put models into production without building custom infrastructure from scratch.