From b4347aaddd42830c100eb7b99c1f7124ac79585a Mon Sep 17 00:00:00 2001 From: Sofia Eleftheriou Date: Mon, 29 Jun 2026 15:14:26 +0000 Subject: [PATCH 1/3] ALTO-5340: surface intensity regression score (batch + streaming); release 0.4.0 --- protos/api.proto | 2 ++ pyproject.toml | 2 +- src/behavioralsignals/generated/api_pb2.py | 24 +++++++++++----------- src/behavioralsignals/models.py | 5 +++++ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/protos/api.proto b/protos/api.proto index 0d45a89..0aade3d 100644 --- a/protos/api.proto +++ b/protos/api.proto @@ -68,6 +68,8 @@ message Prediction { optional string posterior = 2; // logit optional string logit = 3; + // regression score for continuous tasks (e.g. intensity), in (0,1) + optional string score = 4; } message InferenceResult { diff --git a/pyproject.toml b/pyproject.toml index 5ae86de..35636df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "behavioralsignals" -version = "0.3.0" +version = "0.4.0" description = "Python SDK for Behavioral Signals API" readme = "README.md" authors = [] diff --git a/src/behavioralsignals/generated/api_pb2.py b/src/behavioralsignals/generated/api_pb2.py index 36017dc..f0e9f87 100644 --- a/src/behavioralsignals/generated/api_pb2.py +++ b/src/behavioralsignals/generated/api_pb2.py @@ -24,27 +24,27 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tapi.proto\x12\x16\x62\x65havioral_api.grpc.v1\"\xf4\x01\n\x0b\x41udioConfig\x12\x37\n\x08\x65ncoding\x18\x01 \x01(\x0e\x32%.behavioral_api.grpc.v1.AudioEncoding\x12\x19\n\x11sample_rate_hertz\x18\x02 \x01(\x05\x12\x31\n\x05level\x18\x03 \x01(\x0e\x32\x1d.behavioral_api.grpc.v1.LevelH\x00\x88\x01\x01\x12\x13\n\x06logits\x18\x04 \x01(\x08H\x01\x88\x01\x01\x12\x1e\n\x11\x66\x65\x61ture_embedding\x18\x05 \x01(\x08H\x02\x88\x01\x01\x42\x08\n\x06_levelB\t\n\x07_logitsB\x14\n\x12_feature_embedding\"|\n\x0b\x41udioStream\x12\x0b\n\x03\x63id\x18\x01 \x01(\x03\x12\x14\n\x0cx_auth_token\x18\x02 \x01(\t\x12\x33\n\x06\x63onfig\x18\x03 \x01(\x0b\x32#.behavioral_api.grpc.v1.AudioConfig\x12\x15\n\raudio_content\x18\x04 \x01(\x0c\"_\n\nPrediction\x12\r\n\x05label\x18\x01 \x01(\t\x12\x16\n\tposterior\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05logit\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x0c\n\n_posteriorB\x08\n\x06_logit\"\x81\x02\n\x0fInferenceResult\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nstart_time\x18\x02 \x01(\t\x12\x10\n\x08\x65nd_time\x18\x03 \x01(\t\x12\x0c\n\x04task\x18\x04 \x01(\t\x12\x36\n\nprediction\x18\x05 \x03(\x0b\x32\".behavioral_api.grpc.v1.Prediction\x12\x13\n\x0b\x66inal_label\x18\x06 \x01(\t\x12\x16\n\tembedding\x18\x07 \x01(\tH\x00\x88\x01\x01\x12\x31\n\x05level\x18\x08 \x01(\x0e\x32\x1d.behavioral_api.grpc.v1.LevelH\x01\x88\x01\x01\x42\x0c\n\n_embeddingB\x08\n\x06_level\"u\n\x0cStreamResult\x12\x0b\n\x03\x63id\x18\x01 \x01(\x03\x12\x0b\n\x03pid\x18\x02 \x01(\x03\x12\x12\n\nmessage_id\x18\x03 \x01(\x05\x12\x37\n\x06result\x18\x04 \x03(\x0b\x32\'.behavioral_api.grpc.v1.InferenceResult*\x1f\n\rAudioEncoding\x12\x0e\n\nLINEAR_PCM\x10\x00*#\n\x05Level\x12\x0b\n\x07segment\x10\x00\x12\r\n\tutterance\x10\x01\x32\xde\x01\n\x16\x42\x65havioralStreamingApi\x12^\n\x0bStreamAudio\x12#.behavioral_api.grpc.v1.AudioStream\x1a$.behavioral_api.grpc.v1.StreamResult\"\x00(\x01\x30\x01\x12\x64\n\x11\x44\x65\x65pfakeDetection\x12#.behavioral_api.grpc.v1.AudioStream\x1a$.behavioral_api.grpc.v1.StreamResult\"\x00(\x01\x30\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tapi.proto\x12\x16\x62\x65havioral_api.grpc.v1\"\xf4\x01\n\x0b\x41udioConfig\x12\x37\n\x08\x65ncoding\x18\x01 \x01(\x0e\x32%.behavioral_api.grpc.v1.AudioEncoding\x12\x19\n\x11sample_rate_hertz\x18\x02 \x01(\x05\x12\x31\n\x05level\x18\x03 \x01(\x0e\x32\x1d.behavioral_api.grpc.v1.LevelH\x00\x88\x01\x01\x12\x13\n\x06logits\x18\x04 \x01(\x08H\x01\x88\x01\x01\x12\x1e\n\x11\x66\x65\x61ture_embedding\x18\x05 \x01(\x08H\x02\x88\x01\x01\x42\x08\n\x06_levelB\t\n\x07_logitsB\x14\n\x12_feature_embedding\"|\n\x0b\x41udioStream\x12\x0b\n\x03\x63id\x18\x01 \x01(\x03\x12\x14\n\x0cx_auth_token\x18\x02 \x01(\t\x12\x33\n\x06\x63onfig\x18\x03 \x01(\x0b\x32#.behavioral_api.grpc.v1.AudioConfig\x12\x15\n\raudio_content\x18\x04 \x01(\x0c\"}\n\nPrediction\x12\r\n\x05label\x18\x01 \x01(\t\x12\x16\n\tposterior\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05logit\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x12\n\x05score\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\x0c\n\n_posteriorB\x08\n\x06_logitB\x08\n\x06_score\"\x81\x02\n\x0fInferenceResult\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nstart_time\x18\x02 \x01(\t\x12\x10\n\x08\x65nd_time\x18\x03 \x01(\t\x12\x0c\n\x04task\x18\x04 \x01(\t\x12\x36\n\nprediction\x18\x05 \x03(\x0b\x32\".behavioral_api.grpc.v1.Prediction\x12\x13\n\x0b\x66inal_label\x18\x06 \x01(\t\x12\x16\n\tembedding\x18\x07 \x01(\tH\x00\x88\x01\x01\x12\x31\n\x05level\x18\x08 \x01(\x0e\x32\x1d.behavioral_api.grpc.v1.LevelH\x01\x88\x01\x01\x42\x0c\n\n_embeddingB\x08\n\x06_level\"u\n\x0cStreamResult\x12\x0b\n\x03\x63id\x18\x01 \x01(\x03\x12\x0b\n\x03pid\x18\x02 \x01(\x03\x12\x12\n\nmessage_id\x18\x03 \x01(\x05\x12\x37\n\x06result\x18\x04 \x03(\x0b\x32\'.behavioral_api.grpc.v1.InferenceResult*\x1f\n\rAudioEncoding\x12\x0e\n\nLINEAR_PCM\x10\x00*#\n\x05Level\x12\x0b\n\x07segment\x10\x00\x12\r\n\tutterance\x10\x01\x32\xde\x01\n\x16\x42\x65havioralStreamingApi\x12^\n\x0bStreamAudio\x12#.behavioral_api.grpc.v1.AudioStream\x1a$.behavioral_api.grpc.v1.StreamResult\"\x00(\x01\x30\x01\x12\x64\n\x11\x44\x65\x65pfakeDetection\x12#.behavioral_api.grpc.v1.AudioStream\x1a$.behavioral_api.grpc.v1.StreamResult\"\x00(\x01\x30\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'api_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_AUDIOENCODING']._serialized_start=886 - _globals['_AUDIOENCODING']._serialized_end=917 - _globals['_LEVEL']._serialized_start=919 - _globals['_LEVEL']._serialized_end=954 + _globals['_AUDIOENCODING']._serialized_start=916 + _globals['_AUDIOENCODING']._serialized_end=947 + _globals['_LEVEL']._serialized_start=949 + _globals['_LEVEL']._serialized_end=984 _globals['_AUDIOCONFIG']._serialized_start=38 _globals['_AUDIOCONFIG']._serialized_end=282 _globals['_AUDIOSTREAM']._serialized_start=284 _globals['_AUDIOSTREAM']._serialized_end=408 _globals['_PREDICTION']._serialized_start=410 - _globals['_PREDICTION']._serialized_end=505 - _globals['_INFERENCERESULT']._serialized_start=508 - _globals['_INFERENCERESULT']._serialized_end=765 - _globals['_STREAMRESULT']._serialized_start=767 - _globals['_STREAMRESULT']._serialized_end=884 - _globals['_BEHAVIORALSTREAMINGAPI']._serialized_start=957 - _globals['_BEHAVIORALSTREAMINGAPI']._serialized_end=1179 + _globals['_PREDICTION']._serialized_end=535 + _globals['_INFERENCERESULT']._serialized_start=538 + _globals['_INFERENCERESULT']._serialized_end=795 + _globals['_STREAMRESULT']._serialized_start=797 + _globals['_STREAMRESULT']._serialized_end=914 + _globals['_BEHAVIORALSTREAMINGAPI']._serialized_start=987 + _globals['_BEHAVIORALSTREAMINGAPI']._serialized_end=1209 # @@protoc_insertion_point(module_scope) diff --git a/src/behavioralsignals/models.py b/src/behavioralsignals/models.py index 723c54b..d5d7e91 100644 --- a/src/behavioralsignals/models.py +++ b/src/behavioralsignals/models.py @@ -193,6 +193,11 @@ class ModelPredictions(BaseModel): posterior: Optional[str] = Field( None, description="The probability of this class being present", example="0.754" ) + score: Optional[str] = Field( + None, + description="The regression score for continuous tasks (e.g. intensity), bounded in (0,1)", + example="0.62", + ) dominantInSegments: Optional[List[int]] = Field( None, description="The segments in which this class is dominant" ) From 313c32788abeb6dd87c682eadeb41e708095b54b Mon Sep 17 00:00:00 2001 From: Sofia Eleftheriou Date: Tue, 30 Jun 2026 13:51:13 +0000 Subject: [PATCH 2/3] omit null fields --- src/behavioralsignals/models.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/behavioralsignals/models.py b/src/behavioralsignals/models.py index d5d7e91..61de154 100644 --- a/src/behavioralsignals/models.py +++ b/src/behavioralsignals/models.py @@ -100,19 +100,21 @@ def validate_meta_json(cls, v): raise ValueError("meta must be valid JSON string") return v + class DeepfakeAudioUploadParams(AudioUploadParams): enable_generator_detection: bool = Field( - False, description="Whether to include prediction for the source of the deepfake (generator model)" + False, + description="Whether to include prediction for the source of the deepfake (generator model)", ) class DeepfakeS3UrlUploadParams(S3UrlUploadParams): enable_generator_detection: bool = Field( - False, description="Whether to include prediction for the source of the deepfake (generator model)" + False, + description="Whether to include prediction for the source of the deepfake (generator model)", ) - class ProcessItem(BaseModel): """Individual process in the list""" @@ -188,7 +190,19 @@ def failed_processes(self) -> List[ProcessItem]: return [p for p in self.processes if p.is_failed] -class ModelPredictions(BaseModel): +class _SerializableModel(BaseModel): + """Base for result models that omits null fields on serialization by default.""" + + def model_dump(self, **kwargs): + kwargs.setdefault("exclude_none", True) + return super().model_dump(**kwargs) + + def model_dump_json(self, **kwargs): + kwargs.setdefault("exclude_none", True) + return super().model_dump_json(**kwargs) + + +class ModelPredictions(_SerializableModel): label: Optional[str] = Field(None, description="The name of the class", example="happy") posterior: Optional[str] = Field( None, description="The probability of this class being present", example="0.754" @@ -203,7 +217,7 @@ class ModelPredictions(BaseModel): ) -class ResultItem(BaseModel): +class ResultItem(_SerializableModel): id: Optional[str] = Field(None, description="The id of the segment/utterance", example="1") startTime: Optional[str] = Field( None, description="The start time of the segment/utterance in seconds", example="0.209" @@ -243,7 +257,7 @@ def et(self) -> float: return float(self.endTime) -class ResultResponse(BaseModel): +class ResultResponse(_SerializableModel): pid: Optional[int] = Field(None, description="Unique ID for the processing job") cid: Optional[int] = Field(None, description="Client ID that requested the processing") code: Optional[int] = Field(None, description="Code indicating status") @@ -251,7 +265,7 @@ class ResultResponse(BaseModel): results: Optional[List[ResultItem]] = None -class StreamingResultResponse(BaseModel): +class StreamingResultResponse(_SerializableModel): pid: Optional[int] = Field(None, description="Unique ID for the processing job") cid: Optional[int] = Field(None, description="Client ID that requested the processing") message_id: Optional[int] = Field( From e0ef676ffd5d66c9e96708ea3ab18c8752c38029 Mon Sep 17 00:00:00 2001 From: Sofia Eleftheriou Date: Tue, 30 Jun 2026 14:33:19 +0000 Subject: [PATCH 3/3] update api_pb2 --- src/behavioralsignals/generated/api_pb2.pyi | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/behavioralsignals/generated/api_pb2.pyi b/src/behavioralsignals/generated/api_pb2.pyi index 579c0de..6e2b149 100644 --- a/src/behavioralsignals/generated/api_pb2.pyi +++ b/src/behavioralsignals/generated/api_pb2.pyi @@ -46,14 +46,16 @@ class AudioStream(_message.Message): def __init__(self, cid: _Optional[int] = ..., x_auth_token: _Optional[str] = ..., config: _Optional[_Union[AudioConfig, _Mapping]] = ..., audio_content: _Optional[bytes] = ...) -> None: ... class Prediction(_message.Message): - __slots__ = ("label", "posterior", "logit") + __slots__ = ("label", "posterior", "logit", "score") LABEL_FIELD_NUMBER: _ClassVar[int] POSTERIOR_FIELD_NUMBER: _ClassVar[int] LOGIT_FIELD_NUMBER: _ClassVar[int] + SCORE_FIELD_NUMBER: _ClassVar[int] label: str posterior: str logit: str - def __init__(self, label: _Optional[str] = ..., posterior: _Optional[str] = ..., logit: _Optional[str] = ...) -> None: ... + score: str + def __init__(self, label: _Optional[str] = ..., posterior: _Optional[str] = ..., logit: _Optional[str] = ..., score: _Optional[str] = ...) -> None: ... class InferenceResult(_message.Message): __slots__ = ("id", "start_time", "end_time", "task", "prediction", "final_label", "embedding", "level")