Skip to content

schemacast.bench.saved

Score saved cast output against a corpus, without a model.

A saved cast is a JSON file holding a POST /cast response body, or a wrapper {"episodes": [...], "cast": <body>}: the episodes sent and the body returned. Each episode's record graph is scored as the run scores a fresh cast. What only a live cast knows — the first reply before repair, the slice, tokens, a re-cast, picks — is not measured and is reported as such.

Attributes

NOT_SAVED = 'not_saved' module-attribute

UNMEASURED_COUNTS = frozenset({'emitted_first', 'nonconforming_first', 'emitted_final', 'nonconforming_final', 'ungrounded_first', 'vertices_first', 'unknown_type_first', 'edges_first', 'undeclared_edge_first', 'properties_first', 'undeclared_property_first', 'unfaithful_value_first', 'sliced', 'gold_types', 'gold_types_seeded', 'gold_types_in_slice', 'gold_edges', 'gold_edges_in_slice', 'recast_vertices', 'recast_added', 'recast_minted', 'recast_duplicated', 'picks_expected', 'picks_offered', 'picks_correct', 'picks_linked', 'picks_wrong', 'picks_rejected', 'input_tokens', 'output_tokens', 'billed_input_tokens', 'billed_output_tokens'}) module-attribute

Classes

SavedCastError

Bases: ValueError

A file that is not a saved cast, or two casts of one episode.

Source code in schemacast/bench/saved.py
class SavedCastError(ValueError):
    """A file that is not a saved cast, or two casts of one episode."""

SavedCasts

Episode outcomes read from saved casts, by source id.

Source code in schemacast/bench/saved.py
class SavedCasts:
    """Episode outcomes read from saved casts, by source id."""

    def __init__(self) -> None:
        self.outcomes: dict[str, EpisodeOutcome] = {}
        self.models: list[dict[str, Any]] = []

    def add(self, path: Path) -> None:
        """Read one saved cast.

        Raises:
            SavedCastError: not a cast body or wrapper, or an episode cast twice.
        """
        try:
            data = json.loads(path.read_text(encoding="utf-8"))
        except json.JSONDecodeError as error:
            raise SavedCastError(f"{path}: not JSON: {error.msg}") from error
        body = data.get("cast") if isinstance(data, dict) and "cast" in data else data
        if not isinstance(body, dict) or not {"mentions", "episodes"} <= set(body):
            raise SavedCastError(f"{path}: not a cast body (mentions, episodes)")
        try:
            graphs = {
                graph.source_id: graph
                for graph in (RecordGraph.model_validate(g) for g in body["mentions"])
            }
            results = {
                result.source_id: result
                for result in (
                    EpisodeResult.model_validate(r) for r in body["episodes"]
                )
            }
        except ValueError as error:
            raise SavedCastError(f"{path}: {error}") from error
        model = (body.get("manifest") or {}).get("model")
        if isinstance(model, dict):
            self.models.append(model)
        for source_id in [*results, *(g for g in graphs if g not in results)]:
            if source_id in self.outcomes:
                raise SavedCastError(f"{path}: episode {source_id!r} is cast twice")
            self.outcomes[source_id] = outcome(
                results.get(source_id)
                or EpisodeResult(source_id=source_id, status="ok"),
                graphs.get(source_id),
            )

    def label(self, paths: list[Path]) -> str:
        """The model the casts name, when they agree; else the first file's stem."""
        names = {str(model.get("model")) for model in self.models}
        if len(names) == 1 and len(self.models) == len(paths):
            return names.pop()
        return paths[0].stem

Attributes

models = [] instance-attribute
outcomes = {} instance-attribute

Methods:

__init__()
Source code in schemacast/bench/saved.py
def __init__(self) -> None:
    self.outcomes: dict[str, EpisodeOutcome] = {}
    self.models: list[dict[str, Any]] = []
add(path)

Read one saved cast.

Raises:

Type Description
SavedCastError

not a cast body or wrapper, or an episode cast twice.

Source code in schemacast/bench/saved.py
def add(self, path: Path) -> None:
    """Read one saved cast.

    Raises:
        SavedCastError: not a cast body or wrapper, or an episode cast twice.
    """
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as error:
        raise SavedCastError(f"{path}: not JSON: {error.msg}") from error
    body = data.get("cast") if isinstance(data, dict) and "cast" in data else data
    if not isinstance(body, dict) or not {"mentions", "episodes"} <= set(body):
        raise SavedCastError(f"{path}: not a cast body (mentions, episodes)")
    try:
        graphs = {
            graph.source_id: graph
            for graph in (RecordGraph.model_validate(g) for g in body["mentions"])
        }
        results = {
            result.source_id: result
            for result in (
                EpisodeResult.model_validate(r) for r in body["episodes"]
            )
        }
    except ValueError as error:
        raise SavedCastError(f"{path}: {error}") from error
    model = (body.get("manifest") or {}).get("model")
    if isinstance(model, dict):
        self.models.append(model)
    for source_id in [*results, *(g for g in graphs if g not in results)]:
        if source_id in self.outcomes:
            raise SavedCastError(f"{path}: episode {source_id!r} is cast twice")
        self.outcomes[source_id] = outcome(
            results.get(source_id)
            or EpisodeResult(source_id=source_id, status="ok"),
            graphs.get(source_id),
        )
label(paths)

The model the casts name, when they agree; else the first file's stem.

Source code in schemacast/bench/saved.py
def label(self, paths: list[Path]) -> str:
    """The model the casts name, when they agree; else the first file's stem."""
    names = {str(model.get("model")) for model in self.models}
    if len(names) == 1 and len(self.models) == len(paths):
        return names.pop()
    return paths[0].stem

Functions:

outcome(result, graph)

An episode outcome as a fresh cast would hold it, from its saved parts.

Source code in schemacast/bench/saved.py
def outcome(result: EpisodeResult, graph: RecordGraph | None) -> EpisodeOutcome:
    """An episode outcome as a fresh cast would hold it, from its saved parts."""
    if graph is None:
        return EpisodeOutcome(result=result)
    known = [r for r in graph.records if "known" in (r.evidence or {})]
    validation = Validation(
        mentions=[r for r in graph.records if r not in known],
        relations=list(graph.relations),
        known=known,
        fixes=list(result.fixes),
    )
    return EpisodeOutcome(result=result, validation=validation, graph=graph)

score_saved(cases, saved, label, *, corpus_ids=None)

One row: every episode of cases scored from saved.

An episode with no saved cast counts as failed and fully missed. A saved cast of an episode not in corpus_ids (default: those of cases) is listed in unmatched_casts.

Source code in schemacast/bench/saved.py
def score_saved(
    cases: list[BenchCase],
    saved: SavedCasts,
    label: str,
    *,
    corpus_ids: set[str] | None = None,
) -> Row:
    """One row: every episode of *cases* scored from *saved*.

    An episode with no saved cast counts as failed and fully missed. A saved
    cast of an episode not in *corpus_ids* (default: those of *cases*) is
    listed in ``unmatched_casts``.
    """
    records: list[CaseRecord] = []
    for case in cases:
        episodes: list[EpisodeRecord] = []
        for episode in case.episodes:
            found = saved.outcomes.get(episode.source_id) or EpisodeOutcome(
                result=EpisodeResult(
                    source_id=episode.source_id, status="failed", failure=NOT_SAVED
                )
            )
            context = {item.source_id: item.text for item in episode.context}
            misses = Misses()
            counts = score_episode(
                found,
                case.expected[episode.source_id],
                case.schema_,
                episode.text,
                None,
                context,
                misses,
                episode.known,
            )
            episodes.append(
                EpisodeRecord(
                    source_id=episode.source_id,
                    status=found.result.status,
                    failure=found.result.failure,
                    counts=unmeasured(counts),
                    misses=misses,
                )
            )
        records.append(
            CaseRecord(
                name=case.name,
                family=case.family,
                instance=case.instance,
                split=case.split,
                digest=case.digest,
                counts=total([e.counts for e in episodes]),
                episodes=episodes,
            )
        )
    known = (
        corpus_ids
        if corpus_ids is not None
        else {e.source_id for case in cases for e in case.episodes}
    )
    return Row(
        label=label,
        model=saved.models[0] if saved.models else {},
        cases=records,
        saved=True,
        unmatched_casts=sorted(set(saved.outcomes) - known),
    )

unmeasured(counts)

counts with what a saved cast cannot supply set to zero.

Source code in schemacast/bench/saved.py
def unmeasured(counts: Counts) -> Counts:
    """*counts* with what a saved cast cannot supply set to zero."""
    return counts.model_copy(update=dict.fromkeys(UNMEASURED_COUNTS, 0))