Skip to content

[MODEL-1448] Upsert label feedback method #1684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/labelbox/src/labelbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from labelbox.schema.slice import Slice, CatalogSlice, ModelSlice
from labelbox.schema.queue_mode import QueueMode
from labelbox.schema.task_queue import TaskQueue
from labelbox.schema.label_score import LabelScore
from labelbox.schema.identifiables import UniqueIds, GlobalKeys, DataRowIds
from labelbox.schema.identifiable import UniqueId, GlobalKey
from labelbox.schema.ontology_kind import OntologyKind
Expand Down
24 changes: 24 additions & 0 deletions libs/labelbox/src/labelbox/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2216,3 +2216,27 @@ def get_embedding_by_name(self, name: str) -> Embedding:
return e
raise labelbox.exceptions.ResourceNotFoundError(Embedding,
dict(name=name))

def upsert_label_feedback(self, label_id: str, feedback: str,
scores: Dict[str, float]) -> Entity.Label:
Copy link
Collaborator

@mihhail-m mihhail-m Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type is Entity.Label, but actual type is List[Entity.Label]. Probably its better to match the actual returned type

"""

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra whitespace

Args:
label_id: Target label ID
feedback: Free text comment regarding the label
scores: A dict of scores, the key is a score name and the value is the score value
Returns: A list of LabelScore instances
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

white space between to keep style consistent with other methods


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra whitespace

"""
mutation_str = """mutation UpsertAutoQaLabelFeedbackPyApi($labelId: ID!, $feedback: String!, $scores: Json!){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to keep mutation string formatted to keep style consistency across the file

upsertAutoQaLabelFeedback(input: {labelId: $labelId, feedback: $feedback, scores: $scores}) { id scores {id name score} }
}
"""
res = self.execute(mutation_str, {
"labelId": label_id,
"feedback": feedback,
"scores": scores
})
scores_raw = res["upsertAutoQaLabelFeedback"]["scores"]

return [Entity.LabelScore(self, x) for x in scores_raw]
1 change: 1 addition & 0 deletions libs/labelbox/src/labelbox/orm/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ class Entity(metaclass=EntityMeta):
CatalogSlice: Type[labelbox.CatalogSlice]
ModelSlice: Type[labelbox.ModelSlice]
TaskQueue: Type[labelbox.TaskQueue]
LabelScore: Type[labelbox.LabelScore]

@classmethod
def _attributes_of_type(cls, attr_type):
Expand Down
19 changes: 19 additions & 0 deletions libs/labelbox/src/labelbox/schema/label_score.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from labelbox.orm.db_object import DbObject
from labelbox.orm.model import Field


class LabelScore(DbObject):
"""
a label score

Attributes
name
score

"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to preserve the same docs style format as with the rest of schema objects. Look Dataset, Batch for examples.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this class correctly, this is just a data class.
My personal opinion you should use a pydantic model instead of DbObject as a parent here. DbObject is our home-grown graphql client framework. Unless your class executes queries using this framework, no need to use it


name = Field.String("name")
data_row_count = Field.Float("score")

def __init__(self, client, *args, **kwargs):
super().__init__(client, *args, **kwargs)
28 changes: 21 additions & 7 deletions libs/labelbox/tests/integration/test_label.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import time

from labelbox import Client
import pytest
import requests
import os

from labelbox import Label
Expand Down Expand Up @@ -29,11 +29,13 @@ def test_labels(configured_project_with_label):


# TODO: Skipping this test in staging due to label not updating
@pytest.mark.skipif(condition=os.environ['LABELBOX_TEST_ENVIRON'] == "onprem" or
os.environ['LABELBOX_TEST_ENVIRON'] == "staging" or
os.environ['LABELBOX_TEST_ENVIRON'] == "local" or
os.environ['LABELBOX_TEST_ENVIRON'] == "custom",
reason="does not work for onprem")
@pytest.mark.skipif(
condition=os.environ["LABELBOX_TEST_ENVIRON"] == "onprem"
or os.environ["LABELBOX_TEST_ENVIRON"] == "staging"
or os.environ["LABELBOX_TEST_ENVIRON"] == "local"
or os.environ["LABELBOX_TEST_ENVIRON"] == "custom",
reason="does not work for onprem",
)
def test_label_update(configured_project_with_label):
_, _, _, label = configured_project_with_label
label.update(label="something else")
Expand All @@ -57,7 +59,7 @@ def test_label_bulk_deletion(configured_project_with_label):
project, _, _, _ = configured_project_with_label

for _ in range(2):
#only run twice, already have one label in the fixture
# only run twice, already have one label in the fixture
project.create_label()
labels = project.labels()
l1 = next(labels)
Expand All @@ -74,3 +76,15 @@ def test_label_bulk_deletion(configured_project_with_label):
time.sleep(5)

assert set(project.labels()) == {l2}


def test_upsert_label_scores(configured_project_with_label, client: Client):
project, _, _, _ = configured_project_with_label

label = next(project.labels())

scores = client.upsert_label_feedback(
label_id=label.uid, feedback="That's a great label!", scores={"overall": 5}
)
assert len(scores) == 1
assert scores[0].score == 5
Loading