Follow

API Endpoints & Model Deployment

Domino lets you publish R or Python models as web services and invoke them using our REST API, so you can easily integrate your data science projects into existing applications and business processes without involving engineers or devops resources.  

Overview

An API Endpoint is a wrapper on top of a function in your code. The arguments to the API are the arguments to your function; the response from the API includes the return value from your function. Typically this function will make a prediction or classification given some input.

When the endpoint is published, Domino first runs the script containing the function. The process then waits for input (so any objects or functions from the script remain available in-memory). Each call to the endpoint runs the function within this same process. Because the script is only sourced once — at publish time — any more expensive initialization can happen up front, rather than happening on each call. We have a short video to demonstrate how API endpoints are used.

Preparation

Your script has access to your project's files, including serialized model files (e.g., pickle or Rda). So you can save a model with a long-running training task, and then read it into memory with your endpoint script for classification.

There are three ways to publish an endpoint: (1) through the UI; (2) programmatically through the Domino API or CLI; (3) through a scheduled run.

  1. In the UI, click on "Publish" on the left-hand sidebar. This brings you to a page where you can input the name of the script and function you wish to publish:
  2. To programmatically re-publish an Endpoint after a run, start a run from the CLI using the flag --publishAPIEndpoint:
    domino run --publish-api-endpoint command arguments
  3. Alternatively, use the Domino API to start a run and set the publishApiEndpoint parameter to true. If the run is successful it will be deployed to your project's endpoint.
  4. Scheduled runs can automatically update your API endpoint. So you can set up scheduled, long-running training tasks to re-deploy your API endpoint with the latest version of your model. 

Calling the API

At the bottom of the "API Endpoints" page in the UI, you will see a code snippet that you can modify and use to call the API. 

You'll need to make 2 changes to the code snippet. First, add your API key, which is used to authenticate requests to the endpoint. This is found on the account settings page (the button at the top right of the UI with your username):

Next, add the arguments to the function in the "parameters" list. Each element of the list will be passed to the function as positional arguments. The elements themselves may be lists or arbitrary objects, as long as they are valid JSON.

Concretely, if you have

my_function(x, y, z)

and you send

-d '{"parameters": [1, 2, 3]}'

then the endpoint will call

my_function(1, 2, 3)

Once the request is received, Domino will take care of converting the inputs to the proper types in the language of your endpoint function:

JSON Type Python Type R Type
dictionary dictionary named list
array list vector
string str character
number (int) int integer
number (real) float numeric
true True TRUE
false False FALSE
null None NA

Updating your Endpoint

You can publish a new version of the endpoint at any time. For example, you may want to re-train the model with new data, or switch to a different machine learning algorithm.

You can also unpublish the endpoint and Domino will stop serving it. 

Examples in Python and R

The example referenced here is found in this project: https://app.dominodatalab.com/u/nick/winequality. Feel free to fork it and try for yourself.

Python

The model is first trained using train.py, and stored at results/classifier.pkl:

import pandas as pd
from sklearn.ensemble import RandomForestClassifier 
from sklearn import svm
from sklearn import cross_validation
import time
import joblib
 
train = pd.read_csv("./winequality-red.csv", sep=";")
cols = train.columns[:11]

clf = RandomForestClassifier(n_estimators=100, max_features=5, min_samples_split=5)

start = time.time()
clf.fit(train[cols], train.quality)

print "training time:", time.time() - start

print "cross validation scores", cross_validation.cross_val_score(clf, train[cols], train.quality, cv=5)

joblib.dump(clf, 'results/classifier.pkl')

The endpoint is bound to the predict(...) function, defined in predict.py (which loads and uses results/classifier.pkl):

import joblib
import numpy as np

clf = joblib.load("results/classifier.pkl")


def predict(features):
	return np.asscalar(clf.predict(features))

R

The model is trained using train.R, and stored at classifier.Rda:

library(randomForest)

df <- read.csv("./winequality-red.csv", h=T, sep = ";")
df$quality <- factor(df$quality)

cols <- names(df)[1:11]
system.time({
  clf <- randomForest(df$quality ~ ., data=df[,cols], ntree=30, nodesize=4, mtry=8)
})


save(clf, file="classifier.Rda")
save(cols, file="cols.Rda")

The endpoint is bound to the predictQuality(...) function, defined in predict.R (which loads and uses classifier.Rda):

library(randomForest)

load("classifier.Rda")
load("cols.Rda")

predictQuality <- function(features) {
  to_predict <- as.data.frame(features)
  colnames(to_predict) <- cols    
  prediction <- predict(clf,  to_predict)
  cbind(prediction)[1]
}

Example API Request

In either case, once published, a request might look something like this:

curl -v -X POST 'https://app.dominodatalab.com/v1/nick/winequality/endpoint' \
-H 'Content-Type: application/json' \
-H 'X-Domino-Api-Key: YOUR_API_KEY' \
-d '{"parameters": [[5.6, 0.615, 0, 1.6, 0.089, 16, 59, 0.9943, 3.58, 0.52, 9.9]]}'

Note that the v1 in the URL denotes the version of the Domino API and not your API endpoint. The version of your API endpoint is included in the API response.

Example API Response

The API call above returns the following JSON. The model's output is returned in the "result" object while the "release" object contains information about the version of your API endpoint and the file and function which powers it.

{
    "release": {
        "commitId": "e1dfab96a6c6014767e9561059224a3153cb6de6",
        "file": "predict.R",
        "function": "predictQuality",
        "version": 109
    },
    "requestId": "PXHPKJNPWQKTIFWI",
    "result": [
        3
    ]
}


 

Common issues, pitfalls, and other tips

dataTypeError: don't know how to serialize class
You may see this error with Python endpoints if you return values that are NumPy objects, rather than native Python primitives. To fix this, just convert your NumPy values to Python primitives. An easy way to do this is to call numpy.asscalar, described in this Stack Overflow answer.

TypeError: <result> is not JSON serializable
The result of your endpoint function gets serialized to JSON before being sent in the response. However some object types, such as Decimal or certain NumPy data types, are not JSON serializable by default. For Decimal data types, cast the Decimal type to a Float type as described in this Stack Overflow answer. For NumPy data types, convert the values to Python primitives. An easy way to do this is to call numpy.asscalar as described above.

Nested data
You can send data of varying structure to your endpoint by nesting it inside of an array or JSON object which occupies a single element of the "parameters" array. For instance, if your endpoint function expects a single argument, then it can accept

parameters: [[1, 2, 3]]

as well as

parameters: [[1, 2, 3, 4]]

or even

parameters: [{"foo": "bar", "boo": "baz"}]

To access the elements in Python, use standard list and dictionary access syntax. E.g. if you have a function of the following form:

def endpoint(input):
...
return output

then if input was sent as a JSON array, you could access the first element via input[0]. If it was sent as a JSON object, you could access the value associated with the "foo" key via input["foo"].

To access the elements in R, use double-brace syntax as shown below. Assuming a function of the form:

endpoint <- function(input) {
...
return(output)
}

then to access the first element of data sent as an array, use input[[1]]. For data sent as a JSON object, you can access the value associated with "foo" via input[["foo"]].

Was this article helpful?
0 out of 0 found this helpful

Comments