Skip to content

Commit

Permalink
feat: expand Model API Pydantic types
Browse files Browse the repository at this point in the history
Signed-off-by: Miguel Brandão <[email protected]>
Signed-off-by: Panos Vagenas <[email protected]>
Co-authored-by: Miguel Brandão <[email protected]>
  • Loading branch information
vagenas and HolyMichael authored Jul 21, 2023
1 parent b3e7bda commit ead7246
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 114 deletions.
36 changes: 23 additions & 13 deletions deepsearch/model/base/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,42 @@
from typing import Optional

from deepsearch.model.base.model import BaseDSModel
from deepsearch.model.base.types import BaseModelConfig
from deepsearch.model.server.inference_types import ControllerInput, ControllerOutput
from deepsearch.model.base.types import BaseModelConfig, BaseModelMetadata
from deepsearch.model.server.inference_types import (
AppModelInfoOutput,
CtrlPredInput,
CtrlPredOutput,
)


class BaseController(ABC):
_config: Optional[BaseModelConfig] = None

def get_info(self) -> dict:
model = self._get_model()
@abstractmethod
def get_info(self) -> AppModelInfoOutput:
raise NotImplementedError()

def _get_api_version(self) -> str:
return "v1"

def _get_metadata(self) -> BaseModelMetadata:
cfg = self._get_config()
result = { # TODO refactor with pydantic
"definitions": {
"apiVersion": "v1",
"kind": cfg.kind,
"spec": model.get_definition_spec(),
}
}
return result
return BaseModelMetadata(
name=cfg.name,
version=cfg.version,
url=cfg.url,
author=cfg.author,
description=cfg.description,
expected_compute_time=cfg.expected_compute_time,
)

def _get_config(self):
if self._config is None:
self._config = self._get_model().get_config()
return self._config

@abstractmethod
def dispatch_predict(self, spec: ControllerInput) -> ControllerOutput:
def dispatch_predict(self, spec: CtrlPredInput) -> CtrlPredOutput:
raise NotImplementedError()

@abstractmethod
Expand Down
14 changes: 0 additions & 14 deletions deepsearch/model/base/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,3 @@ class BaseDSModel(ABC):
@abstractmethod
def get_config(self) -> BaseModelConfig:
raise NotImplementedError()

def get_definition_spec(self) -> dict:
cfg = self.get_config()
spec = { # TODO refactor with pydantic
"metadata": {
"name": cfg.name,
"version": cfg.version,
"url": cfg.url,
"author": cfg.author,
"description": cfg.description,
"expected_compute_time": cfg.expected_compute_time,
},
}
return spec
26 changes: 22 additions & 4 deletions deepsearch/model/base/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime
from enum import Enum
from typing import Any, Optional
from typing import Any, Dict, Optional

from pydantic import BaseModel, Extra, Field, PositiveFloat

Expand Down Expand Up @@ -34,18 +34,36 @@ class Metadata(StrictModel):
annotations: Annotations


class BaseInfReq(StrictModel):
class BaseAppPredInput(StrictModel):
apiVersion: str
kind: Kind
metadata: Metadata
spec: Any


class BaseModelConfig(StrictModel):
kind: Kind
class BaseModelMetadata(StrictModel):
name: str
version: str
url: Optional[str] = None
author: Optional[str] = None
description: Optional[str] = None
expected_compute_time: Optional[PositiveFloat] = None


class BaseModelConfig(BaseModelMetadata):
kind: Kind


class ModelInfoOutputDefsSpec(BaseModel):
definition: Dict
metadata: BaseModelMetadata


class CtrlInfoOutputDefs(BaseModel):
apiVersion: str
kind: Kind
spec: ModelInfoOutputDefsSpec


class CtrlInfoOutput(BaseModel):
definitions: CtrlInfoOutputDefs
25 changes: 13 additions & 12 deletions deepsearch/model/examples/dummy_nlp_annotator/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from deepsearch.model.base.types import Kind
from deepsearch.model.kinds.nlp.model import BaseNLPModel
from deepsearch.model.kinds.nlp.types import (
AnnotateEntitiesEntry,
AnnotateEntitiesOutput,
AnnotatePropertiesOutput,
AnnotateRelationshipsOutput,
Expand Down Expand Up @@ -41,18 +42,18 @@ def annotate_batched_entities(
results.append(
{
k: [
{
"type": k,
"match": f"a '{k}' match in '{item}'",
"original": f"a '{k}' original in '{item}'",
"range": [1, 5],
},
{
"type": k,
"match": f"another '{k}' match in '{item}'",
"original": f"another '{k}' original in '{item}'",
"range": [12, 42],
},
AnnotateEntitiesEntry(
type=k,
match=f"a '{k}' match in '{item}'",
original=f"a '{k}' original in '{item}'",
range=[1, 5],
),
AnnotateEntitiesEntry(
type=k,
match=f"another '{k}' match in '{item}'",
original=f"another '{k}' original in '{item}'",
range=[12, 42],
),
]
for k in _entity_names
}
Expand Down
4 changes: 2 additions & 2 deletions deepsearch/model/examples/simple_geo_nlp_annotator/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def annotate_batched_entities(

def _annotate_entities_in_item(
self, object_type: str, item: str, entity_names: Optional[List[str]]
) -> List[dict]:
) -> List:
# In this case entity_names is never None, however since BaseAnnotator defines the signature of this method as
# Optionally having entity names we must ensure that they are defined.
if entity_names is None:
Expand Down Expand Up @@ -200,7 +200,7 @@ def annotate_batched_relationships(
if relation in self.relationship_names:
result[relation] = self._rel_annots[
relation
].annotate_relationships_text(text, entity_map)
].annotate_relationships_text(text, entity_map, relation)

if result:
results.append(result)
Expand Down
37 changes: 29 additions & 8 deletions deepsearch/model/kinds/nlp/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,48 @@
FindEntitiesText,
FindPropertiesText,
FindRelationshipsText,
NLPEntitiesControllerOutput,
NLPEntitiesReqSpec,
NLPPropertiesControllerOutput,
NLPEntsCtrlPredOuput,
NLPInfoOutput,
NLPInfoOutputDefinitions,
NLPInfoOutputDefinitionsSpec,
NLPModelMetadata,
NLPPropertiesReqSpec,
NLPRelationshipsControllerOutput,
NLPPropsCtrlPredOutput,
NLPRelationshipsReqSpec,
NLPRelsCtrlPredOutput,
)
from deepsearch.model.server.inference_types import ControllerInput, ControllerOutput
from deepsearch.model.server.inference_types import CtrlPredInput, CtrlPredOutput


class NLPController(BaseController):
def __init__(self, model: BaseNLPModel):
self._model = model

def get_info(self) -> NLPInfoOutput:
cfg = self._model.get_nlp_config()
metadata = NLPModelMetadata(
supported_object_types=cfg.supported_types,
**self._get_metadata().dict(), # passing parent metadata dict as kwargs
)
spec = NLPInfoOutputDefinitionsSpec(
definition=cfg.labels,
metadata=metadata,
)
definitions = NLPInfoOutputDefinitions(
apiVersion=self._get_api_version(),
kind=self.get_kind(),
spec=spec,
)
return NLPInfoOutput(definitions=definitions)

def get_kind(self) -> str:
return Kind.NLPModel

def _get_model(self) -> BaseDSModel:
return self._model

def dispatch_predict(self, spec: ControllerInput) -> ControllerOutput:
def dispatch_predict(self, spec: CtrlPredInput) -> CtrlPredOutput:
cfg = self._model.get_nlp_config()
type_ok = True

Expand All @@ -43,7 +64,7 @@ def dispatch_predict(self, spec: ControllerInput) -> ControllerOutput:
items=spec.findEntities.texts,
entity_names=spec.findEntities.entityNames,
)
return NLPEntitiesControllerOutput(entities=entities)
return NLPEntsCtrlPredOuput(entities=entities)
elif (
isinstance(spec, NLPRelationshipsReqSpec)
and isinstance(spec.findRelationships, FindRelationshipsText)
Expand All @@ -55,7 +76,7 @@ def dispatch_predict(self, spec: ControllerInput) -> ControllerOutput:
entities=spec.findRelationships.entities,
relationship_names=spec.findRelationships.relationshipNames,
)
return NLPRelationshipsControllerOutput(relationships=relationships)
return NLPRelsCtrlPredOutput(relationships=relationships)
elif (
isinstance(spec, NLPPropertiesReqSpec)
and isinstance(spec.findProperties, FindPropertiesText)
Expand All @@ -71,7 +92,7 @@ def dispatch_predict(self, spec: ControllerInput) -> ControllerOutput:
entities=entities,
property_names=spec.findProperties.propertyNames,
)
return NLPPropertiesControllerOutput(properties=properties)
return NLPPropsCtrlPredOutput(properties=properties)
elif not type_ok:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
Expand Down
13 changes: 0 additions & 13 deletions deepsearch/model/kinds/nlp/model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from abc import abstractmethod
from copy import deepcopy
from typing import List, Optional

from deepsearch.model.base.model import BaseDSModel
Expand All @@ -13,8 +12,6 @@


class BaseNLPModel(BaseDSModel):
_cached_def_spec: dict = {}

@abstractmethod
def annotate_batched_entities(
self,
Expand Down Expand Up @@ -44,16 +41,6 @@ def annotate_batched_properties(
) -> AnnotatePropertiesOutput:
raise NotImplementedError()

def get_definition_spec(self) -> dict:
cfg = self.get_nlp_config()
if not self._cached_def_spec:
self._cached_def_spec = deepcopy(super().get_definition_spec())
self._cached_def_spec["definition"] = cfg.labels
self._cached_def_spec["metadata"][
"supported_object_types"
] = cfg.supported_types
return self._cached_def_spec

@abstractmethod
def get_nlp_config(self) -> NLPConfig:
raise NotImplementedError()
Expand Down
64 changes: 50 additions & 14 deletions deepsearch/model/kinds/nlp/types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
from enum import Enum
from typing import List, Literal, Optional, Union
from typing import Dict, List, Literal, Optional, Union

from deepsearch.model.base.types import BaseInfReq, BaseModelConfig, Kind, StrictModel
from deepsearch.model.base.types import (
BaseAppPredInput,
BaseModelConfig,
BaseModelMetadata,
CtrlInfoOutput,
CtrlInfoOutputDefs,
Kind,
ModelInfoOutputDefsSpec,
StrictModel,
)


class NLPType(str, Enum):
Expand Down Expand Up @@ -48,7 +57,7 @@ class NLPRelationshipsReqSpec(StrictModel):
]


class NLPRequest(BaseInfReq):
class NLPAppPredInput(BaseAppPredInput):
kind: Literal[Kind.NLPModel]
spec: NLPReqSpec

Expand Down Expand Up @@ -80,33 +89,60 @@ class AnnotationLabels(StrictModel):
properties: List[PropertyLabel]


# TODO Annotate*Input pydantic models needed?
class AnnotateEntitiesEntry(StrictModel):
type: str
match: str
original: str
range: List[int]

AnnotateEntitiesOutput = List[dict] # TODO provide real implementation
AnnotateRelationshipsOutput = List[dict] # TODO provide real implementation
AnnotatePropertiesOutput = List[dict] # TODO provide real implementation

class AnnotateRelationshipsEntry(StrictModel):
header: list
data: list

class NLPEntitiesControllerOutput(StrictModel):

AnnotateEntitiesOutput = List[Dict[str, List[AnnotateEntitiesEntry]]]
AnnotateRelationshipsOutput = List[Dict[str, AnnotateRelationshipsEntry]]
AnnotatePropertiesOutput = List[Dict] # TODO specify


class NLPEntsCtrlPredOuput(StrictModel):
entities: AnnotateEntitiesOutput


class NLPRelationshipsControllerOutput(StrictModel):
class NLPRelsCtrlPredOutput(StrictModel):
relationships: AnnotateRelationshipsOutput


class NLPPropertiesControllerOutput(StrictModel):
class NLPPropsCtrlPredOutput(StrictModel):
properties: AnnotatePropertiesOutput


NLPControllerOutput = Union[
NLPEntitiesControllerOutput,
NLPRelationshipsControllerOutput,
NLPPropertiesControllerOutput,
NLPCtrlPredOutput = Union[
NLPEntsCtrlPredOuput,
NLPRelsCtrlPredOutput,
NLPPropsCtrlPredOutput,
]


class NLPConfig(BaseModelConfig):
kind: Literal[Kind.NLPModel]
supported_types: List[NLPType]
labels: AnnotationLabels


class NLPModelMetadata(BaseModelMetadata):
supported_object_types: List[Literal["text", "table", "image"]]


class NLPInfoOutputDefinitionsSpec(ModelInfoOutputDefsSpec):
metadata: NLPModelMetadata


class NLPInfoOutputDefinitions(CtrlInfoOutputDefs):
kind: Literal[Kind.NLPModel]
spec: NLPInfoOutputDefinitionsSpec


class NLPInfoOutput(CtrlInfoOutput):
definitions: NLPInfoOutputDefinitions
Loading

0 comments on commit ead7246

Please sign in to comment.