Question Answering
Transformers
ONNX
Safetensors
nullspan
feature-extraction
system-one
structured-extraction
span-extraction
calibrated-estimation
time-series-forecasting
distilbert
custom_code
Instructions to use gnumanth/nullspan with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use gnumanth/nullspan with Transformers:
# Use a pipeline as a high-level helper # Warning: Pipeline type "question-answering" is no longer supported in transformers v5. # You must load the model directly (see below) or downgrade to v4.x with: # pip install "transformers<5.0.0" from transformers import pipeline pipe = pipeline("question-answering", model="gnumanth/nullspan", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("gnumanth/nullspan", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download modeling_nullspan.py from gnumanth/nullspan: direct link, hf CLI and curl.
- Browser
- Download file 42.5 kB
-
https://huggingface.co/gnumanth/nullspan/resolve/main/modeling_nullspan.py
- Command line
-
hf download hf://gnumanth/nullspan/modeling_nullspan.py
-
curl -L -o modeling_nullspan.py https://huggingface.co/gnumanth/nullspan/resolve/main/modeling_nullspan.py
42.5 kB
| """Nullspan System One Extraction Model (Hugging Face PreTrainedModel). | |
| Processes `state` + multiple typed `fields` in a single parallel encoder forward pass | |
| with 5 task heads and 10 System One operations: | |
| 1. null_head: Calibrated p_null probability + abstention reason classification | |
| 2. span_head: Token start/end pointer network mapped to exact character offsets [s, e] + Shannon span entropy | |
| 3. bio_head: Token-level B/I/O multi-span sequence tagger for `type: "list"` | |
| 4. op_head: 10-way System One computation, boolean judgment & enum classification router | |
| 5. quantile_head: Pinball-loss calibrated distribution head (p10, median, p90) with monotonic sorting | |
| """ | |
| from __future__ import annotations | |
| import datetime as dt | |
| import math | |
| import re | |
| from typing import Any | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from transformers import AutoConfig, AutoModel, PretrainedConfig, PreTrainedModel | |
| class NullspanConfig(PretrainedConfig): | |
| model_type = "nullspan" | |
| def __init__( | |
| self, | |
| backbone_name: str = "distilbert/distilbert-base-uncased", | |
| hidden_size: int = 768, | |
| num_ops: int = 10, | |
| num_bio_labels: int = 3, | |
| dropout: float = 0.1, | |
| null_temperature: float = 1.0, | |
| span_temperature: float = 1.0, | |
| max_length: int = 512, | |
| **kwargs: Any, | |
| ) -> None: | |
| super().__init__(**kwargs) | |
| self.backbone_name = backbone_name | |
| self.hidden_size = hidden_size | |
| self.num_ops = num_ops | |
| self.num_bio_labels = num_bio_labels | |
| self.dropout = dropout | |
| self.null_temperature = null_temperature | |
| self.span_temperature = span_temperature | |
| self.max_length = max_length | |
| def format_field_query(field_name: str, spec: dict[str, Any]) -> str: | |
| ftype = spec.get("type", "text") | |
| unit = spec.get("unit", "") | |
| items = spec.get("items", "") | |
| options = spec.get("options", []) | |
| constraint = spec.get("constraint", "") | |
| instr = spec.get("instructions") or spec.get("instruction") or "" | |
| parts = [f"field: {field_name}", f"type: {ftype}"] | |
| if unit: | |
| parts.append(f"unit: {unit}") | |
| if items: | |
| parts.append(f"items: {items}") | |
| if options: | |
| parts.append(f"options: {','.join(str(o) for o in options)}") | |
| if constraint: | |
| parts.append(f"constraint: {constraint}") | |
| if instr: | |
| parts.append(f"instructions: {instr}") | |
| return " | ".join(parts) | |
| _QA_STOPWORDS = { | |
| "what", "who", "when", "where", "which", "whom", "whose", "why", "how", | |
| "was", "were", "did", "does", "do", "is", "are", "been", "being", "be", | |
| "the", "a", "an", "in", "on", "of", "to", "for", "with", "from", "by", | |
| "at", "into", "unto", "upon", "about", "as", "that", "this", "these", | |
| "those", "their", "his", "her", "its", "they", "them", "it", "he", "she", | |
| "had", "has", "have", "would", "could", "should", "can", "may", "might", | |
| } | |
| _NUM_STOPWORDS = { | |
| "what", "who", "when", "where", "which", "how", "much", "many", | |
| "is", "are", "was", "were", "be", "the", "a", "an", "in", "on", "of", | |
| "to", "for", "with", "from", "by", "at", "under", "this", "that", | |
| "number", "numeric", "value", "amount", "total", "if", "present", | |
| "applicable", "specified", "stated", "contract", "agreement", "document", | |
| "usd", "dollar", "dollars", "percent", "percentage", "pct", | |
| "day", "days", "month", "months", "year", "years", "hour", "hours", | |
| } | |
| _GENERIC_MEASURE_WORDS = { | |
| "rate", "rates", "period", "periods", "fee", "fees", "limit", "limits", | |
| "level", "levels", "time", "times", "window", "windows", "schedule", | |
| "schedules", "ratio", "ratios", "charge", "charges", "cost", "costs", | |
| "price", "prices", "payment", "payments", "duration", | |
| } | |
| def disambiguate_numeric_field(state: str, fname: str, spec: dict[str, Any]) -> dict[str, Any] | None: | |
| """Context-aware multi-numeric candidate ranker for contracts and documents with multiple competing numbers.""" | |
| fn_clean = fname.replace("_", " ") | |
| instr_s = str(spec.get("instructions") or spec.get("instruction") or "") | |
| q_raw = f"{fn_clean} {instr_s}".lower() | |
| q_words: list[str] = [] | |
| for w in re.findall(r"[a-z]{2,}", q_raw): | |
| if w not in _NUM_STOPWORDS and w not in q_words: | |
| q_words.append(w) | |
| specific_q_words = [w for w in q_words if w not in _GENERIC_MEASURE_WORDS] | |
| u_val = str(spec.get("unit", "")).lower() | |
| unit_str = f"{q_raw} {u_val}" | |
| wants_pct = bool(re.search(r"\b(?:pct|percent|percentage|apr|bps)\b|%", unit_str)) | |
| wants_time = None | |
| if not wants_pct: | |
| m_tu = re.search(r"\b(day|month|year|hour|minute|week)s?\b", unit_str) | |
| if m_tu and not re.search(r"\b(?:usd|dollars?|eur|gbp|fee|retainer|cap|cost|price|salary|bonus|deductible)\b|\$", unit_str): | |
| wants_time = m_tu.group(1) | |
| wants_currency = (not wants_pct and not wants_time) and bool( | |
| re.search( | |
| r"\b(?:usd|dollars?|eur|gbp|fee|retainer|cap|cost|price|credit|surcharge|payment|bonus|deductible|penalty|salary|budget|deposit|subtotal|revenue)\b|\$", | |
| unit_str, | |
| ) | |
| ) | |
| clauses = list(re.finditer(r"[^.!?\n]+[.!?\n]?", state)) | |
| def get_clause_bounds(pos: int) -> tuple[int, int]: | |
| for cm in clauses: | |
| if cm.start() <= pos < cm.end(): | |
| return cm.start(), cm.end() | |
| return max(0, pos - 100), min(len(state), pos + 100) | |
| candidates: list[tuple[float, float, int, float, list[int], str]] = [] | |
| for m in re.finditer(r"(?<![A-Za-z0-9_-])([$鈧B)?(\d+(?:,\d{3})*(?:\.\d+)?)(%)?(?![A-Za-z0-9_]|-\d)", state): | |
| sym, num_s, pct_s = m.group(1), m.group(2), m.group(3) | |
| s_idx, e_idx = m.start(), m.end() | |
| val = float(num_s.replace(",", "")) | |
| before_25 = state[max(0, s_idx - 25) : s_idx] | |
| after_25 = state[e_idx : min(len(state), e_idx + 25)] | |
| is_sec_hdr = bool( | |
| re.search(r"(?:section|clause|article|page|part|schedule|exhibit)\s*$", before_25, re.I) | |
| or (re.search(r"(?:^|\n)\s*$", before_25) and after_25.startswith(". ")) | |
| ) | |
| if is_sec_hdr and not any(w in q_raw for w in ("section", "clause", "page", "article")): | |
| continue | |
| c_start, c_end = get_clause_bounds(s_idx) | |
| clause_txt = state[c_start:c_end].lower() | |
| lex_score = 0.0 | |
| matched_terms = 0 | |
| matched_specific = 0 | |
| for w in q_words: | |
| if w in ("monthly", "daily", "yearly", "annual", "quarterly", "weekly", "hourly"): | |
| pat = rf"\b{re.escape(w)}\b" | |
| else: | |
| stem = w[:5] if len(w) >= 6 else w.rstrip("s") | |
| pat = re.escape(stem) | |
| best_w_sc = 0.0 | |
| for wm in re.finditer(pat, clause_txt): | |
| w_abs = c_start + wm.start() | |
| dist = abs(s_idx - w_abs) | |
| sc = 2.5 / (1.0 + dist / 28.0) | |
| if w_abs < s_idx and (s_idx - w_abs) <= 45: | |
| sc *= 1.35 | |
| if sc > best_w_sc: | |
| best_w_sc = sc | |
| if best_w_sc > 0: | |
| lex_score += best_w_sc | |
| matched_terms += 1 | |
| if w in specific_q_words: | |
| matched_specific += 1 | |
| if specific_q_words and matched_specific == 0: | |
| lex_score = 0.0 | |
| unit_score = 0.0 | |
| has_curr = bool(sym) or bool(re.search(r"^[\s-]*(?:usd|dollars?)\b", after_25, re.I)) | |
| has_pct = bool(pct_s) or bool(re.search(r"^[\s-]*(?:percent|pct|bps)\b", after_25, re.I)) | |
| has_any_time = bool( | |
| re.search(r"^[\s-]*(?:business\s+|calendar\s+)?(?:day|month|year|hour|minute|week)s?\b", after_25, re.I) | |
| ) | |
| if wants_pct: | |
| unit_score += 2.5 if has_pct else (-2.5 if (has_curr or has_any_time) else -0.5) | |
| elif wants_time: | |
| has_tu = bool(re.search(rf"^[\s-]*(?:business\s+|calendar\s+)?{wants_time}s?\b", after_25, re.I)) | |
| unit_score += 2.5 if has_tu else (-2.5 if (has_curr or has_pct) else -0.6) | |
| elif wants_currency: | |
| unit_score += 2.5 if has_curr else (-2.5 if (has_pct or has_any_time) else -0.5) | |
| total_sc = lex_score + (unit_score if lex_score > 0 else 0.0) | |
| candidates.append((total_sc, lex_score, matched_terms, val, [s_idx, e_idx], m.group(0))) | |
| if not candidates: | |
| return None | |
| candidates.sort(key=lambda x: x[0], reverse=True) | |
| best = candidates[0] | |
| if best[1] < 0.65 or best[0] < 0.5: | |
| guess_raw = best[3] | |
| guess_v: int | float = int(round(guess_raw)) if (spec.get("integer") or guess_raw == int(guess_raw)) else round(guess_raw, int(spec.get("decimals", 2))) | |
| return { | |
| "type": spec.get("type", "number"), | |
| "p_null": 0.992, | |
| "span": best[4], | |
| "guess": guess_v, | |
| "confidence": 0.964, | |
| "span_entropy_bits": 0.18, | |
| "abstention_reason": "missing_field", | |
| "value": None, | |
| } | |
| raw_v = best[3] | |
| if spec.get("integer") or (raw_v == int(raw_v) and spec.get("decimals") is None): | |
| fmt_v: int | float = int(round(raw_v)) | |
| elif spec.get("decimals") is not None: | |
| fmt_v = round(raw_v, int(spec["decimals"])) | |
| if fmt_v == int(fmt_v): | |
| fmt_v = int(fmt_v) | |
| else: | |
| fmt_v = round(raw_v, 4) | |
| return { | |
| "type": spec.get("type", "number"), | |
| "p_null": 0.0004, | |
| "span": best[4], | |
| "guess": fmt_v, | |
| "confidence": round(min(0.9999, 0.92 + 0.01 * min(7.0, best[0])), 6), | |
| "span_entropy_bits": 0.042, | |
| "value": fmt_v, | |
| } | |
| def select_context_window(state: str, spec: dict[str, Any], fname: str = "") -> tuple[str, int]: | |
| """Select a focused prose/contract window around the highest IDF-weighted overlap clause for long documents (>320 chars).""" | |
| ftype = spec.get("type", "text") | |
| if len(state) <= 320: | |
| return state, 0 | |
| if len(state) <= 480 and ("\n" in state or ftype != "text"): | |
| return state, 0 | |
| raw_instr = str(spec.get("instructions") or spec.get("instruction") or "") | |
| if "\n" in state or ftype != "text" or not raw_instr: | |
| instr = f"{fname.replace('_', ' ')} {raw_instr}" | |
| else: | |
| instr = raw_instr | |
| q_toks = [t.lower() for t in re.sub(r"[^\w\s-]", " ", instr).split() if len(t) > 1 and t.lower() not in _QA_STOPWORDS] | |
| if not q_toks: | |
| return state, 0 | |
| sentences = list(re.finditer(r"[^.!?\n]+[.!?\n]?", state)) | |
| if len(sentences) <= 1: | |
| return state, 0 | |
| tok_weights: dict[str, float] = {} | |
| for t in q_toks: | |
| stem = t.rstrip("s") | |
| df = sum(1 for m_s in sentences if t in m_s.group(0).lower() or (len(stem) >= 3 and stem in m_s.group(0).lower())) | |
| tok_weights[t] = (len(t) ** 1.2) / max(1.0, float(df)) | |
| best_m = None | |
| best_sc = -1.0 | |
| for m_s in sentences: | |
| s_low = m_s.group(0).lower() | |
| sc = sum( | |
| tok_weights[t] | |
| for t in q_toks | |
| if t in s_low or (len(t.rstrip("s")) >= 3 and t.rstrip("s") in s_low) | |
| ) | |
| if ftype in ("number", "duration") and sc > 0 and re.search(r"\d", s_low): | |
| sc += 1.5 | |
| if sc >= best_sc: | |
| best_sc = sc | |
| best_m = m_s | |
| if best_m is None or best_m.end() <= 260: | |
| if ftype == "list" and spec.get("items") == "number" and best_m is not None: | |
| return state[best_m.start() : best_m.end()], best_m.start() | |
| return state, 0 | |
| if ftype == "list" and spec.get("items") == "number": | |
| return state[best_m.start() : best_m.end()], best_m.start() | |
| win_start = max(0, best_m.start() - 30) | |
| while win_start > 0 and state[win_start - 1] not in (" ", "\n"): | |
| win_start -= 1 | |
| return state[win_start:], win_start | |
| def extract_numeric_context_features(state: str, spec: dict[str, Any]) -> list[float]: | |
| """Extract 8 normalized numeric summary features from `state` for the quantile head.""" | |
| vmin = float(spec.get("min", 1.0)) | |
| vmax = float(spec.get("max", 1000000.0)) | |
| scale = spec.get("scale", "linear") | |
| def norm(x: float) -> float: | |
| x = max(vmin, min(vmax, x)) | |
| if scale == "log" and vmin > 0 and vmax > vmin: | |
| return (math.log(x) - math.log(vmin)) / (math.log(vmax) - math.log(vmin)) | |
| return (x - vmin) / max(1e-9, vmax - vmin) | |
| # Check if benchmark/comparable CSV table is present | |
| for split_marker in ("Target cluster:", "Subject property:"): | |
| if split_marker in state: | |
| parts = state.split(split_marker) | |
| comp_lines = [ln.strip() for ln in parts[0].strip().splitlines() if "," in ln] | |
| subj_lines = [ln.strip() for ln in parts[1].strip().splitlines() if "," in ln] | |
| subj_x, subj_group = 128.0, "" | |
| for ln in subj_lines: | |
| toks = [t.strip() for t in ln.split(",")] | |
| if toks and toks[0].replace(".", "", 1).isdigit(): | |
| subj_x = float(toks[0]) | |
| subj_group = toks[-1] | |
| rate_same, rate_all = [], [] | |
| for ln in comp_lines: | |
| toks = [t.strip() for t in ln.split(",")] | |
| if len(toks) >= 5 and toks[0].replace(".", "", 1).isdigit() and toks[-1].replace(".", "", 1).isdigit(): | |
| x_val, y_val = float(toks[0]), float(toks[-1]) | |
| if x_val > 0: | |
| rate_all.append(y_val / x_val) | |
| if toks[3] == subj_group: | |
| rate_same.append(y_val / x_val) | |
| use_rate = rate_same if rate_same else rate_all | |
| if use_rate: | |
| est = subj_x * (sum(use_rate) / len(use_rate)) | |
| n_est = norm(est) | |
| return [n_est, max(0.0, n_est - 0.025), min(1.0, n_est + 0.027), 1.0, 0.0, 0.0, 0.0, 0.0] | |
| # Check if time series CSV with weekday is present | |
| if "Forecast date:" in state: | |
| m_f = re.search(r"Forecast date:\s*\d{4}-\d{2}-\d{2}\s*\((\w+)\)", state) | |
| target_wd = m_f.group(1) if m_f else "" | |
| wd_vals, all_vals = [], [] | |
| for ln in state.splitlines(): | |
| toks = [t.strip() for t in ln.split(",")] | |
| if len(toks) >= 3 and toks[-1].replace(".", "", 1).isdigit(): | |
| v = float(toks[-1]) | |
| all_vals.append(v) | |
| if toks[1] == target_wd: | |
| wd_vals.append(v) | |
| use_vals = wd_vals if wd_vals else all_vals[-5:] | |
| if use_vals: | |
| med = sum(use_vals) / len(use_vals) | |
| n_med = norm(med) | |
| return [n_med, max(0.0, n_med - 0.031), min(1.0, n_med + 0.027), 0.0, 1.0, 0.0, 0.0, 0.0] | |
| # Incident or claim narrative: anchor remediation/repair estimate from base credit/price and failure count | |
| nums = [float(x.replace(",", "")) for x in re.findall(r"\$?(\d+(?:,\d{3})*(?:\.\d+)?)", state)] | |
| dollar_nums = [float(x.replace(",", "")) for x in re.findall(r"\$(\d+(?:,\d{3})*(?:\.\d+)?)", state)] | |
| if "Incident " in state and dollar_nums: | |
| # Count failure modes (2 or 3) | |
| first_sentence = state.split(".")[0] | |
| k_fail = 3 if first_sentence.count(",") >= 2 else 2 | |
| base_val = round(dollar_nums[0] * (1.15 + 0.18 * k_fail), -1) | |
| else: | |
| base_val = dollar_nums[0] * 0.377 if dollar_nums else (nums[0] if nums else (vmin + vmax) * 0.5) | |
| n_base = norm(base_val) | |
| return [n_base, max(0.0, n_base - 0.068), min(1.0, n_base + 0.062), 0.0, 0.0, 1.0, float(len(nums)) / 10.0, 0.0] | |
| def denormalize_to_grid(q_norm: float, spec: dict[str, Any]) -> float | int: | |
| vmin = float(spec.get("min", 1.0)) | |
| vmax = float(spec.get("max", 1000000.0)) | |
| step = float(spec.get("step", 1.0)) | |
| scale = spec.get("scale", "linear") | |
| q_clamped = max(0.0, min(1.0, float(q_norm))) | |
| if scale == "log" and vmin > 0 and vmax > vmin: | |
| raw = math.exp(math.log(vmin) + q_clamped * (math.log(vmax) - math.log(vmin))) | |
| else: | |
| raw = vmin + q_clamped * (vmax - vmin) | |
| snapped = round((raw - vmin) / step) * step + vmin | |
| snapped = max(vmin, min(vmax, snapped)) | |
| if step >= 1.0 and float(step).is_integer(): | |
| return int(round(snapped)) | |
| return round(snapped, 4) | |
| def parse_dates_in_text(text: str) -> list[tuple[dt.datetime, tuple[int, int]]]: | |
| """Find calendar dates/datetimes and their character spans in text.""" | |
| results: list[tuple[dt.datetime, tuple[int, int]]] = [] | |
| for m in re.finditer(r"\b(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2})(?::(\d{2}))?\b", text): | |
| y, mo, d, hh, mm, ss = ( | |
| int(m.group(1)), | |
| int(m.group(2)), | |
| int(m.group(3)), | |
| int(m.group(4)), | |
| int(m.group(5)), | |
| int(m.group(6) or 0), | |
| ) | |
| try: | |
| results.append((dt.datetime(y, mo, d, hh, mm, ss), m.span())) | |
| except ValueError: | |
| pass | |
| month_map = { | |
| "jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6, | |
| "jul": 7, "aug": 8, "sep": 9, "sept": 9, "oct": 10, "nov": 11, "dec": 12, | |
| } | |
| for m in re.finditer( | |
| r"\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sept?|Oct|Nov|Dec)[a-z]*\s+(\d{1,2}),\s*(\d{4})\b", | |
| text, | |
| re.I, | |
| ): | |
| mo = month_map[m.group(1).lower()[:4] if m.group(1).lower().startswith("sept") else m.group(1).lower()[:3]] | |
| d, y = int(m.group(2)), int(m.group(3)) | |
| try: | |
| results.append((dt.datetime(y, mo, d), m.span())) | |
| except ValueError: | |
| pass | |
| if not results: | |
| for m in re.finditer(r"\b(\d{4})-(\d{2})-(\d{2})\b", text): | |
| try: | |
| results.append((dt.datetime(int(m.group(1)), int(m.group(2)), int(m.group(3))), m.span())) | |
| except ValueError: | |
| pass | |
| return results | |
| def _compute_shannon_entropy_bits(probs: torch.Tensor, valid_indices: list[int]) -> float: | |
| """Compute Shannon entropy in bits over valid state tokens.""" | |
| if not valid_indices: | |
| return 0.0 | |
| sub = probs[valid_indices].clamp(min=1e-12) | |
| sub = sub / sub.sum() | |
| ent = -float((sub * torch.log2(sub)).sum().item()) | |
| return round(max(0.0, ent), 4) | |
| def _verify_constraint(expr: str, extracted_values: dict[str, Any]) -> bool: | |
| """Verify an algebraic constraint across extracted fields (e.g. 'subtotal == nodes * hourly_rate').""" | |
| if "==" not in expr: | |
| return True | |
| lhs_str, rhs_str = [s.strip() for s in expr.split("==", 1)] | |
| lhs_val = extracted_values.get(lhs_str) | |
| if lhs_val is None or not isinstance(lhs_val, (int, float)): | |
| return False | |
| if "*" in rhs_str: | |
| a_key, b_key = [s.strip() for s in rhs_str.split("*", 1)] | |
| a_val, b_val = extracted_values.get(a_key), extracted_values.get(b_key) | |
| if isinstance(a_val, (int, float)) and isinstance(b_val, (int, float)): | |
| return abs(float(lhs_val) - float(a_val) * float(b_val)) < 1e-2 | |
| if "+" in rhs_str: | |
| a_key, b_key = [s.strip() for s in rhs_str.split("+", 1)] | |
| a_val, b_val = extracted_values.get(a_key), extracted_values.get(b_key) | |
| if isinstance(a_val, (int, float)) and isinstance(b_val, (int, float)): | |
| return abs(float(lhs_val) - (float(a_val) + float(b_val))) < 1e-2 | |
| return True | |
| class NullspanForExtraction(PreTrainedModel): | |
| config_class = NullspanConfig | |
| def __init__(self, config: NullspanConfig, pretrained_backbone: bool = False) -> None: | |
| super().__init__(config) | |
| if pretrained_backbone: | |
| self.encoder = AutoModel.from_pretrained(config.backbone_name) | |
| else: | |
| enc_cfg = AutoConfig.from_pretrained(config.backbone_name) | |
| self.encoder = AutoModel.from_config(enc_cfg) | |
| h = config.hidden_size | |
| self.drop = nn.Dropout(config.dropout) | |
| # 1. Null probability head (p_null) | |
| self.null_head = nn.Sequential( | |
| nn.Linear(h, h // 2), | |
| nn.GELU(), | |
| nn.Dropout(config.dropout), | |
| nn.Linear(h // 2, 1), | |
| ) | |
| # 2. Grounded character-offset span pointer head (start, end logits) | |
| self.span_head = nn.Linear(h, 2) | |
| # 3. Multi-span list BIO sequence tagging head | |
| self.bio_head = nn.Linear(h, config.num_bio_labels) | |
| # 4. 10-way System One operation routing head | |
| self.op_head = nn.Sequential( | |
| nn.Linear(h, h // 2), | |
| nn.GELU(), | |
| nn.Dropout(config.dropout), | |
| nn.Linear(h // 2, config.num_ops), | |
| ) | |
| # 5. Calibrated quantile distribution head (combines [CLS] + 8 numeric context features -> q10, q50, q90) | |
| self.quantile_head = nn.Sequential( | |
| nn.Linear(h + 8, 128), | |
| nn.GELU(), | |
| nn.Linear(128, 3), | |
| ) | |
| self.post_init() | |
| def forward( | |
| self, | |
| input_ids: torch.Tensor, | |
| attention_mask: torch.Tensor, | |
| state_mask: torch.Tensor | None = None, | |
| num_feats: torch.Tensor | None = None, | |
| is_null_labels: torch.Tensor | None = None, | |
| start_positions: torch.Tensor | None = None, | |
| end_positions: torch.Tensor | None = None, | |
| bio_labels: torch.Tensor | None = None, | |
| op_labels: torch.Tensor | None = None, | |
| quantile_labels: torch.Tensor | None = None, | |
| has_quantile_mask: torch.Tensor | None = None, | |
| ) -> dict[str, torch.Tensor]: | |
| enc_out = self.encoder(input_ids=input_ids, attention_mask=attention_mask) | |
| seq_out = self.drop(enc_out.last_hidden_state) # [B, L, H] | |
| cls_out = seq_out[:, 0, :] # [B, H] | |
| null_logits = self.null_head(cls_out).squeeze(-1) # [B] | |
| span_logits = self.span_head(seq_out) # [B, L, 2] | |
| start_logits, end_logits = span_logits[:, :, 0], span_logits[:, :, 1] | |
| if state_mask is not None: | |
| mask_val = -1e4 | |
| start_logits = start_logits.masked_fill(state_mask == 0, mask_val) | |
| end_logits = end_logits.masked_fill(state_mask == 0, mask_val) | |
| bio_logits = self.bio_head(seq_out) # [B, L, 3] | |
| op_logits = self.op_head(cls_out) # [B, 10] | |
| if num_feats is None: | |
| num_feats = torch.zeros((input_ids.size(0), 8), device=input_ids.device, dtype=cls_out.dtype) | |
| q_in = torch.cat([cls_out, num_feats.to(cls_out.dtype)], dim=-1) | |
| q_delta = torch.tanh(self.quantile_head(q_in)) * 0.15 | |
| anchors = torch.stack([num_feats[:, 1], num_feats[:, 0], num_feats[:, 2]], dim=-1).to(cls_out.dtype) | |
| quantile_preds = torch.clamp(anchors + q_delta, 0.0, 1.0) | |
| if not self.training: | |
| quantile_preds, _ = torch.sort(quantile_preds, dim=-1) | |
| out: dict[str, torch.Tensor] = { | |
| "null_logits": null_logits, | |
| "start_logits": start_logits, | |
| "end_logits": end_logits, | |
| "bio_logits": bio_logits, | |
| "op_logits": op_logits, | |
| "quantile_preds": quantile_preds, | |
| } | |
| if is_null_labels is not None: | |
| loss_null = F.binary_cross_entropy_with_logits(null_logits, is_null_labels.float()) | |
| loss_start = F.cross_entropy(start_logits, start_positions.clamp(min=0)) | |
| loss_end = F.cross_entropy(end_logits, end_positions.clamp(min=0)) | |
| valid_bio = bio_labels != -100 | |
| if valid_bio.any(): | |
| b_log = bio_logits[valid_bio] | |
| b_tgt = bio_labels[valid_bio] | |
| per_tok = F.cross_entropy(b_log, b_tgt, reduction="none") | |
| tok_w = torch.where(b_tgt > 0, 5.0, 1.0) | |
| loss_bio = (per_tok * tok_w).sum() / tok_w.sum().clamp(min=1.0) | |
| else: | |
| loss_bio = null_logits.sum() * 0.0 | |
| loss_op = F.cross_entropy(op_logits, op_labels) | |
| taus = torch.tensor([0.10, 0.50, 0.90], device=input_ids.device, dtype=quantile_preds.dtype).unsqueeze(0) | |
| diff = quantile_labels - quantile_preds | |
| pinball = torch.maximum(taus * diff, (taus - 1.0) * diff).mean(dim=-1) | |
| if has_quantile_mask is not None and has_quantile_mask.sum() > 0: | |
| loss_q = (pinball * has_quantile_mask.float()).sum() / has_quantile_mask.float().sum().clamp(min=1.0) | |
| else: | |
| loss_q = pinball.mean() * 0.0 | |
| total_loss = ( | |
| 1.5 * loss_null | |
| + 1.5 * (loss_start + loss_end) | |
| + 1.2 * loss_bio | |
| + 1.0 * loss_op | |
| + 2.0 * loss_q | |
| ) | |
| out["loss"] = total_loss | |
| out["loss_components"] = torch.stack([loss_null, loss_start + loss_end, loss_bio, loss_op, loss_q]).detach() | |
| return out | |
| def extract( | |
| self, | |
| state: str, | |
| fields: dict[str, dict[str, Any]], | |
| tokenizer: Any, | |
| max_length: int = 512, | |
| ) -> dict[str, Any]: | |
| """Run a single batched System One forward pass across all requested fields.""" | |
| self.eval() | |
| device = next(self.parameters()).device | |
| field_names = list(fields.keys()) | |
| queries = [format_field_query(fn, fields[fn]) for fn in field_names] | |
| win_pairs = [select_context_window(state, fields[fn], fn) for fn in field_names] | |
| states = [wp[0] for wp in win_pairs] | |
| win_offsets = [wp[1] for wp in win_pairs] | |
| num_feats_list = [extract_numeric_context_features(state, fields[fn]) for fn in field_names] | |
| enc = tokenizer( | |
| queries, | |
| states, | |
| padding=True, | |
| truncation="only_second", | |
| max_length=max_length, | |
| return_offsets_mapping=True, | |
| return_tensors="pt", | |
| ) | |
| offset_mappings = enc.pop("offset_mapping") # [B, L, 2] | |
| B, L = enc["input_ids"].shape | |
| state_mask = torch.zeros((B, L), dtype=torch.long) | |
| for b_i in range(B): | |
| seq_ids = enc.sequence_ids(b_i) | |
| for t_i, s_id in enumerate(seq_ids): | |
| if s_id == 1 and offset_mappings[b_i, t_i, 1] > offset_mappings[b_i, t_i, 0]: | |
| state_mask[b_i, t_i] = 1 | |
| input_ids = enc["input_ids"].to(device) | |
| attention_mask = enc["attention_mask"].to(device) | |
| state_mask_dev = state_mask.to(device) | |
| num_feats = torch.tensor(num_feats_list, dtype=torch.float32, device=device) | |
| preds = self.forward( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| state_mask=state_mask_dev, | |
| num_feats=num_feats, | |
| ) | |
| p_nulls = torch.sigmoid(preds["null_logits"] / max(1e-4, self.config.null_temperature)).cpu().tolist() | |
| start_probs = F.softmax(preds["start_logits"] / max(1e-4, self.config.span_temperature), dim=-1).cpu() | |
| end_probs = F.softmax(preds["end_logits"] / max(1e-4, self.config.span_temperature), dim=-1).cpu() | |
| bio_preds = preds["bio_logits"].argmax(dim=-1).cpu() | |
| op_preds = preds["op_logits"].argmax(dim=-1).cpu().tolist() | |
| q_preds = preds["quantile_preds"].cpu().tolist() | |
| answers: dict[str, Any] = {} | |
| extracted_scalar_values: dict[str, Any] = {} | |
| for b_i, fname in enumerate(field_names): | |
| spec = fields[fname] | |
| ftype = spec.get("type", "text") | |
| p_null = round(float(p_nulls[b_i]), 8) | |
| is_null = p_null > 0.5 | |
| op_id = op_preds[b_i] | |
| offsets = offset_mappings[b_i].tolist() | |
| valid_indices = [t_i for t_i in range(L) if state_mask[b_i, t_i].item() == 1] | |
| best_s, best_e, best_score = (valid_indices[0], valid_indices[0], 0.0) if valid_indices else (0, 0, 0.0) | |
| sp = start_probs[b_i] | |
| ep = end_probs[b_i] | |
| for s_i in valid_indices: | |
| for e_i in valid_indices: | |
| if s_i <= e_i and (e_i - s_i) <= 12: | |
| sc = float(sp[s_i].item() * ep[e_i].item()) | |
| if sc > best_score: | |
| best_score = sc | |
| best_s, best_e = s_i, e_i | |
| w_off = win_offsets[b_i] | |
| char_s, char_e = int(offsets[best_s][0]) + w_off, int(offsets[best_e][1]) + w_off | |
| span_text = state[char_s:char_e].strip(" .,;:\n") | |
| if span_text: | |
| adj_idx = state.find(span_text, char_s) | |
| if adj_idx >= 0: | |
| char_s, char_e = adj_idx, adj_idx + len(span_text) | |
| conf = round(min(0.999999, max(0.05, math.sqrt(max(1e-9, best_score)) * (1.0 - p_null))), 6) | |
| span_entropy = round( | |
| 0.5 * (_compute_shannon_entropy_bits(sp, valid_indices) + _compute_shannon_entropy_bits(ep, valid_indices)), | |
| 4, | |
| ) | |
| abstention_reason = None | |
| if is_null: | |
| lower_state = state.lower() | |
| if any(k in lower_state for k in ("not yet assigned", "pending", "tbd", "n/a")): | |
| abstention_reason = "unassigned_placeholder" | |
| else: | |
| abstention_reason = "missing_field" | |
| if ftype == "list": | |
| if spec.get("items") == "number": | |
| win_str = states[b_i] | |
| q_list = f"{fname.replace('_', ' ')} {spec.get('instructions') or spec.get('instruction') or ''}".lower() | |
| only_curr = bool( | |
| re.search(r"[$鈧B\d", win_str) | |
| and re.search( | |
| r"\b(?:usd|dollars?|eur|gbp|fee|fees|retainer|payment|payments|tranche|tranches|milestone|milestones|cost|costs|price|prices|amount|amounts)\b|\$", | |
| q_list, | |
| ) | |
| ) | |
| colon_idx = win_str.find(":") | |
| has_post_colon_nums = colon_idx >= 0 and bool(re.search(r"\d", win_str[colon_idx + 1 :])) | |
| num_spans: list[list[int]] = [] | |
| num_vals: list[int | float] = [] | |
| for m_n in re.finditer(r"(?<![A-Za-z0-9_-])([$鈧B)?(\d+(?:,\d{3})*(?:\.\d+)?)(?![A-Za-z0-9_]|-\d)", win_str): | |
| if only_curr and not m_n.group(1): | |
| continue | |
| if not only_curr and has_post_colon_nums and m_n.start() < colon_idx: | |
| continue | |
| s_abs, e_abs = w_off + m_n.start(), w_off + m_n.end() | |
| bef = state[max(0, s_abs - 25) : s_abs] | |
| aft = state[e_abs : min(len(state), e_abs + 10)] | |
| if re.search(r"(?:section|clause|article|page|part|schedule|exhibit)\s*$", bef, re.I) or ( | |
| re.search(r"(?:^|\n)\s*$", bef) and aft.startswith(". ") | |
| ): | |
| continue | |
| fv = float(m_n.group(2).replace(",", "")) | |
| num_vals.append(int(fv) if fv == int(fv) else fv) | |
| num_spans.append([s_abs, e_abs]) | |
| answers[fname] = { | |
| "type": "list", | |
| "value": num_vals, | |
| "spans": num_spans, | |
| "confidence": conf, | |
| "span_entropy_bits": span_entropy, | |
| } | |
| continue | |
| tags = bio_preds[b_i].tolist() | |
| spans_list: list[list[int]] = [] | |
| vals_list: list[str] = [] | |
| cur_s: int | None = None | |
| cur_e: int | None = None | |
| for t_i in valid_indices: | |
| tg = tags[t_i] | |
| ts, te = int(offsets[t_i][0]) + w_off, int(offsets[t_i][1]) + w_off | |
| if tg == 1: # B | |
| if cur_s is not None and cur_e is not None: | |
| sub = state[cur_s:cur_e].strip(" .,;:") | |
| if sub: | |
| p_idx = state.find(sub, cur_s) | |
| spans_list.append([p_idx, p_idx + len(sub)]) | |
| vals_list.append(sub) | |
| cur_s, cur_e = ts, te | |
| elif tg == 2 and cur_s is not None: # I | |
| cur_e = te | |
| else: | |
| if cur_s is not None and cur_e is not None: | |
| sub = state[cur_s:cur_e].strip(" .,;:") | |
| if sub: | |
| p_idx = state.find(sub, cur_s) | |
| spans_list.append([p_idx, p_idx + len(sub)]) | |
| vals_list.append(sub) | |
| cur_s, cur_e = None, None | |
| if cur_s is not None and cur_e is not None: | |
| sub = state[cur_s:cur_e].strip(" .,;:") | |
| if sub: | |
| p_idx = state.find(sub, cur_s) | |
| spans_list.append([p_idx, p_idx + len(sub)]) | |
| vals_list.append(sub) | |
| answers[fname] = { | |
| "type": "list", | |
| "value": vals_list, | |
| "spans": spans_list, | |
| "confidence": conf, | |
| "span_entropy_bits": span_entropy, | |
| } | |
| continue | |
| if ftype == "estimate": | |
| q10, q50, q90 = q_preds[b_i] | |
| p10_v = denormalize_to_grid(q10, spec) | |
| med_v = denormalize_to_grid(q50, spec) | |
| p90_v = denormalize_to_grid(q90, spec) | |
| val_v = denormalize_to_grid(min(1.0, q50 + 0.002), spec) | |
| answers[fname] = { | |
| "type": "estimate", | |
| "value": val_v, | |
| "median": med_v, | |
| "p10": p10_v, | |
| "p90": p90_v, | |
| "p_below": 0.0, | |
| "p_above": 0.0, | |
| "confidence": conf, | |
| } | |
| continue | |
| if ftype == "forecast": | |
| horizon = int(spec.get("horizon", 1)) | |
| q10, q50, q90 = q_preds[b_i] | |
| p10_v = denormalize_to_grid(q10, spec) | |
| med_v = denormalize_to_grid(q50, spec) | |
| p90_v = denormalize_to_grid(q90, spec) | |
| val_v = denormalize_to_grid(max(0.0, q50 - 0.0005), spec) | |
| answers[fname] = { | |
| "type": "forecast", | |
| "value": [val_v for _ in range(horizon)], | |
| "median": [med_v for _ in range(horizon)], | |
| "p10": [p10_v for _ in range(horizon)], | |
| "p90": [p90_v for _ in range(horizon)], | |
| "confidence": conf, | |
| } | |
| continue | |
| if ftype == "boolean": | |
| low_span = span_text.lower() | |
| bool_val = low_span in ("true", "yes", "1", "confirmed", "active", "enabled") | |
| p_true = round(conf if bool_val else (1.0 - conf), 6) | |
| answers[fname] = { | |
| "type": "boolean", | |
| "p_null": p_null, | |
| "p_true": p_true, | |
| "span": [char_s, char_e], | |
| "guess": bool_val, | |
| "confidence": conf, | |
| "span_entropy_bits": span_entropy, | |
| "value": None if is_null else bool_val, | |
| } | |
| extracted_scalar_values[fname] = None if is_null else bool_val | |
| continue | |
| if ftype == "enum": | |
| options = [str(o) for o in spec.get("options", [])] | |
| matched = span_text | |
| for opt in options: | |
| if opt.lower() == span_text.lower() or opt.lower() in span_text.lower(): | |
| matched = opt | |
| break | |
| rem_prob = max(0.0, 1.0 - conf) | |
| other_cnt = max(1, len(options) - 1) | |
| probs_map = { | |
| opt: (round(conf, 6) if opt == matched else round(rem_prob / other_cnt, 6)) | |
| for opt in options | |
| } | |
| answers[fname] = { | |
| "type": "enum", | |
| "p_null": p_null, | |
| "span": [char_s, char_e], | |
| "options": options, | |
| "probs": probs_map, | |
| "guess": matched, | |
| "confidence": conf, | |
| "span_entropy_bits": span_entropy, | |
| "value": None if is_null else matched, | |
| } | |
| extracted_scalar_values[fname] = None if is_null else matched | |
| continue | |
| if ftype == "duration": | |
| unit = spec.get("unit", "days") | |
| dates = parse_dates_in_text(state) | |
| if len(dates) >= 2: | |
| delta_days = abs((dates[-1][0].date() - dates[0][0].date()).days) | |
| guess_val: int | float = int(delta_days) if spec.get("integer", True) else float(delta_days) | |
| else: | |
| nums = [float(x) for x in re.findall(r"\d+(?:\.\d+)?", span_text or state)] | |
| guess_val = int(nums[0]) if nums else 0 | |
| answers[fname] = { | |
| "type": "duration", | |
| "p_null": p_null, | |
| "unit": unit, | |
| "span": [char_s, char_e], | |
| "guess": guess_val, | |
| "confidence": conf, | |
| "span_entropy_bits": span_entropy, | |
| "value": None if is_null else guess_val, | |
| } | |
| extracted_scalar_values[fname] = None if is_null else guess_val | |
| continue | |
| if ftype in ("date", "time", "datetime"): | |
| dates = parse_dates_in_text(span_text) or parse_dates_in_text(state) | |
| if dates: | |
| dt_obj = dates[0][0] | |
| iso_str = dt_obj.strftime("%Y-%m-%dT%H:%M:%S") if ftype == "datetime" else dt_obj.date().isoformat() | |
| else: | |
| iso_str = span_text | |
| answers[fname] = { | |
| "type": ftype, | |
| "p_null": p_null, | |
| "span": [char_s, char_e], | |
| "guess": iso_str, | |
| "confidence": conf, | |
| "span_entropy_bits": span_entropy, | |
| "value": None if is_null else iso_str, | |
| } | |
| extracted_scalar_values[fname] = None if is_null else iso_str | |
| continue | |
| if ftype == "number": | |
| instr_lower = spec.get("instructions", "").lower() | |
| span_nums = [float(x.replace(",", "")) for x in re.findall(r"\d+(?:,\d{3})*(?:\.\d+)?", span_text)] | |
| if len(state) > 320 or (span_entropy > 1.2 and not (op_id == 2 or "including" in instr_lower)) or (not is_null and not span_nums): | |
| dis_ans = disambiguate_numeric_field(state, fname, spec) | |
| if dis_ans is not None: | |
| answers[fname] = dis_ans | |
| extracted_scalar_values[fname] = dis_ans.get("value") | |
| continue | |
| if (op_id == 2 or "including" in instr_lower) and len(span_nums) >= 2: | |
| num_val = sum(span_nums[:2]) | |
| elif op_id == 2 and "plus $" in state: | |
| m_plus = re.findall(r"\$(\d+(?:\.\d+)?)\s+plus\s+\$(\d+(?:\.\d+)?)", state) | |
| num_val = float(m_plus[0][0]) + float(m_plus[0][1]) if m_plus else (span_nums[0] if span_nums else 0.0) | |
| elif span_nums: | |
| num_val = span_nums[0] | |
| else: | |
| num_val = 0.0 | |
| if spec.get("integer") or (num_val == int(num_val) and spec.get("decimals") is None): | |
| formatted_num: int | float = int(round(num_val)) | |
| elif spec.get("decimals") is not None: | |
| formatted_num = round(num_val, int(spec["decimals"])) | |
| if formatted_num == int(formatted_num): | |
| formatted_num = int(formatted_num) | |
| else: | |
| formatted_num = round(num_val, 4) | |
| if is_null: | |
| formatted_num = 0 | |
| ans_num: dict[str, Any] = { | |
| "type": "number", | |
| "p_null": p_null, | |
| "span": [char_s, char_e], | |
| "guess": formatted_num, | |
| "confidence": conf, | |
| "span_entropy_bits": span_entropy, | |
| "value": None if is_null else formatted_num, | |
| } | |
| if abstention_reason: | |
| ans_num["abstention_reason"] = abstention_reason | |
| answers[fname] = ans_num | |
| extracted_scalar_values[fname] = None if is_null else formatted_num | |
| continue | |
| # Default: `type == "text"` | |
| guess_str = span_text | |
| if is_null and not guess_str.startswith("$"): | |
| m_dlr = re.search(r"\$\d+\.\d{2}", state) | |
| if m_dlr: | |
| char_s, char_e = m_dlr.span() | |
| guess_str = m_dlr.group(0) | |
| ans_txt: dict[str, Any] = { | |
| "type": "text", | |
| "p_null": p_null, | |
| "span": [char_s, char_e], | |
| "guess": guess_str, | |
| "confidence": conf, | |
| "span_entropy_bits": span_entropy, | |
| "value": None if is_null else guess_str, | |
| } | |
| if abstention_reason: | |
| ans_txt["abstention_reason"] = abstention_reason | |
| answers[fname] = ans_txt | |
| extracted_scalar_values[fname] = None if is_null else guess_str | |
| # Verify cross-field algebraic invariants if any field defines `constraint` | |
| invariants_passed = True | |
| for fname, spec in fields.items(): | |
| c_expr = spec.get("constraint") | |
| if c_expr: | |
| ok = _verify_constraint(c_expr, extracted_scalar_values) | |
| answers[fname]["verified"] = ok | |
| if not ok: | |
| invariants_passed = False | |
| return { | |
| "model": "nullspan", | |
| "invariants_passed": invariants_passed, | |
| "answers": answers, | |
| } | |