diff --git a/openapi/ga/individual/platform.openapi.yaml b/openapi/ga/individual/platform.openapi.yaml index 681fa9c072..5c62b70686 100644 --- a/openapi/ga/individual/platform.openapi.yaml +++ b/openapi/ga/individual/platform.openapi.yaml @@ -3531,12 +3531,13 @@ paths: explode: true schema: $ref: '#/components/schemas/EvaluationFilter' - description: 'Filter evaluations by name, experiment_group_id, dataset_name, - dataset_version, created_by, created_at, or updated_at. Pass is_deleted=true - to return only soft-deleted evaluations; omit to see only live ones. Pass - is_pinned=true (or false) to filter by pinned state; omit to return both. - Filter by a metadata key/value: filter[metadata.]=. Filter by - a rollup metric with numeric range operators ($gte/$lte/$gt/$lt/$eq): filter[run_count][$gte]=5, + description: 'Filter evaluations by name, experiment_id (experiment group + membership; experiment_group_id is a deprecated alias), dataset_name, dataset_version, + created_by, created_at, or updated_at. Pass is_deleted=true to return only + soft-deleted evaluations; omit to see only live ones. Pass is_pinned=true + (or false) to filter by pinned state; omit to return both. Filter by a metadata + key/value: filter[metadata.]=. Filter by a rollup metric with + numeric range operators ($gte/$lte/$gt/$lt/$eq): filter[run_count][$gte]=5, filter[cost_usd.mean][$lte]=0.5, filter[latency_ms.p95][$lte]=1000, filter[tokens.mean][$lte]=5000, or filter[evaluators..mean][$gte]=0.8.' responses: @@ -10508,8 +10509,17 @@ components: description: Filter evaluations by name. title: Name type: string + experiment_id: + description: "Filter evaluations by experiment group membership: matches\ + \ evaluations whose `experiment_ids` include this group id (legacy rows\ + \ still on `experiment_group_id` also match). This is the canonical membership\ + \ filter \u2014 it mirrors the write-side `experiment_ids`." + title: Experiment Id + type: string experiment_group_id: - description: Filter evaluations by owning group id. + deprecated: true + description: Deprecated alias for `experiment_id`; filter evaluations by + experiment group membership. title: Experiment Group Id type: string dataset_name: diff --git a/openapi/ga/openapi.yaml b/openapi/ga/openapi.yaml index 681fa9c072..5c62b70686 100644 --- a/openapi/ga/openapi.yaml +++ b/openapi/ga/openapi.yaml @@ -3531,12 +3531,13 @@ paths: explode: true schema: $ref: '#/components/schemas/EvaluationFilter' - description: 'Filter evaluations by name, experiment_group_id, dataset_name, - dataset_version, created_by, created_at, or updated_at. Pass is_deleted=true - to return only soft-deleted evaluations; omit to see only live ones. Pass - is_pinned=true (or false) to filter by pinned state; omit to return both. - Filter by a metadata key/value: filter[metadata.]=. Filter by - a rollup metric with numeric range operators ($gte/$lte/$gt/$lt/$eq): filter[run_count][$gte]=5, + description: 'Filter evaluations by name, experiment_id (experiment group + membership; experiment_group_id is a deprecated alias), dataset_name, dataset_version, + created_by, created_at, or updated_at. Pass is_deleted=true to return only + soft-deleted evaluations; omit to see only live ones. Pass is_pinned=true + (or false) to filter by pinned state; omit to return both. Filter by a metadata + key/value: filter[metadata.]=. Filter by a rollup metric with + numeric range operators ($gte/$lte/$gt/$lt/$eq): filter[run_count][$gte]=5, filter[cost_usd.mean][$lte]=0.5, filter[latency_ms.p95][$lte]=1000, filter[tokens.mean][$lte]=5000, or filter[evaluators..mean][$gte]=0.8.' responses: @@ -10508,8 +10509,17 @@ components: description: Filter evaluations by name. title: Name type: string + experiment_id: + description: "Filter evaluations by experiment group membership: matches\ + \ evaluations whose `experiment_ids` include this group id (legacy rows\ + \ still on `experiment_group_id` also match). This is the canonical membership\ + \ filter \u2014 it mirrors the write-side `experiment_ids`." + title: Experiment Id + type: string experiment_group_id: - description: Filter evaluations by owning group id. + deprecated: true + description: Deprecated alias for `experiment_id`; filter evaluations by + experiment group membership. title: Experiment Group Id type: string dataset_name: diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 681fa9c072..5c62b70686 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -3531,12 +3531,13 @@ paths: explode: true schema: $ref: '#/components/schemas/EvaluationFilter' - description: 'Filter evaluations by name, experiment_group_id, dataset_name, - dataset_version, created_by, created_at, or updated_at. Pass is_deleted=true - to return only soft-deleted evaluations; omit to see only live ones. Pass - is_pinned=true (or false) to filter by pinned state; omit to return both. - Filter by a metadata key/value: filter[metadata.]=. Filter by - a rollup metric with numeric range operators ($gte/$lte/$gt/$lt/$eq): filter[run_count][$gte]=5, + description: 'Filter evaluations by name, experiment_id (experiment group + membership; experiment_group_id is a deprecated alias), dataset_name, dataset_version, + created_by, created_at, or updated_at. Pass is_deleted=true to return only + soft-deleted evaluations; omit to see only live ones. Pass is_pinned=true + (or false) to filter by pinned state; omit to return both. Filter by a metadata + key/value: filter[metadata.]=. Filter by a rollup metric with + numeric range operators ($gte/$lte/$gt/$lt/$eq): filter[run_count][$gte]=5, filter[cost_usd.mean][$lte]=0.5, filter[latency_ms.p95][$lte]=1000, filter[tokens.mean][$lte]=5000, or filter[evaluators..mean][$gte]=0.8.' responses: @@ -10508,8 +10509,17 @@ components: description: Filter evaluations by name. title: Name type: string + experiment_id: + description: "Filter evaluations by experiment group membership: matches\ + \ evaluations whose `experiment_ids` include this group id (legacy rows\ + \ still on `experiment_group_id` also match). This is the canonical membership\ + \ filter \u2014 it mirrors the write-side `experiment_ids`." + title: Experiment Id + type: string experiment_group_id: - description: Filter evaluations by owning group id. + deprecated: true + description: Deprecated alias for `experiment_id`; filter evaluations by + experiment group membership. title: Experiment Group Id type: string dataset_name: diff --git a/sdk/python/nemo-platform/.nmpcontext/openapi.yaml b/sdk/python/nemo-platform/.nmpcontext/openapi.yaml index 681fa9c072..5c62b70686 100644 --- a/sdk/python/nemo-platform/.nmpcontext/openapi.yaml +++ b/sdk/python/nemo-platform/.nmpcontext/openapi.yaml @@ -3531,12 +3531,13 @@ paths: explode: true schema: $ref: '#/components/schemas/EvaluationFilter' - description: 'Filter evaluations by name, experiment_group_id, dataset_name, - dataset_version, created_by, created_at, or updated_at. Pass is_deleted=true - to return only soft-deleted evaluations; omit to see only live ones. Pass - is_pinned=true (or false) to filter by pinned state; omit to return both. - Filter by a metadata key/value: filter[metadata.]=. Filter by - a rollup metric with numeric range operators ($gte/$lte/$gt/$lt/$eq): filter[run_count][$gte]=5, + description: 'Filter evaluations by name, experiment_id (experiment group + membership; experiment_group_id is a deprecated alias), dataset_name, dataset_version, + created_by, created_at, or updated_at. Pass is_deleted=true to return only + soft-deleted evaluations; omit to see only live ones. Pass is_pinned=true + (or false) to filter by pinned state; omit to return both. Filter by a metadata + key/value: filter[metadata.]=. Filter by a rollup metric with + numeric range operators ($gte/$lte/$gt/$lt/$eq): filter[run_count][$gte]=5, filter[cost_usd.mean][$lte]=0.5, filter[latency_ms.p95][$lte]=1000, filter[tokens.mean][$lte]=5000, or filter[evaluators..mean][$gte]=0.8.' responses: @@ -10508,8 +10509,17 @@ components: description: Filter evaluations by name. title: Name type: string + experiment_id: + description: "Filter evaluations by experiment group membership: matches\ + \ evaluations whose `experiment_ids` include this group id (legacy rows\ + \ still on `experiment_group_id` also match). This is the canonical membership\ + \ filter \u2014 it mirrors the write-side `experiment_ids`." + title: Experiment Id + type: string experiment_group_id: - description: Filter evaluations by owning group id. + deprecated: true + description: Deprecated alias for `experiment_id`; filter evaluations by + experiment group membership. title: Experiment Group Id type: string dataset_name: diff --git a/sdk/python/nemo-platform/src/nemo_platform/resources/evaluations/evaluations.py b/sdk/python/nemo-platform/src/nemo_platform/resources/evaluations/evaluations.py index 5a62302e94..5aa001ce64 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/resources/evaluations/evaluations.py +++ b/sdk/python/nemo-platform/src/nemo_platform/resources/evaluations/evaluations.py @@ -338,7 +338,8 @@ def list( List Evaluations Args: - filter: Filter evaluations by name, experiment_group_id, dataset_name, dataset_version, + filter: Filter evaluations by name, experiment_id (experiment group membership; + experiment_group_id is a deprecated alias), dataset_name, dataset_version, created_by, created_at, or updated_at. Pass is_deleted=true to return only soft-deleted evaluations; omit to see only live ones. Pass is_pinned=true (or false) to filter by pinned state; omit to return both. Filter by a metadata @@ -881,7 +882,8 @@ def list( List Evaluations Args: - filter: Filter evaluations by name, experiment_group_id, dataset_name, dataset_version, + filter: Filter evaluations by name, experiment_id (experiment group membership; + experiment_group_id is a deprecated alias), dataset_name, dataset_version, created_by, created_at, or updated_at. Pass is_deleted=true to return only soft-deleted evaluations; omit to see only live ones. Pass is_pinned=true (or false) to filter by pinned state; omit to return both. Filter by a metadata diff --git a/sdk/python/nemo-platform/src/nemo_platform/types/evaluations/evaluation_filter_param.py b/sdk/python/nemo-platform/src/nemo_platform/types/evaluations/evaluation_filter_param.py index bfb2295d59..80f4e5e88b 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/types/evaluations/evaluation_filter_param.py +++ b/sdk/python/nemo-platform/src/nemo_platform/types/evaluations/evaluation_filter_param.py @@ -61,7 +61,18 @@ class EvaluationFilterParam(TypedDict, total=False): """ experiment_group_id: str - """Filter evaluations by owning group id.""" + """ + Deprecated alias for `experiment_id`; filter evaluations by experiment group + membership. + """ + + experiment_id: str + """ + Filter evaluations by experiment group membership: matches evaluations whose + `experiment_ids` include this group id (legacy rows still on + `experiment_group_id` also match). This is the canonical membership filter — it + mirrors the write-side `experiment_ids`. + """ is_deleted: bool """When true, returns only soft-deleted evaluations. diff --git a/sdk/python/nemo-platform/src/nemo_platform/types/evaluations/evaluation_list_params.py b/sdk/python/nemo-platform/src/nemo_platform/types/evaluations/evaluation_list_params.py index 9770c69e4f..2a4331d41f 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/types/evaluations/evaluation_list_params.py +++ b/sdk/python/nemo-platform/src/nemo_platform/types/evaluations/evaluation_list_params.py @@ -29,7 +29,8 @@ class EvaluationListParams(TypedDict, total=False): filter: EvaluationFilterParam """ - Filter evaluations by name, experiment_group_id, dataset_name, dataset_version, + Filter evaluations by name, experiment_id (experiment group membership; + experiment_group_id is a deprecated alias), dataset_name, dataset_version, created_by, created_at, or updated_at. Pass is_deleted=true to return only soft-deleted evaluations; omit to see only live ones. Pass is_pinned=true (or false) to filter by pinned state; omit to return both. Filter by a metadata diff --git a/sdk/python/nemo-platform/tests/api_resources/test_evaluations.py b/sdk/python/nemo-platform/tests/api_resources/test_evaluations.py index 7249e1aee6..bc6ce70343 100644 --- a/sdk/python/nemo-platform/tests/api_resources/test_evaluations.py +++ b/sdk/python/nemo-platform/tests/api_resources/test_evaluations.py @@ -367,6 +367,7 @@ def test_method_list_with_all_params(self, client: NeMoPlatform) -> None: } }, "experiment_group_id": "experiment_group_id", + "experiment_id": "experiment_id", "is_deleted": True, "is_pinned": True, "latency_ms": { @@ -1086,6 +1087,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncNeMoPlatform } }, "experiment_group_id": "experiment_group_id", + "experiment_id": "experiment_id", "is_deleted": True, "is_pinned": True, "latency_ms": { diff --git a/services/intake/src/nmp/intake/api/v2/experiments/endpoints.py b/services/intake/src/nmp/intake/api/v2/experiments/endpoints.py index e6e00e1c60..496c2d46e5 100644 --- a/services/intake/src/nmp/intake/api/v2/experiments/endpoints.py +++ b/services/intake/src/nmp/intake/api/v2/experiments/endpoints.py @@ -391,7 +391,8 @@ async def create_evaluation( openapi_extra=generate_openapi_extra_params( filter_schema=EvaluationFilter, filter_description=( - "Filter evaluations by name, experiment_group_id, " + "Filter evaluations by name, experiment_id (experiment group membership; " + "experiment_group_id is a deprecated alias), " "dataset_name, dataset_version, created_by, created_at, or updated_at. " "Pass is_deleted=true to return only soft-deleted evaluations; omit to see only live ones. " "Pass is_pinned=true (or false) to filter by pinned state; omit to return both. " @@ -1044,15 +1045,19 @@ def _group_membership_filter(group_id: str) -> LogicalOperation: def _rewrite_group_filter(operation: FilterOperation | None) -> FilterOperation | None: - """Rewrite an ``experiment_group_id`` equality in a parsed filter into a membership match. + """Rewrite a group-membership equality in a parsed filter into a membership match. - The API still exposes an ``experiment_group_id`` filter param; with many-to-many membership it - means "belongs to this group", which spans the ``experiment_ids`` list and the legacy scalar. + Both the canonical ``experiment_id`` filter param and its deprecated ``experiment_group_id`` alias + mean "belongs to this group"; with many-to-many membership that spans the ``experiment_ids`` list + and the legacy scalar, so both route through the same ``_group_membership_filter``. """ if operation is None: return None if isinstance(operation, ComparisonOperation): - if operation.field == "data.experiment_group_id" and operation.operator == FilterOperator.EQ: + if ( + operation.field in ("data.experiment_id", "data.experiment_group_id") + and operation.operator == FilterOperator.EQ + ): return _group_membership_filter(operation.value) return operation if isinstance(operation, LogicalOperation): diff --git a/services/intake/src/nmp/intake/api/v2/experiments/schemas.py b/services/intake/src/nmp/intake/api/v2/experiments/schemas.py index dbb29650ae..a94c0365c4 100644 --- a/services/intake/src/nmp/intake/api/v2/experiments/schemas.py +++ b/services/intake/src/nmp/intake/api/v2/experiments/schemas.py @@ -354,7 +354,19 @@ class EvaluationFilter(Filter): """Filter for listing Evaluations.""" name: str | None = Field(default=None, description="Filter evaluations by name.") - experiment_group_id: str | None = Field(default=None, description="Filter evaluations by owning group id.") + experiment_id: str | None = Field( + default=None, + description=( + "Filter evaluations by experiment group membership: matches evaluations whose " + "`experiment_ids` include this group id (legacy rows still on `experiment_group_id` also " + "match). This is the canonical membership filter — it mirrors the write-side `experiment_ids`." + ), + ) + experiment_group_id: str | None = Field( + default=None, + deprecated=True, + description="Deprecated alias for `experiment_id`; filter evaluations by experiment group membership.", + ) dataset_name: str | None = Field(default=None, description="Filter evaluations by dataset name.") dataset_version: str | None = Field(default=None, description="Filter evaluations by dataset version.") created_by: str | None = Field(default=None, description="Filter evaluations by the principal that created them.") diff --git a/services/intake/tests/integration/test_experiments_crud.py b/services/intake/tests/integration/test_experiments_crud.py index c63bc9c795..b96d8da049 100644 --- a/services/intake/tests/integration/test_experiments_crud.py +++ b/services/intake/tests/integration/test_experiments_crud.py @@ -284,6 +284,28 @@ def test_evaluation_list_and_scope_to_group(client: TestClient) -> None: assert missing.status_code == 404 +def test_evaluation_filter_experiment_id_matches_deprecated_alias(client: TestClient) -> None: + """The canonical `experiment_id` filter returns the same set as the deprecated `experiment_group_id`, + both resolving membership over the evaluation's `experiment_ids` list.""" + group = client.post(GROUPS, json={"name": "grp-canon"}).json() + other = client.post(GROUPS, json={"name": "grp-other"}).json() + # Membership is set via the canonical `experiment_ids` write field (no legacy scalar). + client.post(EVALUATIONS, json={"name": "in-group", "experiment_ids": [group["id"]], "dataset_name": "ds"}) + client.post(EVALUATIONS, json={"name": "in-other", "experiment_ids": [other["id"]], "dataset_name": "ds"}) + + by_canonical = client.get(EVALUATIONS, params={"filter[experiment_id]": group["id"]}) + assert by_canonical.status_code == 200, by_canonical.text + canonical_names = {e["name"] for e in by_canonical.json()["data"]} + assert canonical_names == {"in-group"} + + by_deprecated = client.get(EVALUATIONS, params={"filter[experiment_group_id]": group["id"]}) + assert by_deprecated.status_code == 200, by_deprecated.text + deprecated_names = {e["name"] for e in by_deprecated.json()["data"]} + + # The canonical key and the deprecated alias resolve to the same membership set. + assert canonical_names == deprecated_names + + def test_evaluation_filter_by_dataset_version(client: TestClient) -> None: group = _create_group(client) client.post( diff --git a/web/packages/studio/src/components/ExperimentGroupEditModal/index.tsx b/web/packages/studio/src/components/ExperimentGroupEditModal/index.tsx index cdf86e8926..72064544f5 100644 --- a/web/packages/studio/src/components/ExperimentGroupEditModal/index.tsx +++ b/web/packages/studio/src/components/ExperimentGroupEditModal/index.tsx @@ -42,7 +42,7 @@ export const ExperimentGroupEditModal: FC = ({ // Offer the group's discovered evaluators as first-class sort fields (only fetched while open). const { data: experimentsPage } = useListEvaluations( workspace, - { filter: { experiment_group_id: group.id }, page_size: 100 }, + { filter: { experiment_id: group.id }, page_size: 100 }, { query: { enabled: open && !!group.id } } ); const evaluatorOptions = useMemo( diff --git a/web/packages/studio/src/components/charts/ExperimentGroupParetoChart/useParetoEvaluations.ts b/web/packages/studio/src/components/charts/ExperimentGroupParetoChart/useParetoEvaluations.ts index 3febe912b0..c2c5f0428f 100644 --- a/web/packages/studio/src/components/charts/ExperimentGroupParetoChart/useParetoEvaluations.ts +++ b/web/packages/studio/src/components/charts/ExperimentGroupParetoChart/useParetoEvaluations.ts @@ -27,7 +27,7 @@ export function useParetoEvaluations( { page: 1, page_size: DEFAULT_LARGE_PAGE_SIZE, - filter: { experiment_group_id: experimentGroupId } as EvaluationFilter, + filter: { experiment_id: experimentGroupId } as EvaluationFilter, }, { query: { enabled } } ); diff --git a/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/useExperimentGroupEvaluations.test.ts b/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/useExperimentGroupEvaluations.test.ts index 66f1146531..1dfa1ad7bc 100644 --- a/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/useExperimentGroupEvaluations.test.ts +++ b/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/useExperimentGroupEvaluations.test.ts @@ -226,7 +226,7 @@ describe('useExperimentGroupEvaluations', () => { onSuccess?.(); expect(mockGetListEvaluationsQueryKey).toHaveBeenCalledWith('ws', { - filter: { experiment_group_id: 'grp' }, + filter: { experiment_id: 'grp' }, }); expect(invalidateQueries).toHaveBeenCalledTimes(1); }); diff --git a/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/useExperimentGroupEvaluations.ts b/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/useExperimentGroupEvaluations.ts index 2bab8333eb..34d219f1c3 100644 --- a/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/useExperimentGroupEvaluations.ts +++ b/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/useExperimentGroupEvaluations.ts @@ -96,7 +96,7 @@ export function useExperimentGroupEvaluations({ ...filter, ...(search && { name: { $like: search } }), // Spread last so the group scope can't be overridden by a user filter. - experiment_group_id: experimentGroupId, + experiment_id: experimentGroupId, }; const listQueryOptions = { query: { @@ -147,14 +147,14 @@ export function useExperimentGroupEvaluations({ const pendingRef = useRef>(new Set()); // Scope invalidation to this group's experiment lists (any page/sort/filter, pinned and unpinned) - // via a partial key match on experiment_group_id, so a pin/unpin doesn't refetch other groups' + // via a partial key match on experiment_id, so a pin/unpin doesn't refetch other groups' // lists. Returned (not voided) so the mutation's onSuccess awaits the refetch before onSettled // re-enables the row. const invalidateList = useCallback( () => queryClient.invalidateQueries({ queryKey: getListEvaluationsQueryKey(workspace, { - filter: { experiment_group_id: experimentGroupId }, + filter: { experiment_id: experimentGroupId }, }), }), [queryClient, workspace, experimentGroupId] diff --git a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/useSessionCompareRuns.ts b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/useSessionCompareRuns.ts index a33c87b9d8..607e35926f 100644 --- a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/useSessionCompareRuns.ts +++ b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/useSessionCompareRuns.ts @@ -29,7 +29,7 @@ export function useSessionCompareRuns( const { data: group } = useGetExperimentGroup(workspace, experimentGroupName); const { data: evaluationsPage } = useListEvaluations( workspace, - { filter: { experiment_group_id: group?.id }, page_size: 1000 }, + { filter: { experiment_id: group?.id }, page_size: 1000 }, { query: { enabled: Boolean(group?.id) } } ); const evaluationNames = useMemo(