Skip to content

feat(evaluator): add Taskset entity for grouping tasks (AALGO-307) - #561

Merged
SandyChapman merged 1 commit into
mainfrom
aalgo-307-taskset-entity/schapman
Jul 7, 2026
Merged

feat(evaluator): add Taskset entity for grouping tasks (AALGO-307)#561
SandyChapman merged 1 commit into
mainfrom
aalgo-307-taskset-entity/schapman

Conversation

@SandyChapman

@SandyChapman SandyChapman commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Taskset half of the AALGO-307 entity work: a flexible grouping of stored tasks with metadata, mirroring the merged Task entity stack (#527) end-to-end.

  • TasksetEntity (entity_type="taskset") + Taskset / TasksetInput DTOs
  • TasksetService (create/get/list/delete)
  • /tasksets CRUD routes with decorator authz via TasksetPerms
  • client.evaluator.tasksets SDK resource (sync + async)
  • mounted in service.py and sdk/resources.py

Design

  • Members by reference. A taskset references its tasks by workspace/name (no inline tasks). Metadata reuses MetadataItem / TaskMetadataList; the grouping carries an optional description.
  • Set semantics. Order is not significant and duplicate members are rejected — both byte-identically (field validator) and by resolved (workspace, name) in the service, so task-a and default/task-a can't both slip in.
  • Existence validated at create. Each referenced task must resolve to a stored task; missing/duplicate refs surface as typed errors (TaskRefNotFoundError / DuplicateTaskRefError) mapped to 422, name collisions to 409.
  • Factors the shared workspace/name ref parser into schemas.parse_entity_ref, now reused by metric_refs.parse_metric_ref.

Not covered (follow-ups)

  • Validate-then-create is not atomic (a referenced task could be deleted between the existence check and insert). Inherent to the "validate at create" design and consistent with metric-ref handling; left as-is.
  • task_refs on the agent-eval submit path (run an eval over a stored taskset) — separate follow-up.

Testing

  • New: entity round-trip, service, routes, SDK resource tests (incl. resolved-dedup + missing-ref 422).
  • Full evaluator unit suite: 469 pass.
  • ruff check / format, CI lint-python-types.sh (exit 0), lint-all.sh all pass.
  • make refresh-openapi: only plugins/nemo-evaluator/openapi/openapi.yaml changed.

Summary by CodeRabbit

  • New Features
    • Added evaluator tasksets API (create, list, retrieve, delete) under workspace-scoped routes, including paginated filtering/sorting.
    • Added tasksets support to the evaluator SDK for both sync and async clients.
    • Added taskset DTO validation (task reference handling and duplicate-member protection).
  • Bug Fixes
    • Improved log safety by reusing a shared sanitizer across evaluator logging for tasks, metrics, and results.
  • Tests
    • Added comprehensive taskset service, route, SDK, and entity serialization coverage.

@SandyChapman
SandyChapman requested review from a team as code owners July 3, 2026 12:50
@github-actions github-actions Bot added the feat label Jul 3, 2026
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 23227/30391 76.4% 61.2%
Integration Tests 13573/29071 46.7% 19.9%

Comment thread plugins/nemo-evaluator/src/nemo_evaluator/api/schemas.py Outdated
Comment thread plugins/nemo-evaluator/src/nemo_evaluator/api/schemas.py Outdated
Comment thread plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasksets.py Outdated
Comment thread plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasksets.py Outdated
Comment thread plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasksets.py
@SandyChapman
SandyChapman force-pushed the aalgo-307-taskset-entity/schapman branch from 150a615 to 3b97f97 Compare July 6, 2026 15:40
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e13d3882-fbba-4f06-9ecd-d4f7cc95f5c2

📥 Commits

Reviewing files that changed from the base of the PR and between 3b97f97 and 244922c.

📒 Files selected for processing (20)
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/log_utils.py
  • plugins/nemo-evaluator/openapi/openapi.yaml
  • plugins/nemo-evaluator/src/nemo_evaluator/api/dependencies.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/schemas.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/metric_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/task_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/taskset_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/metrics.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/results.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasks.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasksets.py
  • plugins/nemo-evaluator/src/nemo_evaluator/entities.py
  • plugins/nemo-evaluator/src/nemo_evaluator/metric_refs.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/taskset_resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/service.py
  • plugins/nemo-evaluator/tests/api/service/test_taskset_service.py
  • plugins/nemo-evaluator/tests/api/v2/test_tasksets_routes.py
  • plugins/nemo-evaluator/tests/sdk/test_taskset_sdk_resources.py
  • plugins/nemo-evaluator/tests/test_taskset_entity.py
🚧 Files skipped from review as they are similar to previous changes (19)
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/log_utils.py
  • plugins/nemo-evaluator/src/nemo_evaluator/service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/dependencies.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/metric_service.py
  • plugins/nemo-evaluator/tests/sdk/test_taskset_sdk_resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/task_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasks.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/taskset_resources.py
  • plugins/nemo-evaluator/tests/api/service/test_taskset_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/schemas.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/metrics.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/results.py
  • plugins/nemo-evaluator/src/nemo_evaluator/entities.py
  • plugins/nemo-evaluator/openapi/openapi.yaml
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/taskset_service.py
  • plugins/nemo-evaluator/tests/api/v2/test_tasksets_routes.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasksets.py
  • plugins/nemo-evaluator/src/nemo_evaluator/metric_refs.py

📝 Walkthrough

Walkthrough

Adds taskset CRUD across schemas, persistence, service, REST routes, SDK, OpenAPI, and tests. Also extracts a shared sanitize_for_log helper and switches metric, task, and results logging to it.

Changes

Taskset CRUD feature

Layer / File(s) Summary
Shared refs and taskset models
plugins/nemo-evaluator/src/nemo_evaluator/api/schemas.py, plugins/nemo-evaluator/src/nemo_evaluator/metric_refs.py
Adds shared entity-ref parsing, TaskRef, duplicate-ref rejection, and taskset DTO/query schemas; parse_metric_ref delegates to the shared parser.
Taskset entity and service logic
plugins/nemo-evaluator/src/nemo_evaluator/entities.py, plugins/nemo-evaluator/src/nemo_evaluator/api/service/taskset_service.py, plugins/nemo-evaluator/src/nemo_evaluator/api/dependencies.py, plugins/nemo-evaluator/tests/api/service/test_taskset_service.py, plugins/nemo-evaluator/tests/test_taskset_entity.py
Adds the persisted taskset entity, taskset CRUD service, dependency provider, and service/entity tests covering validation, lookup, listing, deletion, and round-trips.
Taskset routes and SDK clients
plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasksets.py, plugins/nemo-evaluator/src/nemo_evaluator/service.py, plugins/nemo-evaluator/src/nemo_evaluator/sdk/taskset_resources.py, plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py, plugins/nemo-evaluator/tests/api/v2/test_tasksets_routes.py, plugins/nemo-evaluator/tests/sdk/test_taskset_sdk_resources.py
Adds taskset REST endpoints, mounts them in the plugin router, exposes sync/async SDK resources, and covers route/SDK behavior with tests.
OpenAPI taskset specification
plugins/nemo-evaluator/openapi/openapi.yaml
Adds taskset endpoints plus TaskRef and taskset component schemas.

Shared log sanitization

Layer / File(s) Summary
Shared sanitizer adoption
packages/nemo_platform_plugin/src/nemo_platform_plugin/log_utils.py, plugins/nemo-evaluator/src/nemo_evaluator/api/service/metric_service.py, plugins/nemo-evaluator/src/nemo_evaluator/api/service/task_service.py, plugins/nemo-evaluator/src/nemo_evaluator/api/v2/metrics.py, plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasks.py, plugins/nemo-evaluator/src/nemo_evaluator/api/v2/results.py
Adds sanitize_for_log(value) and replaces local per-module sanitizers in metric, task, and results logging code.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant TasksetsRouter
  participant TasksetService
  participant TaskService
  participant EntityClient
  Client->>TasksetsRouter: POST /tasksets/{name}
  TasksetsRouter->>TasksetService: create_taskset(name, input, workspace)
  TasksetService->>TaskService: get_task(workspace, name) per TaskRef
  TaskService-->>TasksetService: exists / None
  TasksetService->>EntityClient: create(TasksetEntity)
  EntityClient-->>TasksetService: entity or conflict
  TasksetService-->>TasksetsRouter: Taskset DTO
  TasksetsRouter-->>Client: 201 / 409 / 422
Loading

Possibly related PRs

Suggested reviewers: ngoncharenko, arpitsardhana

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding Taskset support for grouping tasks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aalgo-307-taskset-entity/schapman

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/nemo_platform_plugin/src/nemo_platform_plugin/log_utils.py (1)

9-11: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Only strips \r/\n; misses other log-injection vectors.

ANSI escape sequences and unicode line separators (\u2028/\u2029) can still forge log entries or manipulate terminal output. Since this is now the single shared sanitizer for all callers, hardening here benefits every site at once.

🛡️ Broader sanitization
+import re
+
+_CONTROL_CHARS = re.compile(r"[\r\n\x00-\x1f\x7f\u2028\u2029]")
+
+
 def sanitize_for_log(value: object) -> str:
     """Strip line-break/control characters from a value before logging (prevents log injection)."""
-    return str(value).replace("\r", "").replace("\n", "")
+    return _CONTROL_CHARS.sub("", str(value))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nemo_platform_plugin/src/nemo_platform_plugin/log_utils.py` around
lines 9 - 11, The shared sanitize_for_log helper only removes carriage returns
and newlines, so it still allows ANSI escape sequences and unicode line
separators that can be used for log injection. Harden sanitize_for_log in
log_utils.py to also strip or neutralize ANSI control/escape sequences and
unicode separators such as U+2028 and U+2029, while preserving the existing
caller-facing string conversion behavior. Keep the fix localized to
sanitize_for_log so every caller gets the stronger protection automatically.
plugins/nemo-evaluator/src/nemo_evaluator/api/service/taskset_service.py (1)

93-111: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential per-ref existence checks (N+1).

Each task ref triggers a separate awaited get_task call inside the loop. For tasksets with many members this serializes N round-trips. Resolve/dedupe first, then batch the existence checks concurrently.

⚡ Proposed fix using asyncio.gather
+import asyncio
+
     async def _validate_tasks_exist(self, tasks: list[TaskRef], *, workspace: str) -> None:
         seen: set[tuple[str, str]] = set()
+        resolved_refs: list[tuple[str, tuple[str, str]]] = []
         for ref in tasks:
             resolved = parse_entity_ref(ref.root, workspace)
             if resolved in seen:
                 raise DuplicateTaskRefError(
                     f"Task reference '{ref.root}' resolves to '{resolved[0]}/{resolved[1]}', already in this taskset"
                 )
             seen.add(resolved)
-            if await self.task_service.get_task(*resolved) is None:
-                raise TaskRefNotFoundError(f"Task reference '{ref.root}' not found in workspace '{resolved[0]}'")
+            resolved_refs.append((ref.root, resolved))
+        results = await asyncio.gather(*(self.task_service.get_task(*r) for _, r in resolved_refs))
+        for (ref_root, resolved), task in zip(resolved_refs, results):
+            if task is None:
+                raise TaskRefNotFoundError(f"Task reference '{ref_root}' not found in workspace '{resolved[0]}'")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-evaluator/src/nemo_evaluator/api/service/taskset_service.py`
around lines 93 - 111, The _validate_tasks_exist method in TasksetService is
doing one awaited get_task lookup per TaskRef inside the loop, which creates a
serial N+1 pattern. First resolve each ref with parse_entity_ref and keep the
deduped resolved tuples, then check existence for all unique tasks concurrently
with asyncio.gather against task_service.get_task. Preserve the current
DuplicateTaskRefError and TaskRefNotFoundError behavior by mapping results back
to the original refs when reporting missing tasks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/nemo_platform_plugin/src/nemo_platform_plugin/log_utils.py`:
- Around line 9-11: The shared sanitize_for_log helper only removes carriage
returns and newlines, so it still allows ANSI escape sequences and unicode line
separators that can be used for log injection. Harden sanitize_for_log in
log_utils.py to also strip or neutralize ANSI control/escape sequences and
unicode separators such as U+2028 and U+2029, while preserving the existing
caller-facing string conversion behavior. Keep the fix localized to
sanitize_for_log so every caller gets the stronger protection automatically.

In `@plugins/nemo-evaluator/src/nemo_evaluator/api/service/taskset_service.py`:
- Around line 93-111: The _validate_tasks_exist method in TasksetService is
doing one awaited get_task lookup per TaskRef inside the loop, which creates a
serial N+1 pattern. First resolve each ref with parse_entity_ref and keep the
deduped resolved tuples, then check existence for all unique tasks concurrently
with asyncio.gather against task_service.get_task. Preserve the current
DuplicateTaskRefError and TaskRefNotFoundError behavior by mapping results back
to the original refs when reporting missing tasks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1048ca83-aee3-4aa6-96d4-2127230d0078

📥 Commits

Reviewing files that changed from the base of the PR and between 4b9b24e and 3b97f97.

📒 Files selected for processing (20)
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/log_utils.py
  • plugins/nemo-evaluator/openapi/openapi.yaml
  • plugins/nemo-evaluator/src/nemo_evaluator/api/dependencies.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/schemas.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/metric_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/task_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/service/taskset_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/metrics.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/results.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasks.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/tasksets.py
  • plugins/nemo-evaluator/src/nemo_evaluator/entities.py
  • plugins/nemo-evaluator/src/nemo_evaluator/metric_refs.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/taskset_resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/service.py
  • plugins/nemo-evaluator/tests/api/service/test_taskset_service.py
  • plugins/nemo-evaluator/tests/api/v2/test_tasksets_routes.py
  • plugins/nemo-evaluator/tests/sdk/test_taskset_sdk_resources.py
  • plugins/nemo-evaluator/tests/test_taskset_entity.py

Adds the Taskset half of the AALGO-307 entity work: a flexible grouping
of stored tasks with metadata, mirroring the Task entity stack end-to-end.

- TasksetEntity (entity_type "taskset") + Taskset/TasksetInput DTOs
- TasksetService with create/get/list/delete
- /tasksets CRUD routes with decorator authz via TasksetPerms
- client.evaluator.tasksets SDK resource (sync + async)
- mounted in service.py and sdk/resources.py

Members are referenced by workspace/name (no inline tasks) with set
semantics: order is not significant and duplicate references are rejected
both byte-identically (field validator) and by resolved (workspace, name)
in the service. Referenced tasks are validated to exist at create time.
Missing/duplicate refs surface as typed errors mapped to 422.

Factors the shared workspace/name ref parser into schemas.parse_entity_ref,
reused by metric_refs.parse_metric_ref.

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman
SandyChapman added this pull request to the merge queue Jul 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 7, 2026
@SandyChapman
SandyChapman added this pull request to the merge queue Jul 7, 2026
Merged via the queue into main with commit 016ba5d Jul 7, 2026
55 checks passed
@SandyChapman
SandyChapman deleted the aalgo-307-taskset-entity/schapman branch July 7, 2026 15:29
arpitsardhana pushed a commit that referenced this pull request Jul 9, 2026
)

Adds the Taskset half of the AALGO-307 entity work: a flexible grouping
of stored tasks with metadata, mirroring the Task entity stack end-to-end.

- TasksetEntity (entity_type "taskset") + Taskset/TasksetInput DTOs
- TasksetService with create/get/list/delete
- /tasksets CRUD routes with decorator authz via TasksetPerms
- client.evaluator.tasksets SDK resource (sync + async)
- mounted in service.py and sdk/resources.py

Members are referenced by workspace/name (no inline tasks) with set
semantics: order is not significant and duplicate references are rejected
both byte-identically (field validator) and by resolved (workspace, name)
in the service. Referenced tasks are validated to exist at create time.
Missing/duplicate refs surface as typed errors mapped to 422.

Factors the shared workspace/name ref parser into schemas.parse_entity_ref,
reused by metric_refs.parse_metric_ref.

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants