Skip to content

Commit

Permalink
Merge pull request #462 from dba/feature/DBACLD-47417-Extend_OPS_to_s…
Browse files Browse the repository at this point in the history
…upport_downloading_of_Ruleset_Models

Feature/dbacld 47417 extend ops to support downloading of ruleset models
  • Loading branch information
Ke LI authored and GitHub Enterprise committed Jan 19, 2023
2 parents 8ce1a00 + 087f8dd commit 2acc436
Show file tree
Hide file tree
Showing 22 changed files with 1,026 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Open Prediction Service HUB

## 2.8.0 _2022/11/28_

* [DBACLD-47417] Extend OPS to support downloading of Ruleset Models

## 2.7.1 _2022/09/15_

* [DBACLD-58320] Fix ads ml service rule import
Expand Down
66 changes: 64 additions & 2 deletions open-prediction-service.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Open Prediction Service
version: 2.7.4-SNAPSHOT
version: 2.8.0-SNAPSHOT
description: The Open Prediction Service API is an effort to provide an Open API that enables unsupported native ML Providers in Decision Designer or Decision Runtime.
tags:
- name: info
Expand Down Expand Up @@ -213,6 +213,47 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/models/{model_id}/download:
get:
tags:
- discover
summary: Download model binary
operationId: get_binary_by_id
parameters:
- $ref: '#/components/parameters/ModelIDParam'
responses:
'200':
description: Successful Response
content:
# A binary file:
application/octet-stream: {}
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/models/{model_id}/metadata:
get:
tags:
- discover
summary: Get parsed model metadata
operationId: get_metadata_by_id
parameters:
- $ref: '#/components/parameters/ModelIDParam'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AdditionalModelInfo'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/endpoints:
get:
tags:
Expand Down Expand Up @@ -823,13 +864,15 @@ components:
unknown_file_size:
type: boolean
default: false
description: Whether the user can upload a file whose length is unknown at the time of the request.
description: Whether the user can upload a file whose length(Content-Length in HTTP header) is unknown at the time of the request.
example:
capabilities:
- info
- discover
- manage
- prediction
- download
- metadata
managed_capabilities:
supported_input_data_structure:
- "auto"
Expand All @@ -854,6 +897,10 @@ components:
- discover
- manage
- run
# Download model binary
- download
# Inspect model specific metadata, for example for PMML model return subType (Scorecard, Ruleset)
- metadata
Parameter:
title: Parameter
description: Parameter for ml model invocation
Expand Down Expand Up @@ -907,6 +954,21 @@ components:
target:
- rel: endpoint
href: 'http://open-prediction-service.org/endpoints/8c2af534-cdce-11ea-87d0-0242ac130003'
AdditionalModelInfo:
type: object
title: Additional Model Information
required:
- modelPackage
- modelType
properties:
modelPackage:
type: string
description: The file format of binary model
example: pmml
modelType:
type: string
description: Model type of binary model
example: RuleSetModel
parameters:
ModelIDParam:
in: path
Expand Down
2 changes: 1 addition & 1 deletion ops-client-sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.ibm.decision</groupId>
<artifactId>ops-client-sdk</artifactId>
<version>2.7.4-SNAPSHOT</version>
<version>2.8.0-SNAPSHOT</version>
<inceptionYear>2020</inceptionYear>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ async def server_capabilities() -> typing.Dict[typing.Text, typing.Any]:
ops_model.Capability.info,
ops_model.Capability.discover,
ops_model.Capability.manage,
ops_model.Capability.run
ops_model.Capability.run,
ops_model.Capability.download,
ops_model.Capability.metadata
],
'managed_capabilities': {
'supported_input_data_structure': ['auto', 'DataFrame', 'ndarray', 'DMatrix', 'list'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import logging
import typing
import io

import fastapi
import fastapi.encoders as encoders
Expand All @@ -28,10 +29,12 @@
import app.crud as crud
import app.gen.schemas.ops_schemas as ops_schemas
import app.runtime.model_upload as app_model_upload
import app.runtime.inspection as app_runtime_inspection
import app.schemas as schemas
import app.schemas.binary_config as app_binary_config
import app.schemas.impl as impl
import app.core.configuration as app_conf
import app.models as models

router = fastapi.APIRouter()
LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -182,3 +185,76 @@ async def add_binary(
model_id=model_id
)
return impl.EndpointImpl.from_database(crud.endpoint.get(db, id=m))


@router.get(
path='/models/{model_id}/metadata',
response_model=ops_schemas.AdditionalModelInfo,
tags=['discover'])
def get_model_metadata(
model_id: int,
db: saorm.Session = fastapi.Depends(deps.get_db)):
LOGGER.info('Retrieving model metadata for id: %s', model_id)
match crud.binary_ml_model.get(db=db, id=model_id):
case None:
raise fastapi.HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=f'Model with id {model_id} is not found')
case models.BinaryMlModel(format=app_binary_config.ModelWrapper.PICKLE | app_binary_config.ModelWrapper.JOBLIB as format):
return ops_schemas.AdditionalModelInfo(
modelPackage=format.value,
modelType='other')
case models.BinaryMlModel(format=app_binary_config.ModelWrapper.PMML, model_b64=binary):
match app_runtime_inspection.inspect_pmml_subtype(binary):
case 'Scorecard' | 'RuleSetModel' as typ:
return ops_schemas.AdditionalModelInfo(
modelPackage='pmml',
modelType=typ)
case _:
return ops_schemas.AdditionalModelInfo(
modelPackage='pmml',
modelType='other')
case _:
return ops_schemas.AdditionalModelInfo(
modelPackage='other',
modelType='other')


@router.get(
path='/models/{model_id}/download',
response_class=responses.StreamingResponse,
tags=['discover'])
def get_model_binary(
model_id: int,
db: saorm.Session = fastapi.Depends(deps.get_db)):
LOGGER.info('Retrieving model binary for id: %s', model_id)

model = crud.model.get(db=db, id=model_id)

if model is None:
raise fastapi.HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=f'Model with id {model_id} is not found')

filename = model.config.configuration['name']

try:
binary = model.endpoint.binary
except AttributeError:
raise fastapi.HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=f'Model binary with id {model_id} is not found')

if binary.format == app_binary_config.ModelWrapper.PMML:
file_extension = 'pmml'
elif binary.format == app_binary_config.ModelWrapper.PICKLE:
file_extension = 'pickle'
elif binary.format == app_binary_config.ModelWrapper.JOBLIB:
file_extension = 'joblib'
else:
file_extension = 'bin'

LOGGER.info('Downloading model binary with name %s and format %s', filename, file_extension)

return responses.StreamingResponse(
content=io.BytesIO(binary.model_b64),
media_type='application/octet-stream',
headers={'Content-Disposition': 'attachment; filename="{basename}.{extension}"'.format(
basename=filename, extension=file_extension)})
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async def upload(
model_binary,
input_data_structure,
output_data_structure,
format_,
file_format,
name=model_name
)

Expand Down
6 changes: 5 additions & 1 deletion ops-implementations/ads-ml-service/app/db/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ def get_db_opts() -> typing.Dict[str, typing.Any]:
return opt


def get_engine():
return create_engine(get_db_url(), **get_db_opts())


SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=create_engine(get_db_url(), **get_db_opts()))
bind=get_engine())
66 changes: 64 additions & 2 deletions ops-implementations/ads-ml-service/app/gen/tmp.schemas.ops.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Open Prediction Service
version: 2.7.0-SNAPSHOT
version: 2.8.0-SNAPSHOT
description: The Open Prediction Service API is an effort to provide an Open API that enables unsupported native ML Providers in Decision Designer or Decision Runtime.
tags:
- name: info
Expand Down Expand Up @@ -213,6 +213,47 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/models/{model_id}/download:
get:
tags:
- discover
summary: Download model binary
operationId: get_binary_by_id
parameters:
- $ref: '#/components/parameters/ModelIDParam'
responses:
'200':
description: Successful Response
content:
# A binary file:
application/octet-stream: {}
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/models/{model_id}/metadata:
get:
tags:
- discover
summary: Get parsed model metadata
operationId: get_metadata_by_id
parameters:
- $ref: '#/components/parameters/ModelIDParam'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AdditionalModelInfo'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/endpoints:
get:
tags:
Expand Down Expand Up @@ -823,13 +864,15 @@ components:
unknown_file_size:
type: boolean
default: false
description: Whether the user can upload a file whose length is unknown at the time of the request.
description: Whether the user can upload a file whose length(Content-Length in HTTP header) is unknown at the time of the request.
example:
capabilities:
- info
- discover
- manage
- prediction
- download
- metadata
managed_capabilities:
supported_input_data_structure:
- "auto"
Expand All @@ -854,6 +897,10 @@ components:
- discover
- manage
- run
# Download model binary
- download
# Inspect model specific metadata, for example for PMML model return subType (Scorecard, Ruleset)
- metadata
Parameter:
title: Parameter
description: Parameter for ml model invocation
Expand Down Expand Up @@ -907,6 +954,21 @@ components:
target:
- rel: endpoint
href: 'http://open-prediction-service.org/endpoints/8c2af534-cdce-11ea-87d0-0242ac130003'
AdditionalModelInfo:
type: object
title: Additional Model Information
required:
- modelPackage
- modelType
properties:
modelPackage:
type: string
description: The file format of binary model
example: pmml
modelType:
type: string
description: Model type of binary model
example: RuleSetModel
parameters:
ModelIDParam:
in: path
Expand Down
6 changes: 6 additions & 0 deletions ops-implementations/ads-ml-service/app/runtime/inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import logging
import typing
import pickletools

import pypmml.base as pypmml_base

Expand Down Expand Up @@ -65,3 +66,8 @@ def inspect_pmml_model_name(model_file: bytes) -> typing.Optional[str]:
return wrapper.model.modelName
else:
return None


def inspect_pmml_subtype(model_file: bytes) -> typing.Optional[str]:
wrapper = load_pmml_model(model_file).loaded_model
return wrapper.model.modelElement
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def test_get_server_capabilities(
assert 'discover' in content['capabilities']
assert 'manage' in content['capabilities']
assert 'run' in content['capabilities']
assert 'download' in content['capabilities']
assert 'metadata' in content['capabilities']


def test_get_managed_capabilities(
Expand Down
Loading

0 comments on commit 2acc436

Please sign in to comment.