Skip to content

feat(evaluator): unify dataset + task evaluation under one Evaluator#736

Draft
SandyChapman wants to merge 2 commits into
mainfrom
unify-evaluator-entrypoints/schapman
Draft

feat(evaluator): unify dataset + task evaluation under one Evaluator#736
SandyChapman wants to merge 2 commits into
mainfrom
unify-evaluator-entrypoints/schapman

Conversation

@SandyChapman

Copy link
Copy Markdown
Contributor

Draft — seeking team input on the evaluator SDK UX + naming before I polish.

What this does

Merges the two SDK entrypoints — dataset-driven Evaluator and task-driven AgentEvaluator — into a single Evaluator whose methods map 1:1 onto the backend protocol, so local ↔ platform and dataset ↔ agent eval differ only by which backend you inject.

Evaluator (public):

  • run_dataset_metric_eval — one metric over a dataset → backend evaluate_metric
  • run_dataset_benchmark_eval — many metrics over a dataset → backend evaluate_benchmark
  • run_task_eval — tasks (each carries its own metrics) → backend evaluate_tasks
  • each has a _sync twin; run / run_sync remain as backward-compatible dataset dispatchers.

Backend protocol (EvaluationBackend / SyncEvaluationBackend): evaluate_metric / evaluate_benchmark / evaluate_tasks.

Plugin: the executor implements the full protocol; client.evaluator.as_backend() returns it, so Evaluator(NeMoPlatform(...).evaluator.as_backend()) runs both dataset and task eval through the platform. AgentEvaluator becomes a thin deprecated shim; the agent-eval job runner is migrated onto the unified path.

Questions for reviewers

  1. Namingrun_dataset_metric_eval / run_dataset_benchmark_eval / run_task_eval on Evaluator, mirroring evaluate_metric / evaluate_benchmark / evaluate_tasks on the backend. Good alignment, or too verbose?
  2. Drop-in — should Evaluator(client=NeMoPlatform(...).evaluator) work without the .as_backend() hop (i.e. make the resource itself satisfy the protocol)? Deliberately deferred — a guard test currently keeps evaluate* off the resource surface.
  3. Sync surface — keep the _sync twins + SyncEvaluationBackend, or go async-only? (Leaning keep: notebook + job-runner callers, and run_sync is Jupyter-safe.)
  4. DeprecationAgentEvaluator as a warning-emitting shim → Evaluator().run_task_eval — path OK?

Out of scope (follow-ups)

Durable task submit, the remaining agent-eval UX papercuts, and result-persistence for the new runner targets.

Merge the two SDK entrypoints (dataset-driven `Evaluator` and task-driven
`AgentEvaluator`) into a single `Evaluator` whose methods mirror the backend
protocol one-to-one, so moving local -> platform and dataset -> agent eval is a
matter of which backend you inject.

SDK core (packages/nemo_evaluator_sdk):
- Backend protocol gains `evaluate_tasks`; `evaluate` -> `evaluate_metric` so the
  trio (`evaluate_metric` / `evaluate_benchmark` / `evaluate_tasks`) is parallel.
- `Evaluator` exposes `run_dataset_metric_eval` / `run_dataset_benchmark_eval` /
  `run_task_eval` (+ `_sync`), each mapping to one backend op; `run` / `run_sync`
  remain backward-compatible dataset dispatchers.
- Online-inference transport moves onto `LocalBackend`; `AgentEvaluator` becomes a
  thin deprecated shim forwarding to `Evaluator().run_task_eval`.

Plugin (plugins/nemo-evaluator):
- The executor implements the full backend protocol, rejecting local hooks and,
  for tasks, building an AgentEvalInputSpec + running the agent-eval job locally.
- Exposed via `client.evaluator.as_backend()`, so a NeMoPlatform evaluator resource
  can back the unified `Evaluator` for both dataset and task evaluation.
- Job runner migrated off the deprecated shim.

Draft for team input on the UX + naming.

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

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 25589/32791 78.0% 62.6%
Integration Tests 14769/31440 47.0% 19.3%

return namespace_result(metric_key, result, aggregate_fields)

async def evaluate(
async def evaluate_metric(

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.

for consistency, should it be evaluate_datasets?

Raises:
ValueError: If both ``inference_fn`` and ``agent_inference_fn_factory`` are set.
"""
if inference_fn is not None and agent_inference_fn_factory is not None:

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.

also need a check that only one or the other is provided

Comment on lines +261 to +262
tasks: Tasks to evaluate; each task carries its own metrics.
trials: Precomputed trials to score. Mutually exclusive with ``target``.

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.

considering tasks and trials are mutually exclusive, we should expose this, so it's clear before invoking it

raise TypeError("metrics must be a Metric or a sequence of Metric objects")
return await self._backend.evaluate(
metric=metrics,
async def run(

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.

should probably become run_dataset_eval since it's specific to that path


from nemo_evaluator_sdk.execution.evaluator import Evaluator

evaluator = Evaluator(client.evaluator.as_backend())

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.

are we going to allow sdk to be an interface with platform again via injected backend? At some point in the past (~April), we reverted this change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We should discuss today if we can find time, otherwise Monday. I'm not sure why we wouldn't support this as it aligns with the early design we had to make moving from SDK to Plugin as straight-forward as possible.

@ngoncharenko

Copy link
Copy Markdown
Contributor

Some initial thoughts:

  • I'm not sure whether combining both eval types under one class resolves ambiguity for a user. Two use cases are fundamentally different from one another to merge them - one is input/output eval, another one is comprehensive multi-turn agent that acts in an env, and we evaluate env state change
    • ambiguity now moves from having to choose between two classes to choosing between many functions. Not sure if it will be easy to explain what to choose for someone seeing this for the first time
    • combining two classes also goes against single responsibility principle from my perspective
  • run_dataset_metric_eval / run_dataset_benchmark_eval are a bit verbose

…ming

Address review feedback (Nick) on the unified Evaluator, items 2-5:

- Consolidate the split dataset methods back into one overloaded method per layer:
  `Evaluator.run_dataset_eval` (single metric -> EvaluationResult, sequence ->
  BenchmarkEvaluationResult) and backend `evaluate_dataset`. `run` / `run_sync` are
  plain aliases of `run_dataset_eval` again.
- Rename the task-eval surface to the `taskset` noun (a collection of tasks):
  backend `evaluate_taskset`, `Evaluator.run_taskset_eval` (+ `_sync`), `taskset=`
  parameter. Wire/data-model fields (AgentEvalInputSpec.tasks, AgentEvalResult.tasks)
  and the runtime runner protocol keep `tasks` (separate breaking-schema concern).
- Backend protocol trio is now `evaluate_dataset` / `evaluate_taskset`, mirrored 1:1
  by the Evaluator methods.
- Expose mutual-exclusivity in the type signature: `run_taskset_eval` overloads on
  `target` vs `trials`; `LocalBackend.__init__` overloads on `inference_fn` vs
  `agent_inference_fn_factory`.

Backend protocol + adapter + plugin executor stay flat (union input/return) so
implementers conform cleanly; the intent-revealing overloads live on the user-facing
Evaluator.

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the feat label Jul 17, 2026
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