Skip to content

feat(evaluator): persist eval results as queryable entities - #515

Merged
SandyChapman merged 4 commits into
mainfrom
eval-results-persistence/schapman
Jul 2, 2026
Merged

feat(evaluator): persist eval results as queryable entities#515
SandyChapman merged 4 commits into
mainfrom
eval-results-persistence/schapman

Conversation

@SandyChapman

@SandyChapman SandyChapman commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Re-adds queryable persistence of eval-job results (removed with the legacy service in #231), now on the plugin-job API. Results become first-class, filterable entities again — without resurrecting the old service.

Stacked on #496. Base is aalgo-297-agent-eval-job/schapman; the diff is just the persistence work. GitHub will retarget to main once #496 merges.

What's included

  • Result entitiesAgentEvalResultEntity / EvaluateResultEntity store the aggregated scores plus the filterable traits (target, dataset); the full per-trial/per-row bundle stays in the run's fileset, referenced by bundle_ref. Jobs persist best-effort in run() — a transient entity-store error never fails an eval that already succeeded.
  • API DTOsAgentEvalResult / EvaluateResult (mapped from the entities, like MetricMetricBundleEntity) back the read routes, so id/created_at/updated_at round-trip cleanly on the wire and in the SDK (an EntityBase does not).
  • Read routes/agent-eval-results and /eval-results list/get/delete, with trait filtering. A small DataFilter base translates custom fields to data.* for the entity store; MetricFilter adopts it too, which also fixes metric_type filtering on the existing /metrics route (same latent bug).
  • SDKclient.evaluator.{agent_eval_results,eval_results} resources (retrieve/list/delete, typed DTOs, trait filters), plus metric_type filtering on client.evaluator.metrics.list.
  • get_async_task_sdk — async counterpart of get_task_sdk (protocol + both providers) so a synchronous job run() can drive the async entity-store write with the full on-behalf-of identity (id + email + groups).

Verification

  • Live, against a real platform (RUN_AGENT_EVAL_INTEGRATION=1): agent-eval submit under auth (proves the delegated identity authorizes the entity write), row-eval submit, and metric-type filtering. These caught three real bugs unit tests couldn't (response exclude_none round-trip, target wiring, custom-field filter 500).
  • Unit: result entity round-trip, persistence helpers, service, routes, SDK resources, filter translation, async-task-SDK header parity. Full evaluator + plugin + nmp-common suites green.
  • ruff + ty clean; OpenAPI spec regenerated.

Notes

  • Datetime-range filtering is intentionally not exposed in the SDK yet (needs an operator shape; equality-only for now).
  • Intake remains the optional denormalized downstream copy; these entities are the evaluator's source of truth.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added async task SDK initialization for job execution (so jobs can use async clients).
    • Added evaluator v2 persisted evaluation results APIs (agent and row) with matching SDK support for list/retrieve/delete.
    • Metrics listing now supports sorting and filtering by metric type.
  • Bug Fixes
    • Improved metric filtering so custom fields are translated into the correct query namespace.
    • Result persistence is more robust: jobs continue even if persistence fails, with safe target URL handling.
  • Tests
    • Expanded unit and integration coverage for async SDK wiring, results persistence, v2 routes, and metric/result filtering.

@github-actions github-actions Bot added the feat label Jun 30, 2026
@SandyChapman
SandyChapman force-pushed the eval-results-persistence/schapman branch 2 times, most recently from 5984400 to 60e6cba Compare June 30, 2026 15:17
@SandyChapman
SandyChapman marked this pull request as ready for review June 30, 2026 17:52
@SandyChapman
SandyChapman requested review from a team as code owners June 30, 2026 17:52
Base automatically changed from aalgo-297-agent-eval-job/schapman to main June 30, 2026 18:03
@SandyChapman
SandyChapman force-pushed the eval-results-persistence/schapman branch from 60e6cba to dcef462 Compare June 30, 2026 18:11
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 22523/29645 76.0% 60.9%
Integration Tests 13010/28325 45.9% 19.4%

Comment thread plugins/nemo-evaluator/src/nemo_evaluator/entities.py
Comment thread plugins/nemo-evaluator/src/nemo_evaluator/sdk/result_resources.py

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

LGTM with minor nits

Comment thread plugins/nemo-evaluator/src/nemo_evaluator/jobs/agent_evaluate.py Outdated
Comment thread plugins/nemo-evaluator/src/nemo_evaluator/api/v2/results.py
Re-add queryable persistence of eval-job results (removed with the legacy
service in #231), now on the plugin-job API.

- AgentEvalResultEntity / EvaluateResultEntity store aggregated scores plus
  filterable target/dataset traits; the full bundle stays in the run's fileset
  (bundle_ref). Jobs persist best-effort in run() (a store error never fails
  the eval).
- AgentEvalResult / EvaluateResult API DTOs (mapped from the entities) back the
  read routes, so id/created_at round-trip cleanly on the wire and in the SDK.
- /agent-eval-results and /eval-results list/get/delete routes with trait
  filtering: a DataFilter base translates custom fields to data.* (MetricFilter
  adopts it too, fixing metric_type filtering).
- client.evaluator.{agent_eval_results,eval_results} SDK resources, plus
  metric-type filtering on client.evaluator.metrics.list.
- get_async_task_sdk: async counterpart of get_task_sdk so a sync job run() can
  drive the async entity-store write with the full on-behalf-of identity.

Verified end-to-end on a live platform (agent-eval submit under auth, row-eval
submit, metric-type filtering) plus unit coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman
SandyChapman force-pushed the eval-results-persistence/schapman branch from dcef462 to cfc423d Compare July 2, 2026 13:07
@coderabbitai

coderabbitai Bot commented Jul 2, 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: f7d2926c-ddd8-4256-ac9d-d7f3b6023a89

📥 Commits

Reviewing files that changed from the base of the PR and between 06b33ed and 042c652.

📒 Files selected for processing (3)
  • plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py
  • plugins/nemo-evaluator/tests/test_result_entity.py
  • plugins/nemo-evaluator/tests/test_result_persistence.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • plugins/nemo-evaluator/tests/test_result_entity.py
  • plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py

📝 Walkthrough

Walkthrough

Adds async task SDK support, persisted evaluator result records, result read/delete APIs and SDK resources, metric filtering updates, and tests for persistence, routing, SDK encoding, and integration behavior.

Changes

Evaluator Result Persistence and API

Layer / File(s) Summary
Async task SDK support
packages/nemo_platform_plugin/src/nemo_platform_plugin/sdk_provider.py, packages/nemo_platform_plugin/tests/test_sdk_provider.py, packages/nmp_common/src/nmp/common/sdk_factory.py, plugins/nemo-evaluator/src/nemo_evaluator/tasks/runner.py, plugins/nemo-evaluator/tests/test_agent_evaluate.py, plugins/nemo-evaluator/tests/test_evaluate_job.py
Adds get_async_task_sdk across SDK providers, builds async_sdk in the task runner, and updates tests to cover async SDK wiring and header parity.
Persisted result entities and writes
plugins/nemo-evaluator/src/nemo_evaluator/entities.py, plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py, plugins/nemo-evaluator/src/nemo_evaluator/jobs/agent_evaluate.py, plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py, plugins/nemo-evaluator/tests/test_result_entity.py, plugins/nemo-evaluator/tests/test_result_persistence.py, plugins/nemo-evaluator/tests/integration/test_agent_evaluate_job.py, plugins/nemo-evaluator/tests/integration/test_evaluate_job.py
Adds persisted agent-eval and row-eval entity schemas, best-effort persistence helpers, job hooks that save queryable result records, and tests for round-trips, skip paths, failure handling, and integration retrieval.
Result API schemas, service, and routes
plugins/nemo-evaluator/src/nemo_evaluator/api/schemas.py, plugins/nemo-evaluator/src/nemo_evaluator/api/service/result_service.py, plugins/nemo-evaluator/src/nemo_evaluator/api/dependencies.py, plugins/nemo-evaluator/src/nemo_evaluator/api/v2/results.py, plugins/nemo-evaluator/src/nemo_evaluator/service.py, plugins/nemo-evaluator/openapi/openapi.yaml
Adds DataFilter, result DTOs, ResultService, workspace-scoped v2 routes, router registration, and OpenAPI coverage for the new result endpoints.
SDK result resources and metric filtering
plugins/nemo-evaluator/src/nemo_evaluator/sdk/result_resources.py, plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py, plugins/nemo-evaluator/src/nemo_evaluator/sdk/metric_resources.py, plugins/nemo-evaluator/tests/sdk/test_result_sdk_resources.py, plugins/nemo-evaluator/tests/sdk/test_metric_sdk_resources.py, plugins/nemo-evaluator/tests/integration/test_metric_filtering.py, plugins/nemo-evaluator/tests/api/service/test_result_service.py, plugins/nemo-evaluator/tests/api/v2/test_results_routes.py, plugins/nemo-evaluator/tests/api/v2/test_metrics_routes.py
Adds sync/async result resources on the evaluator SDK, extends metric list query params with sort and metric_type, and tests request encoding plus end-to-end filtering and route behavior.

Possibly related PRs

  • NVIDIA-NeMo/nemo-platform#496: Touches plugins/nemo-evaluator/src/nemo_evaluator/tasks/runner.py, which now initializes and forwards async_sdk.

Suggested reviewers: 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 matches the main change: adding queryable persistence for evaluator results.
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 eval-results-persistence/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.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/nemo_platform_plugin/src/nemo_platform_plugin/sdk_provider.py (1)

189-211: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate header-building logic with get_task_sdk.

Lines 168-187 and 189-211 are near-identical (principal lookup, warning, header dict). Docstring at Line 354 explains why this shouldn't wrap get_async_platform_sdk, but a private helper local to this class (e.g. _task_headers(service_name)) could still de-duplicate without going through that wrapper, reducing drift risk the parity test was written to guard against.

♻️ Suggested extraction
+    `@staticmethod`
+    def _task_headers(service_name: str, *, async_variant: bool) -> dict[str, str]:
+        headers: dict[str, str] = {
+            "X-NMP-Principal-Id": f"service:{service_name}",
+            _INTERNAL_REQUEST_HEADER: "true",
+        }
+        principal = _read_principal_from_env()
+        if principal is not None:
+            headers.update(_on_behalf_of_headers(principal))
+        else:
+            kind = "async task" if async_variant else "task"
+            logger.warning(
+                "%s not set; %s SDK will authenticate as service:%s without on-behalf-of delegation",
+                _NMP_PRINCIPAL_ENVVAR, kind, service_name,
+            )
+        return headers
+
     def get_task_sdk(self, service_name: str) -> NeMoPlatform:
-        headers: dict[str, str] = {...}
-        ...
-        return NeMoPlatform(base_url=self._base_url(), default_headers=headers)
+        return NeMoPlatform(base_url=self._base_url(), default_headers=self._task_headers(service_name, async_variant=False))

     def get_async_task_sdk(self, service_name: str) -> AsyncNeMoPlatform:
-        headers: dict[str, str] = {...}
-        ...
-        return AsyncNeMoPlatform(base_url=self._base_url(), default_headers=headers)
+        return AsyncNeMoPlatform(base_url=self._base_url(), default_headers=self._task_headers(service_name, async_variant=True))
🤖 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/sdk_provider.py`
around lines 189 - 211, The header-building logic in get_async_task_sdk is
duplicated from get_task_sdk and should be extracted into a private helper on
the same class, such as _task_headers(service_name), that returns the shared
headers and warning behavior. Update get_task_sdk and get_async_task_sdk to both
call this helper so the service principal, internal marker, and on-behalf-of
delegation stay in sync without routing through get_async_platform_sdk.
packages/nmp_common/src/nmp/common/sdk_factory.py (1)

194-221: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication with get_task_sdk (principal lookup + warning).

Both functions repeat the principal_from_env() / warning-log pattern before delegating to their respective platform-sdk factory. Could extract a small shared helper, but low priority since behavior is correct and delegation targets differ (sync vs async).

🤖 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/nmp_common/src/nmp/common/sdk_factory.py` around lines 194 - 221,
Both get_task_sdk and get_async_task_sdk duplicate the principal_from_env lookup
and missing-principal warning logic before delegating to their SDK factories.
Refactor this repeated pattern into a small shared helper used by get_task_sdk
and get_async_task_sdk, keeping the existing warning text and on_behalf_of
behavior intact while leaving the sync/async delegation to get_platform_sdk and
get_async_platform_sdk unchanged.
plugins/nemo-evaluator/tests/api/v2/test_results_routes.py (1)

26-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate fixture scaffolding across test files.

_FakeEntityClient, _agent_entity, and _eval_entity here are near-identical copies of the ones in test_result_service.py. Consider hoisting into a shared conftest/fixture module for the api test tree to avoid drift between the two.

🤖 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/tests/api/v2/test_results_routes.py` around lines 26 -
92, The test helpers `_FakeEntityClient`, `_agent_entity`, and `_eval_entity`
are duplicated in multiple test files and should be centralized. Move these
shared fixtures into a common `conftest` or shared test utility module for the
`api` test tree, then update `test_results_routes` and `test_result_service` to
import and reuse the same symbols so their behavior stays in sync.
plugins/nemo-evaluator/src/nemo_evaluator/sdk/result_resources.py (1)

30-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate _query_params with metric_resources.py's _list_params.

Both build {page, page_size, sort?, filter[...]} query dicts; metric_resources.py:29-38 has the same shape for a single filter. A shared helper in http_utils (accepting a filters dict) would serve both call sites.

🤖 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/sdk/result_resources.py` around
lines 30 - 38, Consolidate the duplicated query-dict building logic in
_query_params by extracting a shared helper in http_utils that builds {page,
page_size, sort?, filter[...]} from a filters dict; then update _query_params in
result_resources.py and _list_params in metric_resources.py to call the shared
helper instead of maintaining separate implementations.
🤖 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 `@plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py`:
- Around line 47-61: Sanitize the target URL fields before persisting result
records, because _target_fields and _row_target_fields currently use
str(target.*.url) and may store sensitive userinfo or query tokens. Update the
URL handling in these helpers to redact credentials and sensitive query values,
or omit the URL entirely when it cannot be safely normalized. Keep the existing
tuple shape returned by _target_fields and _row_target_fields so downstream
persistence and read APIs continue to work.

---

Nitpick comments:
In `@packages/nemo_platform_plugin/src/nemo_platform_plugin/sdk_provider.py`:
- Around line 189-211: The header-building logic in get_async_task_sdk is
duplicated from get_task_sdk and should be extracted into a private helper on
the same class, such as _task_headers(service_name), that returns the shared
headers and warning behavior. Update get_task_sdk and get_async_task_sdk to both
call this helper so the service principal, internal marker, and on-behalf-of
delegation stay in sync without routing through get_async_platform_sdk.

In `@packages/nmp_common/src/nmp/common/sdk_factory.py`:
- Around line 194-221: Both get_task_sdk and get_async_task_sdk duplicate the
principal_from_env lookup and missing-principal warning logic before delegating
to their SDK factories. Refactor this repeated pattern into a small shared
helper used by get_task_sdk and get_async_task_sdk, keeping the existing warning
text and on_behalf_of behavior intact while leaving the sync/async delegation to
get_platform_sdk and get_async_platform_sdk unchanged.

In `@plugins/nemo-evaluator/src/nemo_evaluator/sdk/result_resources.py`:
- Around line 30-38: Consolidate the duplicated query-dict building logic in
_query_params by extracting a shared helper in http_utils that builds {page,
page_size, sort?, filter[...]} from a filters dict; then update _query_params in
result_resources.py and _list_params in metric_resources.py to call the shared
helper instead of maintaining separate implementations.

In `@plugins/nemo-evaluator/tests/api/v2/test_results_routes.py`:
- Around line 26-92: The test helpers `_FakeEntityClient`, `_agent_entity`, and
`_eval_entity` are duplicated in multiple test files and should be centralized.
Move these shared fixtures into a common `conftest` or shared test utility
module for the `api` test tree, then update `test_results_routes` and
`test_result_service` to import and reuse the same symbols so their behavior
stays in sync.
🪄 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: d0434803-5508-4b34-a4e3-0c3e32ea343e

📥 Commits

Reviewing files that changed from the base of the PR and between 12bfaff and cfc423d.

📒 Files selected for processing (29)
  • packages/nemo_platform_plugin/src/nemo_platform_plugin/sdk_provider.py
  • packages/nemo_platform_plugin/tests/test_sdk_provider.py
  • packages/nmp_common/src/nmp/common/sdk_factory.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/result_service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/api/v2/results.py
  • plugins/nemo-evaluator/src/nemo_evaluator/entities.py
  • plugins/nemo-evaluator/src/nemo_evaluator/jobs/agent_evaluate.py
  • plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py
  • plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/metric_resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/result_resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/service.py
  • plugins/nemo-evaluator/src/nemo_evaluator/tasks/runner.py
  • plugins/nemo-evaluator/tests/api/service/test_result_service.py
  • plugins/nemo-evaluator/tests/api/v2/test_metrics_routes.py
  • plugins/nemo-evaluator/tests/api/v2/test_results_routes.py
  • plugins/nemo-evaluator/tests/integration/test_agent_evaluate_job.py
  • plugins/nemo-evaluator/tests/integration/test_evaluate_job.py
  • plugins/nemo-evaluator/tests/integration/test_metric_filtering.py
  • plugins/nemo-evaluator/tests/sdk/test_metric_sdk_resources.py
  • plugins/nemo-evaluator/tests/sdk/test_result_sdk_resources.py
  • plugins/nemo-evaluator/tests/test_agent_evaluate.py
  • plugins/nemo-evaluator/tests/test_evaluate_job.py
  • plugins/nemo-evaluator/tests/test_result_entity.py
  • plugins/nemo-evaluator/tests/test_result_persistence.py

Comment thread plugins/nemo-evaluator/src/nemo_evaluator/jobs/result_persistence.py Outdated
SandyChapman and others added 3 commits July 2, 2026 10:19
- Best-effort result persistence: wrap persist_agent_eval_result and
  persist_evaluate_result in try/except with a logged warning. The
  authoritative output (bundle/result artifacts) is already saved, so a
  persistence failure no longer fails an otherwise-successful eval job.
  Regression tests cover both jobs.
- Results GET routes now set response_model_exclude_none=True to match
  the list routes' serialization.
- Wrap results GET and DELETE handlers in try/except -> 500 (re-raising
  HTTPException so the 404 is preserved), matching the metrics routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
target_url is stored on the result entity and returned by the read APIs,
so a target endpoint carrying userinfo or a token query param would leak.
Route both target-field helpers through a new _safe_target_url() that
strips userinfo, redacts sensitive query values, and omits the URL when
it has no host. Tuple shape is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
…ession

The negative test suppressed with `# type: ignore[call-arg]` (a mypy code
ty doesn't recognize), so ty's `missing-argument` leaked through and failed
the lint-python-types CI check. Use `# ty: ignore[missing-argument]`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman
SandyChapman force-pushed the eval-results-persistence/schapman branch from 06b33ed to 042c652 Compare July 2, 2026 14:09
@SandyChapman
SandyChapman added this pull request to the merge queue Jul 2, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 2, 2026
@SandyChapman
SandyChapman added this pull request to the merge queue Jul 2, 2026
Merged via the queue into main with commit b8f6dc6 Jul 2, 2026
54 checks passed
@SandyChapman
SandyChapman deleted the eval-results-persistence/schapman branch July 2, 2026 16:11
arpitsardhana pushed a commit that referenced this pull request Jul 9, 2026
* feat(evaluator): persist eval results as queryable entities

Re-add queryable persistence of eval-job results (removed with the legacy
service in #231), now on the plugin-job API.

- AgentEvalResultEntity / EvaluateResultEntity store aggregated scores plus
  filterable target/dataset traits; the full bundle stays in the run's fileset
  (bundle_ref). Jobs persist best-effort in run() (a store error never fails
  the eval).
- AgentEvalResult / EvaluateResult API DTOs (mapped from the entities) back the
  read routes, so id/created_at round-trip cleanly on the wire and in the SDK.
- /agent-eval-results and /eval-results list/get/delete routes with trait
  filtering: a DataFilter base translates custom fields to data.* (MetricFilter
  adopts it too, fixing metric_type filtering).
- client.evaluator.{agent_eval_results,eval_results} SDK resources, plus
  metric-type filtering on client.evaluator.metrics.list.
- get_async_task_sdk: async counterpart of get_task_sdk so a sync job run() can
  drive the async entity-store write with the full on-behalf-of identity.

Verified end-to-end on a live platform (agent-eval submit under auth, row-eval
submit, metric-type filtering) plus unit coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>

* fix(evaluator): address review feedback on eval-results persistence

- Best-effort result persistence: wrap persist_agent_eval_result and
  persist_evaluate_result in try/except with a logged warning. The
  authoritative output (bundle/result artifacts) is already saved, so a
  persistence failure no longer fails an otherwise-successful eval job.
  Regression tests cover both jobs.
- Results GET routes now set response_model_exclude_none=True to match
  the list routes' serialization.
- Wrap results GET and DELETE handlers in try/except -> 500 (re-raising
  HTTPException so the 404 is preserved), matching the metrics routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>

* fix(evaluator): redact credentials from persisted target URLs

target_url is stored on the result entity and returned by the read APIs,
so a target endpoint carrying userinfo or a token query param would leak.
Route both target-field helpers through a new _safe_target_url() that
strips userinfo, redacts sensitive query values, and omits the URL when
it has no host. Tuple shape is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>

* fix(evaluator): use ty rule name in result-entity negative-test suppression

The negative test suppressed with `# type: ignore[call-arg]` (a mypy code
ty doesn't recognize), so ty's `missing-argument` leaked through and failed
the lint-python-types CI check. Use `# ty: ignore[missing-argument]`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>

---------

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.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