mixle.reason.llm module

Uncertainty quantification for LLM output – the thing LLMs famously lack, on the mixle UQ spine.

An LLM emits fluent text with no honest sense of whether it knows the answer. Wrapping any generate(prompt) -> str callable, LLMUncertainty turns repeated stochastic samples into calibrated uncertainty:

  • Semantic entropy (Kuhn et al. 2023): sample the model n times, cluster the answers by meaning (not surface form), and take the entropy over meaning-clusters. High = the model disagrees with itself about what the answer is – a hallucination signal – while merely rephrasing one answer clusters to low entropy. (mixle.inference.semantic_entropy().)

  • Epistemic vs aleatoric split: draw samples under several members (paraphrased prompts, or higher temperature as a proxy ensemble); the disagreement across members is epistemic (the model is unsure), the spread within is aleatoric (the question is genuinely open). (mixle.inference.decompose_entropy().)

  • Conformal answer-or-abstain: calibrate a confidence threshold on labeled examples so that when the model answers, it is correct with probability >= 1 - alpha – a finite-sample selective- risk guarantee. The model abstains on questions it does not know instead of confabulating.

Domain-neutral in its dependency: it takes a plain generate callable and an equivalent relation, so it works with a local mixle.task model, an OpenAI-compatible endpoint (mlops), or a mock – no hard LLM dependency here.

sentence_claims(text)[source]

Split a response into atomic claims (sentence-ish units) – the default claim extractor.

Parameters:

text (str)

Return type:

list[str]

content_overlap(sample, claim, *, threshold=0.6)[source]

Simple corroboration test: does sample cover >= threshold of claim’s content words?

Counts every content word equally, so boilerplate shared across responses (“the tower is located in …”) can mask that the informative word (the city) differs. information_corroborator() fixes that by weighting words by their information content; it is the default in LLMUncertainty.assess_claims().

Parameters:
Return type:

bool

information_corroborator(samples, *, overlap=0.5)[source]

Build a corroboration test that weights each word by its information content over samples.

A word appearing in nearly every sample is boilerplate (low information, low weight); a rare word carries the actual claim (high weight). A sample corroborates a claim when it covers at least overlap of the claim’s information-weighted words – so whether the distinctive fact (a city, a number, a name) matches drives the decision, not the shared filler. Inverse-document-frequency weighting: w(word) = log((N + 1) / (df + 0.5)).

Parameters:
Return type:

Callable[[str, str], bool]

class LLMAssessment(answer, confidence, semantic_entropy, clusters, samples)[source]

Bases: object

One prompt’s assessed answer with uncertainty.

answer is the majority meaning-cluster’s representative; confidence its cluster share in [0, 1]; semantic_entropy the nats of meaning-uncertainty; clusters the [(representative, probability), ...] distribution over meanings; samples the raw draws.

Parameters:
answer: Any
confidence: float
semantic_entropy: float
clusters: list[tuple[Any, float]]
samples: list[Any]
class ClaimAssessment(claim, support, reliable)[source]

Bases: object

Reliability of one claim inside a response, by cross-sample corroboration.

support is the fraction of independent resamples that corroborate the claim (in [0, 1]); reliable is support >= threshold. A claim the model actually knows recurs across samples (high support); a fabricated one appears once and vanishes (low support).

Parameters:
claim: str
support: float
reliable: bool
class InformationAssessment(claims, reliability)[source]

Bases: object

UQ over the information content of a response: every claim scored, plus a summary.

claims is the per-claim reliability; reliability the mean support (how trustworthy the response’s information is overall); fabricated the claims below threshold (likely hallucinated).

Parameters:
  • claims (list[ClaimAssessment])

  • reliability (float)

claims: list[ClaimAssessment]
reliability: float
property fabricated: list[ClaimAssessment]
class FactualityModel(calibrator, signal, discrimination)[source]

Bases: object

A fitted map from a per-prompt uncertainty signal to a calibrated P(answer is correct).

The signal (self-consistency, a token likelihood, …) is only a raw number; the calibrator turns it into a genuine probability of the information being correct, learned against labeled facts. discrimination (held-out AUC on the fit set) reports how much the signal actually knew about correctness – ~0.5 means the signal was unrelated to truth, no matter how confident it looked.

Parameters:
calibrator: ProbabilityCalibrator
signal: Callable[[str], float]
discrimination: float
probability(prompt)[source]

Calibrated probability that the answer’s information is correct.

Parameters:

prompt (str)

Return type:

float

class LLMUncertainty(generate, *, equivalent=None, n=10)[source]

Bases: object

Calibrated uncertainty and selective prediction for any generate(prompt) -> str LLM.

Parameters:
  • generate (Callable[[str], Any]) – callable(prompt) -> str – one stochastic sample from the model.

  • equivalent (Callable[[Any, Any], bool] | None) – callable(a, b) -> bool deciding whether two answers mean the same thing (default exact match; pass a normalizer / embedding / entailment check for real text).

  • n (int) – default number of samples per prompt.

sample(prompt, n=None)[source]

Draw n stochastic responses to prompt.

generate may return a plain string, or a (text, logprob) pair – the sequence log-probability log P(s). When logprobs are provided they are used to marginalize the string distribution over meaning classes exactly (mixle.inference.marginalize_meaning()) rather than by sample counting.

Parameters:
Return type:

list[Any]

assess(prompt, n=None)[source]

Sample, marginalize the string distribution over meaning classes, and report the answer.

The reported confidence is the marginal probability of the top meaning (summed over its equivalence class of strings), and semantic_entropy the entropy of that meaning marginal – not a per-string token probability.

Parameters:
Return type:

LLMAssessment

decompose(prompts, n=None)[source]

Epistemic/aleatoric split across member prompts (paraphrases of one question).

Each prompt is a member; all members’ samples are pooled to define shared meaning-clusters, then each member’s distribution over those clusters feeds decompose_entropy(). Epistemic = disagreement across paraphrasings (prompt-sensitivity / model uncertainty); aleatoric = within-member spread.

Parameters:
Return type:

UncertaintyDecomposition

assess_claims(prompt, *, extract=None, corroborates=None, n=None, threshold=0.5)[source]

Score the reliability of each claim in the response by cross-sample corroboration.

Finer-grained than assess(): a response can be internally consistent (low semantic entropy) yet contain one fabricated fact. This decomposes the response into claims and checks each unit of information separately – a claim the model knows recurs across independent resamples; a hallucinated one appears once. This is UQ on the information in what is said, not just on the answer as a whole.

Parameters:
  • prompt (str) – the query.

  • extract (Callable[[str], Sequence[str]] | None) – response -> [claim, ...] (default sentence_claims()).

  • corroborates (Callable[[str, str], bool] | None) – (other_sample, claim) -> bool – does a resample support the claim? (default content_overlap(); pass an entailment/NLI check for real text).

  • n (int | None) – number of samples (the first is the response scored; the rest corroborate).

  • threshold (float) – support below which a claim is flagged as unreliable/fabricated.

Return type:

InformationAssessment

calibrate(examples, *, correct=None, alpha=0.1, n=None)[source]

Calibrate a confidence threshold for selective risk <= alpha on labeled (prompt, gold).

For each example, the model’s answer (majority meaning-cluster) and its confidence are computed; correct(answer, gold) (default the equivalent relation) marks it right or wrong. The threshold is the lowest confidence at which the selective error rate on the calibration set is <= alpha – so answer() abstains below it and, when it answers, is right with probability about 1 - alpha.

Parameters:
Return type:

LLMUncertainty

answer(prompt, n=None)[source]

Answer prompt if confident enough, else None (abstain).

Requires a prior calibrate(). Returns the LLMAssessment when confidence >= threshold (so the answer meets the selective-risk guarantee), else None.

Parameters:
Return type:

LLMAssessment | None

fit_factuality(examples, *, signal=None, correct=None, method='isotonic', n=None)[source]

Learn a calibrated P(answer is correct) from a raw signal, on labeled (prompt, gold).

The model’s raw confidence (its self-consistency, or a token likelihood) is not a probability that the information is correct – it can be systematically over/under-confident, or unrelated to truth. This fits a ProbabilityCalibrator mapping the signal to the empirical correctness rate, so the output is a probability of the information being right. discrimination (AUC of signal vs correctness) reports how much the raw signal knew at all – ~0.5 means it was unrelated to truth, calibration or not.

Parameters:
  • examples (Sequence[tuple[str, Any]]) – labeled (prompt, gold_answer) pairs.

  • signal (Callable[[str], float] | None) – prompt -> float raw score (default: the self-consistency confidence from assess()).

  • correct (Callable[[Any, Any], bool] | None) – (answer, gold) -> bool (default the equivalent relation).

  • method (str) – calibration map – "isotonic" or "platt".

  • n (int | None) – samples per prompt.

Return type:

FactualityModel