chore: add eval-config validation check#120
Conversation
7dff249 to
9ce2973
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Adds an eval-config check to the repo’s scripts/check.py harness to validate evals/**/config.json files against a shared JSON Schema and additional cross-file rules (refs, prompt files, sha drift, placeholders), and updates existing evaluators/fixtures/notebooks to conform to the new shared config + fixtures conventions.
Changes:
- Introduces
scripts/checks/eval_config.pyand wires it into the harness; addsjsonschemadependency. - Adds shared schemas under
evals/_schemas/and updates evaluatorconfig.jsonfiles to reference them via$schema. - Normalizes fixtures + notebooks to use
fixtures.pathand updates fixture expected keys (complexity_level→complexity_score) where applicable.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/requirements.txt | Adds jsonschema dependency for schema-based validation. |
| scripts/README.md | Documents the new eval-config check and updates planned follow-ups. |
| scripts/checks/eval_config.py | Implements the new evaluator config validation check (schema + cross-file rules). |
| scripts/checks/base.py | Adds shared helpers for locating evaluator configs and loading JSON. |
| scripts/checks/init.py | Registers EvalConfig in the harness check registry. |
| evals/standards/math-question-alignment/config.json | Migrates config to $schema-based shared schema and aligns step/preprocessing fields. |
| evals/purpose_evaluator.ipynb | Updates notebook fixture loading to fixtures.path and expected key naming. |
| evals/prompts/purpose/fixtures.json | Renames expected field to complexity_score to match output schema naming. |
| evals/prompts/purpose/config.json | Migrates to shared $schema, adds required evaluator fields, and standardizes fixtures path. |
| evals/literacy/qualitative-text-complexity/intertextuality/fixtures.json | Renames expected field to complexity_score. |
| evals/literacy/qualitative-text-complexity/intertextuality/example_notebook.ipynb | Updates notebook fixture loading + expected key naming to match shared conventions. |
| evals/literacy/qualitative-text-complexity/intertextuality/config.json | Removes obsolete LangChain-era fields and migrates to shared $schema conventions. |
| evals/feedback/productive-coaching-writing-feedback/manageable/example_notebook.ipynb | Updates fixture loading to fixtures.path. |
| evals/feedback/productive-coaching-writing-feedback/manageable/config.json | Migrates to shared $schema, adds required evaluator fields, standardizes fixtures path. |
| evals/feedback/productive-coaching-writing-feedback/appropriate-feedback/example_notebook.ipynb | Updates fixture loading to fixtures.path. |
| evals/feedback/productive-coaching-writing-feedback/appropriate-feedback/config.json | Migrates to shared $schema, adds required evaluator fields, standardizes fixtures path. |
| evals/feedback/productive-coaching-writing-feedback/anchored-in-student-response/example_notebook.ipynb | Updates fixture loading to fixtures.path. |
| evals/feedback/productive-coaching-writing-feedback/anchored-in-student-response/config.json | Migrates to shared $schema, adds required evaluator fields, standardizes fixtures path. |
| evals/feedback/productive-coaching-writing-feedback/actionable-revision/example_notebook.ipynb | Updates fixture loading to fixtures.path. |
| evals/feedback/productive-coaching-writing-feedback/actionable-revision/config.json | Migrates to shared $schema, adds required evaluator fields, standardizes fixtures path. |
| evals/feedback/productive-coaching-writing-feedback/acknowledges-strength/example_notebook.ipynb | Updates fixture loading to fixtures.path. |
| evals/feedback/productive-coaching-writing-feedback/acknowledges-strength/config.json | Migrates to shared $schema, adds required evaluator fields, standardizes fixtures path. |
| evals/_schemas/fixtures.schema.json | Adds shared fixtures schema contract (structural only). |
| evals/_schemas/config.schema.json | Adds shared config schema contract used by evaluator config.json via $schema. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…fix notebook typo
czi-fsisenda
left a comment
There was a problem hiding this comment.
Looks good! Approved not to block, but got a couple of comments.
Separately, the configs are clean and well structured, but a lot less ergonomic for tinkering with the notebooks.
It's not impossible to do experimentation with the notebooks. Just more complex.
We should check how users of the notebooks feel about the updates.
| "evaluator": { | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": ["id", "name", "description", "supported_grades"], |
There was a problem hiding this comment.
[P0] I don't think we should require supported_grades. We have seen evals that don't have grade as an input. input_schema is required and can enforce this.
| "id": { "type": "string", "minLength": 1 }, | ||
| "name": { "type": "string", "minLength": 1 }, | ||
| "description": { "type": "string", "minLength": 1 }, |
There was a problem hiding this comment.
[P1?] "minLength": 1 feels like it implies a single character id, name, or description is ok. Can we go with "required": true or "allow_empty": false instead. Functionally the same, but no implicit endorsement of single character values for these fields.
| "type": "array", | ||
| "minItems": 1, | ||
| "items": { | ||
| "enum": ["K", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] |
There was a problem hiding this comment.
[P1] I've been using int for grades with 0 = 'K'. I don't see a strong reason to prefer one over the other. Maybe ints are safer for mapping grade bands to grades? Either way, we should be consistent.
[P0] And from earlier comment, I think supported_grades should be an input field not an evaluator property.
| "required": ["id", "type"], | ||
| "properties": { | ||
| "id": { "type": "string", "minLength": 1 }, | ||
| "type": { "enum": ["llm", "api"] }, |
There was a problem hiding this comment.
[P1] Should we allow for a "function" type too? If there's a common step in a several evals that is not an llm or api call, we could still represent it as a step, e.g. Flesch-Kincaid.
| "additionalProperties": false, | ||
| "required": ["provider", "name"], | ||
| "properties": { | ||
| "provider": { "enum": ["google", "openai", "anthropic"] }, |
There was a problem hiding this comment.
[P1] Should we include "custom" even if we haven't implemented support yet?
| }, | ||
| "expected": { | ||
| "complexity_level": "slightly_complex" | ||
| "complexity_score": "slightly_complex" |
There was a problem hiding this comment.
[P2] Common outputs or output components for evaluator suites too?
| "preprocessing": [ | ||
| { | ||
| "id": "fk_score", | ||
| "type": "computation", |
There was a problem hiding this comment.
[P1] Oh! We do have a "function" step. What is the benefit in having a distinction between step and preprocessing?
|
|
||
| 2. Cross-file — rules a schema cannot express because they span files: | ||
| - referenced files actually exist ($ref schemas, prompt files, fixtures) | ||
| - each declared sha256 matches the prompt file on disk (drift tripwire) |
There was a problem hiding this comment.
[P2] An alternative approach could be to generate a flat file from all the config jsons and have that represent a version of the eval. If the generated file changes, then we have a new version.
The generation step can be a proxy for a number of the checks. Generation fails if files are missing or are invalid.
| - referenced files actually exist ($ref schemas, prompt files, fixtures) | ||
| - each declared sha256 matches the prompt file on disk (drift tripwire) | ||
| - placeholders declared in config line up with the {vars} in the prompts | ||
| - system prompts carry no user-input placeholders |
There was a problem hiding this comment.
[P0] Makes logical sense, but is it a necessary constraint? What if there is some valid reason to have a dynamic system prompt? This constraint may be too eager.
| - each declared sha256 matches the prompt file on disk (drift tripwire) | ||
| - placeholders declared in config line up with the {vars} in the prompts | ||
| - system prompts carry no user-input placeholders | ||
| - no obsolete format-instruction placeholders survive anywhere |
There was a problem hiding this comment.
[P2] Good for us to have automated enforcement for established patterns. But also wondering if this is too strict; having us lose flexibility. Could a warning work rather than a failure?
What
Adds a second check to the harness:
eval-configs, validating everyevals/**/config.jsonagainst repo conventions.Rules enforced
$refschemas, messagesource_path, fixtures path)sha256matches the prompt file (drift tripwire)parser.kind == "structured_output"Check-only — config intent isn't safely auto-fixable (notably
sha256, a deliberate drift tripwire).It immediately caught a real leftover
intertextuality/config.jsonstill declared aformat_instructionsplaceholder (and staleparsersub-fields) from the old LangChain pattern — unused since the switch tostructured_output. Fixed here so the repo is green.Deferred (noted in
scripts/README.md)Fixture-label-in-enum validation needs a declared fixture→output-field mapping first, since some evaluators rename the field (
complexity_score→complexity_level). Naming/structure conventions to follow.