feat(llm): eval-tags --dump writes every golden comment's shortlist scores and verdicts for offline threshold analysis (refs #577)

This commit is contained in:
kert
2026-09-22 17:05:06 -04:00
parent c354047e17
commit 7000d2779f
2 changed files with 31 additions and 1 deletions

View File

@@ -361,6 +361,11 @@ def eval_tags(
top: int = typer.Option(8, "--top"),
max_tags: int = typer.Option(4, "--max-tags"),
min_confidence: float = typer.Option(0.0, "--min-confidence"),
dump: str = typer.Option(
"",
"--dump",
help="With --live: write every comment's shortlist, verdicts and confidences to this JSON file.",
),
) -> None:
"""Score the theme tagger against the golden set (P35 gate, #577):
per-theme precision/recall, micro-F1 and the abstain rate, then the
@@ -392,6 +397,7 @@ def eval_tags(
from llm.tagger import tag_comment
rt = _tagging_runtime(llm_config.load())
dumped: dict[str, Any] = {}
def tag_one(key: str):
rows = sorted(rt["load_chunks"](key), key=lambda r: r[0])
@@ -409,9 +415,22 @@ def eval_tags(
min_confidence=min_confidence,
)
typer.echo(f"{key} {', '.join(r.slugs) or '(none)'}")
dumped[key] = {
"tags": {slug: info.confidence for slug, info in r.tags.items()},
"shortlist": [
{"slug": c.slug, "score": c.score, "verdict": r.judged.get(c.slug)}
for c in r.shortlisted
],
}
return r.slugs
preds = live_predictions(rt["store"], ids, tag=tag_one)
if dump:
import json as _json
from pathlib import Path as _Path
_Path(dump).write_text(_json.dumps(dumped, indent=1))
typer.echo(f"dumped {len(dumped)} comments → {dump}")
else:
preds = stored_predictions(bib(), ids)
from llm.tageval import Golden

View File

@@ -137,9 +137,20 @@ class TestEvalTags:
def test_live_scoring_uses_the_runtime(self, rt, tmp_path):
store, key, judged = rt
g = self._golden(tmp_path, ["CMS-2026-2377-1"])
res = runner.invoke(app, ["llm", "eval-tags", "--golden", str(g), "--live"])
dump = tmp_path / "dump.json"
res = runner.invoke(
app, ["llm", "eval-tags", "--golden", str(g), "--live", "--dump", str(dump)]
)
assert res.exit_code == 0, res.output
assert f"{key} telehealth" in res.output and "gate: PASS" in res.output
import json as _json
d = _json.loads(dump.read_text())
assert d[key]["tags"] == {"telehealth": 1.0}
assert {x["slug"]: x["verdict"] for x in d[key]["shortlist"]} == {
"telehealth": True,
"drugs": False,
}
assert judged and not any(
t.startswith("llm:") for t in store.get(key).tags
) # live never writes