operonx.telemetry.scores¶
Where experiments, their items and every score live: an eval's checks, a judge's verdicts, a human's review, an online rule's results — one score row with a target and an id derived from what it judges. Backends are the run stores': files (the default), SQLite, and ClickHouse (schema version 3, in the runs' database). The guide: Evals.
scores
¶
Score stores — where experiments, their items, and every score live.
An eval run is an experiment; each case × repeat is an item; each
judgement — an eval's check, a judge, a human review, an online rule, a
pairwise preference — is a score, one row type with a target (item,
trace, op, session, pair) and an id derived from what it judges, so the
same judgement written twice is one row. A :class:ScoreStore keeps them
on the run stores' backends:
files— JSONL files plus a SQLite index (the default, zero setup)sqlite— one fileclickhouse— the runs' database, schema version 3
score_store: in resources.yaml declares one; :func:open_score_store
opens one from a mapping; :func:project_score_store says which one a
project uses ([evals], else its [tracing] sinks). The contract is in :mod:.base.
The contract¶
ScoreStore
¶
Bases: ABC
See the module docstring.
Methods:¶
put_experiment
abstractmethod
¶
put_items
abstractmethod
¶
list_experiments
abstractmethod
¶
list_experiments(
where: Optional[ExperimentFilter] = None,
limit: int = 50,
cursor: Optional[str] = None,
) -> ExperimentPage
Experiments matching where, newest first, one page at a time.
get_experiment
abstractmethod
¶
put_scores
abstractmethod
¶
scores
abstractmethod
¶
Scores matching where, oldest first (created_at, score_id).
An online score past its retention is not returned.
score_series
abstractmethod
¶
Scores matching where in buckets of bucket_s seconds of
created_at, per score name: oldest bucket first.
cache_get
abstractmethod
¶
cache_put
abstractmethod
¶
Cache verdict under key; ttl_s None keeps it until replaced.
refresh
¶
Pick up what other writers left (the files backend reads lines other processes appended). Returns how many rows; most backends have nothing to do.
The data¶
Experiment
dataclass
¶
Experiment(
experiment_id: str,
eval: str,
project: str = "",
dataset: str = "",
dataset_version: str = "",
split: Optional[str] = None,
graph: str = "",
code_version: Optional[str] = None,
version_dirty: Optional[bool] = None,
graph_hash: str = "",
config_hash: str = "",
evaluators_hash: str = "",
operonx_version: str = "",
variant: Optional[str] = None,
repeats: int = 1,
baseline_id: Optional[str] = None,
status: str = "running",
started_at: float = 0.0,
ended_at: Optional[float] = None,
cases: int = 0,
errored: int = 0,
cost_usd: Optional[float] = None,
judge_cost_usd: Optional[float] = None,
p50_ms: Optional[float] = None,
p95_ms: Optional[float] = None,
metrics: Dict[str, Any] = dict(),
gate: Dict[str, Any] = dict(),
metadata: Dict[str, Any] = dict(),
)
One eval run. experiment_id is the job's run_id; times are
epoch seconds; metrics and gate are run.json["eval"]'s.
ExperimentItem
dataclass
¶
ExperimentItem(
experiment_id: str,
case_id: str,
repeat: int = 0,
case_hash: str = "",
trace_id: Optional[str] = None,
status: str = "ok",
ms: float = 0.0,
cost_usd: Optional[float] = None,
tokens_in: int = 0,
tokens_out: int = 0,
passed: Optional[bool] = None,
tags: List[str] = list(),
cluster: Optional[str] = None,
output: Any = None,
error: Optional[str] = None,
)
One case × repeat of an experiment. output is the clipped value
the job record holds; cost_usd and tokens are the case run's own.
Score
dataclass
¶
Score(
score_name: str,
target: str = "item",
source: str = "code",
data_type: str = "bool",
value: Optional[float] = None,
passed: Optional[bool] = None,
label: Optional[str] = None,
reason: str = "",
trace_id: Optional[str] = None,
op_id: Optional[str] = None,
session_id: Optional[str] = None,
experiment_id: Optional[str] = None,
pair_experiment_id: Optional[str] = None,
case_id: Optional[str] = None,
repeat: Optional[int] = None,
origin: str = "",
name: str = "",
evaluator_version: str = "",
judge_trace_id: Optional[str] = None,
cost_usd: Optional[float] = None,
author: Optional[str] = None,
rule: Optional[str] = None,
queue: Optional[str] = None,
snapshot: Any = None,
metadata: Dict[str, Any] = dict(),
created_at: float = 0.0,
score_id: str = "",
)
One judgement. target says what was judged and which ids say
it (see :data:TARGETS); source who judged. A bool score
also holds value 0/1, so means work across sources. score_id
and created_at fill themselves in when left empty.
score_id_of
¶
The id a score's own fields give it — so the same judgement is always the same row:
- pairwise: (experiment, pair experiment, case, score name);
- human: (what was judged, score name, author) — an edit replaces;
- an experiment's item: (experiment, case, repeat, score name);
- a run, an op, a session (online, rescore), an evaluator (its alignment): (trace, op, session, score name, evaluator version) — a new evaluator is a new score, the same one's record is replaced.
Source code in operonx/telemetry/scores/model.py
ExperimentRecord
dataclass
¶
One experiment and its items (by case, then repeat).
ExperimentPage
dataclass
¶
ExperimentPage(
items: List[Experiment],
next_cursor: Optional[str] = None,
total: Optional[int] = None,
)
One page of experiments, newest first; next_cursor None at the end.
ExperimentFilter
dataclass
¶
ExperimentFilter(
project: Optional[str] = None,
eval: Optional[str] = None,
dataset: Optional[str] = None,
status: Optional[str] = None,
code_version: Optional[str] = None,
since: Optional[float] = None,
until: Optional[float] = None,
experiment_ids: Optional[Sequence[str]] = None,
)
Which experiments — a small data object, as :class:RunFilter is.
Times are epoch seconds over started_at, since inclusive.
ScoreFilter
dataclass
¶
ScoreFilter(
experiment_id: Optional[str] = None,
trace_id: Optional[str] = None,
op_id: Optional[str] = None,
session_id: Optional[str] = None,
case_id: Optional[str] = None,
score_name: Optional[str] = None,
origin: Optional[str] = None,
name: Optional[str] = None,
source: Optional[str] = None,
target: Optional[str] = None,
rule: Optional[str] = None,
since: Optional[float] = None,
until: Optional[float] = None,
score_ids: Optional[Sequence[str]] = None,
)
Which scores. Times are epoch seconds over created_at.
Bucket
dataclass
¶
One time bucket of one score: how many, their mean value, and
the share that passed (of those with a passed).
Configuration¶
ScoreStoreConfig
¶
Bases: YamlModel
YAML-configurable :class:ScoreStore.
open_score_store
¶
A store from a plain mapping (the YAML block's fields).
Source code in operonx/telemetry/scores/config.py
Backends¶
FilesScoreStore
¶
FilesScoreStore(
root: Any = "",
refresh_every: float = 2.0,
online_ttl_days: float = ONLINE_TTL_DAYS,
)
Bases: SqliteScoreStore
See the module docstring. root unset → <runs root>/scores
($OPERONX_RUNS_DIR, else <project>/.operonx/runs); reads look
for other writers' lines at most every refresh_every seconds.
Source code in operonx/telemetry/scores/files.py
Methods:¶
refresh
¶
Index lines appended since the last read; return how many rows.
Source code in operonx/telemetry/scores/files.py
SqliteScoreStore
¶
Bases: ScoreStore
See the module docstring. path defaults to scores.sqlite
under the runs root (<project>/.operonx/runs); an online score
(one with a rule) is kept online_ttl_days.
Source code in operonx/telemetry/scores/sqlite.py
ClickHouseScoreStore
¶
ClickHouseScoreStore(
host: str = "localhost",
port: int = 0,
user: str = "default",
password: str = "",
database: str = "operonx",
secure: bool = False,
timeout: float = 10.0,
online_ttl_days: float = ONLINE_TTL_DAYS,
client: Any = None,
)
Bases: ClickHouseConnection, ScoreStore
See the module docstring. The connection fields are the run store's
(host, port, user, password, database, secure,
timeout); client= takes a ready client (tests hand in a fake).