Skip to content

feat(evaluator): expose per-task attempts with trial identity - #1224

Open
ngoncharenko wants to merge 4 commits into
mainfrom
ngoncharenko/aalgo-310-passatk-harbor-runner
Open

feat(evaluator): expose per-task attempts with trial identity#1224
ngoncharenko wants to merge 4 commits into
mainfrom
ngoncharenko/aalgo-310-passatk-harbor-runner

Conversation

@ngoncharenko

@ngoncharenko ngoncharenko commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Why: the run summary only published cross-task averages and pass@k. Answering "which tasks were flaky, and which attempt failed?" meant re-deriving it by hand from the flat task × trial × metric score list.
  • What: adds AgentEvalSummary.task_metric_attempts — the ordered attempts per task and metric output — and persists it to summary.json.
  • Each attempt names the trial that produced it, so it joins across metric outputs and out to trials.jsonl:
"task-47": { "harbor_reward.reward": [
  { "trial_id": "task-47__7f3a9c", "value": 1.0 },
  { "trial_id": "task-47__1b8e42", "value": null }
]}

Important notes

  • value: null = the trial died before scoring — an attempt that did not pass. A failed metric leaves no entry at all: unmeasured, not unsuccessful.
  • pass@k now derives from that same mapping instead of a second walk over the scores, so the per-attempt view and the published pass@k cannot drift apart. Aggregates are unchanged.

Related Issue

  • AALGO-310 (P3.1) — this is the deliverable.
  • Unblocks AALGO-441 (P3.4, Harbor reward_stats) and AALGO-428 (P3.2, exception rollup).

How this change moves us closer to Harbor parity

Target shape: AgentDatasetStats — Harbor's per-dataset aggregate, the thing summary.json has to be able to reproduce. Per-trial source is VerifierResult.rewards; it is populated by JobStats.increment as reward_stats.setdefault(value, []).append(trial_result.trial_name).

Harbor field Status From
pass_at_k summary.score("<metric>.<output>.pass@k").mean — pre-existing
reward_stats new task_metric_attempts — trial ids + raw floats
n_trials summary.trial_count
n_errors newly possible distinct trial_id where value is None
exception_stats needs the exception type → AALGO-428
  • The unlock — Harbor groups by trial_name; we grouped by task_id. harbor_runtime already stamps trial_name onto AgentEvalTrial.id, so carrying trial_id on each attempt makes the shapes the same, not merely similar:
# Harbor's real reward_stats, from summary.json alone — no re-walk of result.scores
for attempt in attempts:
    stats.setdefault(attempt.value, []).append(attempt.trial_id)
# -> {"reward": {1.0: ["alpha__a", "beta__a", "beta__b"], 0.0: ["alpha__b", "gamma__a"]}}
  • Two divergences closed — grouping key (trial_name, not task_id) and inner key type (raw float | int, not stringified). Pinned by test_harbor_reward_stats_is_derivable_from_summary_task_metric_attempts.
  • n_errors needs the trial id specifically — one dead trial contributes a null to every metric key, so a naive count over-counts by the number of metrics. Deduping requires identity.
  • Caveats, so this doesn't overclaimpass_at_k matches in content but not shape (Harbor keys {k: float}, we emit named aggregates), and n_errors is derivable but not implemented.

Why attempts carry an id but no ordinal

Worth stating, since "shouldn't each attempt have an index?" is the obvious review question:

  • Harbor has no attempt ordinal to carry. It expands n_attempts by discarding the loop index — job.py:416: for _ in range(self.config.n_attempts) — and runs the repeats concurrently (job.py:129). Each repeat gets a fresh random trial_name. There is no first attempt.
  • That is a feature, not a gap. Exchangeable repeats are exactly what the unbiased pass@k estimator assumes — _pass_at_k(n, c, k) is a function of counts and never of position. An ordinal would imply an ordering the runs do not have.
  • So identity does the work, not order. For Harbor the list position is sorted(glob("*/result.json")) over a random suffix, i.e. arbitrary; trial_id is the only meaningful handle, which is why it is carried rather than parsed. For contrast, the experimentalist's _trial_attempt recovers the ordinal by parsing the trial-name suffix, which isdigit()-fails against Harbor's ShortUUID — so it returns None on every real Harbor run (verified against a live n_attempts=2 job: hello-world__4c3VrKY, hello-world__NXRG3pE).
  • The one case where an ordinal would be real: retries. A retried trial is causally after the one it replaces, unlike a parallel repeat. Harbor counts retries only at job level (JobStats.n_retries) and its per-trial TrialResult carries no retry field, so "did the retry do better?" is a gap in Harbor's model — not something the evaluator can synthesize. Flagging it in case it matters later.

Follow-ups

  • AALGO-441 (P3.4) — unblocked. reward_payload_from_result still walks result.scores and emits the legacy task-keyed, stringified shape; rewiring it onto the summary is now mechanical.
  • AALGO-428 (P3.2)exception_stats is the last gap. With trial_id present it's a join against trials.jsonl (already in the bundle), not a schema change. Recommend it first add a typed error to AgentEvalTrial, replacing the untyped metadata["exception_type"] convention.

What to review

  • _task_metric_attempts in agent_eval/results.py — the core. Docstring carries an in → out worked example; the two bullet lists map 1:1 onto the branches below them.
  • The asymmetry is deliberate — a failed trial is value: null (counted in n); a failed metric is no entry (kept out of n, so a judge timeout is never charged to the agent). Most likely thing to get wrong later.
  • Why a list of records, not a dict keyed by trial_idpersistence.py writes with sort_keys=True (would reorder attempts lexicographically), and nothing enforces trial-id uniqueness, so a dict would collapse two attempts into one and silently drop pass@k's n. Pinned by test_duplicate_trial_ids_are_two_attempts_not_one.
  • AgentEvalAttemptValue is frozen, and value has no default — both guard the same class of silent corruption:
    • Frozen: the summary hands these out by reference, so a consumer rescaling in place (Gym reports reward on 0-100 where we use 0-1) would rewrite the run's own results, and a later persist would save the rewrite.
    • Required value: None means "the trial died" and pass@k counts it toward n, so a record that merely omits the key must not quietly become a failed attempt. Explicit "value": null still works.
  • nan_count semanticstest_a_task_that_produced_no_trial_is_unmeasured_and_counted_in_pass_at_k_nan. Previously such a task was absent from the denominator; now reported as missing coverage. Means unaffected. Note this is a from_scores path only — a full run still fails loudly on a trial-less task, and this PR deliberately does not relax that guard.
  • Schema exclusions are keyed by task — the flat set in the first commit let one task's declaration suppress another task's output. Worth a look if you think exclusions should instead be run-global.
  • Exception type deferred to AALGO-428 — the only source is untyped trial.metadata["exception_type"] that only Harbor stamps, and exception_type already means two opposite things in this codebase. With trial_id present it becomes a join against trials.jsonl.

Changes

  • New fieldtask_metric_attempts: dict[task_id, dict["<metric_type>.<output>", list[AgentEvalAttemptValue]]], where AgentEvalAttemptValue = {trial_id, value}.
  • pass@k reads it through a one-line projectionvalues_by_task = [attempt_values(outputs[key]) ...]. Everything below it (measured, unmeasured, max_n, _pass_at_k) is byte-identical.
  • Retention follows the declared schema — continuous/discrete/boolean kept; labels and free models dropped even when the emitted value is numeric. With tasks=None there are no specs to filter on, so every numeric output observed is kept.
  • Coverage change — a task that produced no trial now lands in pass@k nan_count instead of silently shrinking the denominator. Reachable only via AgentEvalSummary.from_scores called directly with a task list wider than the scores; a full run cannot get here, because _score_trials refuses to score when a task produced no trial (test_run_rejects_tasks_without_trials).
  • Harbor parity unlockedtrial_id is Harbor's trial_name (_trial_from_harbor_result stamps it), so Harbor's own reward_stats is rebuildable from the summary alone. See the parity section above.
  • Schema exclusions are per task — one task declaring an output under an unretained schema must not strip it from another task that never declared it. Tasks in one run need not agree on an output's schema.
  • NaN survives JSON — this is the first summary field carrying a raw metric value rather than a filtered aggregate, so a NaN score would have been written as a bare NaN token and made summary.json unparseable by strict readers. Serialized as "NaN", matching MetricOutput; round-trips back to a float.
  • Single pass over scores — grouped by (task_id, metric_type) once, replacing a per-task/per-key rescan.
  • Example rewritten — gym inspect_results.py reads the summary directly; per_task_outcomes keeps its bare-value shape, new per_task_attempts exposes the records. It now rejects a bundle predating the field rather than loading it and silently showing no per-task section.
  • Vendored SDK copy regenerated via make vendor, pinned byte-exact by a test.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with documentation updates
  • Documentation only
  • Contributor tooling or automation
  • CI, build, or test infrastructure

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Documentation updated for user-visible behavior
  • Documentation not applicable — justification:

Verification

  • Pull request title follows the repository's Conventional Commit format
  • Every commit includes an appropriate Signed-off-by: trailer
  • uv run pre-commit run -a passes, or any blocked checks are identified below
  • Targeted tests pass, or tests are marked not applicable above
  • No secrets, API keys, or credentials are included

Targeted validation:

Command Result
pytest packages/nemo_evaluator_sdk/tests (live/docker deselected) 1496 passed
pytest packages/nemo_evaluator_sdk/tests/agent_eval (live/docker deselected) 588 passed
pytest .../test_task_metric_attempts.py 18 passed
pytest plugins/nemo-evaluator/tests plugins/nemo-optimization/tests 849 passed, 22 skipped
uv run ruff check packages/nemo_evaluator_sdk passed
uv run ruff format --check packages/nemo_evaluator_sdk passed (232 files)
uv run --frozen ty check <changed files> 9 diagnostics, all pre-existing (12 at HEAD)
make vendor mirror only; byte-exact test passes
end-to-end: real Harbor bundle → persist_runinspect_results records in summary.json; per-task output unchanged

pass@k invariance — a golden {name: (mean, count, nan_count)} table was captured from the pre-change implementation before the producer was touched, over a fixture covering every branch (always-passes, dead trial, metric-raised, two never-measured). Post-change it compares identical, and is pinned by test_pass_at_k_aggregates_are_unchanged_by_carrying_trial_ids.

Not passing / not run:

  • uv-lock pre-commit hook: needs uv 0.9.14 to match CI; local toolchain is 0.9.30. Environment mismatch, not from this PR — no pyproject.toml is touched, and uv-lock-check passes.
  • Live/e2e suites: test_harbor_runtime_e2e.py, test_codex_runtime_live.py, test_sandbox_docker_provider_live.py, test_sandbox_compose_provider_live.py need Docker and provider credentials.

Summary by CodeRabbit

  • New Features

    • Results now include ordered, trial-linked metric attempts for each task, preserving failed trials and missing measurements.
    • Pass@k aggregation more accurately handles failed, empty, and unmeasured attempt lists.
    • Harbor-compatible reward details can be reconstructed directly from evaluation summaries.
  • Bug Fixes

    • Improved persistence and round-trip handling of attempt order, null values, and special numeric values.
  • Documentation

    • Updated result-reading guides and the Gym inspection example with new attempt formats and failure semantics.

@ngoncharenko
ngoncharenko requested review from a team as code owners August 10, 2026 22:03
@github-actions github-actions Bot added the feat label Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 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: c69c7d4d-dce8-41ba-8972-2cbbf7ea5ee3

📥 Commits

Reviewing files that changed from the base of the PR and between 378ba22 and 3eebc8b.

⛔ Files ignored due to path filters (1)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/results.py is excluded by !sdk/**
📒 Files selected for processing (3)
  • packages/nemo_evaluator_sdk/examples/gym/inspect_results.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/results.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_task_metric_attempts.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/results.py
  • packages/nemo_evaluator_sdk/examples/gym/inspect_results.py

📝 Walkthrough

Walkthrough

Changes

Agent evaluation summaries now retain ordered, trial-linked metric attempts. Failed trials use None; failed metrics are omitted. Pass@k, persistence, Harbor reconstruction, and Gym inspection consume this data.

Task metric aggregation

Layer / File(s) Summary
Collect and aggregate task metric attempts
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/results.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_task_metric_attempts.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_pass_at_k.py
The SDK collects supported numeric outputs, preserves trial order and IDs, distinguishes failed trials from failed metrics, and updates pass@k handling.
Persist and reconstruct trial-aware attempts
packages/nemo_evaluator_sdk/tests/agent_eval/test_persistence.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_harbor_runtime.py
Persistence tests verify round trips and ordering. Harbor tests reconstruct reward details and statistics from summary attempts.
Read summary data in the Gym inspector
packages/nemo_evaluator_sdk/examples/gym/inspect_results.py, packages/nemo_evaluator_sdk/examples/gym/README.md, docs/evaluator/agent-eval/reading-results.mdx, packages/nemo_evaluator_sdk/tests/agent_eval/test_task_metric_attempts.py
The Gym example reads summary.json, exposes trial-linked attempts, marks empty measurements as unmeasured, and documents joins by trial_id.

Suggested reviewers: sandychapman, arpitsardhana

Sequence Diagram(s)

sequenceDiagram
  participant Evaluation
  participant AgentEvalSummary
  participant summary_json
  participant GymInspector
  Evaluation->>AgentEvalSummary: build task_metric_attempts
  AgentEvalSummary->>AgentEvalSummary: aggregate pass@k values
  AgentEvalSummary->>summary_json: persist trial-linked attempts
  GymInspector->>summary_json: load summary
  summary_json-->>GymInspector: return attempts and aggregates
  GymInspector->>GymInspector: classify measured and unmeasured tasks
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.07% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: exposing ordered per-task attempts with trial identity.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ngoncharenko/aalgo-310-passatk-harbor-runner

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

🤖 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 `@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/results.py`:
- Around line 591-604: Update the output-schema tracking around output_keys and
excluded so exclusions are keyed by task.id as well as (metric_type, spec.name).
Apply exclusion checks only within the current task when filtering persisted
values and pass@k inputs, preserving retained outputs for other tasks; add
coverage where one task retains a model output and another excludes the same key
as a continuous score.
🪄 Autofix

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: a940a624-881c-44d9-bf66-e89732790587

📥 Commits

Reviewing files that changed from the base of the PR and between 33cecaf and b7843e8.

⛔ Files ignored due to path filters (1)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/results.py is excluded by !sdk/**
📒 Files selected for processing (6)
  • packages/nemo_evaluator_sdk/examples/gym/README.md
  • packages/nemo_evaluator_sdk/examples/gym/inspect_results.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/results.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_pass_at_k.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_persistence.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_task_metric_values.py

Comment thread packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/results.py Outdated
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 32066/40756 78.7% 63.5%
Integration Tests 18593/38591 48.2% 20.9%

@ngoncharenko ngoncharenko changed the title feat(evaluator): expose per-task attempt values feat(evaluator): expose per-task attempts with trial identity Aug 11, 2026
@ngoncharenko
ngoncharenko force-pushed the ngoncharenko/aalgo-310-passatk-harbor-runner branch from b8d501a to 3d85361 Compare August 11, 2026 18:51
@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

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

🧹 Nitpick comments (1)
packages/nemo_evaluator_sdk/examples/gym/inspect_results.py (1)

137-164: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Docstring omits the new unmeasured verdict.

The function now emits a fourth classification and counter. Add it to the docstring so the four printed states are documented.

📝 Proposed edit
     ``None`` is a trial that died: it counts as an attempt and never as a pass, so a task that passed
     once and crashed once reads as flaky rather than solved.
+
+    An empty attempt list is ``unmeasured``: the task produced no usable measurement, either because
+    its metric failed or because no trial ran.
     """
🤖 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_evaluator_sdk/examples/gym/inspect_results.py` around lines 137
- 164, Update the show_per_task docstring to document all four emitted verdicts:
solved, flaky, failed, and unmeasured, including that unmeasured applies when a
task has no recorded attempts. Keep the existing attempt and pass@k behavior
description unchanged.
🤖 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.

Nitpick comments:
In `@packages/nemo_evaluator_sdk/examples/gym/inspect_results.py`:
- Around line 137-164: Update the show_per_task docstring to document all four
emitted verdicts: solved, flaky, failed, and unmeasured, including that
unmeasured applies when a task has no recorded attempts. Keep the existing
attempt and pass@k behavior description unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a2eb5db6-98da-4d10-8bf5-c6d10e2e8fb1

📥 Commits

Reviewing files that changed from the base of the PR and between 11f923a and 3d85361.

⛔ Files ignored due to path filters (1)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/results.py is excluded by !sdk/**
📒 Files selected for processing (8)
  • docs/evaluator/agent-eval/reading-results.mdx
  • packages/nemo_evaluator_sdk/examples/gym/README.md
  • packages/nemo_evaluator_sdk/examples/gym/inspect_results.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/results.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_harbor_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_pass_at_k.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_persistence.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_task_metric_attempts.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_pass_at_k.py
  • packages/nemo_evaluator_sdk/examples/gym/README.md

@ngoncharenko
ngoncharenko force-pushed the ngoncharenko/aalgo-310-passatk-harbor-runner branch from 378ba22 to 8193be9 Compare August 11, 2026 19:18
@ngoncharenko

Copy link
Copy Markdown
Contributor Author

@coderabbitai continue the review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@ngoncharenko Continuing the review of #1224.

⚠️ Action not completed

No files to review.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

"'<metric_type>.<output>', plus per-semantic-view rollups named 'view.<name>'. "
"Failed or missing scores are surfaced as nan_count."
),
examples=[

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.

nit: Will these examples show up in openapi.yaml? In service mode, i am wondering if extensive example can blow up the size of openapi spec

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.

  • On the service side, this is stored in summary.json and not exposed in the API spec
  • Added this on purpose because the results is not a straightforward data structure and having illustrative examples should help both agents and humans.

),
examples=[
{
"contract-review-msa-indemnity": {

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.

So this essentially builds the reverse index of results ie task : {runner: [{trials: (name, score)}]}

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.

yes, to match harbor format

Add `AgentEvalSummary.task_metric_values`: the ordered per-attempt values for
each task, keyed `<metric_type>.<output>`, persisted into `summary.json`.
Answering "which tasks were flaky, and on which attempt?" previously meant
regrouping the flat task x trial x metric score list by hand.

Rebuild pass@k on top of that mapping instead of rescanning the scores, so the
per-attempt view and the published pass@k figures cannot disagree. pass@k means
are unchanged; a task that produced no trial at all now surfaces in `nan_count`
rather than silently shrinking the denominator.

Retention follows the declared output schema: continuous, discrete and boolean
values are kept, while labels and free models (token measurements) stay out even
when their emitted value happens to be numeric.

Rework the gym `inspect_results.py` example to read the summary directly rather
than re-deriving per-task outcomes from `scores.jsonl`.

Signed-off-by: Nick Goncharenko <ngoncharenko@nvidia.com>
Signed-off-by: Nick Goncharenko <ngoncharenko@nvidia.com>
Signed-off-by: Nick Goncharenko <ngoncharenko@nvidia.com>
Signed-off-by: Nick Goncharenko <ngoncharenko@nvidia.com>
@ngoncharenko
ngoncharenko force-pushed the ngoncharenko/aalgo-310-passatk-harbor-runner branch from 328f40d to 3eebc8b Compare August 12, 2026 03:55
*,
metric_type: str,
output_name: str,
) -> dict[str, list[float | 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.

I wonder if we should provide a stronger type here. Instead of:

{
"task-47": { "harbor_reward.reward": [
  { "trial_id": "task-47__7f3a9c", "value": 1.0 },
  { "trial_id": "task-47__1b8e42", "value": null }
]}
}

maybe use a model like:

class TrialOutcome:
  trial_id: str
  value: float | int | bool | None

class PerTaskOutcome(BaseModel):
  metric_name: str
  trials: list[TrialOutcome]

class PerTaskOutcomes(BaseModel):
  task_id: str
  outcomes: list[PerTaskOutcome]

We actually have the type AgentEvalAttemptValue which seems to match this TrialOutcome.

Comment on lines +114 to +118
if "task_metric_attempts" not in payload:
raise SystemExit(
f"{bundle / 'summary.json'} predates summary.task_metric_attempts, which this script reads "
"per-task outcomes from. Re-run the eval to produce a current bundle."
)

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.

SystemExit seems extreme for the exception, right? Maybe a custom exception would be appropriate here. Also, I assume the AgentEvalSummary.model_validate would have failed anyway? Why couldn't we just handle the model_validate failure instead?

"(trials.jsonl) and AgentEvalTaskScore.trial_id (scores.jsonl)."
)
)
value: float | None = Field(

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 there more values than float | None? I think this would correspond to int | bool too, right? What about str? I know our native metrics can output labels too.

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.

3 participants