semantica.evals measures the quality of decision intelligence outputs. It takes
the decisions, audit trails, and reasoning text your pipeline produces and scores
them against expectations you define, returning a structured summary you can log,
assert on in tests, or track across runs.
- A registry of named evaluators, from exact string matching to ROUGE overlap and LLM-as-judge
decision_scores, a composite evaluator forDecisionobjects that checks outcome, confidence bounds, required fields, provenance, and (optionally) policy compliance- A
evaluate()runner that applies several evaluators to a list of cases and aggregates pass / fail / error counts - Per-evaluator objectives that let you override an evaluator’s built-in verdict at the run level
The module is versioned separately from the package:
semantica.evals.__version__
is "0.1.0". The public surface described here is stable, but expect additive
changes (new evaluators, new objective options) before it reaches 1.0.Public API
Built-in evaluators
Every evaluator is a plain functionfn(actual, expected, config=None) -> EvalMetric
registered under a stable name. list_evaluators() returns the current set:
An evaluator that cannot run (bad regex, unparseable datetime, no
judge_fn) returns an
EvalMetric with an "error" key in meta rather than raising. Evaluators that
require numeric bounds (numeric_range, length_range) instead return a failing
metric with a "reason" key when the bound is missing — they do not raise and do
not set "error".
decision_scores
decision_scores accepts a Decision (from semantica.context.decision_models)
or its dict form and runs a set of field-level and governance checks. The score is
the fraction of checks that passed; passed is True only when all of them did.
Passing
causal_chain_exists in config raises NotImplementedError. That key is a
reserved slot for a future release.
Running an evaluation
evaluate() takes a list of cases and a list of evaluator names. A case is either
a (expected, actual) tuple or a dict:
actual is missing, the runner calls the case’s target_fn (or the
target_fn passed to evaluate()) to produce it. Per-case config is deep-merged
over the top-level config, so a case can override one evaluator’s settings
without discarding the rest.
status becomes
"error" and the exception text is captured in the metric’s meta; the rest of
the run continues.
Objectives
By default each evaluator decides its own pass / fail. An objective overrides that verdict at the run level, keyed by evaluator name underconfig:
maximizewiththreshold: pass iffscore >= threshold.maximizewith no threshold is a no-op and the evaluator’s own verdict stands.minimizewiththreshold: pass iffscore <= threshold.minimizerequires a threshold; omitting it raisesValueError.expect(True/False): pass iffbool(score)equals it. Cannot be combined withdirectionorthreshold, and must be a real boolean.- A metric that already carries an
"error"in itsmetais unaffected by any objective. - Invalid objective config is validated for every case before any evaluator runs, so a bad objective fails the whole run up front rather than partway through.
Reading the summary
EvalMetric, SampleStats, and RepeatedCaseResult are frozen dataclasses
(score: float, passed: bool, meta: dict, …). CaseResult is a
namedtuple and the *Summary classes are plain dataclasses, so all are
straightforward to serialize for logging or regression tracking.
Repeated sampling
For nondeterministic targets (LLM-backed extraction, agent pipelines, sampling-based judges) a single verdict says little.evaluate_repeated reruns
target_fn per case n times and aggregates per-evaluator statistics:
SampleStats carries n, passes, errors, pass_rate
(passes / n), mean_score, stddev, and the derived booleans any_passed
(observed pass@n) and all_passed (observed pass^n). Verdicts pool all samples:
stable_pass when every run passed every evaluator, stable_fail when none
passed, flaky when the pool mixes passes and fails, and error when a run
errored.
Requirements and edge cases:
- Each case can be a dict or a
(expected, actual)tuple, matchingevaluate(). A case that carries a non-null staticactualhas nothing to sample; using it withruns > 1raisesValueError. AnactualofNoneis treated as absent, so it falls back to the resolver exactly as inevaluate(). target_fnexceptions and evaluator failures become per-run error samples and mark the caseerror; they never crash the run.- The objective layer applies per run as in
evaluate(), and the same objective gates the aggregatepass_ratethroughSampleStats.objective_passed(e.g.{direction: maximize, threshold: 0.8}requires an 80% pass rate across runs). - At least one evaluator is required, and evaluator names must be unique.
runsmust be>= 1.
Notes
llm_as_judgeneedsconfig["judge_fn"], a callablejudge_fn(actual, expected) -> boolyou supply. No LLM backend is imported unless you pass one in.decision_scoresgovernance checks are opt-in: policy compliance is only evaluated when bothpolicy_engineandpolicy_idare present.
See also
- Decision Intelligence — producing the
Decisionrecords this module scores - Reasoning — inference output that reasoning-text evaluators can measure
- Policy Engine — the
policy_engineused bydecision_scores - Ontology Evaluator — separate tooling for ontology quality metrics
