mixle.reason.language_bridge module

The language<->belief bridge (roadmap M5, part (c)): NL/record -> M0 evidence via a declared schema, and posterior -> calibrated text through A1’s CalibratedGenerator.

Two independent directions, each a thin composition of already-tested machinery – no new extraction or generation model is built here (see notes/designs/M5.md part (c) for the full contract):

  • parse_evidence() – an extractor callable (the same teacher(x) -> dict shape mixle.task.structured_out.solve_structured() decomposes) produces a raw {field: value} dict from NL/record input; this module validates it against a caller-declared schema ({field: "categorical" | "numeric"} – the same shape schema already returns) BEFORE it reaches infer() / run_inference_program(), so a schema violation is a clear ValueError here rather than a confusing downstream log_density crash.

  • PosteriorDescriber / claim_score() – draft k candidate Claims at different ABSOLUTE precision widths (multiples of a required tol, the same “caller declares the precision that counts” contract mixle.task.regress.solve_regression() already uses for numeric fields – NOT widths relative to the posterior’s own spread, which would be scale-invariant and could never detect “too diffuse to answer”), score each against the posterior it describes, and serve the best one under A1’s conformal accept-or-abstain guarantee. claim_score() is exported standalone (not only reachable through PosteriorDescriber) so B2 (claim-checking, built elsewhere) can score an ALREADY-EMITTED claim against any posterior directly, with no dependency on candidate generation/calibration at all.

parse_evidence(text, schema, extractor)[source]

NL scenario/constraint (or any record extractor accepts) -> validated M0/L2 evidence.

extractor(text) -> {field: raw_value} does the actual parsing (a keyword/regex rule, a calibrated solve_structured() student, an LLM call – this module is agnostic to how); this function’s only job is enforcing the declared schema BEFORE the result is trusted as evidence: every returned field must be declared, numeric fields must actually be numbers, categorical fields are normalized to str. The returned dict is ready to pass straight to CrossModalJoint.infer(...) or as run_inference_program’s evidence=.

Parameters:
Return type:

dict[str, Any]

class Claim(field, lo, hi, probe=())[source]

Bases: object

A declared interval assertion about one posterior field: field lies in [lo, hi].

probe caches the sample batch a PosteriorDescriber drew to score this claim at generation time, so CalibratedGenerator()’s single-argument score(candidate) contract can call claim_score() with no extra prompt/posterior plumbing. A hand-authored Claim (e.g. from B2) simply omits probe and passes posterior= to claim_score() explicitly instead.

Parameters:
claim_score(claim, posterior=None, *, n_samples=200, seed=0)[source]

How well claim is supported by the posterior it describes: coverage of [claim.lo, claim.hi] under fresh posterior draws, PER UNIT WIDTH (coverage / width) – a density-like score, not a linear coverage-minus-penalty one. The ratio form matters, not just tie-breaking: a coverage-minus-linear-penalty score is shift-invariant under softmax whenever coverage SATURATES to the same constant across every candidate width, which happens in BOTH the confident regime (a sharp posterior’s mass fits inside every candidate width, coverage saturates near 1) and the clueless regime (a posterior far more diffuse than the widest candidate has near-locally-uniform density, so coverage saturates near density(center) * width for every candidate) – softmax over a constant offset cannot tell those two regimes apart. coverage / width does not saturate the same way: in the confident regime it grows as 1 / width (the NARROWEST candidate wins decisively), while in the clueless regime coverage / width -> density(center), the SAME value for every candidate width (a uniform, non-committal softmax) – exactly the “no candidate is more informative than any other” signal PosteriorDescriber.describe() abstains on.

Reusable standalone by B2’s claim-checking: pass posterior= to score an independently-authored claim against any posterior; a PosteriorDescriber-generated claim can instead be re-scored with no posterior argument, reusing the sample batch cached at generation time.

Parameters:
  • claim (Claim)

  • posterior (Any)

  • n_samples (int)

  • seed (int | None)

Return type:

float

class PosteriorDescriber(field_name, *, tol, k=3, alpha=0.1, width_multiples=(1.0, 3.0, 10.0), n_probe=300, seed=0)[source]

Bases: object

Posterior -> calibrated text for one field, via A1’s CalibratedGenerator.

tol is the caller’s required precision (the same “the caller states what precision counts as an answer” contract solve_regression() uses) – candidate claim widths are ABSOLUTE multiples of tol, not relative to the posterior’s own spread, so a genuinely diffuse posterior (spread >> tol) cannot fake confidence by simply widening every candidate in lockstep: none of them will cover well enough to clear the calibrated threshold, and describe() abstains (acceptance criterion (d)).

Parameters:
calibrate(calibration_set, *, seed=None)[source]

Fit the conformal threshold from (posterior, true_value) held-out pairs.

is_correct assigns EXCLUSIVE correctness across the k nested candidate widths – the true value’s distance from the shared center falls in exactly one of the k disjoint precision BANDS (0, tol], (tol, 3*tol], ... (widths are nested/overlapping as claims, but only the tightest band a true value actually falls in counts as “correct”). Without this, a wide claim trivially contains the true value whenever a narrower nested claim does too, so every calibration point would credit ALL of them at once and the conformal threshold would never learn to prefer – or reject – any one candidate.

Parameters:
Return type:

PosteriorDescriber

describe(posterior, *, seed=None)[source]

The best calibrated claim about posterior, or ABSTAIN (None) when no candidate width conformally clears the threshold – i.e. the posterior is too diffuse relative to tol for any of this describer’s claims to be trustworthy.

Parameters:
  • posterior (Any)

  • seed (int | None)

Return type:

Claim | None