Skip to content

fix(judge): reject ambiguous JSON responses - #772

Merged
seonghobae merged 1 commit into
mainfrom
codex/fast-judge-json-hardening
Aug 11, 2026
Merged

fix(judge): reject ambiguous JSON responses#772
seonghobae merged 1 commit into
mainfrom
codex/fast-judge-json-hardening

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • reject duplicate JSON object members at every nesting level
  • require the exact mode-specific top-level judge schema, including the advisory boolean
  • keep acceptance derived from validated scores; no keyword or positional repair

Validation

  • targeted Judge/IRT tests: 18 passed
  • full Python suite: 3402 passed, 2 warnings (existing optional PyYAML configuration warnings)
  • Rust workspace: cargo test --workspace passed

This change is intentionally fail-closed for LLM-as-a-Judge. Please review the exact-head checks and merge only through the repository's normal review and protection gates.

Summary by CodeRabbit

  • Bug Fixes
    • Improved validation of automated evaluation responses.
    • Responses with duplicate, missing, or unexpected fields are now rejected.
    • Ensured evaluation results include a valid boolean acceptance value.
  • Tests
    • Added coverage for invalid duplicate and unknown fields in evaluation responses.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d7a20d2d-e79e-4bee-b338-62c1f298d506

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The LLM judge now validates JSON responses against an exact schema. It rejects duplicate, missing, and unexpected fields. It also requires accepted to be boolean. Tests cover duplicate top-level, unknown top-level, and duplicate nested criterion fields.

Changes

Judge response validation

Layer / File(s) Summary
Strict response schema and validation
python/fast_mlsirm/llm_judge.py, tests/test_llm_judge.py
The parser detects duplicate JSON keys and requires an exact response field set. judge selects the required criterion field by category mode and requires boolean accepted. Tests cover duplicate and unexpected fields at the top level and inside criterion_scores.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 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 clearly and concisely describes the main change: rejecting ambiguous JSON responses in the judge.
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 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch codex/fast-judge-json-hardening
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fast-judge-json-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Validation update for exact head 7ccef2c:

  • Duplicate JSON object members are rejected at every nesting level.
  • Exact mode-specific top-level fields are required, including boolean accepted; no keyword or positional repair is introduced.
  • Targeted Judge/IRT tests: 18 passed. Full Python suite: 3402 passed with 2 existing optional-PyYAML warnings. Rust workspace test command cargo test --workspace passed.
  • PR remains open and unmerged while required checks and an independent authorized review are pending. Review and merge must use this exact head.

@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 (1)
python/fast_mlsirm/llm_judge.py (1)

425-434: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for non-boolean accepted.

The new validation rejects values such as 0, "true", and null. The changed tests cover duplicate and unexpected fields, but not this type contract. Add parameterized cases for non-boolean JSON values.

🤖 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 `@python/fast_mlsirm/llm_judge.py` around lines 425 - 434, Add a regression
test for the validation in the response-judging path around _response_object and
advisory_accepted, parameterizing non-boolean JSON values such as 0, "true", and
null. Assert each case raises JudgeFormatError with the expected
boolean-validation message, while preserving existing valid-boolean coverage.
🤖 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 `@tests/test_llm_judge.py`:
- Around line 111-126: Update the pytest.raises call in
test_judge_rejects_duplicate_and_unknown_top_level_fields to use a raw regex
string for its match argument, changing the existing "exactly|duplicate" pattern
to the raw-string form while preserving the test behavior.

---

Nitpick comments:
In `@python/fast_mlsirm/llm_judge.py`:
- Around line 425-434: Add a regression test for the validation in the
response-judging path around _response_object and advisory_accepted,
parameterizing non-boolean JSON values such as 0, "true", and null. Assert each
case raises JudgeFormatError with the expected boolean-validation message, while
preserving existing valid-boolean coverage.
🪄 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: Pro Plus

Run ID: f1ea7d49-235e-4e1f-94ea-b5e3c32b7f48

📥 Commits

Reviewing files that changed from the base of the PR and between 02fe62d and 7ccef2c.

📒 Files selected for processing (2)
  • python/fast_mlsirm/llm_judge.py
  • tests/test_llm_judge.py

Comment thread tests/test_llm_judge.py
Comment on lines +111 to +126
def test_judge_rejects_duplicate_and_unknown_top_level_fields() -> None:
duplicate = (
'{"score":0.8,"accepted":true,"rationale":"supported",'
'"criterion_scores":{"task_alignment":0.8,"factual_support":0.8},'
'"score":0.2}'
)
unknown = json.loads(_payload())
unknown["unexpected"] = "ignored fields are unsafe"
for answer in (duplicate, json.dumps(unknown)):
with pytest.raises(JudgeFormatError, match="exactly|duplicate"):
ContextualOrchestratorJudge(_FakeOrchestrator(answer)).judge(
task="task",
answer="answer",
criteria=CRITERIA,
)

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target lines ---'
cat -n tests/test_llm_judge.py | sed -n '100,135p'

printf '%s\n' '--- Ruff configuration and references ---'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
  'RUF043|ruff|pytest-raises|exactly\|duplicate' \
  pyproject.toml setup.cfg tox.ini .ruff.toml ruff.toml tests 2>/dev/null || true

printf '%s\n' '--- available Ruff executable ---'
if command -v ruff >/dev/null 2>&1; then
  ruff --version
  ruff check tests/test_llm_judge.py --select RUF043
else
  echo 'ruff is not installed'
fi

printf '%s\n' '--- relevant dependency declarations ---'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
  'ruff|pytest' . 2>/dev/null | head -80

Repository: ContextualWisdomLab/fast-mlsirm

Length of output: 8441


Use a raw regex literal for match.

Ruff RUF043 reports match="exactly|duplicate". Change it to match=r"exactly|duplicate".

🧰 Tools
🪛 ast-grep (0.45.1)

[info] 118-118: use jsonify instead of json.dumps for JSON output
Context: json.dumps(unknown)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 Ruff (0.16.1)

[warning] 120-120: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

🤖 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 `@tests/test_llm_judge.py` around lines 111 - 126, Update the pytest.raises
call in test_judge_rejects_duplicate_and_unknown_top_level_fields to use a raw
regex string for its match argument, changing the existing "exactly|duplicate"
pattern to the raw-string form while preserving the test behavior.

Source: Linters/SAST tools

Copy link
Copy Markdown
Contributor Author

@opencode-agent address

Exact-current bounded reconciliation/repair handoff for PR #772. Immediately before any write, refetch source head 7ccef2c914fe6a51a2ebeea0787c23dc8a42fcdf, protected main 02fe62d2e008634290c4d36c31b8771db61115d2, reviews/threads, and branch writer state; abort this lane if either ref moved or another source writer is active.

RCA: the PR is currently non-mergeable because its lineage diverged from current main; its net intended diff is only python/fast_mlsirm/llm_judge.py plus tests/test_llm_judge.py. Current protected main also contains the accepted #764/#768 JSON nesting-depth contract (MAX_JUDGE_JSON_DEPTH, _validate_raw_json_depth, public export) that this stale branch version must preserve. Reconcile current main non-destructively—no force push/destructive rebase/ours-theirs blanket resolution—and retain only the intended duplicate-key/exact-schema hardening on top of all accepted-main behavior.

Then fix the two valid current CodeRabbit findings test-first: (1) use a raw regex literal for the existing pytest.raises(... match=...) pattern; (2) add parameterized regression cases proving JSON accepted values 0, "true", and null fail with JudgeFormatError("accepted must be a boolean") while true/false remain valid. Preserve duplicate-key rejection at every nesting level, exact mode-specific top-level field sets, score-derived final acceptance, nesting-depth fail-closed behavior, bounded text/criteria/categories, and no keyword/positional repair.

Run focused LLM-judge/IRT tests, Ruff for changed tests/source, then the complete applicable Python suite and repository-required Rust/PyO3/package/security checks on one unchanged reconciled head. Update the PR body with exact head/base/run evidence; keep the PR Ready only if source is stable. Do not change dependencies, provider credentials, numerical psychometrics, version/release state, or unrelated docs. Stop writing this branch after the coherent repair so exact-head review/merge can proceed.

Combine nesting-depth bounds with duplicate-key rejection and exact
mode-specific top-level schema requirements so untrusted judge payloads
cannot smuggle alternate scores or unknown fields.
@seonghobae
seonghobae force-pushed the codex/fast-judge-json-hardening branch from 7ccef2c to 2ceeaea Compare August 11, 2026 20:07
@seonghobae
seonghobae merged commit c91ae21 into main Aug 11, 2026
23 of 25 checks passed
@seonghobae
seonghobae deleted the codex/fast-judge-json-hardening branch August 11, 2026 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant