Advisory confidence scoring for extracted claims.
Confidence is ADVISORY under the auto-publish policy: it is recorded/flagged, not
a gate. The seam is the Driver's optional score primitive
(supports_logprobs). A logprob-capable backend (e.g. GptDriver against a
local vLLM open-weight model) yields calibrated probabilities; backends without
logprobs degrade gracefully to a sampling proxy or can be routed to a scorer that
does support them.
This module is deliberately store-agnostic: it returns Score objects. Writing
those onto Note.grounding and surfacing them in tables.py cells lives in
the zettelkasten layer and is wired separately.
pick_scorer
Return a logprob-capable driver, preferring primary.
Falls back to fallback when the primary cannot produce logprobs. Returns
None when neither can (caller should skip or use the sampling proxy).
Source code in stream/confidence.py
| def pick_scorer(primary: Driver, fallback: Driver | None = None) -> Driver | None:
"""Return a logprob-capable driver, preferring ``primary``.
Falls back to ``fallback`` when the primary cannot produce logprobs. Returns
``None`` when neither can (caller should skip or use the sampling proxy).
"""
if getattr(primary, "supports_logprobs", False):
return primary
if fallback is not None and getattr(fallback, "supports_logprobs", False):
return fallback
return None
|
score_claim
score_claim(claim: str, *, driver: Driver, evidence: str = '', fallback: Driver | None = None, model: str = '') -> Score
Score how well evidence supports claim (advisory).
Uses a logprob-capable driver when available (method "logprob"). When
none is available it degrades to a single sampled yes/no judgment
(method "sampling", confidence=None) so callers always get a Score.
Source code in stream/confidence.py
| def score_claim(
claim: str,
*,
driver: Driver,
evidence: str = "",
fallback: Driver | None = None,
model: str = "",
) -> Score:
"""Score how well ``evidence`` supports ``claim`` (advisory).
Uses a logprob-capable driver when available (method ``"logprob"``). When
none is available it degrades to a single sampled yes/no judgment
(method ``"sampling"``, ``confidence=None``) so callers always get a Score.
"""
prompt = (
"Does the evidence support the claim? Answer with a single word: "
"yes or no.\n\n"
f"CLAIM: {claim}\n"
f"EVIDENCE: {evidence or '(none provided)'}"
)
scorer = pick_scorer(driver, fallback)
if scorer is not None:
score = scorer.score(prompt, choices=["yes", "no"], model=model)
# Normalize so confidence reflects support (probability of "yes").
if score.value.strip().lower().startswith("no") and score.confidence is not None:
score.confidence = 1.0 - score.confidence
return score
# Sampling proxy: no logprobs anywhere. A bare judgment, confidence unknown.
text = driver.run_agent(
role="checker",
persona="You are a strict grounding judge. Reply yes or no only.",
prompt=prompt,
tools=[],
model=model,
).text
supported = text.strip().lower().startswith("y")
return Score(value="yes" if supported else "no", confidence=None, method="sampling")
|
is_low_confidence
is_low_confidence(score: Score, threshold: float = _LOW_CONFIDENCE) -> bool
Flag a score as low-confidence (advisory only; never gates publish).
Source code in stream/confidence.py
| def is_low_confidence(score: Score, threshold: float = _LOW_CONFIDENCE) -> bool:
"""Flag a score as low-confidence (advisory only; never gates publish)."""
return score.confidence is not None and score.confidence < threshold
|