Implementation:Haifengl Smile Model Discovery REST API
Overview
The Model Discovery REST API provides two HTTP endpoints for enumerating deployed models and inspecting individual model metadata. These endpoints are implemented in InferenceResource (the JAX-RS resource class) and return data structures defined in ModelMetadata. Clients use these endpoints to discover available models and their input contracts before sending prediction requests.
Source Location
| Class | Location |
|---|---|
InferenceResource (list + get) |
serve/src/main/java/smile/serve/InferenceResource.java (Lines 51-62) |
ModelMetadata |
serve/src/main/java/smile/serve/ModelMetadata.java (Lines 29-54) |
InferenceService (backing logic) |
serve/src/main/java/smile/serve/InferenceService.java |
Import Statements
N/A -- these are REST endpoints consumed via HTTP. For server-side development:
import smile.serve.InferenceResource;
import smile.serve.ModelMetadata;
External Dependencies
| Dependency | Annotation/Class | Purpose |
|---|---|---|
| JAX-RS (Jakarta REST) | @GET |
Marks methods as HTTP GET handlers |
| JAX-RS | @Path |
Defines the URL path for the resource |
| JAX-RS | @Produces(MediaType.APPLICATION_JSON) |
Specifies JSON response content type |
| JAX-RS | @PathParam |
Extracts path parameters from the URL |
| Jackson | ObjectMapper (implicit via Quarkus) |
Serializes Java objects to JSON responses |
Type
API Doc
REST Endpoints
List Models
| Property | Value |
|---|---|
| Method | GET
|
| Path | /api/v1/models
|
| Produces | application/json
|
| Response | JSON array of model ID strings |
| Errors | None (returns empty array if no models loaded) |
Java implementation:
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<String> list() {
return service.models();
}
Example request with curl:
curl -s http://localhost:8080/api/v1/models
Example response:
["iris-classifier-2", "boston-regression-1", "wine-gbm-3"]
Get Model Metadata
| Property | Value |
|---|---|
| Method | GET
|
| Path | /api/v1/models/{id}
|
| Path Parameter | id -- the model identifier (e.g., "iris-classifier-2")
|
| Produces | application/json
|
| Response | ModelMetadata JSON object
|
| Errors | 404 Not Found if the model ID does not exist
|
Java implementation:
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public ModelMetadata get(@PathParam("id") String id) {
return service.getModel(id).metadata();
}
Example request with curl:
curl -s http://localhost:8080/api/v1/models/iris-classifier-2
Example response:
{
"id": "iris-classifier-2",
"algorithm": "random-forest",
"schema": {
"sepal_length": {"type": "double", "nullable": false},
"sepal_width": {"type": "double", "nullable": false},
"petal_length": {"type": "double", "nullable": false},
"petal_width": {"type": "double", "nullable": false}
},
"tags": {
"id": "iris-classifier",
"version": "2"
}
}
ModelMetadata Structure
public class ModelMetadata {
/** Data type descriptor for a schema field. */
public record Type(String type, boolean nullable) {}
/** The model id (e.g., "iris-classifier-2"). */
public String id;
/** The learning algorithm (e.g., "random-forest"). */
public String algorithm;
/** Input data schema: maps field name -> Type(datatype, nullable). */
public Map<String, Type> schema;
/** User-defined model metadata tags. */
public Properties tags;
}
Constructor Logic
The ModelMetadata constructor extracts metadata directly from the Model object:
public ModelMetadata(String id, Model model) {
this.id = id;
this.algorithm = model.algorithm();
this.schema = new TreeMap<>();
this.tags = model.tags();
for (var field : model.schema().fields()) {
schema.put(field.name(),
new Type(field.dtype().name(), field.dtype().isNullable()));
}
}
This iterates over the model's StructType schema (which excludes the response variable), extracting each field's name, data type name, and nullability flag. The schema is stored in a TreeMap for alphabetical ordering in the JSON output.
URL Routing
The base path for all model endpoints is configured in application.properties:
quarkus.rest.path=/api/v1
The InferenceResource class is annotated with @Path("/models"), so the full paths become:
| Endpoint | Full URL |
|---|---|
| List models | GET /api/v1/models
|
| Get metadata | GET /api/v1/models/{id}
|
| Predict | POST /api/v1/models/{id}
|
| Stream predict | POST /api/v1/models/{id}/stream
|
Error Handling
When a client requests metadata for a non-existent model, the InferenceService.getModel() method throws a jakarta.ws.rs.NotFoundException:
public InferenceModel getModel(String id) throws NotFoundException {
var model = models.get(id);
if (model == null) throw new NotFoundException(id);
return model;
}
Quarkus automatically maps this exception to an HTTP 404 response with the model ID in the error message.
Example error response:
curl -v http://localhost:8080/api/v1/models/nonexistent-model
# HTTP/1.1 404 Not Found
Typical Client Workflow
A client interacts with the discovery API in this sequence:
- Enumerate models:
GET /api/v1/modelsto discover available model IDs. - Inspect metadata:
GET /api/v1/models/{id}to learn the input schema for the desired model. - Validate input: Use the schema to validate or construct the request payload.
- Send prediction:
POST /api/v1/models/{id}with the feature data.