Skip to content

Commit 003a1f1

Browse files
authored
fix(framework): make classifier labels optional (#311)
Stop defaulting unlabeled classifier results to their id when building classification items. This keeps the Python SDK aligned with the JavaScript fix and preserves payloads where `label` is omitted. Add regression coverage for bare classifier ids in both the score helpers and Eval output. Aligns with braintrustdata/braintrust-sdk-javascript#1842
1 parent 8ff5e1d commit 003a1f1

3 files changed

Lines changed: 24 additions & 8 deletions

File tree

py/src/braintrust/score.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def __post_init__(self):
5555

5656
class ClassificationItem(TypedDict):
5757
id: str
58-
label: str
58+
label: NotRequired[str]
5959
metadata: NotRequired[Metadata]
6060

6161

@@ -70,7 +70,7 @@ class Classification(SerializableDataClass):
7070
"""The name of this classification result. Defaults to the classifier function name when omitted."""
7171

7272
label: str | None = None
73-
"""Optional human-readable label. Defaults to ``id`` when omitted."""
73+
"""Optional human-readable label."""
7474

7575
metadata: Metadata | None = None
7676
"""Optional metadata attached to the classification result."""
@@ -86,10 +86,9 @@ def as_dict(self):
8686
return result
8787

8888
def as_item(self) -> ClassificationItem:
89-
result: ClassificationItem = {
90-
"id": self.id,
91-
"label": self.label or self.id,
92-
}
89+
result: ClassificationItem = {"id": self.id}
90+
if self.label is not None:
91+
result["label"] = self.label
9392
if self.metadata is not None:
9493
result["metadata"] = self.metadata
9594
return result

py/src/braintrust/test_framework.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,23 @@ async def test_eval_classifier_only_populates_classifications():
498498
}
499499

500500

501+
@pytest.mark.asyncio
502+
async def test_eval_classifier_without_label_omits_label():
503+
def category(input_value, output, expected):
504+
return {"id": "greeting"}
505+
506+
result = await Eval(
507+
"test-classifier-without-label",
508+
data=[{"input": "hello"}],
509+
task=lambda input_value: input_value,
510+
classifiers=[category],
511+
no_send_logs=True,
512+
)
513+
514+
assert len(result.results) == 1
515+
assert result.results[0].classifications == {"category": [{"id": "greeting"}]}
516+
517+
501518
@pytest.mark.asyncio
502519
async def test_eval_multiple_classifications_append_same_name():
503520
def category(input_value, output, expected):

py/src/braintrust/test_score.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,9 @@ def test_as_dict_omits_unset_optional_fields(self):
156156
classification = Classification(id="greeting")
157157
self.assertEqual(classification.as_dict(), {"id": "greeting"})
158158

159-
def test_as_item_defaults_label_to_id(self):
159+
def test_as_item_omits_label_when_unset(self):
160160
classification = Classification(id="greeting")
161-
self.assertEqual(classification.as_item(), {"id": "greeting", "label": "greeting"})
161+
self.assertEqual(classification.as_item(), {"id": "greeting"})
162162

163163
def test_as_item_includes_metadata(self):
164164
classification = Classification(

0 commit comments

Comments
 (0)