Skip to content

feat(experiments): Experiment group 'ranked fields' (default sort + basis for rank)#512

Merged
shanaiabuggy merged 4 commits into
mainfrom
sbuggy/ase-322
Jun 30, 2026
Merged

feat(experiments): Experiment group 'ranked fields' (default sort + basis for rank)#512
shanaiabuggy merged 4 commits into
mainfrom
sbuggy/ase-322

Conversation

@shanaiabuggy

@shanaiabuggy shanaiabuggy commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Problem. No way to set a group's ranking, so users can't see the "winner" of a group at a glance.

Approach. Add a ranking: [{ field, direction }]| null config (ordered list, optional) on ExperimentGroup, settable at create/update. field is restricted to sortable (numeric) metrics — v1: cost_usd and latency_ms; evaluator-score fields follow once ASE-320 lands. The list is ordered by the fields in priority order (first = primary, rest = tiebreakers). This one config does double duty:

The experiments-list endpoint applies it as the default sort when no explicit sort is passed (explicit sort
still overrides). Pinned experiments float to top, then ranked-fields order. If no ranked field has resolved data, ordering falls back to -created_at.

It's the basis for the computed rank (see the rank ticket).

Acceptance. Setting a group's ranked fields changes the default order of its experiments list; an explicit sort
query overrides it; pinned-first is preserved; only numeric metrics / evaluator names are accepted; an expected evaluator with no data yet leaves those experiments unranked on that field.

Summary by CodeRabbit

  • New Features

    • Experiment groups can now configure an ordered default_sort for experiments.
    • When experiments are listed without an explicit sort, results use the experiment group’s default sort (pinned experiments first, then metric criteria, with created_at fallback).
    • Metric-based sorting documentation now includes the p99 aggregate-stat variant.
  • Bug Fixes

    • Improved behavior around missing rollup data: only requests that truly depend on rollups now fail, while default metric-based sorting degrades gracefully to created_at.
  • Tests

    • Added coverage for default-sort ordering, validation, pinned-first behavior, and fallback/rollup failure semantics.
  • Documentation

    • Updated OpenAPI contracts for the new default_sort and SortCriterion schema, and clarified sort parameter behavior.

@shanaiabuggy
shanaiabuggy requested review from a team as code owners June 29, 2026 22:39
@github-actions github-actions Bot added the feat label Jun 29, 2026
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 22154/29335 75.5% 60.3%
Integration Tests 12774/28015 45.6% 19.0%

@coderabbitai

coderabbitai Bot commented Jun 29, 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: a7805591-eeb6-4af1-a93d-ccf140173ee9

📥 Commits

Reviewing files that changed from the base of the PR and between 5459148 and 253d77e.

📒 Files selected for processing (1)
  • services/intake/tests/test_experiment_default_sort.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/intake/tests/test_experiment_default_sort.py

📝 Walkthrough

Walkthrough

Adds default_sort to experiment groups via a new SortCriterion schema. list_experiments now uses group-configured default ordering when sort is omitted, with pinned-first behavior and created_at fallback. OpenAPI specs and tests were updated.

Changes

Experiment Group Default Sort

Layer / File(s) Summary
SortCriterion entity and API schemas
services/intake/src/nmp/intake/entities/experiments.py, services/intake/src/nmp/intake/api/v2/experiments/schemas.py
Adds SortCriterion and default_sort on ExperimentGroup, ExperimentGroupRequest, and ExperimentGroupResponse.
OpenAPI contract updates
openapi/ga/individual/platform.openapi.yaml, openapi/ga/openapi.yaml, openapi/openapi.yaml
Updates sort docs, adds default_sort fields, and adds the SortCriterion component.
Endpoint wiring and multi-key sort
services/intake/src/nmp/intake/api/v2/experiments/endpoints.py
Persists default_sort, resolves fallback keys, narrows 503 behavior, and replaces single-key sorting with multi-key pinned-first sorting.
Default sort and validation tests
services/intake/tests/test_experiment_default_sort.py, services/intake/tests/test_experiment_sort.py
Adds coverage for sorting, validation, CRUD wiring, and rollup-unavailable fallback; updates sort tests to the new keys API.

Sequence Diagram(s)

sequenceDiagram
  participant list_experiments
  participant _default_sort_keys
  participant _sort_experiments
  list_experiments->>_default_sort_keys: resolve default_sort fallback when sort is omitted
  list_experiments->>_sort_experiments: sort rows with keys and pinned_first
  _sort_experiments-->>list_experiments: ordered experiments
Loading

Possibly related PRs

Suggested reviewers

  • BrianNewsom
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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: experiment-group ranked/default sorting plus rank-related groundwork.
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 sbuggy/ase-322

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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openapi/openapi.yaml (1)

3711-3728: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

sort is now nullable at runtime, but the response schema still says string.

When this parameter is omitted, the handler returns Page(sort=sort) with sort=None. ExperimentResponsesPage.sort still only allows type: string, so the documented default-ranking path produces a response that strict OpenAPI clients will reject.

🤖 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 `@openapi/openapi.yaml` around lines 3711 - 3728, The OpenAPI definition for
the sort query parameter and the resulting ExperimentResponsesPage.sort field
are inconsistent with runtime behavior because omitted sort now yields null.
Update the schema in openapi/openapi.yaml so the sort response/property accepts
null in addition to string, and make sure the documented default-ranking path
matches the actual Page(sort=sort) behavior in the handler.
🤖 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.

Inline comments:
In `@openapi/ga/individual/platform.openapi.yaml`:
- Around line 10375-10383: The ExperimentGroupRequest.ranking and
ExperimentGroupResponse.ranking schemas are currently array-only, but the
backend models allow null via list[RankingField] | None. Update the Ranking
property in the openapi schema for both ExperimentGroupRequest and
ExperimentGroupResponse to explicitly permit null so requests and responses can
use ranking: null without violating the contract.

In `@openapi/ga/openapi.yaml`:
- Around line 10375-10383: The GA OpenAPI contract for the Ranking schema
currently only allows an array, but the backend model and intake schema allow
ranking to be null, so generated clients will reject valid responses. Update the
Ranking definition in the openapi spec so the ranking property is nullable in
the components schema used by the experiments group, and make the same nullable
change anywhere the duplicate RankingField array definition appears. Keep the
existing ranking array structure and description, but ensure the schema
explicitly permits null for groups without ranking.

In `@openapi/openapi.yaml`:
- Around line 10375-10383: The Ranking schema for the group model is currently
array-only, but the backend uses RankingField list-or-none, so update the
ranking field in the referenced schema definition and the related duplicate
schema block to allow null in both request and response. Keep the existing array
item reference to RankingField, and make sure the nullable setting is applied
consistently wherever ranking is defined so updates can clear it and responses
can represent groups without a configured ranking.

In `@services/intake/tests/test_experiment_ranking.py`:
- Around line 112-123: The test in test_default_order_floats_pinned_first is not
strong enough because exp-b is both pinned and newer, so a newest-first fallback
would still pass. Change the setup in test_default_order_floats_pinned_first to
pin the older experiment (or create the pinned one first) and assert the full
returned order from the EXPERIMENTS listing so the default ordering path in the
experiment ranking logic is actually verified.

---

Outside diff comments:
In `@openapi/openapi.yaml`:
- Around line 3711-3728: The OpenAPI definition for the sort query parameter and
the resulting ExperimentResponsesPage.sort field are inconsistent with runtime
behavior because omitted sort now yields null. Update the schema in
openapi/openapi.yaml so the sort response/property accepts null in addition to
string, and make sure the documented default-ranking path matches the actual
Page(sort=sort) behavior in the handler.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a27da37b-9d95-40aa-8619-9e7099665036

📥 Commits

Reviewing files that changed from the base of the PR and between 40df17d and 308a751.

⛔ Files ignored due to path filters (14)
  • sdk/python/nemo-platform/.nmpcontext/openapi.yaml is excluded by !sdk/**
  • sdk/python/nemo-platform/.nmpcontext/stainless.yaml is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/resources/experiment_groups/api.md is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/resources/experiment_groups/experiment_groups.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/resources/experiments/experiments.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/__init__.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/experiment_group_create_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/experiment_group_response.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/experiment_group_update_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/ranking_field.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/ranking_field_param.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/experiment_list_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/tests/api_resources/test_experiment_groups.py is excluded by !sdk/**
  • sdk/stainless.yaml is excluded by !sdk/**
📒 Files selected for processing (8)
  • openapi/ga/individual/platform.openapi.yaml
  • openapi/ga/openapi.yaml
  • openapi/openapi.yaml
  • services/intake/src/nmp/intake/api/v2/experiments/endpoints.py
  • services/intake/src/nmp/intake/api/v2/experiments/schemas.py
  • services/intake/src/nmp/intake/entities/experiments.py
  • services/intake/tests/test_experiment_ranking.py
  • services/intake/tests/test_experiment_sort.py

Comment thread openapi/ga/individual/platform.openapi.yaml Outdated
Comment thread openapi/ga/openapi.yaml Outdated
Comment thread openapi/openapi.yaml Outdated
Comment thread services/intake/tests/test_experiment_ranking.py Outdated
Signed-off-by: shanaiabuggy <[email protected]>
Comment thread services/intake/src/nmp/intake/entities/experiments.py Outdated

@BrianNewsom BrianNewsom 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.

Discussed live, approved after updates!

Signed-off-by: shanaiabuggy <[email protected]>

@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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
services/intake/tests/test_experiment_default_sort.py (1)

130-144: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Degradation test doesn't assert fallback ordering.

Test confirms default path returns 200 and explicit sort returns 503, but never asserts the fallback actually orders by -created_at. The core claim under test ("default sort degrades to -created_at instead of 503") is left unverified beyond status code.

Suggested strengthening
     client.post(EXPERIMENTS, json={"name": "e1", "experiment_group_id": group["id"], "dataset_name": "ds"})
+    client.post(EXPERIMENTS, json={"name": "e2", "experiment_group_id": group["id"], "dataset_name": "ds"})
 
     default_sorted = client.get(EXPERIMENTS, params={"filter[experiment_group_id]": group["id"]})
     assert default_sorted.status_code == 200, default_sorted.text
+    names = [e["name"] for e in default_sorted.json()["data"]]
+    assert names == ["e2", "e1"]  # newest first via -created_at fallback
🤖 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 `@services/intake/tests/test_experiment_default_sort.py` around lines 130 -
144, The test in test_default_metric_sort_degrades_without_rollups only checks
the response codes, but it does not verify that the group’s default metric sort
actually falls back to -created_at. Strengthen the test by asserting the
ordering of the experiments returned from the EXPERIMENTS GET for the default
path, using the existing setup in
test_default_metric_sort_degrades_without_rollups and comparing the returned
items against created_at order.
🤖 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.

Inline comments:
In `@openapi/ga/openapi.yaml`:
- Around line 10375-10383: The ExperimentGroupRequest.default_sort and
ExperimentGroupResponse.default_sort schemas are currently defined as
non-nullable arrays, but the generated models treat them as list[SortCriterion]
| None. Update the OpenAPI definitions for default_sort to allow null (for
example via anyOf or an explicit null type alongside the array schema) in both
ExperimentGroupRequest and ExperimentGroupResponse so clients can send and
receive unset sorts correctly.

---

Nitpick comments:
In `@services/intake/tests/test_experiment_default_sort.py`:
- Around line 130-144: The test in
test_default_metric_sort_degrades_without_rollups only checks the response
codes, but it does not verify that the group’s default metric sort actually
falls back to -created_at. Strengthen the test by asserting the ordering of the
experiments returned from the EXPERIMENTS GET for the default path, using the
existing setup in test_default_metric_sort_degrades_without_rollups and
comparing the returned items against created_at order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: eb43476e-6f1f-4fd0-b7a7-045b7140c3f8

📥 Commits

Reviewing files that changed from the base of the PR and between 62bf597 and 5459148.

⛔ Files ignored due to path filters (14)
  • sdk/python/nemo-platform/.nmpcontext/openapi.yaml is excluded by !sdk/**
  • sdk/python/nemo-platform/.nmpcontext/stainless.yaml is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/resources/experiment_groups/api.md is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/resources/experiment_groups/experiment_groups.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/resources/experiments/experiments.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/__init__.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/experiment_group_create_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/experiment_group_response.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/experiment_group_update_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/sort_criterion.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiment_groups/sort_criterion_param.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/experiment_list_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/tests/api_resources/test_experiment_groups.py is excluded by !sdk/**
  • sdk/stainless.yaml is excluded by !sdk/**
📒 Files selected for processing (7)
  • openapi/ga/individual/platform.openapi.yaml
  • openapi/ga/openapi.yaml
  • openapi/openapi.yaml
  • services/intake/src/nmp/intake/api/v2/experiments/endpoints.py
  • services/intake/src/nmp/intake/api/v2/experiments/schemas.py
  • services/intake/src/nmp/intake/entities/experiments.py
  • services/intake/tests/test_experiment_default_sort.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/intake/src/nmp/intake/entities/experiments.py

Comment thread openapi/ga/openapi.yaml
Signed-off-by: shanaiabuggy <[email protected]>
@shanaiabuggy
shanaiabuggy added this pull request to the merge queue Jun 30, 2026
Merged via the queue into main with commit a9bf6f6 Jun 30, 2026
54 checks passed
@shanaiabuggy
shanaiabuggy deleted the sbuggy/ase-322 branch June 30, 2026 23:06
arpitsardhana pushed a commit that referenced this pull request Jul 9, 2026
…asis for rank) (#512)

* feat(experiments): Experiment group 'ranked fields' (default sort + basis for rank)

Signed-off-by: shanaiabuggy <[email protected]>

* bunny

Signed-off-by: shanaiabuggy <[email protected]>

* renamez

Signed-off-by: shanaiabuggy <[email protected]>

* rabbit

Signed-off-by: shanaiabuggy <[email protected]>

---------

Signed-off-by: shanaiabuggy <[email protected]>
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