feat: deterministic checks with judge-free runs and a --fail-on-checks CI gate - #52
Open
brainsparker wants to merge 1 commit into
Open
feat: deterministic checks with judge-free runs and a --fail-on-checks CI gate#52brainsparker wants to merge 1 commit into
brainsparker wants to merge 1 commit into
Conversation
…hema) with judge-free runs and a --fail-on-checks CI gate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 this adds
Deterministic checks: local, zero-cost assertions declared per test case, evaluated against the model response with reproducible verdicts.
Six check types:
contains,not_contains,regex,exact_match,json_valid,json_schema(a minimal built-in subset: type, required, properties, items, enum; no new dependency). JSON checks tolerate fenced ```json blocks.How to use:
checks:to any golden set test case. Outcomes land in the console summary, JSON and CSV exports, and the JUnit report (failed check maps to a<failure type="CheckFailure">).promptlens run config.yaml --fail-on-checks(exit code 2 on any failed check, consistent with--fail-under).judge: { enabled: false }. A case whose checks all pass now counts as a JUnit pass even without a judge score.examples/golden_sets/structured_output.yamlandexamples/configs/checks_only.yaml.Also fixed in passing:
output.formatsvalidation rejected"junit"even though the CLI wires a JUnit exporter;junitis now an accepted format.Why this, why now (market rationale)
PromptLens already has a strong CI story (JUnit export,
--fail-under,promptlens compare --fail-on-regression), but every verdict flowed through the LLM judge. That means CI needs a judge API key, every run costs money, and the gate is not reproducible: the same output can score 4 one run and 3 the next.The field has converged on deterministic assertions as table stakes, with LLM-as-judge reserved for what string matching cannot see:
exact,contains,regex,json_schemagraders and runs CI with--deterministic-onlybecause judge scores "aren't reproducible enough to gate on": https://pypi.org/project/gatecheck/--fail-underCI gating: https://github.com/harness/harness-evalsThis was the largest capability gap between PromptLens and the tools it competes with, and it strengthens the existing differentiators (CI-native gates, local-first, no cloud backend) rather than adding a side feature.
Runner-ups considered today: a GitHub Copilot instructions export target for you.md, and further A2A interop work on youagent (already in flight via the Handler guide PR).
Implementation notes
promptlens/models/checks.py:Check(validated at load time, so a bad regex or missing field failspromptlens validate, not mid-run) andCheckResult.promptlens/checks.py: pure evaluation engine, no I/O.EvaluationResultgainscheck_resultspluschecks_passedandfailed_checkshelpers;RunResult.get_check_stats()aggregates per model. Old stored results still parse (fields default empty).judge.enabledis false, so checks-only runs never construct a judge provider.Test status
python3 -m pytest tests/), including 53 new tests covering the engine (all six types, case sensitivity, any/all modes, fenced JSON, schema subset edge cases like bool vs integer), model validation, YAML loading, JUnit mapping precedence, CSV columns, the gate helper, and backward compatibility.judge.enabled: false: 4 golden set cases ran with zero API keys, the deliberately failing regex case surfaced asCheckFailurein junit.xml and tripped--fail-on-checks.