feat(dataset-quality): count skills in trajectory coverage - #568
Conversation
…uality evaluation
The bump to 30 came from an earlier design with two activation scorers sharing a 30 budget, which we did not build. Covering more channels makes the measurement more complete, not more important than error recovery or composition, and upweighting the lowest-scoring metric lowers every dataset's grade for no stated reason. Restores the registry budget to 100.
|
/gcbrun |
…d tools and skills
…ine descriptions in trajectory coverage scorer
|
The denominator quadruples (0/15 → 5/63 in your own test), so dataset_quality scores for skills-bearing products aren't comparable across this change. What's the plan for the step change on the trends page — new series, or annotate the boundary? Same question for the dq_* rename: code is fine (the viewer renders metrics generically), but historical BigQuery rows keep the old keys, so any saved query over the blob breaks at the boundary. |
| raise SkillCatalogError(f"clone failed for {url}: {last_error}") | ||
|
|
||
|
|
||
| def _scan_dir(root: str, wanted: set[str] | None = None) -> list[Skill]: |
There was a problem hiding this comment.
This raises only when zero wanted names match. With skills: ["alpha", "typo", "gamma"] against a root holding alpha/beta/gamma, it returns 2 and stays silent — which is exactly the shrunken denominator the module docstring says it prevents. Can we raise on any unmatched name in wanted, and extend test_a_narrowing_key_matching_nothing_is_fatal to the partial-miss case?
There was a problem hiding this comment.
Good catch. Any unmatched name now raises with all the missing ones listed, and added a test for the partial-miss case.
| ), | ||
| )] | ||
| tools = self._fetch_tools(setup) | ||
| skills = resolve_skills(setup) |
There was a problem hiding this comment.
A skills-resolution failure now nulls the entire report, including the five judge sub-scorers that never touch skills. gemini_cli_skills_model.yaml:12 hardcodes an absolute path under one person's home dir — on any other machine that config goes from "grades the tools" to "ungraded." Should a skills failure degrade skill coverage rather than the whole grade?
There was a problem hiding this comment.
There's no separate skills sub-scorer to degrade, trajectory_coverage grades tools, scripts and skills as one union catalog, so a skills failure would shrink denominator and inflate the score rather lower it.
On gemini_cli_skills_model.yaml: that absolute path is worth fixing but it's separate file config change.
There was a problem hiding this comment.
Agreed that silently shrinking the catalog is worse than failing. But the degradable unit is trajectory_coverage, not a separate skills scorer: _run_scorers already catches a sub-scorer exception, marks it applicable=False, and grading.py reports it in excluded_scorers with the remaining weight. Moving resolve_skills into TrajectoryCoverageScorer.run would drop just that scorer's 20 weight on a bad catalog and still grade the five judges — no inflated denominator, no lost report. As it stands a bad setup.skills path nulls vague_examples, cuj_diversity, error_recovery, composition, and parameter_coverage, none of which touch the catalog.
There was a problem hiding this comment.
Good call. Split the try. Tool discovery still nulls the report since every sub-scorer reads tool_names, but a SkillCatalogError now just marks trajectory_coverage inapplicable, so it lands in excluded_scorers and the other 80 weight grades as normal. Added a test asserting exactly that.
Thanks for the review @prernakakkar-google! For the dq_ rename, nothing consumes those keys by name; the viewer renders whatever the blob holds. |
…te related documentation and versioning.
| # standard agent pipeline: a no-op generator does nothing, and the `dataset_quality` | ||
| # scorer grades the whole dataset holistically (each sub-scorer computes a 0-100 | ||
| # sub-score; grading rolls them into a weighted global score + letter grade). | ||
| # Grades a product's CUJ configs, not a model: no agent runs and nothing is |
There was a problem hiding this comment.
Can we simplify this to say It helps with Datset CUJ grading.
There was a problem hiding this comment.
Makes sense!
| stem, suffix = os.path.splitext(filename) | ||
| if filename.startswith(".") or filename == "__init__.py": | ||
| return False | ||
| if suffix.lower() in _NON_SCRIPT_SUFFIXES: |
There was a problem hiding this comment.
I think it would be more accurate to check in given list of .js, .ts, .py, .sh
| ), | ||
| )] | ||
| tools = self._fetch_tools(setup) | ||
| skills = resolve_skills(setup) |
There was a problem hiding this comment.
Agreed that silently shrinking the catalog is worse than failing. But the degradable unit is trajectory_coverage, not a separate skills scorer: _run_scorers already catches a sub-scorer exception, marks it applicable=False, and grading.py reports it in excluded_scorers with the remaining weight. Moving resolve_skills into TrajectoryCoverageScorer.run would drop just that scorer's 20 weight on a bad catalog and still grade the five judges — no inflated denominator, no lost report. As it stands a bad setup.skills path nulls vague_examples, cuj_diversity, error_recovery, composition, and parameter_coverage, none of which touch the catalog.
|
Few minor comments, otherwise LGTM |
…d of failing the entire dataset evaluation
trajectory_coverageonly looked at MCP tools. So a product that ships skills gotgraded against a surface its CUJs never name, and a skills-only product fell out of
the grade entirely.
The catalog is now every channel the product declares:
expected_trajectoryexpected_skillsNew
generators/models/skills_catalog.pyreadssetup.skills— clones a git URL orscans a local path, and takes the skill name from
SKILL.mdfrontmatter. An entrythat resolves to nothing raises instead of being skipped, since a short catalog
quietly inflates coverage.
Also renamed the sub-scorer metric keys — dropped the
dq_prefix and fixed namesthat no longer matched what they count (
total_tools→capabilities_total). Theylive inside a JSON blob, so nothing downstream changes.
Test plan
pytest test/dataset_quality_test.py -q— 88 passed0/15→5/63