fix(critics): read instance_id from raw JSON in get_completed_instances#740
Conversation
get_completed_instances() was calling EvalOutput.model_validate(data) on every line and silently dropping any line that failed validation. When a resume run loads a partial archive written by an older SDK (e.g. with browser tool/observation events whose pydantic discriminators no longer match current models), every completed row gets dropped — so _get_instances_for_attempt() thinks nothing has been completed and re-runs every selected instance from scratch. This is the root cause of evaluation#515. Read 'instance_id' directly from the parsed JSON dict instead. We do not need a fully-validated EvalOutput to decide whether an instance was already processed; we just need its id. Malformed JSON and rows missing instance_id are still skipped with a warning. get_failed_instances() legitimately needs the full EvalOutput model to hand to critic.evaluate(), so its behaviour is left unchanged — on a stale archive it will simply find zero failed instances, which combined with the now-correct completed set produces an empty retry list (the conservative "trust the prior completion" choice). Regression test asserts a JSONL with a mix of stale-schema rows, fresh rows, blank lines, malformed JSON, and rows missing instance_id is parsed correctly. The new test fails on main and passes with this fix. Co-authored-by: openhands <openhands@all-hands.dev>
|
✅ Review complete. This review was performed through OpenHands Cloud Automation. You can log in and view the conversation here. |
all-hands-bot
left a comment
There was a problem hiding this comment.
Code Review
Taste Rating: 🟢 Good taste — Elegant, simple solution
Summary
Clean, well-scoped fix that solves the stale schema problem without adding complexity. The change is minimal and focused: read instance_id directly from raw JSON instead of validating the full EvalOutput model.
Changes
-
benchmarks/utils/critics.py: Modifiedget_completed_instances()to parseinstance_iddirectly from raw JSON lines, bypassingEvalOutput.model_validate()which fails on stale archives with incompatible pydantic discriminators. -
tests/test_iterative_resume.py: Added regression test covering stale schema rows, fresh rows, blank lines, malformed JSON, and missinginstance_id.
Observations
-
Good error handling: Blank lines, malformed JSON, and missing
instance_idare all handled gracefully with warnings. -
Clear docstring: The updated docstring explains exactly why raw JSON parsing is needed (schema drift tolerance).
-
Test coverage: The test covers 5 edge cases including the stale schema regression case (
evaluation#515). -
No breaking changes: The function signature and return type are unchanged. All existing behavior is preserved.
-
Minimal diff: The change is focused and doesn't introduce unnecessary abstractions.
[RISK ASSESSMENT]
- [Overall PR]
⚠️ Risk Assessment: 🟢 LOW - No breaking changes to existing behavior. The change only affects error handling (what was previously a validation error is now handled gracefully). Type signatures unchanged.
VERDICT: ✅ Worth merging — Core logic is sound, well-tested, and solves a real compatibility issue.
KEY INSIGHT: Bypassing pydantic validation for schema drift tolerance is the right trade-off here — instance_id is a simple string field that doesn't need validation.
This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation. View conversation
|
@OpenHands please fix precommits checks |
1 similar comment
|
@OpenHands please fix precommits checks |
|
I'm on it! juanmichelini can track my progress at all-hands.dev |
|
I'm on it! juanmichelini can track my progress at all-hands.dev |
…mat) Co-authored-by: openhands <openhands@all-hands.dev>
SummaryRequest: "@OpenHands please fix precommits checks" on PR #740. Work done:
Checklist:
The other passing check ( |
|
@OpenHands-AI[bot] it looks like you haven't created an OpenHands account yet. Please sign up at OpenHands Cloud and try again. |
SummaryThe pre-commit failure on PR #740 has already been resolved on the remote branch — no new changes were required from me. What I found
Final PR #740 check status
Checklist against the user's request
No further action is needed. |
Root-cause half of the swebenchmultimodal resume failure tracked as evaluation#515. The other half is evaluation#576 (revert of a wrapper-script workaround that made things worse); the two PRs together restore working resume for both fresh and stale archives.
Bug
get_completed_instances()callsEvalOutput.model_validate(data)on every line and silently swallows any exception:When a resume run loads a partial archive written by an older SDK whose tool/observation event schema has since drifted (e.g.
BrowserNavigateTool,BrowserObservationentries that the current pydantic models no longer recognise), every completed row hits theexceptbranch and gets dropped._get_instances_for_attempt()then computesall_instances - completed_in_attemptand concludes that nothing has been completed, so the iterative loop re-runs every selected instance from scratch.Fix
Read
instance_iddirectly from the parsed JSON dict. We do not need a fully validatedEvalOutputto decide whether an instance was already processed — we just need its id. Malformed JSON, blank lines, and objects missinginstance_idare still skipped with a warning.get_failed_instances()legitimately needs the full model (to hand tocritic.evaluate()), so it's left as is. On a stale archive it will find zero failed instances; combined with the now-correct completed set this produces an empty retry list — the conservative "trust the prior completion" choice, which matches what a resume is supposed to do.Test
Added
test_get_completed_instances_tolerates_stale_archive_schemawhich writes a JSONL with:BrowserNavigateToolhistory event that failsEvalOutput.model_validateEvalOutputinstance_idAsserts only rows 1 and 2 land in the returned set. The test fails on
main(row 1 gets dropped, set is{"instance_fresh"}) and passes with this PR.Companion PR
--output-dir "$OUTPUT_DIR_PATH"line it added was nesting<dataset>/<model_folder>under an already-leaf path, so the restored archive was sitting at one path while the benchmark was looking at another. That bug also surfaces as fix: cache SDK sdist across image builds #515.Either PR alone fixes one shape of the bug; landing both fixes the user-visible "swebenchmultimodal resume runs all 67 instances anyway" problem (e.g. eval-monitor run
27027881388) end-to-end.cc @simonrosenberg @juanmichelini
This PR was created by an AI agent (OpenHands) on behalf of @juanmichelini.
@juanmichelini can click here to continue refining the PR