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 sameteacher(x) -> dictshapemixle.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 BEFORE it reachesinfer()/run_inference_program(), so a schema violation is a clearValueErrorhere rather than a confusing downstreamlog_densitycrash. A schema field may be declared with the plain shorthand string"categorical" | "numeric"– the same shapeschemaalready returns, treated as a REQUIRED field with no further restriction – or a fullSchemaFieldfor an OPTIONAL field or a categorical field restricted to a declared closed set of values. Validation happens in two passes: the schema itself is checked (every declared field’s kind, and categories if any) BEFORE the extractor ever runs, so a malformed schema cannot hide behind a call that happens not to exercise the bad field; then the extractor’s actual output is checked field-by-field against it – every required field must be present, every numeric value must be finite, and every categorical value must already be a legitimatestr(a member ofcategorieswhen declared) rather than an arbitrary object silently stringified into a fabricated label.PosteriorDescriber/claim_score()– draftkcandidateClaims at different ABSOLUTE precision widths (multiples of a requiredtol, the same “caller declares the precision that counts” contractmixle.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 held-out selective-risk gate.claim_score()is exported standalone (not only reachable throughPosteriorDescriber) 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.
- class SchemaField(kind, required=True, categories=None)[source]
Bases:
objectOne declared evidence field: its value kind, whether the extractor must supply it, and (for categorical fields) the closed set of values that count as legitimate.
kind="numeric"accepts only finiteint/float(boolexcluded – never a genuine numeric measurement; NaN/Infinity excluded – “validated” evidence must be usable arithmetic, not merely type-correct).kind="categorical"accepts only values that are ALREADY astr– never an arbitrary object (includingNone) silently stringified into a fabricated label – and, whencategoriesis declared, only a value already a MEMBER of that closed set (an undeclared label is rejected, not silently admitted as a new de facto category).required=True(the default) meansparse_evidence()raises if the extractor omits this field;required=Falsepreserves the “partial evidence is fine” contract for fields the caller genuinely expects may go unmentioned.
- parse_evidence(text, schema, extractor)[source]
NL scenario/constraint (or any record
extractoraccepts) -> validated M0/L2 evidence.extractor(text) -> {field: raw_value}does the actual parsing (a keyword/regex rule, a calibratedsolve_structured()student, an LLM call – this module is agnostic to how); this function’s only job is enforcing the declaredschemaBEFORE the result is trusted as evidence: the schema itself is validated first (so a malformed schema raises even on a call whose extractor happens not to touch the bad field), then every required field must be present, numeric fields must be finite numbers, and categorical fields must already be astr(and a declared category, when one is set) – never an arbitrary object silently stringified into a fabricated label. The returned dict is ready to pass straight toCrossModalJoint.infer(...)or asrun_inference_program’sevidence=.
- class Claim(field, lo, hi, probe=())[source]
Bases:
objectA declared interval assertion about one posterior field:
fieldlies in[lo, hi].probecaches the sample batch aPosteriorDescriberdrew to score this claim at generation time, soCalibratedGenerator()’s single-argumentscore(candidate)contract can callclaim_score()with no extra prompt/posterior plumbing. A hand-authoredClaim(e.g. from B2) simply omitsprobeand passesposterior=toclaim_score()explicitly instead.
- claim_score(claim, posterior=None, *, n_samples=200, seed=0)[source]
How well
claimis 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 neardensity(center) * widthfor every candidate) – softmax over a constant offset cannot tell those two regimes apart.coverage / widthdoes not saturate the same way: in the confident regime it grows as1 / width(the NARROWEST candidate wins decisively), while in the clueless regimecoverage / 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” signalPosteriorDescriber.describe()abstains on.Reusable standalone by B2’s claim-checking: pass
posterior=to score an independently-authored claim against any posterior; aPosteriorDescriber-generated claim can instead be re-scored with noposteriorargument, reusing the sample batch cached at generation time.
- 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:
objectPosterior -> calibrated text for one field, via A1’s
CalibratedGenerator.tolis the caller’s required precision (the same “the caller states what precision counts as an answer” contractsolve_regression()uses) – candidate claim widths are ABSOLUTE multiples oftol, 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, anddescribe()abstains (acceptance criterion (d)).- Parameters:
- calibrate(calibration_set, *, seed=None)[source]
Fit the selective-risk threshold from
(posterior, true_value)held-out pairs.MXR-080-0291:
is_correctis EXACTLY “does the emitted interval cover the truth” –claim.contains(true_value), i.e.claim.lo <= true_value <= claim.hi– checked independently per candidate, non-strict on both ends. Nothing else. This module previously assigned correctness to a single artificial distance BAND per candidate (the true value’s distance from the shared center had to land in an EXCLUSIVE, disjoint slice(prev_half, this_half]), which is a different and unsound event: a truth sitting exactly on the shared center (distance 0) failed the narrowest band’s strict0 < distlower bound and therefore failed EVERY band, so the objectively best-supported truth was scored as covered by nothing; and a wider candidate that genuinely contains the truth (dist <= its own half-width) was marked wrong whenever a narrower nested candidate ALSO contained it, purely because the narrower band “claimed” that distance range first – docking a candidate for a coverage fact about a DIFFERENT candidate. Both are boundary/exclusivity artifacts of the banding scheme itself, not properties of whether the claim actually covers the truth.The direct correctness check has no such artifact: it evaluates exactly the top candidate the selective generator would serve. Calibration proposes a top-score threshold on one split and certifies the accepted interval-error rate on an independent split; it does not treat the prompt-specific interval candidates as a fixed conformal label space.
Scope of the certificate (it is
CalibratedGenerator.calibrate()’s): assuming the calibration pairs and future posteriors are i.i.d. draws from the same distribution, it holds with probability at least the underlying gate’sconfidenceover the calibration draw; distribution shift voids it silently, so re-calibrate on drifted traffic.
- describe(posterior, *, seed=None)[source]
The best calibrated claim about
posterior, orABSTAIN(None) when no candidate width clears the selective-risk threshold – i.e. the posterior is too diffuse relative totolfor any of this describer’s claims to be trustworthy.