Skip to content

Commit 7627658

Browse files
authored
[PLT-772] SDK Ontology, project, editor support for chat evaluation (#1594)
Merging with failing tests, this is an ADV issue under investigation
2 parents 70b1ac0 + 8344add commit 7627658

File tree

11 files changed

+378
-44
lines changed

11 files changed

+378
-44
lines changed

docs/labelbox/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Labelbox Python SDK Documentation
3131
model
3232
model-run
3333
ontology
34+
ontology_kind
3435
organization
3536
pagination
3637
project

docs/labelbox/ontology-kind.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
OntologyKind
2+
===============================================================================================
3+
4+
.. automodule:: labelbox.schema.ontology_kind
5+
:members:
6+
:exclude-members: EditorTaskType
7+
:show-inheritance:

libs/labelbox/src/labelbox/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@
3535
from labelbox.schema.queue_mode import QueueMode
3636
from labelbox.schema.task_queue import TaskQueue
3737
from labelbox.schema.identifiables import UniqueIds, GlobalKeys, DataRowIds
38-
from labelbox.schema.identifiable import UniqueId, GlobalKey
38+
from labelbox.schema.identifiable import UniqueId, GlobalKey
39+
from labelbox.schema.ontology_kind import OntologyKind

libs/labelbox/src/labelbox/client.py

Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from labelbox.adv_client import AdvClient
2222
from labelbox.orm import query
2323
from labelbox.orm.db_object import DbObject
24-
from labelbox.orm.model import Entity
24+
from labelbox.orm.model import Entity, Field
2525
from labelbox.pagination import PaginatedCollection
2626
from labelbox.schema import role
2727
from labelbox.schema.conflict_resolution_strategy import ConflictResolutionStrategy
@@ -52,6 +52,8 @@
5252
from labelbox.schema.slice import CatalogSlice, ModelSlice
5353
from labelbox.schema.task import Task
5454
from labelbox.schema.user import User
55+
from labelbox.schema.ontology_kind import (OntologyKind, EditorTaskTypeMapper,
56+
EditorTaskType)
5557

5658
logger = logging.getLogger(__name__)
5759

@@ -563,14 +565,16 @@ def get_labeling_frontends(self, where=None) -> List[LabelingFrontend]:
563565
"""
564566
return self._get_all(Entity.LabelingFrontend, where)
565567

566-
def _create(self, db_object_type, data):
568+
def _create(self, db_object_type, data, extra_params={}):
567569
""" Creates an object on the server. Attribute values are
568570
passed as keyword arguments:
569571
570572
Args:
571573
db_object_type (type): A DbObjectType subtype.
572574
data (dict): Keys are attributes or their names (in Python,
573575
snake-case convention) and values are desired attribute values.
576+
extra_params (dict): Additional parameters to pass to GraphQL.
577+
These have to be Field(...): value pairs.
574578
Returns:
575579
A new object of the given DB object type.
576580
Raises:
@@ -585,6 +589,7 @@ def _create(self, db_object_type, data):
585589
for attr, value in data.items()
586590
}
587591

592+
data = {**data, **extra_params}
588593
query_string, params = query.create(db_object_type, data)
589594
res = self.execute(query_string, params)
590595
res = res["create%s" % db_object_type.type_name()]
@@ -699,17 +704,26 @@ def create_project(self, **kwargs) -> Project:
699704
)
700705

701706
media_type = kwargs.get("media_type")
702-
if media_type:
703-
if MediaType.is_supported(media_type):
704-
media_type = media_type.value
705-
else:
706-
raise TypeError(f"{media_type} is not a valid media type. Use"
707-
f" any of {MediaType.get_supported_members()}"
708-
" from MediaType. Example: MediaType.Image.")
707+
if media_type and MediaType.is_supported(media_type):
708+
media_type_value = media_type.value
709+
elif media_type:
710+
raise TypeError(f"{media_type} is not a valid media type. Use"
711+
f" any of {MediaType.get_supported_members()}"
712+
" from MediaType. Example: MediaType.Image.")
709713
else:
710714
logger.warning(
711715
"Creating a project without specifying media_type"
712716
" through this method will soon no longer be supported.")
717+
media_type_value = None
718+
719+
ontology_kind = kwargs.pop("ontology_kind", None)
720+
if ontology_kind and OntologyKind.is_supported(ontology_kind):
721+
editor_task_type_value = EditorTaskTypeMapper.to_editor_task_type(
722+
ontology_kind, media_type).value
723+
elif ontology_kind:
724+
raise OntologyKind.get_ontology_kind_validation_error(ontology_kind)
725+
else:
726+
editor_task_type_value = None
713727

714728
quality_mode = kwargs.get("quality_mode")
715729
if not quality_mode:
@@ -728,12 +742,47 @@ def create_project(self, **kwargs) -> Project:
728742
else:
729743
raise ValueError(f"{quality_mode} is not a valid quality mode.")
730744

731-
return self._create(Entity.Project, {
732-
**data,
733-
**({
734-
"media_type": media_type
735-
} if media_type else {})
736-
})
745+
params = {**data}
746+
if media_type_value:
747+
params["media_type"] = media_type_value
748+
if editor_task_type_value:
749+
params["editor_task_type"] = editor_task_type_value
750+
751+
extra_params = {
752+
Field.String("dataset_name_or_id"):
753+
params.pop("dataset_name_or_id", None),
754+
Field.Boolean("append_to_existing_dataset"):
755+
params.pop("append_to_existing_dataset", None),
756+
Field.Int("data_row_count"):
757+
params.pop("data_row_count", None),
758+
}
759+
extra_params = {k: v for k, v in extra_params.items() if v is not None}
760+
761+
return self._create(Entity.Project, params, extra_params)
762+
763+
def create_model_evaluation_project(
764+
self,
765+
dataset_name_or_id: str,
766+
append_to_existing_dataset: bool = False,
767+
data_row_count: int = 100,
768+
**kwargs) -> Project:
769+
"""
770+
Use this method exclusively to create a chat model evaluation project.
771+
Args:
772+
dataset_name_or_id: The name or id of the dataset to use for the project
773+
append_to_existing_dataset: If True, the project will append assets (data rows) to the existing dataset
774+
data_row_count: The number of data row assets to use for the project
775+
**kwargs: Additional parameters to pass to the the create_project method
776+
Returns:
777+
Project: The created project
778+
"""
779+
kwargs["media_type"] = MediaType.Conversational
780+
kwargs["ontology_kind"] = OntologyKind.ModelEvaluation
781+
kwargs["dataset_name_or_id"] = dataset_name_or_id
782+
kwargs["append_to_existing_dataset"] = append_to_existing_dataset
783+
kwargs["data_row_count"] = data_row_count
784+
785+
return self.create_project(**kwargs)
737786

738787
def get_roles(self) -> List[Role]:
739788
"""
@@ -938,18 +987,22 @@ def rootSchemaPayloadToFeatureSchema(client, payload):
938987
rootSchemaPayloadToFeatureSchema,
939988
['rootSchemaNodes', 'nextCursor'])
940989

941-
def create_ontology_from_feature_schemas(self,
942-
name,
943-
feature_schema_ids,
944-
media_type=None) -> Ontology:
990+
def create_ontology_from_feature_schemas(
991+
self,
992+
name,
993+
feature_schema_ids,
994+
media_type: MediaType = None,
995+
ontology_kind: OntologyKind = None) -> Ontology:
945996
"""
946997
Creates an ontology from a list of feature schema ids
947998
948999
Args:
9491000
name (str): Name of the ontology
9501001
feature_schema_ids (List[str]): List of feature schema ids corresponding to
9511002
top level tools and classifications to include in the ontology
952-
media_type (MediaType or None): Media type of a new ontology
1003+
media_type (MediaType or None): Media type of a new ontology. NOTE for chat evaluation, we currently foce media_type to Conversational
1004+
ontology_kind (OntologyKind or None): set to OntologyKind.ModelEvaluation if the ontology is for chat evaluation,
1005+
leave as None otherwise.
9531006
Returns:
9541007
The created Ontology
9551008
"""
@@ -979,7 +1032,20 @@ def create_ontology_from_feature_schemas(self,
9791032
"Neither `tool` or `classification` found in the normalized feature schema"
9801033
)
9811034
normalized = {'tools': tools, 'classifications': classifications}
982-
return self.create_ontology(name, normalized, media_type)
1035+
1036+
if ontology_kind and ontology_kind is OntologyKind.ModelEvaluation:
1037+
if media_type is None:
1038+
media_type = MediaType.Conversational
1039+
else:
1040+
if media_type is not MediaType.Conversational:
1041+
raise ValueError(
1042+
"For chat evaluation, media_type must be Conversational."
1043+
)
1044+
1045+
return self.create_ontology(name=name,
1046+
normalized=normalized,
1047+
media_type=media_type,
1048+
ontology_kind=ontology_kind)
9831049

9841050
def delete_unused_feature_schema(self, feature_schema_id: str) -> None:
9851051
"""
@@ -1166,7 +1232,11 @@ def get_unused_feature_schemas(self, after: str = None) -> List[str]:
11661232
"Failed to get unused feature schemas, message: " +
11671233
str(response.json()['message']))
11681234

1169-
def create_ontology(self, name, normalized, media_type=None) -> Ontology:
1235+
def create_ontology(self,
1236+
name,
1237+
normalized,
1238+
media_type: MediaType = None,
1239+
ontology_kind: OntologyKind = None) -> Ontology:
11701240
"""
11711241
Creates an ontology from normalized data
11721242
>>> normalized = {"tools" : [{'tool': 'polygon', 'name': 'cat', 'color': 'black'}], "classifications" : []}
@@ -1184,26 +1254,43 @@ def create_ontology(self, name, normalized, media_type=None) -> Ontology:
11841254
name (str): Name of the ontology
11851255
normalized (dict): A normalized ontology payload. See above for details.
11861256
media_type (MediaType or None): Media type of a new ontology
1257+
ontology_kind (OntologyKind or None): set to OntologyKind.ModelEvaluation if the ontology is for chat evaluation,
1258+
leave as None otherwise.
1259+
11871260
Returns:
11881261
The created Ontology
1262+
1263+
NOTE caller of this method is expected to set media_type to Conversational if ontology_kind is ModelEvaluation
11891264
"""
11901265

1266+
media_type_value = None
11911267
if media_type:
11921268
if MediaType.is_supported(media_type):
1193-
media_type = media_type.value
1269+
media_type_value = media_type.value
11941270
else:
11951271
raise get_media_type_validation_error(media_type)
11961272

1273+
if ontology_kind and OntologyKind.is_supported(ontology_kind):
1274+
editor_task_type_value = EditorTaskTypeMapper.to_editor_task_type(
1275+
ontology_kind, media_type).value
1276+
elif ontology_kind:
1277+
raise OntologyKind.get_ontology_kind_validation_error(ontology_kind)
1278+
else:
1279+
editor_task_type_value = None
1280+
11971281
query_str = """mutation upsertRootSchemaNodePyApi($data: UpsertOntologyInput!){
11981282
upsertOntology(data: $data){ %s }
11991283
} """ % query.results_query_part(Entity.Ontology)
12001284
params = {
12011285
'data': {
12021286
'name': name,
12031287
'normalized': json.dumps(normalized),
1204-
'mediaType': media_type
1288+
'mediaType': media_type_value
12051289
}
12061290
}
1291+
if editor_task_type_value:
1292+
params['data']['editorTaskType'] = editor_task_type_value
1293+
12071294
res = self.execute(query_str, params)
12081295
return Entity.Ontology(self, res['upsertOntology'])
12091296

libs/labelbox/src/labelbox/schema/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424
import labelbox.schema.identifiables
2525
import labelbox.schema.identifiable
2626
import labelbox.schema.catalog
27+
import labelbox.schema.ontology_kind

libs/labelbox/src/labelbox/schema/ontology.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ class Type(Enum):
252252
LINE = "line"
253253
NER = "named-entity"
254254
RELATIONSHIP = "edge"
255+
MESSAGE_SINGLE_SELECTION = 'message-single-selection'
256+
MESSAGE_MULTI_SELECTION = 'message-multi-selection'
257+
MESSAGE_RANKING = 'message-ranking'
255258

256259
tool: Type
257260
name: str
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from enum import Enum
2+
from typing import Optional
3+
4+
from labelbox.schema.media_type import MediaType
5+
6+
7+
class OntologyKind(Enum):
8+
"""
9+
OntologyKind is an enum that represents the different types of ontologies
10+
At the moment it is only limited to ModelEvaluation
11+
"""
12+
ModelEvaluation = "MODEL_EVALUATION"
13+
Missing = None
14+
15+
@classmethod
16+
def is_supported(cls, value):
17+
return isinstance(value, cls)
18+
19+
@classmethod
20+
def get_ontology_kind_validation_error(cls, ontology_kind):
21+
return TypeError(f"{ontology_kind}: is not a valid ontology kind. Use"
22+
f" any of {OntologyKind.__members__.items()}"
23+
" from OntologyKind.")
24+
25+
26+
class EditorTaskType(Enum):
27+
ModelChatEvaluation = "MODEL_CHAT_EVALUATION"
28+
Missing = None
29+
30+
@classmethod
31+
def is_supported(cls, value):
32+
return isinstance(value, cls)
33+
34+
35+
class EditorTaskTypeMapper:
36+
37+
@staticmethod
38+
def to_editor_task_type(ontology_kind: OntologyKind,
39+
media_type: MediaType) -> EditorTaskType:
40+
if ontology_kind and OntologyKind.is_supported(
41+
ontology_kind) and media_type and MediaType.is_supported(
42+
media_type):
43+
editor_task_type = EditorTaskTypeMapper.map_to_editor_task_type(
44+
ontology_kind, media_type)
45+
else:
46+
editor_task_type = EditorTaskType.Missing
47+
48+
return editor_task_type
49+
50+
@staticmethod
51+
def map_to_editor_task_type(onotology_kind: OntologyKind,
52+
media_type: MediaType) -> EditorTaskType:
53+
if onotology_kind == OntologyKind.ModelEvaluation and media_type == MediaType.Conversational:
54+
return EditorTaskType.ModelChatEvaluation
55+
else:
56+
return EditorTaskType.Missing

0 commit comments

Comments
 (0)