Skip to content

Add manual eval suite for /bills LLM features#25

Open
mikaalnaik wants to merge 2 commits into
mainfrom
mikaal/bills-evals
Open

Add manual eval suite for /bills LLM features#25
mikaalnaik wants to merge 2 commits into
mainfrom
mikaal/bills-evals

Conversation

@mikaalnaik

Copy link
Copy Markdown
Contributor

Context

The /bills feature has two LLM touchpoints, both calling OpenAI gpt-5 via the Responses API, with no test or eval coverage today:

  • summarizeBillText (services/billApi.ts) — summary, 8 tenet evaluations, final_judgment (yes|no|abstain), 3 Question Period questions.
  • socialIssueGrader (services/social-issue-grader.ts) — binary "is this primarily a social issue" classifier.

This adds a manually-run eval suite so prompt edits, model changes, and parsing changes can be validated before shipping. It is run explicitly (pnpm eval:bills) and never wired into build, lint, or CI — LLM tokens are expensive.

What it does

  • Standalone tsx runner (pnpm eval:bills) with --refresh, --only=social|analysis, --grep, and --fallback flags. No test-framework dependency added (tsx already exists for seed:bills-admin).
  • Disk cache keyed by a hash of input text + prompt text (.cache/, gitignored). Editing a prompt invalidates its entries automatically; re-runs read from disk so iterating on checks/fixtures costs no tokens. --refresh forces a re-call.
  • Structural checks (deterministic, gate the run): non-empty summary/steel_man, exactly 8 tenets with valid aligns|conflicts|neutral, valid final_judgment, exactly 3 non-empty QP questions with no "Mr./Madam Speaker" prefix, no Build Canada/we/our self-reference.
  • Accuracy reporting (not gated — probabilistic): final_judgment vs label, social-issue confusion matrix with precision/recall.
  • Cross-consistency warning: flags where summarizeBillText abstains but socialIssueGrader disagrees — surfacing the known divergence that the two use separate prompts/criteria.
  • 8 hand-labeled synthetic fixtures spanning the decision space (social issue / pro-tenet economic / red-tape / administrative), plus a fetch-fixture.ts helper that bootstraps real bills via the existing fetchers + xmlToMarkdown.

Minimal edits to existing code: export SOCIAL_ISSUE_GRADER_PROMPT (for cache-keying), add the eval:bills script, gitignore .cache/.

Running

```bash
export OPENAI_API_KEY=sk-...
pnpm eval:bills # all fixtures (cached where possible)
pnpm eval:bills --refresh # bypass cache, re-call the API
pnpm eval:bills --fallback # force no-key fallback path (0 tokens)
```

Verification

  • Zero-token fallback run (--fallback): harness runs end-to-end — imports resolve, fixtures load, checks execute, report prints + writes report.json, exit code gates on structural failures.
  • tsc --noEmit clean; eslint clean on the new files.
  • Live API runs (accuracy numbers, cache hit/refresh, prompt-regression sensitivity) are intentionally left to run manually.

🤖 Generated with Claude Code

mikaalnaik and others added 2 commits July 6, 2026 15:21
Adds a token-conscious, manually-run eval suite for the two LLM
touchpoints in /bills: summarizeBillText and socialIssueGrader.

- Standalone tsx runner (`pnpm eval:bills`) with --refresh, --only,
  --grep, and --fallback flags; never wired into build/lint/CI.
- Disk cache keyed by input + prompt hash so grader/fixture iteration
  costs no tokens; --refresh forces a re-call.
- Structural checks on BillAnalysis (8 tenets, valid enums, exactly 3
  QP questions, no speaker prefix, no self-reference) gate the run;
  judgment/social-issue accuracy and cross-consistency are reported.
- 8 hand-labeled synthetic bill fixtures spanning the decision space,
  plus a fetch-fixture helper that bootstraps real bills via the
  existing fetchers + xmlToMarkdown.

Minimal edits to existing code: export SOCIAL_ISSUE_GRADER_PROMPT for
cache-keying, add eval:bills script, gitignore .cache/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrates the /bills LLM eval suite from synthetic fixtures to real
Parliament-45 bills, grounds the labels and prompt in Build Canada's
documented positions, fixes two eval-runner bugs, and documents the
Builder MP feature.

Eval runner:
- Load .env.local via tsx --env-file-if-exists in the eval:bills script,
  so a real OPENAI_API_KEY is actually picked up (tsx is not Next.js and
  does not auto-load env files).
- Drop the steel-man-present structural check: steel_man is a
  human-editable editorial field, not produced by the analysis prompt, so
  the check failed on every live run. README updated to match.

Real fixtures (replaces the 8 synthetic bills):
- 10 committed real bills spanning the decision space: social/rights ->
  abstain (S-227, S-221, C-9), clear tenet alignment -> yes (C-5, C-266),
  clear conflict -> no (C-284, C-202, C-253), and two genuinely two-sided/
  administrative bills left unlabeled for judgment (C-291, C-230).
- Labels grounded in deep research on Build Canada's stances; C-5 (the
  One Canadian Economy Act) is their named flagship-alignment example.

Prompt:
- Sharpen tenets 3 and 4 to name internal free trade + labour mobility
  and domestic value-added processing (their strongest documented levers).
- Add explicit align/conflict judgment signals so borderline calls stay
  consistent.

Docs:
- Add src/app/bills/README.md documenting the Builder MP feature
  (analysis model, tenets, data flow, auth, env, evals).

Baseline eval (pre-sharpening) scored 8/8 judgment and 100% social-issue
accuracy on the real fixtures; post-sharpening verification run pending.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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