Download src/bayesian_langchain/core.py from Angshul/Bayesian_LangChain: direct link, hf CLI and curl.
- Browser
- Download file 9.64 kB
-
https://huggingface.co/Angshul/Bayesian_LangChain/resolve/main/src/bayesian_langchain/core.py
- Command line
-
hf download hf://Angshul/Bayesian_LangChain/src/bayesian_langchain/core.py
-
curl -L -o core.py https://huggingface.co/Angshul/Bayesian_LangChain/resolve/main/src/bayesian_langchain/core.py
9.64 kB
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Any, Iterable, Mapping, Sequence | |
| import math | |
| import re | |
| import numpy as np | |
| from sklearn.feature_extraction.text import TfidfVectorizer, ENGLISH_STOP_WORDS | |
| def _clip01(x: float, eps: float = 1e-6) -> float: | |
| return min(max(float(x), eps), 1.0) | |
| def _logsumexp(xs: Sequence[float]) -> float: | |
| if not xs: | |
| return float('-inf') | |
| finite = [x for x in xs if math.isfinite(x)] | |
| if not finite: | |
| return float('-inf') | |
| m = max(finite) | |
| return m + math.log(sum(math.exp(x - m) for x in finite)) | |
| class DocumentRecord: | |
| id: str | |
| text: str | |
| title: str = "" | |
| source: str = "unknown" | |
| metadata: Mapping[str, Any] = field(default_factory=dict) | |
| def retrieval_text(self) -> str: | |
| return (self.title + ". " + self.text).strip(". ") | |
| def source_prior(self) -> float: | |
| md = self.metadata | |
| a = max(float(md.get("reliability_alpha", 1.0)), 1e-6) | |
| b = max(float(md.get("reliability_beta", 1.0)), 1e-6) | |
| reliability = a / (a + b) | |
| authority = _clip01(md.get("authority", 0.5)) | |
| freshness = _clip01(md.get("freshness", 0.5)) | |
| return reliability * authority * freshness | |
| class RetrievalHit: | |
| document: DocumentRecord | |
| retrieval_score: float | |
| log_prior: float | |
| log_likelihood: float | |
| log_posterior_unnormalized: float | |
| posterior: float | |
| query_coverage: float | |
| rank: int | |
| def as_dict(self) -> dict[str, Any]: | |
| return { | |
| "id": self.document.id, | |
| "title": self.document.title, | |
| "text": self.document.text, | |
| "source": self.document.source, | |
| "metadata": dict(self.document.metadata), | |
| "retrieval_score": self.retrieval_score, | |
| "log_prior": self.log_prior, | |
| "log_likelihood": self.log_likelihood, | |
| "posterior": self.posterior, | |
| "query_coverage": self.query_coverage, | |
| "rank": self.rank, | |
| } | |
| class BayesianRetrievalConfig: | |
| candidate_pool: int = 12 | |
| likelihood_temperature: float = 2.0 | |
| likelihood_floor: float = 0.02 | |
| reliability_weight: float = 1.0 | |
| authority_weight: float = 1.0 | |
| freshness_weight: float = 0.8 | |
| prior_floor: float = 1e-4 | |
| min_relevance: float = 0.06 | |
| min_top_posterior: float = 0.30 | |
| min_query_coverage: float = 0.30 | |
| min_dominance_ratio: float = 1.20 | |
| class BayesianIndex: | |
| """Generalized-Bayesian reranker over TF-IDF retrieval candidates. | |
| Candidate document hypotheses are scored by | |
| p(d|q) ∝ p(d) exp(tau * sim(q,d)). | |
| p(d) is induced by a Beta source-reliability mean plus authority/freshness | |
| metadata. The exponential similarity term is a Gibbs/generalized-Bayes | |
| likelihood, not claimed to be a literal language-model likelihood. | |
| """ | |
| def __init__(self, documents: Sequence[DocumentRecord], config: BayesianRetrievalConfig | None = None): | |
| if not documents: | |
| raise ValueError("documents must be non-empty") | |
| self.documents = list(documents) | |
| self.config = config or BayesianRetrievalConfig() | |
| self.vectorizer = TfidfVectorizer( | |
| lowercase=True, | |
| stop_words="english", | |
| ngram_range=(1, 2), | |
| sublinear_tf=True, | |
| norm="l2", | |
| ) | |
| self.matrix = self.vectorizer.fit_transform([d.retrieval_text for d in self.documents]) | |
| def _log_prior(self, d: DocumentRecord) -> float: | |
| md = d.metadata | |
| a = max(float(md.get("reliability_alpha", 1.0)), 1e-6) | |
| b = max(float(md.get("reliability_beta", 1.0)), 1e-6) | |
| rel = _clip01(a / (a + b)) | |
| auth = _clip01(md.get("authority", 0.5)) | |
| fresh = _clip01(md.get("freshness", 0.5)) | |
| cfg = self.config | |
| prior = max( | |
| cfg.prior_floor, | |
| (rel ** cfg.reliability_weight) | |
| * (auth ** cfg.authority_weight) | |
| * (fresh ** cfg.freshness_weight), | |
| ) | |
| return math.log(prior) | |
| def lexical_scores(self, query: str) -> np.ndarray: | |
| q = self.vectorizer.transform([str(query)]) | |
| # TF-IDF vectors are L2 normalized, so dot product is cosine similarity. | |
| return (self.matrix @ q.T).toarray().ravel() | |
| def _query_coverage(query: str, text: str) -> float: | |
| qterms = {t for t in re.findall(r"[A-Za-z0-9_-]+", query.lower()) if len(t) > 1 and t not in ENGLISH_STOP_WORDS} | |
| if not qterms: | |
| return 0.0 | |
| dterms = set(re.findall(r"[A-Za-z0-9_-]+", text.lower())) | |
| return len(qterms & dterms) / len(qterms) | |
| def search(self, query: str, k: int = 4) -> list[RetrievalHit]: | |
| if k <= 0: | |
| return [] | |
| scores = self.lexical_scores(query) | |
| pool_n = min(max(k, self.config.candidate_pool), len(self.documents)) | |
| # Stable sort makes benchmark results reproducible under ties. | |
| cand = np.argsort(-scores, kind="stable")[:pool_n] | |
| logu: list[float] = [] | |
| rows: list[tuple[int, float, float, float]] = [] | |
| for i in cand: | |
| d = self.documents[int(i)] | |
| s = max(float(scores[int(i)]), 0.0) | |
| lp = self._log_prior(d) | |
| ll = self.config.likelihood_temperature * math.log(s + self.config.likelihood_floor) | |
| u = lp + ll | |
| rows.append((int(i), s, lp, ll)) | |
| logu.append(u) | |
| lse = _logsumexp(logu) | |
| probs = [math.exp(u - lse) if math.isfinite(lse) else 1.0 / len(logu) for u in logu] | |
| ranked = sorted( | |
| zip(rows, probs), | |
| key=lambda rp: (rp[1], rp[0][1]), | |
| reverse=True, | |
| ) | |
| hits: list[RetrievalHit] = [] | |
| for rank, ((i, s, lp, ll), pr) in enumerate(ranked[:k], start=1): | |
| hits.append(RetrievalHit( | |
| document=self.documents[i], | |
| retrieval_score=s, | |
| log_prior=lp, | |
| log_likelihood=ll, | |
| log_posterior_unnormalized=lp + ll, | |
| posterior=float(pr), | |
| query_coverage=self._query_coverage(query, self.documents[i].retrieval_text), | |
| rank=rank, | |
| )) | |
| return hits | |
| def should_abstain(self, hits: Sequence[RetrievalHit]) -> bool: | |
| if not hits: | |
| return True | |
| top = hits[0] | |
| if ( | |
| top.retrieval_score < self.config.min_relevance | |
| or top.posterior < self.config.min_top_posterior | |
| or top.query_coverage < self.config.min_query_coverage | |
| ): | |
| return True | |
| if len(hits) > 1: | |
| second = hits[1] | |
| dominance = top.posterior / max(second.posterior, 1e-12) | |
| if dominance < self.config.min_dominance_ratio: | |
| return True | |
| return False | |
| class ExtractiveGroundedGenerator: | |
| """Dependency-free grounded answer synthesizer for tests and offline use. | |
| It selects the query-relevant sentence from the highest-posterior evidence | |
| and returns that evidence sentence verbatim with its document id. It never | |
| invents content not present in the retrieved context. | |
| """ | |
| abstain_text = "I do not know based on the provided documents." | |
| def _sentences(text: str) -> list[str]: | |
| parts = re.split(r"(?<=[.!?])\s+|\n+", text.strip()) | |
| return [p.strip() for p in parts if p.strip()] | |
| def generate(self, question: str, hits: Sequence[RetrievalHit]) -> tuple[str, list[str]]: | |
| if not hits: | |
| return self.abstain_text, [] | |
| # The posterior ranking has already selected the evidence hypothesis. | |
| top = hits[0] | |
| sents = self._sentences(top.document.text) | |
| if not sents: | |
| return self.abstain_text, [] | |
| if len(sents) == 1: | |
| sentence = sents[0] | |
| else: | |
| v = TfidfVectorizer(lowercase=True, stop_words="english", ngram_range=(1, 2)) | |
| mat = v.fit_transform([question] + sents) | |
| q = mat[0] | |
| sims = (mat[1:] @ q.T).toarray().ravel() | |
| sentence = sents[int(np.argmax(sims))] | |
| return f"{sentence} [{top.document.id}]", [top.document.id] | |
| class PlainTfidfIndex: | |
| """Conventional lexical baseline used by the end-to-end RAG benchmark.""" | |
| def __init__(self, documents: Sequence[DocumentRecord]): | |
| self.documents = list(documents) | |
| self.vectorizer = TfidfVectorizer(lowercase=True, stop_words="english", ngram_range=(1, 2), sublinear_tf=True, norm="l2") | |
| self.matrix = self.vectorizer.fit_transform([d.retrieval_text for d in self.documents]) | |
| def _query_coverage(query: str, text: str) -> float: | |
| qterms = {t for t in re.findall(r"[A-Za-z0-9_-]+", query.lower()) if len(t) > 1 and t not in ENGLISH_STOP_WORDS} | |
| if not qterms: | |
| return 0.0 | |
| dterms = set(re.findall(r"[A-Za-z0-9_-]+", text.lower())) | |
| return len(qterms & dterms) / len(qterms) | |
| def search(self, query: str, k: int = 4) -> list[RetrievalHit]: | |
| q = self.vectorizer.transform([query]) | |
| scores = (self.matrix @ q.T).toarray().ravel() | |
| order = np.argsort(-scores, kind="stable")[:k] | |
| # Baseline assigns normalized positive retrieval mass only for reporting. | |
| vals = [max(float(scores[int(i)]), 0.0) for i in order] | |
| z = sum(vals) or 1.0 | |
| return [RetrievalHit(self.documents[int(i)], float(scores[int(i)]), 0.0, 0.0, 0.0, vals[j]/z, 0.0, j+1) for j, i in enumerate(order)] | |