Skip to content

Worfklow - Fix and updates following review #1987

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 7 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 15 additions & 5 deletions libs/labelbox/src/labelbox/schema/workflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,18 @@
from labelbox.schema.workflow.graph import ProjectWorkflowGraph

# Import from monolithic workflow.py file
from labelbox.schema.workflow.workflow import ProjectWorkflow, NodeType
from labelbox.schema.workflow.workflow import (
ProjectWorkflow,
NodeType,
InitialNodes,
)

# Import configuration classes
from labelbox.schema.workflow.config import LabelingConfig, ReworkConfig

# Import from monolithic project_filter.py file
from labelbox.schema.workflow.project_filter import (
ProjectWorkflowFilter,
created_by,
labeled_by,
annotation,
dataset,
Expand Down Expand Up @@ -94,13 +100,17 @@
"NodeType",
"ProjectWorkflowGraph",
"ProjectWorkflowFilter",
# Filter construction functions
"created_by",
# Workflow configuration
"InitialNodes",
"LabelingConfig",
"ReworkConfig",
# Filter field objects
"labeled_by",
"annotation",
"sample",
"dataset",
"issue_category",
# Filter construction functions
"sample",
"model_prediction",
"natural_language",
"labeled_at",
Expand Down
44 changes: 44 additions & 0 deletions libs/labelbox/src/labelbox/schema/workflow/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Configuration models for workflow initial nodes."""

from typing import Optional, Union, List
from pydantic import BaseModel, Field


class LabelingConfig(BaseModel):
"""Configuration for InitialLabeling node creation.

Attributes:
instructions: Task instructions for labelers
max_contributions_per_user: Maximum contributions per user (null means infinite)
"""

instructions: Optional[str] = Field(
default=None, description="Task instructions for labelers"
)
max_contributions_per_user: Optional[int] = Field(
default=None,
description="Maximum contributions per user (null means infinite)",
ge=0,
)


class ReworkConfig(BaseModel):
"""Configuration for InitialRework node creation.

Attributes:
instructions: Task instructions for rework
individual_assignment: User IDs for individual assignment
max_contributions_per_user: Maximum contributions per user (null means infinite)
"""

instructions: Optional[str] = Field(
default=None, description="Task instructions for rework"
)
individual_assignment: Optional[Union[str, List[str]]] = Field(
default=None, description="User IDs for individual assignment"
)
max_contributions_per_user: Optional[int] = Field(
default=None,
description="Maximum contributions per user (null means infinite)",
ge=0,
)
2 changes: 1 addition & 1 deletion libs/labelbox/src/labelbox/schema/workflow/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class FilterField(str, Enum):
"""

# User and creation filters
CreatedBy = "CreatedBy"
LabeledBy = "CreatedBy" # Maps to backend CreatedBy field

# Annotation and content filters
Annotation = "Annotation"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class AutoQANode(BaseWorkflowNode):
The evaluation results determine automatic routing without human intervention.
"""

label: str = Field(default="Label Score (AutoQA)")
label: str = Field(default="Label Score (AutoQA)", max_length=50)
filters: List[Dict[str, Any]] = Field(
default_factory=lambda: [],
description="Contains the filters for the AutoQA node",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class CustomReworkNode(BaseWorkflowNode):
rework processes and quality checks.
"""

label: str = Field(default="")
label: str = Field(default="", max_length=50)
node_config: List[ConfigEntry] = Field(
default_factory=lambda: [],
description="Contains assignment rules etc.",
Expand Down Expand Up @@ -117,6 +117,7 @@ class CustomReworkNode(BaseWorkflowNode):
default=None,
description="Maximum contributions per user (null means infinite)",
alias="maxContributionsPerUser",
ge=0,
)
# Has one input and one output
output_else: None = Field(default=None, frozen=True) # Only one output (if)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class DoneNode(BaseWorkflowNode):
and will not flow to any other nodes in the workflow.
"""

label: str = Field(default="Done")
label: str = Field(default="Done", max_length=50)
definition_id: WorkflowDefinitionId = Field(
default=WorkflowDefinitionId.Done, frozen=True, alias="definitionId"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ class InitialLabelingNode(BaseWorkflowNode):
and cannot have incoming connections from other nodes.
"""

label: str = Field(default="Initial labeling task", frozen=True)
label: str = Field(
default="Initial labeling task", frozen=True, max_length=50
)
filter_logic: Literal["and", "or"] = Field(
default=DEFAULT_FILTER_LOGIC_AND, alias="filterLogic"
)
Expand All @@ -84,6 +86,7 @@ class InitialLabelingNode(BaseWorkflowNode):
default=None,
description="Maximum contributions per user (null means infinite)",
alias="maxContributionsPerUser",
ge=0,
)
node_config: List[ConfigEntry] = Field(
default_factory=lambda: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ class InitialReworkNode(BaseWorkflowNode):
to ensure proper routing in the Labelbox platform.
"""

label: str = Field(default="Rework (all rejected)", frozen=True)
label: str = Field(
default="Rework (all rejected)", frozen=True, max_length=50
)
filter_logic: Literal["and", "or"] = Field(
default=DEFAULT_FILTER_LOGIC_AND, alias="filterLogic"
)
Expand Down Expand Up @@ -100,6 +102,7 @@ class InitialReworkNode(BaseWorkflowNode):
default=None,
description="Maximum contributions per user (null means infinite)",
alias="maxContributionsPerUser",
ge=0,
)

@field_validator("individual_assignment", mode="before")
Expand Down
24 changes: 13 additions & 11 deletions libs/labelbox/src/labelbox/schema/workflow/nodes/logic_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ class LogicNode(BaseWorkflowNode):
"""Logic node. One or more instances possible. One input, two outputs (if/else)."""

label: str = Field(
default="Logic", description="Display name for the logic node"
default="Logic",
description="Display name for the logic node",
max_length=50,
)
filters: List[Dict[str, Any]] = Field(
default_factory=lambda: [],
Expand Down Expand Up @@ -199,7 +201,7 @@ def remove_filter(self, filter_field: FilterField) -> "LogicNode":

Args:
filter_field: FilterField enum value specifying which filter type to remove
(e.g., FilterField.CreatedBy, FilterField.Sample, FilterField.LabelingTime)
(e.g., FilterField.LabeledBy, FilterField.Sample, FilterField.LabelingTime)

Returns:
LogicNode: Self for method chaining
Expand All @@ -209,7 +211,7 @@ def remove_filter(self, filter_field: FilterField) -> "LogicNode":
>>>
>>> # Type-safe enum approach (required)
>>> logic.remove_filter(FilterField.Sample)
>>> logic.remove_filter(FilterField.CreatedBy)
>>> logic.remove_filter(FilterField.LabeledBy) # Consistent with labeled_by() function
>>> logic.remove_filter(FilterField.LabelingTime)
>>> logic.remove_filter(FilterField.Metadata)
"""
Expand Down Expand Up @@ -371,7 +373,7 @@ def get_filters(self) -> "ProjectWorkflowFilter":
>>> logic = workflow.get_node_by_id("some-logic-node-id")
>>> user_filters = logic.get_filters()
>>> # Add a new filter
>>> user_filters.append(created_by(["new-user-id"]))
>>> user_filters.append(labeled_by(["new-user-id"]))
>>> # Apply the updated filters back to the node
>>> logic.set_filters(user_filters)
"""
Expand All @@ -393,25 +395,25 @@ def add_filter(self, filter_rule: Dict[str, Any]) -> "LogicNode":

Args:
filter_rule: Filter rule from filter functions
(e.g., created_by(["user_id"]), labeling_time.greater_than(300))
(e.g., labeled_by(["user_id"]), labeling_time.greater_than(300))

Returns:
LogicNode: Self for method chaining

Example:
>>> from labelbox.schema.workflow.project_filter import created_by, labeling_time, metadata, condition
>>> from labelbox.schema.workflow.project_filter import labeled_by, labeling_time, metadata, condition
>>>
>>> logic.add_filter(created_by(["user-123"]))
>>> logic.add_filter(labeled_by(["user-123"]))
>>> logic.add_filter(labeling_time.greater_than(300))
>>> logic.add_filter(metadata([condition.contains("tag", "test")]))
>>> # Adding another created_by filter will replace the previous one
>>> logic.add_filter(created_by(["user-456"])) # Replaces previous created_by filter
>>> # Adding another labeled_by filter will replace the previous one
>>> logic.add_filter(labeled_by(["user-456"])) # Replaces previous labeled_by filter
"""
# Validate that this looks like filter function output
if not self._is_filter_function_output(filter_rule):
raise ValueError(
"add_filter() only accepts output from filter functions. "
"Use functions like created_by(), labeling_time.greater_than(), etc."
"Use functions like labeled_by(), labeling_time.greater_than(), etc."
)

# Get the field name from the filter rule to check for existing filters
Expand Down Expand Up @@ -455,7 +457,7 @@ def _is_filter_function_output(self, filter_rule: Dict[str, Any]) -> bool:

# Map backend field names to FilterField enum values
backend_to_field = {
"CreatedBy": FilterField.CreatedBy,
"CreatedBy": FilterField.LabeledBy, # Backend CreatedBy maps to user-facing LabeledBy
"Annotation": FilterField.Annotation,
"LabeledAt": FilterField.LabeledAt,
"Sample": FilterField.Sample,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class ReviewNode(BaseWorkflowNode):
which default to "and" logic. This allows more flexible routing.
"""

label: str = Field(default="Review task")
label: str = Field(default="Review task", max_length=50)
# For ReviewNode, filter_logic defaults to "or"
filter_logic: Literal["and", "or"] = Field(
default=DEFAULT_FILTER_LOGIC_OR, alias="filterLogic"
Expand All @@ -96,6 +96,7 @@ class ReviewNode(BaseWorkflowNode):
default=None,
description="Maximum contributions per user (null means infinite)",
alias="maxContributionsPerUser",
ge=0,
)
node_config: List[Dict[str, Any]] = Field(
default_factory=lambda: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class ReworkNode(BaseWorkflowNode):
workflow's initial rework entry point without manual routing.
"""

label: str = Field(default="Rework")
label: str = Field(default="Rework", max_length=50)
filter_logic: Literal["and", "or"] = Field(
default=DEFAULT_FILTER_LOGIC_AND, alias="filterLogic"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class UnknownWorkflowNode(BaseWorkflowNode):
as a safety mechanism to prevent data loss during parsing.
"""

label: str = Field(default="")
label: str = Field(default="", max_length=50)
node_config: Optional[List[Dict[str, Any]]] = Field(
default=None, alias="config"
)
Expand Down
Loading
Loading