Skip to content

Normalize eval outputs to binary YES/NO - #49553

Closed
pelikhan with Copilot wants to merge 6 commits into
mainfrom
copilot/evals-daily-feature-report
Closed

Normalize eval outputs to binary YES/NO#49553
pelikhan with Copilot wants to merge 6 commits into
mainfrom
copilot/evals-daily-feature-report

Conversation

Copilot AI commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The daily evals report showed degraded signal quality because binary eval questions were surfacing UNKNOWN, which inflated ambiguity in per-workflow pass rates and aggregate YES-rate reporting. This change makes eval outputs strictly binary so missing or indeterminate evidence is recorded consistently as NO.

  • Eval parsing

    • Normalize parsed eval answers to YES or NO only.
    • Treat missing, malformed, and explicit UNKNOWN outputs as NO.
    • Preserve existing ID-based and positional answer extraction paths.
  • Judge prompt contract

    • Update the eval prompt so the judge is instructed to emit only:
      • <question-id>: YES
      • <question-id>: NO
    • Change the insufficient-evidence fallback from UNKNOWN to NO.
  • Rendered summaries

    • Remove UNKNOWN from step-summary rendering.
    • Show only binary answer rows and YES/NO tallies.
  • Docs and regression coverage

    • Update evals docs to reflect binary-only behavior.
    • Extend JS tests to cover normalization of missing and explicit UNKNOWN answers.

Example of the new behavior:

// before
{"id":"report_created","answer":"UNKNOWN"}

// after
{"id":"report_created","answer":"NO"}

Run: https://github.com/github/gh-aw/actions/runs/30715596263

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 9.8 AIC · ⌖ 7.06 AIC · ⊞ 8.1K ·
Comment /souschef to run again

Copilot AI linked an issue Aug 1, 2026 that may be closed by this pull request
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Update daily evals feature report for 2026-08-01 Normalize eval outputs to binary YES/NO Aug 1, 2026
Copilot AI requested a review from pelikhan August 1, 2026 08:14
@pelikhan

pelikhan commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

@copilot review https://arxiv.org/abs/2606.27226. In particular,

"Each verdict does come with a natural-language explanation alongside the binary judgment, which supports interpretability, but that's a rationale for the yes/no call, not an escape hatch to abstain."

Update the format to support a short single sentence to explain the rationale.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot review https://arxiv.org/abs/2606.27226. In particular,

"Each verdict does come with a natural-language explanation alongside the binary judgment, which supports interpretability, but tha...

Addressed in 230ad4d. Eval responses now support a short single-sentence rationale alongside the binary YES/NO verdict, and the parser, JSONL output, step summary, docs, and regression tests were updated accordingly.

@pelikhan

pelikhan commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

@copilot apply string escaping to rationale

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot apply string escaping to rationale

Addressed in 7e08489. Rationale text in the eval step summary is now escaped for &, <, and > before rendering, and the summary test covers the escaped rationale/output path.

@pelikhan

pelikhan commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

@copilot rational should be passed through the string sanitizer

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot rational should be passed through the string sanitizer

Addressed in 4ae4c1d. Eval rationale is now passed through sanitizeContent before writing evals.jsonl, and the parser test covers rationale sanitization for script-like tags and mentions.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

PR Triage

Category: feature · Risk: low · Priority: medium · Score: 43/100 (impact 18, urgency 12, quality 13)

Recommended action: defer

Normalizes eval outputs to binary YES/NO to reduce ambiguity in daily eval reporting. Draft, 6 comments already in discussion, no CI yet — defer pending review resolution and undraft.

Generated by 🔧 PR Triage Agent · auto · 49.2 AIC · ⌖ 8.64 AIC · ⊞ 8K ·

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

PR Triage

  • Category: feature
  • Risk: low
  • Priority: low
  • Score: 38/100 (Impact20/Urgency10/Quality8)
  • Recommended action: defer

Draft; normalizes eval outputs to binary YES/NO, needs undraft + CI.

Generated by 🔧 PR Triage Agent · auto · 73.4 AIC · ⌖ 10.1 AIC · ⊞ 8K ·

@pelikhan
pelikhan marked this pull request as ready for review August 1, 2026 19:32
Copilot AI review requested due to automatic review settings August 1, 2026 19:32
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (0 additions detected).

Copilot AI 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.

Pull request overview

Normalizes BinEval results to binary YES/NO while adding rationale capture and rendering.

Changes:

  • Converts missing, malformed, and UNKNOWN answers to NO.
  • Adds sanitized rationale parsing and summary display.
  • Updates tests and documentation for the binary contract.
Show a summary per file
File Description
actions/setup/js/run_evals.cjs Implements binary normalization and rationale parsing.
actions/setup/js/run_evals.test.cjs Tests parsing, normalization, and rationales.
actions/setup/js/render_evals_summary.cjs Renders binary tallies and rationales.
actions/setup/js/render_evals_summary.test.cjs Tests updated summary output.
.github/aw/evals.md Documents binary results and rationale records.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +318 to +324
function normalizeRationale(rationale) {
return sanitizeContent(
String(rationale)
.replace(/[\r\n]+/g, " ")
.replace(/\s+/g, " ")
.trim()
).trim();

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

The normalization logic is correct and well-covered by tests.

  • normalizeEvalAnswer cleanly handles empty, UNKNOWN, and mixed-case inputs.
  • The positional fallback now triggers on !parsed.answer (empty string) rather than === "UNKNOWN". This is intentional: an explicit UNKNOWN from the LLM now normalizes to NO via normalizeEvalAnswer instead of falling back to positional lookup — consistent with the stated goal.
  • The searchContent order swap (extracted text first) correctly prioritizes structured assistant output for ID-based matching.
  • Rationale sanitization via sanitizeContent prevents injection through rationale fields.

LGTM.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 25.8 AIC · ⌖ 9.2 AIC · ⊞ 5.4K

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 93/100 — Excellent

Analyzed 17 test(s): 17 design, 0 implementation, 0 violation(s).

📊 Metrics (17 tests)
Metric Value
Analyzed 17 (JS: 17, Go: 0)
✅ Design 17 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 13 (76%)
Duplicate clusters 0
Inflation No (0.79 ratio)
🚨 Violations 0
Test File Classification Issues
parses JSONL, skips malformed lines, and preserves rationale text render_evals_summary.test.cjs behavioral_contract, design_test None
renders tallies, escapes table cells, rationale text, and includes the model render_evals_summary.test.cjs behavioral_contract, design_test None
returns an empty string for empty results render_evals_summary.test.cjs behavioral_contract, design_test None
logs and returns early when the output file is empty or malformed render_evals_summary.test.cjs behavioral_contract, design_test None
writes the evals details section to the step summary render_evals_summary.test.cjs behavioral_contract, design_test None
stores the workflow run id and rationale when writing eval records run_evals.test.cjs behavioral_contract, design_test None
sanitizes rationale text before writing eval records [NEW] run_evals.test.cjs behavioral_contract, design_test None
builds setup prompt with binary YES/NO and rationale guidance run_evals.test.cjs behavioral_contract, design_test None
parses answers from Pi v3 JSONL turn_end events (positional format) run_evals.test.cjs behavioral_contract, design_test None
parses answers from Pi v3 JSONL turn_end events (mixed YES/NO) run_evals.test.cjs behavioral_contract, design_test None
parses answers from Pi v3 JSONL turn_end events (id-based format) run_evals.test.cjs behavioral_contract, design_test None
parses multiple ID-based answers from Claude engine's native assistant JSONL event run_evals.test.cjs behavioral_contract, design_test None
normalizes missing answers to "NO" run_evals.test.cjs behavioral_contract, design_test None
normalizes explicit "UNKNOWN" answers to "NO" [NEW] run_evals.test.cjs behavioral_contract, design_test None
extractAssistantTextFromJsonlLog (6 helper tests) run_evals.test.cjs behavioral_contract, design_test None
✨ Quality Highlights

Design Invariants Protected

  1. Binary normalization: All answers normalize to YES/NO only (2 dedicated tests)
  2. Rationale preservation: Rationale flows through entire pipeline (15+ tests)
  3. Security escaping: HTML entities and script tags properly neutralized (2 security tests)
  4. Format compatibility: Multiple event formats (Pi v3, Claude native, legacy, positional, ID-based)

Coverage Excellence

  • Edge cases: Missing data, malformed JSON, empty timestamps, missing fields ✓
  • Security: HTML escaping, <script> tag sanitization, @ mention escaping ✓
  • Error handling: Fallbacks, field normalization, early exits ✓
  • Format varieties: 6 different parser paths tested for future-proof regression protection ✓

Test Inflation: EXCELLENT

  • render_evals_summary.test.cjs: 0.77 (10 test lines vs 13 prod lines)
  • run_evals.test.cjs: 0.79 (42 test lines vs 53 prod lines)
  • Overall: 0.79 ratio (well below 2:1 threshold)

Verdict

Passed. Implementation tests: 0% (threshold: 30%). All 17 tests are high-value behavioral contracts with zero design debt. Excellent security coverage (sanitization), strong format compatibility (6 parser paths), and low test inflation. No violations.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 25.2 AIC · ⌖ 7.52 AIC · ⊞ 8.4K ·
Comment /review to run again

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

✅ Test Quality Sentinel: 93/100 Excellent. All 17 tests are high-value behavioral contracts (100% design tests, 0% implementation). Zero duplicates, low inflation ratio (0.79), comprehensive security coverage. No violations detected.

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

Skills-Based Review 🧠

Applied /tdd and /codebase-design — two non-blocking observations.

📋 Key Themes & Highlights

Key Themes

  • Missing pure-function unit tests: normalizeEvalAnswer and normalizeRationale are exported and now form the normalization contract, but they are only exercised through parseMain integration tests. Cheap isolated unit tests would protect the contract directly.
  • Rationale normalization divergence: render_evals_summary.cjs does a bare .trim() on rationale when reading stored JSONL, whereas run_evals.cjs uses the full normalizeRationale (including sanitizeContent). These two paths should share the same function.

Positive Highlights

  • ✅ Clean normalizeEvalAnswer function — single responsibility, easy to reason about
  • extractedText + " " + logContent ordering fix is a correct and well-reasoned improvement for the JSONL log search
  • ✅ Judge prompt update is coherent: removing UNKNOWN from the format spec and adding rationale guidance in one atomic change
  • ✅ Good test coverage on the integration paths and edge cases (explicit UNKNOWN, missing answer, sanitized rationale)
  • ✅ HTML entity escaping added to escapeMarkdownCell is a welcome correctness fix

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 37.8 AIC · ⌖ 8.35 AIC · ⊞ 7.1K
Comment /matt to run again

@@ -227,7 +254,22 @@ describe("run_evals.cjs", () => {
await parseMain();

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.

[/tdd] No isolated unit tests for normalizeEvalAnswer and normalizeRationale — both are now exported pure functions but are only tested through parseMain integration paths.

💡 Suggested tests
describe('normalizeEvalAnswer', () => {
  it('normalizes YES (case-insensitive)', () => expect(normalizeEvalAnswer(' yes ')).toBe('YES'));
  it('normalizes UNKNOWN to NO', () => expect(normalizeEvalAnswer('UNKNOWN')).toBe('NO'));
  it('normalizes empty string to NO', () => expect(normalizeEvalAnswer('')).toBe('NO'));
});

describe('normalizeRationale', () => {
  it('collapses newlines to spaces', () => expect(normalizeRationale('a
b')).toBe('a b'));
  it('sanitizes script tags', () => expect(normalizeRationale('<script>')).not.toContain('<script>'));
});

Pure-function tests make normalization-contract regressions immediately visible and cheap to run.

@copilot please address this.

.trim()
.toUpperCase(),
answer: normalizeEvalAnswer(String(record.answer ?? "")),
rationale: String(record.rationale ?? "").trim(),

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.

[/codebase-design] render_evals_summary.cjs re-implements rationale normalization (String(record.rationale ?? '').trim()) independently, while run_evals.cjs has the canonical normalizeRationale for this purpose. Divergence risks subtle differences (no sanitization here) if rationale handling evolves.

💡 Suggestion

Import and reuse normalizeRationale from run_evals.cjs here, the same way normalizeEvalAnswer is already imported:

const { normalizeEvalAnswer, normalizeRationale } = require('./run_evals.cjs');
// ...
rationale: normalizeRationale(String(record.rationale ?? '')),

This keeps the normalization contract in one place.

@copilot please address this.

@pelikhan

pelikhan commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot quick triage for this PR:

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 9.8 AIC · ⌖ 7.06 AIC · ⊞ 8.1K ·
Comment /souschef to run again

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[evals] Daily Evals Feature Report - 2026-08-01

4 participants