feat(evidence): evaluate retrieval with a small fixture

This commit is contained in:
2026-08-25 01:44:12 +02:00
parent dcb5acc312
commit 619ac2e141
15 changed files with 782 additions and 6 deletions
+73
View File
@@ -10,6 +10,7 @@ from typing import Annotated
import typer
from tht.cli.config_cmd import CONFIG_OPT
from tht.evidence import (
EvidencePreparationError,
PiEvidenceRestructurer,
@@ -63,6 +64,48 @@ def _findings_payload(findings) -> list[dict[str, object]]:
return [finding.__dict__ for finding in findings]
def evaluate_from_config(
workspace_root: Path,
config: Path,
*,
generation: str | None = None,
) -> dict[str, object]:
"""Evaluate one stored Evidence generation without changing corpus or vectors."""
from tht.adapters.factory import build_vector_store
from tht.cli.schema_cmd import _load_config_or_exit
from tht.cli.vector_cmd import make_embedder
from tht.evidence.canonical import load_curated_tree
from tht.evidence.corpus.store import CorpusStore
from tht.evidence.evaluation import evaluate_retrieval, load_evaluation_fixture
cfg = _load_config_or_exit(config)
store = CorpusStore(cfg.paths.artifacts.parent / "corpus")
manifest = store.manifest(generation) if generation is not None else store.active_manifest()
if manifest is None:
raise RuntimeError("active Evidence generation is unavailable")
document_generations = manifest.metadata.get("document_generations")
if not isinstance(document_generations, dict):
raise TypeError("Evidence generation is invalid")
language = {"en": "english", "it": "italian"}.get(cfg.language)
if language is None:
raise RuntimeError("workspace language is unsupported for Qdrant BM25")
report = evaluate_retrieval(
load_evaluation_fixture(workspace_root / "evidence" / "evaluation.yaml"),
workspace_revision=cfg._workspace_revision,
vector_generation=manifest.vector_generation,
document_generations=document_generations,
workspace_id=cfg._workspace_id,
language=language,
searcher=build_vector_store(cfg),
embedder=make_embedder(cfg.embeddings),
expected_kinds={
evidence.id: evidence.kind
for evidence in load_curated_tree(workspace_root / "evidence" / "curated")
},
)
return report.model_dump()
@evidence_app.command("prepare")
def prepare_cmd(
workspace_root: Path,
@@ -106,6 +149,36 @@ def validate_cmd(
raise typer.Exit(code=1)
@evidence_app.command("evaluate")
def evaluate_cmd(
workspace_root: Path,
config: Path = CONFIG_OPT,
generation: str | None = typer.Option(None, "--generation"),
json_output: bool = typer.Option(False, "--json"),
) -> None:
"""Evaluate active or selected Evidence retrieval generation without publishing it."""
root = _canonical_worktree(workspace_root)
try:
payload = evaluate_from_config(root, config, generation=generation)
except Exception: # noqa: BLE001 - CLI reports a safe operational failure.
_emit({
"schemaVersion": 1,
"operation": "evidence_evaluate",
"status": "failed",
"code": "evaluation_failed",
}, json_output)
raise typer.Exit(code=1) from None
payload = {
"schemaVersion": 1,
"operation": "evidence_evaluate",
"status": "passed" if payload["passed"] else "failed",
**payload,
}
_emit(payload, json_output)
if not payload["passed"]:
raise typer.Exit(code=1)
@evidence_app.command("resolve")
def resolve_cmd(
workspace_root: Path,