Repository navigation
fix(physics_judge): carry domain through verify so per-domain metrics compute - #4812
Merged
Merged
Conversation
… compute physics_judge inherited math_with_judge's verify request and response models, which declare no `domain`. Pydantic's default extra="ignore" dropped it at /verify, so no rollout carried it and compute_metrics never built the per-domain breakdown the README promises. Declare `domain` on physics-scoped request/response models and echo it from verify(). Only `domain` is added: the other row fields stay out of the verify rows. The checked-in example rollouts gain the field. NVBug 6791004. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: qiaochuz <qiaochuz@nvidia.com>
Contributor
Author
|
/ok to test 20dd937 |
This was referenced Oct 21, 2026
yaoyu-33
reviewed
Oct 22, 2026
yaoyu-33
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the clear root-cause write-up and the before/after evidence. The fix looks right to us: /verify is registered from self.verify, so the PhysicsJudgeVerifyRequest body type takes effect, and domain now reaches the rows that compute_metrics reads.
What we checked at 20dd937c6:
- Reward semantics are unchanged.
_verify_answeris untouched, andPhysicsJudgeVerifyResponse(**response.model_dump(), domain=body.domain)round-trips losslessly, includingjudge_evaluationsandmask_sample. The parent response drops undeclared fields, so there is no duplicate-keyword clash. - Failure and null paths are covered. The judge-failsafe row is built from the subclass body, so
domainsurvives judge failures. A null or emptydomainadds no subset keys, becausecompute_subset_metricsskips falsy values. example_rollouts.jsonlonly gainsdomain. All five rows matchexample.jsonlvia_ng_task_index, and nothing else in them differs frommain.- The new tests are not vacuous. With
main'sapp.pyswapped in,test_shipped_rows_keep_domain_and_get_per_domain_metricsfails ([None]*5 != [...]). With the PR it passes, and the physics_judge test file runs 29 passed locally. - No other server is affected. Nothing else subclasses
PhysicsJudgeResourcesServer. - PR hygiene: the title, body, linked issue, validation list, rollout evidence and compatibility note are all in place.
No blocking issues found. Two optional observations, no action needed for this PR:
- A non-string
domain(for example5) now returns HTTP 422 from/verify, wheremainsilently ignored it. The shipped data uses strings, so this is probably the intended strictness. - The judge-enabled path of the new
verify()is not exercised by a test. It shares the samesuper().verify()call, so the risk is low.
yaoyu-33
approved these changes
Oct 22, 2026
yaoyu-33
left a comment
Contributor
There was a problem hiding this comment.
Approving after the team review above: no blocking issues at the reviewed head.
Contributor
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/39830708645 |
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
physics_judge now produces the per-domain metrics its README promises (
<domain>/pass@1[avg-of-k]/...). Onmain,/verifydrops each row'sdomain, so no rollout carries it and both the eval run's aggregate and a later/aggregate_metricscall return only the top-level keys. Scores themselves were never affected.Fixes NVBug 6791004. Closes #4509.
Cause
physics_judge has never defined its own verify models. It inherits
LibraryJudgeMathVerifyRequest/LibraryJudgeMathVerifyResponsefrom math_with_judge, which declarequestion/expected_answerand the scoring fields but nodomain, so pydantic's defaultextra="ignore"drops it at request validation.PhysicsJudgeResourcesServer.compute_metricsbuilds the breakdown only when a verify row hasdomain(resources_servers/physics_judge/app.py,if any("domain" in r ...)), which never happened. All of this dates from #1163, which added the server, the per-domain gate, the README text and the example rollouts (0/5 rows withdomain, against 5/5 inexample.jsonl). The existing per-domain unit test passed rows that already haddomainstraight tocompute_metrics, so it never went through/verify.Fix
PhysicsJudgeVerifyRequest/PhysicsJudgeVerifyResponseadddomain: Optional[str] = Noneon top of the math_with_judge models, andverify()returns the parent's result withdomaincarried over. A row withoutdomaingetsnull, andcompute_subset_metricsalready skips empty subset values, so it gets no subset keys.data/example_rollouts.jsonl: each committed row gains itsdomain(fromexample.jsonl, paired by_ng_task_index), which is what/verifynow returns. Every row is otherwise byte-identical.task_data.py: the module docstring said physics_judge has no request model of its own; it now names the new one./verifyroute and/aggregate_metrics(no judge call) and must come back with theirdomainand with<domain>/pass@1/...keys for all five domains; a row withoutdomainmust give exactly the keys of a row that has nodomainfield at all.Not chosen:
ConfigDict(extra="allow")on both models, asugphysics_judgedoes forsubject. That echoes every row field into each rollout, including the referencesolutiontext anddifficulty/answer_type/language, which nothing reads and the README doesn't promise. Not chosen either: relaxing the shared math_with_judge models, which math_with_judge itself (referenced by 26 server and benchmark configs) and three other subclasses use.Before the fix
Gym
c07c935d8(mainwhen reproduced; nothing under physics_judge, math_with_judge or the aggregation path changed up tobedf56450): the five shipped rows posted to the real/verifyroute in-process (symbolic only, no model), then the returned rows posted to/aggregate_metrics:New test on
main'sapp.py:QA testcase
physics_judgeon one H100 (the README'sgym env start+gym eval run --agent physics_judge_simple_agentover the five shipped rows, Qwen/Qwen3-8B on vLLM as policy, the shipped NIMopenai/gpt-oss-20bjudge), Gymc1582e18d:The saved rollouts have
domainin 0/5 rows and the aggregate has 0 domain keys, withmean/reward1.0.After the fix
Gym
20dd937c6(this branch, onbedf56450), same script:Same QA testcase, Gym
7feb1bf1a(this change before its rebase ontobedf56450; same diff):The saved rollouts carry
domainin 5/5 rows and the persisted and HTTP aggregates both have all 30 domain keys (e.g.electromagnetism/pass@1/symbolic_accuracy100.0,electromagnetism/pass@1/no_answer0.0), matching the case's independent oracle.Validation
physics_judge: 0/1 onmainc1582e18d, 1/1 on this branch. Both runs used the QA suite with its fix for an unrelated stale routing check (the case expected--agentto restampagent_refas{'name': ...}, which no longer matches main since fix(grl): start GRL READMEs without the unbound gymnasium_agent and keep agent_ref on --agent #4544; that QA fix is pending in the suite), so only Gym differs between them.resources_servers/physics_judge/tests+resources_servers/math_with_judge/tests: 159 passed; the new domain test fails onmain'sapp.py, the null-domain guard passes on both.gym env test +entrypoint=resources_servers/physics_judge +should_validate_data=true: 29 passed, data validated.tests/unit_tests/test_task_data.py: 401 passed.pre-commit run --all-files: passed, no changes.completed, empty failures sidecar, nomask_sample; identical policy outputs on both commits (output tokens 1274/2145/2076/1120/1290), all five correct on the symbolic path (reward 1.0, no judge call). The case's live judge controls made 10 judge calls, 10/10 with a parseable verdict. Onlydomain(0/5 → 5/5 rows) and the per-domain keys (0 → 30) changed.Compatibility
Rollouts from physics_judge gain one field,
domain(nullwhen the row has none), and aggregates gain the per-domain keys when rows have a domain. Top-level metrics and rewards are unchanged. No other server is touched.🤖 Generated with Claude Code