Skip to content

feat(studio): API to expose test case row level data in experiments#198

Merged
shanaiabuggy merged 7 commits into
mainfrom
sbuggy/fp-201
Jun 8, 2026
Merged

feat(studio): API to expose test case row level data in experiments#198
shanaiabuggy merged 7 commits into
mainfrom
sbuggy/fp-201

Conversation

@shanaiabuggy

@shanaiabuggy shanaiabuggy commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • New endpoint to list experiment sessions per workspace/experiment with pagination and deep-object filtering by root-span status and test case ID. Responses include identifiers, timestamps/latency, input, token counts, costs, and evaluator scores; documents 200/404/422/503 responses.
  • Tests

    • Integration tests for listing, pagination, filtering (test_case_id and status), unknown-experiment 404, and rejecting unknown query params (400).

@shanaiabuggy
shanaiabuggy requested review from a team as code owners June 4, 2026 23:00
@shanaiabuggy shanaiabuggy changed the title API to expose test case row level data in experiments feat(studio): API to expose test case row level data in experiments Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Documentation preview is ready

Preview: https://nvidia-nemo.github.io/nemo-platform/pr-preview/pr-198/pr-198/

Built from 9c85657 in workflow run.

This preview is deployed from this PR branch, updates when docs changes are pushed, and will be removed when the PR closes.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds GET /apis/intake/v2/workspaces/{workspace}/experiments/{name}/sessions: OpenAPI contracts and schemas, auth mapping, ClickHouse-backed repository and SQL, shared storage coercion helpers, endpoint wiring with optional repository DI, and integration tests for pagination and filters.

Changes

Experiment Sessions Listing

Layer / File(s) Summary
OpenAPI contract and schemas
openapi/ga/individual/platform.openapi.yaml, openapi/ga/openapi.yaml, openapi/openapi.yaml
Defined new GET /apis/intake/v2/workspaces/{workspace}/experiments/{name}/sessions and added ExperimentSessionFilter, ExperimentSessionResponse, and ExperimentSessionResponsesPage components.
Authorization mapping
services/core/auth/src/nmp/core/auth/assets/static-authz.yaml
Mapped endpoint to intake.experiments.read with intake:read and platform:read scopes.
Pydantic response/filter models
services/intake/src/nmp/intake/api/v2/experiments/schemas.py
Added ExperimentSessionFilter and ExperimentSessionResponse with from_row to hydrate from repository rows.
ClickHouse repository
services/intake/src/nmp/intake/spans/experiment_session_repository.py
Implemented ExperimentSessionRepository.list_sessions with scoping, optional status/test_case_id filters, count/list SQL, token/cost aggregation, evaluator mean-score aggregation, and row-to-domain mapping; added ExperimentSessionRow and ExperimentSessionPage.
Shared coercion & rollup updates
services/intake/src/nmp/intake/spans/storage.py, services/intake/src/nmp/intake/spans/experiment_rollup_repository.py, services/intake/src/nmp/intake/spans/trace_repository.py
Added float_or_none, int_or_none, str_or_none; replaced local converters with shared helpers in rollup and trace repositories.
Endpoint handler
services/intake/src/nmp/intake/api/v2/experiments/endpoints.py
Added list_experiment_sessions handler, ClickHouse client helper, optional get_experiment_session_repository DI, repository error handling (503), and pagination envelope creation.
Integration tests
services/intake/tests/integration/spans/test_experiment_sessions.py
Added tests validating listing, pagination, test_case_id and status filtering, unknown-experiment 404, unsupported query param 400, and ATIF ingest helpers.

Possibly related PRs

  • NVIDIA-NeMo/nemo-platform#154: ClickHouse schema/migration work that affects experiment_sessions data model and related SQL used by this repository.

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately describes the main addition: a new API endpoint to expose test case/session-level data within experiments, which is the primary purpose of the changeset.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sbuggy/fp-201

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

@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: 5

🧹 Nitpick comments (1)
services/intake/tests/integration/spans/test_experiment_sessions.py (1)

86-133: ⚡ Quick win

Add integration coverage for status filtering and the 503 path.

Current tests miss two new endpoint behaviors: status query filtering and deterministic 503 when telemetry storage is unavailable. Add both cases to lock the contract.

🤖 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/integration/spans/test_experiment_sessions.py` around
lines 86 - 133, Add two integration tests: one to cover status filtering and one
to cover deterministic 503 when telemetry storage is unavailable. For status
filtering, create sessions (using the same pattern in
test_list_experiment_sessions_filter_by_test_case via client.post to ATIF_INGEST
and EXPERIMENTS) with different "status" values, then call GET
f"{EXPERIMENTS}/{experiment_name}/sessions" with params={"status":"<value>"} and
assert pagination total_results and returned data match the filtered status. For
the 503 path, add a test (similar setup to
test_list_experiment_sessions_returns_404_for_unknown_experiment) that simulates
telemetry storage being unavailable (mock or configure the telemetry/storage
client used by the service to raise or be None) before calling the sessions
endpoint and assert the response.status_code == 503 and appropriate error shape;
reference the TestClient, EXPERIMENTS, ATIF_INGEST, and the existing test
patterns for setup and assertions.
🤖 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 15425-15429: The schema for the test_case_id property currently
disallows nulls but the description says it may be null; update the OpenAPI
schema for the test_case_id property (the YAML block defining test_case_id) to
include nullable: true so null values are accepted, keeping the existing title,
description, and type entries unchanged.

In `@openapi/ga/openapi.yaml`:
- Around line 15425-15429: The OpenAPI schema for the property test_case_id
declares it can be null in the description but omits the nullable marker; update
the test_case_id property in openapi.yaml to allow nulls by adding nullable:
true beneath the test_case_id definition (keeping title, description and type
intact) so clients will accept null values for test_case_id.
- Around line 15441-15443: Update the ExperimentSessionResponse schema in
openapi.yaml to mark fields that the backend can return as null as nullable:
true; specifically add nullable: true for ended_at, latency_ms, input,
input_tokens, output_tokens, cached_tokens, and cost_total_usd in the
ExperimentSessionResponse object so the OpenAPI spec accepts nulls consistent
with services/intake/src/nmp/intake/spans/experiment_session_repository.py.

In `@openapi/openapi.yaml`:
- Around line 15425-15430: The schema for the property test_case_id currently
lists only type: string but the description states it can be null; update the
test_case_id schema to allow nulls consistently with other fields by adding
nullable: true (or changing the type to a string/null union if your style
prefers) under the test_case_id entry in openapi/openapi.yaml so the spec and
description match.

In `@services/intake/src/nmp/intake/api/v2/experiments/endpoints.py`:
- Around line 432-439: Wrap the call to session_repository.list_sessions in a
try/except that catches ClickHouse-related backend exceptions and converts them
into an HTTP 503 response; specifically, around the block calling
session_repository.list_sessions capture exceptions from the ClickHouse client
(e.g., clickhouse_driver.errors.Error / connection-related exceptions) and raise
fastapi.HTTPException(status_code=503, detail="Telemetry store unavailable")
while logging the original exception for debugging. Ensure you import
fastapi.HTTPException (or use the existing FastAPI HTTPException) and only map
backend/unavailability errors to 503, re-raising other exceptions unchanged.

---

Nitpick comments:
In `@services/intake/tests/integration/spans/test_experiment_sessions.py`:
- Around line 86-133: Add two integration tests: one to cover status filtering
and one to cover deterministic 503 when telemetry storage is unavailable. For
status filtering, create sessions (using the same pattern in
test_list_experiment_sessions_filter_by_test_case via client.post to ATIF_INGEST
and EXPERIMENTS) with different "status" values, then call GET
f"{EXPERIMENTS}/{experiment_name}/sessions" with params={"status":"<value>"} and
assert pagination total_results and returned data match the filtered status. For
the 503 path, add a test (similar setup to
test_list_experiment_sessions_returns_404_for_unknown_experiment) that simulates
telemetry storage being unavailable (mock or configure the telemetry/storage
client used by the service to raise or be None) before calling the sessions
endpoint and assert the response.status_code == 503 and appropriate error shape;
reference the TestClient, EXPERIMENTS, ATIF_INGEST, and the existing test
patterns for setup and assertions.
🪄 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: c63875b3-6333-457d-a1e8-fca92fe0229f

📥 Commits

Reviewing files that changed from the base of the PR and between a9469fb and a5e5c12.

⛔ Files ignored due to path filters (12)
  • 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/experiments/__init__.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/resources/experiments/api.md 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/resources/experiments/sessions.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/__init__.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/experiment_session_response.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/experiment_session_responses_page.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/session_list_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/tests/api_resources/experiments/test_sessions.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/core/auth/src/nmp/core/auth/assets/static-authz.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/spans/experiment_session_repository.py
  • services/intake/tests/integration/spans/test_experiment_sessions.py

Comment thread openapi/ga/individual/platform.openapi.yaml
Comment thread openapi/ga/openapi.yaml
Comment thread openapi/ga/openapi.yaml
Comment thread openapi/openapi.yaml
Comment thread services/intake/src/nmp/intake/api/v2/experiments/endpoints.py Outdated
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 18844/24899 75.7% 62.2%
Integration Tests 12026/23587 51.0% 26.2%

Comment thread services/intake/src/nmp/intake/api/v2/experiments/endpoints.py Fixed
Comment thread services/intake/src/nmp/intake/api/v2/experiments/endpoints.py Fixed

@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)
openapi/openapi.yaml (1)

15436-15439: ⚡ Quick win

Add enum constraint for status field.

Backend validates status against SpanStatus enum (success, error, cancelled, unknown) but OpenAPI schema only declares type: string. Add enum to improve contract and enable client-side validation.

📋 Suggested fix
 status:
   description: Filter by root-span status (success, error, cancelled, unknown).
   title: Status
   type: string
+  enum:
+    - success
+    - error
+    - cancelled
+    - unknown
🤖 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 15436 - 15439, The OpenAPI schema
currently defines the "status" property as type: string but lacks the enum
constraint; update the "status" field in openapi/openapi.yaml to include an enum
with the allowed values matching backend SpanStatus ("success", "error",
"cancelled", "unknown") so clients can validate against the same contract;
locate the "status" property block (title: Status, description: Filter by
root-span status) and add enum: ["success","error","cancelled","unknown"] under
it.
🤖 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 `@services/intake/src/nmp/intake/spans/experiment_session_repository.py`:
- Around line 260-267: The current queries built via current_spans_sql and the
CTE current_page_spans (and related joins against span_versions / spans_table
and page_sessions) only constrain by (workspace, session_id), which allows
reused session_id to mix unrelated traces; update those WHERE/ON predicates and
any IN-subselects to also include trace identity (e.g., trace_id or
root_span_id) so joins are scoped by (workspace, session_id, trace_id). Find
usages of current_spans_sql, current_page_spans, and any joins against
span_versions/page_sessions (including the other occurrences around the same
block) and add the trace_id equality to the tuple predicates and join conditions
so aggregates filter by trace as well as session and workspace.

---

Nitpick comments:
In `@openapi/openapi.yaml`:
- Around line 15436-15439: The OpenAPI schema currently defines the "status"
property as type: string but lacks the enum constraint; update the "status"
field in openapi/openapi.yaml to include an enum with the allowed values
matching backend SpanStatus ("success", "error", "cancelled", "unknown") so
clients can validate against the same contract; locate the "status" property
block (title: Status, description: Filter by root-span status) and add enum:
["success","error","cancelled","unknown"] under it.
🪄 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: e84bab8d-331c-4211-ac3f-68e86d7a3fc4

📥 Commits

Reviewing files that changed from the base of the PR and between 7a8b472 and e9064d6.

⛔ Files ignored due to path filters (10)
  • 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/experiments/api.md 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/resources/experiments/sessions.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/__init__.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/experiment_session_filter_param.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/session_list_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/tests/api_resources/experiments/test_sessions.py is excluded by !sdk/**
  • sdk/stainless.yaml is excluded by !sdk/**
📒 Files selected for processing (10)
  • 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/spans/experiment_rollup_repository.py
  • services/intake/src/nmp/intake/spans/experiment_session_repository.py
  • services/intake/src/nmp/intake/spans/storage.py
  • services/intake/src/nmp/intake/spans/trace_repository.py
  • services/intake/tests/integration/spans/test_experiment_sessions.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • services/intake/src/nmp/intake/api/v2/experiments/schemas.py
  • openapi/ga/individual/platform.openapi.yaml
  • openapi/ga/openapi.yaml
  • services/intake/tests/integration/spans/test_experiment_sessions.py
  • services/intake/src/nmp/intake/api/v2/experiments/endpoints.py

shanaiabuggy and others added 6 commits June 8, 2026 14:37
Signed-off-by: shanaiabuggy <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: shanaiabuggy <[email protected]>
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: 2

🤖 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 `@services/intake/tests/integration/spans/test_experiment_sessions.py`:
- Around line 127-130: Tests call
client.get("{EXPERIMENTS}/{experiment_name}/sessions", params=...) with
top-level keys like "test_case_id" and "status" which fails validation; change
the request params to use deep-object filter syntax expected by
validate_list_query_params (e.g., pass params with keys like
"filter[test_case_id]" and "filter[status]" instead of top-level
"test_case_id"/"status" in the client.get calls in this test file), updating
both the call around the EXPERIMENTS/experiment_name/sessions endpoint (the
client.get invocation) for the lines referenced and the similar invocation at
lines ~176-179 so the endpoint receives filter[...] params and returns 200.
- Line 6: Remove the module-level future import by deleting the line "from
__future__ import annotations" from the test module, and replace any remaining
string-based type hints in functions/classes in this file with concrete type
hints (or import the actual types under normal imports) so the code no longer
relies on postponed evaluation of annotations; verify tests still pass after
converting any quoted annotations.
🪄 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: c900e298-bf11-4ad1-9b60-4bedb012f977

📥 Commits

Reviewing files that changed from the base of the PR and between 53e5c75 and e49c2f1.

⛔ Files ignored due to path filters (13)
  • 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/experiments/__init__.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/resources/experiments/api.md 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/resources/experiments/sessions.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/__init__.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/experiment_session_filter_param.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/experiment_session_response.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/experiment_session_responses_page.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/types/experiments/session_list_params.py is excluded by !sdk/**
  • sdk/python/nemo-platform/tests/api_resources/experiments/test_sessions.py is excluded by !sdk/**
  • sdk/stainless.yaml is excluded by !sdk/**
📒 Files selected for processing (11)
  • openapi/ga/individual/platform.openapi.yaml
  • openapi/ga/openapi.yaml
  • openapi/openapi.yaml
  • services/core/auth/src/nmp/core/auth/assets/static-authz.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/spans/experiment_rollup_repository.py
  • services/intake/src/nmp/intake/spans/experiment_session_repository.py
  • services/intake/src/nmp/intake/spans/storage.py
  • services/intake/src/nmp/intake/spans/trace_repository.py
  • services/intake/tests/integration/spans/test_experiment_sessions.py
🚧 Files skipped from review as they are similar to previous changes (10)
  • services/core/auth/src/nmp/core/auth/assets/static-authz.yaml
  • openapi/openapi.yaml
  • services/intake/src/nmp/intake/api/v2/experiments/schemas.py
  • services/intake/src/nmp/intake/spans/trace_repository.py
  • openapi/ga/openapi.yaml
  • services/intake/src/nmp/intake/spans/storage.py
  • openapi/ga/individual/platform.openapi.yaml
  • services/intake/src/nmp/intake/spans/experiment_session_repository.py
  • services/intake/src/nmp/intake/api/v2/experiments/endpoints.py
  • services/intake/src/nmp/intake/spans/experiment_rollup_repository.py

Comment thread services/intake/tests/integration/spans/test_experiment_sessions.py
Comment thread services/intake/tests/integration/spans/test_experiment_sessions.py
@shanaiabuggy
shanaiabuggy added this pull request to the merge queue Jun 8, 2026
Merged via the queue into main with commit 7f3cb88 Jun 8, 2026
59 of 60 checks passed
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.

3 participants