Skip to content

Feat: Add AnnotationLabel CRUD operations and name-based resolution for AnnotationQueue Methods#19

Merged
NVJKKartik merged 4 commits intodevfrom
fix/sdk-issues-in-annotation-queue
Mar 27, 2026
Merged

Feat: Add AnnotationLabel CRUD operations and name-based resolution for AnnotationQueue Methods#19
NVJKKartik merged 4 commits intodevfrom
fix/sdk-issues-in-annotation-queue

Conversation

@definitelynotchirag
Copy link
Copy Markdown
Contributor

Pull Request

Description

  • Add label CRUD methods (create_label, list_labels, get_label, delete_label) to AnnotationQueue client
  • Add name-based resolution for all methods — every method that accepts queue_id now also accepts queue_name, and every method that accepts label_id now also accepts
    label_name
  • Add AnnotationLabel re-export from fi.queues
  • Add routes for model-hub/annotations-labels/ and model-hub/annotations-labels/{label_id}/

Changes

fi/queues/client.py

  • Added _get_queue_id_from_name() and _get_label_id_from_name() resolvers (case-insensitive match, raises on not found or multiple matches)
  • Added create_label, list_labels, get_label, delete_label methods
  • Added _LabelResponseHandler and _LabelListResponseHandler
  • Updated all public methods to accept queue_name as alternative to queue_id
  • Updated label methods to accept label_name as alternative to label_id
  • Updated create_score to accept label_name

fi/queues/__init__.py

  • Re-exported AnnotationLabel from fi.annotations.types

fi/utils/routes.py

  • Added annotations_labels and annotations_labels_detail routes

Backward Compatibility

All existing ID-based calls work unchanged. Name params are optional keyword args.

# existing (still works)                                                                                                                                                       
client.get(queue_id="uuid-123")                                                                                                                                                  
client.add_label(queue_id="uuid-123", label_id="uuid-456")                                                                                                                       
                                                                                                                                                                                 
# new                                                                                                                                                                            
client.get(queue_name="Sentiment Review")                                                                                                                                        
client.add_label(queue_name="Sentiment Review", label_name="Quality")

Checklist

  • Code compiles correctly.
  • Created/updated tests.
  • Linting and formatting applied.
  • Documentation updated.

Related Issues

Closes #<issue_number>

@entelligence-ai-pr-reviews
Copy link
Copy Markdown

⚠️ Trial Period Expired ⚠️

Your trial period has expired. To continue using this feature, please upgrade to a paid plan here or book a time to chat here.

Comment thread python/fi/queues/__init__.py
Comment thread typescript/futureagi/package.json
Copy link
Copy Markdown
Contributor

@NVJKKartik NVJKKartik left a comment

Choose a reason for hiding this comment

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

  1. assign_items missing item_ids validation — item_ids was changed from a required positional parameter to Optional[List[str]] = None, but no validation guard was added. The sibling method remove_items (line
  1. has if not item_ids: raise ValueError(...), and the TypeScript assignItems also validates. A caller can now call assign_items(queue_id="q1") and the SDK will silently POST {"item_ids": null} to the API.

def assign_items(
self,
queue_id: Optional[str] = None,
item_ids: Optional[List[str]] = None,
*,
queue_name: Optional[str] = None,
user_id: Optional[str] = None,
timeout: Optional[int] = None,
) -> Dict[str, Any]:
"""Assign items to an annotator. Pass ``user_id=None`` to unassign."""
if not queue_id and not queue_name:
raise ValueError("Provide either queue_id or queue_name")
resolved_id = queue_id or self._get_queue_id_from_name(queue_name)
_validate_id(resolved_id, "queue_id")
url = f"{self._base_url}/{Routes.queue_items_assign.value}".format(queue_id=resolved_id)
body: Dict[str, Any] = {"item_ids": item_ids, "user_id": user_id}
config = RequestConfig(method=HttpMethod.POST, url=url, json=body, timeout=timeout)
return self.request(config, _DictResponseHandler)

Compare with remove_items which correctly validates:

def remove_items(
self,
queue_id: Optional[str] = None,
item_ids: Optional[List[str]] = None,
*,
queue_name: Optional[str] = None,
timeout: Optional[int] = None,
) -> Dict[str, Any]:
"""Bulk-remove items from the queue."""
if not item_ids:
raise ValueError("item_ids must be a non-empty list")
if not queue_id and not queue_name:
raise ValueError("Provide either queue_id or queue_name")
resolved_id = queue_id or self._get_queue_id_from_name(queue_name)
_validate_id(resolved_id, "queue_id")
url = f"{self._base_url}/{Routes.queue_items_bulk_remove.value}".format(queue_id=resolved_id)
config = RequestConfig(method=HttpMethod.POST, url=url, json={"item_ids": item_ids}, timeout=timeout)
return self.request(config, _DictResponseHandler)

@NVJKKartik
Copy link
Copy Markdown
Contributor

Code review

Found 1 issue:

  1. create_score changed value from a required positional parameter to value: Any = None but added no validation guard. A caller can now omit value entirely (e.g. create_score("trace", "id", label_id="lbl")) and the SDK will silently send "value": null to the API. The label_id/label_name pair correctly has a guard, but value was missed. The docstring still describes it as required, and the TypeScript createScore keeps value: ScoreValue as non-optional. The fix should use if value is None (not if not value) since 0, False, and "" are all valid score values.

def create_score(
self,
source_type: str,
source_id: str,
label_id: Optional[str] = None,
value: Any = None,
*,
label_name: Optional[str] = None,
score_source: str = "api",
notes: Optional[str] = None,
timeout: Optional[int] = None,
) -> Score:
"""Create a single score (upsert semantics).
Args:
source_type: trace, observation_span, trace_session, call_execution,
prototype_run, or dataset_row.
source_id: UUID of the source entity.
label_id: UUID of the annotation label (or use label_name).
value: Annotation value (str, float, bool, or list depending on label type).
label_name: Label name (alternative to label_id).
score_source: Origin — "human", "api", or "auto" (default: "api").
notes: Optional free-text notes.
"""
if not label_id and not label_name:
raise ValueError("Provide either label_id or label_name")
resolved_label_id = label_id or self._get_label_id_from_name(label_name)
body: Dict[str, Any] = {
"source_type": source_type,
"source_id": source_id,
"label_id": resolved_label_id,
"value": value,
"score_source": score_source,
}
if notes is not None:
body["notes"] = notes
config = RequestConfig(
method=HttpMethod.POST,
url=f"{self._base_url}/{Routes.scores.value}",
json=body,
timeout=timeout,
)
return self.request(config, _ScoreResponseHandler)
def create_scores(
self,
source_type: str,
source_id: str,
scores: List[Dict[str, Any]],
*,

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@NVJKKartik NVJKKartik self-requested a review March 27, 2026 11:00
@NVJKKartik NVJKKartik merged commit 05a3ec9 into dev Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants