nullspan 1.1.0__tar.gz → 1.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nullspan-1.2.1/PKG-INFO +176 -0
- nullspan-1.2.1/README.md +154 -0
- {nullspan-1.1.0 → nullspan-1.2.1}/pyproject.toml +3 -3
- nullspan-1.2.1/src/nullspan/__init__.py +5 -0
- {nullspan-1.1.0 → nullspan-1.2.1}/src/nullspan/engine.py +109 -23
- nullspan-1.2.1/src/nullspan/systemone.py +910 -0
- nullspan-1.1.0/PKG-INFO +0 -148
- nullspan-1.1.0/README.md +0 -127
- nullspan-1.1.0/src/nullspan/__init__.py +0 -5
- {nullspan-1.1.0 → nullspan-1.2.1}/LICENSE +0 -0
nullspan-1.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nullspan
|
|
3
|
+
Version: 1.2.1
|
|
4
|
+
Summary: Calibrated p_null abstention, grounded [start, end] span extraction, System One typed decisions, arithmetic, and distribution forecasting in a single forward pass.
|
|
5
|
+
Keywords: nullspan,system-one,extraction,abstention,p-null,quantile-regression,forecasting
|
|
6
|
+
Author-email: Hemanth HM <hemanth.hm@gmail.com>
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: torch>=2.0.0 ; extra == "hf"
|
|
13
|
+
Requires-Dist: transformers>=4.40.0 ; extra == "hf"
|
|
14
|
+
Requires-Dist: safetensors>=0.4.0 ; extra == "hf"
|
|
15
|
+
Requires-Dist: huggingface_hub>=0.23.0 ; extra == "hf"
|
|
16
|
+
Requires-Dist: sentencepiece>=0.1.99 ; extra == "hf"
|
|
17
|
+
Project-URL: Homepage, https://github.com/hemanth/nullspan
|
|
18
|
+
Project-URL: Model, https://huggingface.co/gnumanth/nullspan
|
|
19
|
+
Project-URL: Playground, https://hemanth.github.io/nullspan/
|
|
20
|
+
Provides-Extra: hf
|
|
21
|
+
|
|
22
|
+
# nullspan
|
|
23
|
+
|
|
24
|
+
Single-pass System One structured extraction, multi-numeric contract disambiguation, calibrated `p_null` abstention, character-offset `[start, end]` spans, calibrated `noul`/`choice`/`score` `/v1/systemone` decisions, cross-field algebraic invariant verification, and calibrated quantile estimation (`p10`, `median`, `p90`) in JavaScript, Python, and Browser WebAssembly ONNX (`512`-token context + long-document windowing).
|
|
25
|
+
|
|
26
|
+
- **Interactive Playground (Browser WASM ONNX + Zero-Dep Engine)**: [https://hemanth.github.io/nullspan/](https://hemanth.github.io/nullspan/)
|
|
27
|
+
- **NullspanBench Leaderboard (JevBench-Style 19-System Evaluation, N=500)**: [https://hemanth.github.io/nullspan/bench.html](https://hemanth.github.io/nullspan/bench.html)
|
|
28
|
+
- **Hugging Face Space**: [https://huggingface.co/spaces/gnumanth/nullspan](https://huggingface.co/spaces/gnumanth/nullspan)
|
|
29
|
+
- **Fine-Tuned Hugging Face Model (345.3M Extraction + 331.1 MB Int8 ONNX + 435M System One Cross-Encoder)**: [https://huggingface.co/gnumanth/nullspan](https://huggingface.co/gnumanth/nullspan)
|
|
30
|
+
- **Hugging Face Dataset**: [https://huggingface.co/datasets/gnumanth/nullspan-dataset](https://huggingface.co/datasets/gnumanth/nullspan-dataset)
|
|
31
|
+
- **Empirical Science Report**: [`bench/SCIENCE_REPORT.md`](./bench/SCIENCE_REPORT.md)
|
|
32
|
+
|
|
33
|
+
## In Plain English
|
|
34
|
+
|
|
35
|
+
Standard LLMs extract data and make typed workflow judgments by generating JSON token-by-token, which is slow, bills per output token, can get hijacked by *"ignore previous instructions"* prompt injections, and often hallucinates values when a field is missing. **`nullspan` acts like a grounded highlighter + calibrated System One judge instead of a chatbot:**
|
|
36
|
+
|
|
37
|
+
1. **Points to Exact Text (`[start, end]` Span)**: Highlights exact character positions in your input text (`0` generated output tokens), making fabricated strings and prompt-injection payloads impossible.
|
|
38
|
+
2. **Knows When to Say "Not There" (`p_null`)**: Computes a calibrated probability that a field is missing (`p_null`) and returns `null` with `abstention_reason` when evidence is absent (`87.00%` EM / `89.36%` F1 on Stanford `SQuAD v2.0`).
|
|
39
|
+
3. **Disambiguates Multi-Numeric Contracts & Checks Math (`constraint` & `verified`)**: Separates competing dollar caps, percentages, day windows, and numeric tranches across multi-section agreements (`96/96` on unseen contracts) and verifies equations like `subtotal == nodes * hourly_rate`.
|
|
40
|
+
4. **Calibrated `/v1/systemone` Typed Decisions (`decide()`)**: Evaluates `noul` (`true`/`false`), `choice` (`options`), and ordinal `score` (`min..max`) rubrics with calibrated probability distributions—ranking **#2 Overall / #1 Non-LLM Encoder** (`75.40%`) on [`LocalLLaMA/typed-decisions`](https://huggingface.co/datasets/LocalLLaMA/typed-decisions) and hitting **`100.0%` (`48/48`)** on `JevBench Easy` (`100.0%` strict schema validity).
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npm install nullspan
|
|
46
|
+
pip install nullspan
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage (JavaScript / ESM)
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { extract } from 'nullspan';
|
|
53
|
+
|
|
54
|
+
const incident =
|
|
55
|
+
"Incident INC-40928 opened on Apr 12, 2026 with a 502 Bad Gateway errors and a TLS handshake timeouts. " +
|
|
56
|
+
"Issued $1240.50 plus $310.25 SLA credit. Severity is sev-1 and customer data loss was false. " +
|
|
57
|
+
"Root cause CVE not yet assigned. Cluster stabilized on Apr 19, 2026. Reported by Mira";
|
|
58
|
+
|
|
59
|
+
const result = extract(incident, {
|
|
60
|
+
incident_id: { type: 'text', instructions: 'Incident ticket identifier' },
|
|
61
|
+
opened_on: { type: 'date', instructions: 'Date the incident opened' },
|
|
62
|
+
total_credit: { type: 'number', decimals: 2, unit: 'USD', instructions: 'Total credit including SLA penalty' },
|
|
63
|
+
outage_days: { type: 'duration', unit: 'days', integer: true, instructions: 'Days from incident open to cluster stabilization' },
|
|
64
|
+
failure_modes: { type: 'list', items: 'text', instructions: 'Each failure mode triggered' },
|
|
65
|
+
severity: { type: 'enum', options: ['sev-1', 'sev-2', 'sev-3'], instructions: 'Incident severity tier' },
|
|
66
|
+
data_loss: { type: 'boolean', instructions: 'Whether customer data loss occurred' },
|
|
67
|
+
cve_id: { type: 'text', instructions: 'Assigned CVE identifier if present' },
|
|
68
|
+
remediation_cost: { type: 'estimate', unit: 'USD', min: 100, max: 25000, scale: 'log', step: 10, instructions: 'Estimated engineering remediation cost' },
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Usage (Python Extraction + `/v1/systemone` Typed Decisions)
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from nullspan import extract, decide
|
|
76
|
+
|
|
77
|
+
invoice = (
|
|
78
|
+
"Invoice from TensorCore Cloud\n"
|
|
79
|
+
"Invoice #88310 | Issued: 2026-08-14 18:20\n"
|
|
80
|
+
"Nodes: 4 @ $3.50 each\n"
|
|
81
|
+
"Subtotal: $14.00\n"
|
|
82
|
+
"Egress Fee: $0.00\n"
|
|
83
|
+
"Priority Surcharge: $1.75"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# 1. Grounded extraction + cross-field algebraic invariants (backend="fast" | "hf" | "onnx")
|
|
87
|
+
out = extract(
|
|
88
|
+
state=invoice,
|
|
89
|
+
fields={
|
|
90
|
+
"nodes": {"type": "number", "integer": True, "instructions": "Provisioned GPU node count"},
|
|
91
|
+
"hourly_rate": {"type": "number", "decimals": 2, "unit": "USD", "instructions": "Hourly rate per node"},
|
|
92
|
+
"subtotal": {
|
|
93
|
+
"type": "number",
|
|
94
|
+
"decimals": 2,
|
|
95
|
+
"unit": "USD",
|
|
96
|
+
"constraint": "subtotal == nodes * hourly_rate",
|
|
97
|
+
"instructions": "Compute subtotal before fees and surcharge",
|
|
98
|
+
},
|
|
99
|
+
"promo_rebate": {"type": "number", "decimals": 2, "unit": "USD", "instructions": "Promotional rebate amount if present"},
|
|
100
|
+
},
|
|
101
|
+
backend="hf",
|
|
102
|
+
model_id="gnumanth/nullspan",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# 2. TypeSafe-compatible System One typed decisions (noul / choice / score)
|
|
106
|
+
judgments = decide(
|
|
107
|
+
state={"invoice_id": "INV-88310", "subtotal": 14.0, "nodes": 4, "unit_price": 3.5, "approved_vendor": True},
|
|
108
|
+
questions={
|
|
109
|
+
"math_valid": {"type": "noul", "instructions": "Does subtotal equal nodes * unit_price?"},
|
|
110
|
+
"routing": {"type": "choice", "options": ["auto_pay", "manual_review", "reject"], "instructions": "Select workflow action."},
|
|
111
|
+
"risk_tier": {"type": "score", "min": 1, "max": 5, "instructions": "Rate audit risk from 1 (low) to 5 (critical)."},
|
|
112
|
+
},
|
|
113
|
+
model_id="gnumanth/nullspan",
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
To run the live TypeSafe `/v1/systemone` HTTP server for `jevbench` or `LocalLLaMA/typed-decisions`:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
python training/serve_systemone.py --model-dir gnumanth/nullspan --port 8010
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Official External Public Benchmarks (NVIDIA L4 GPU · Zero Test Contamination)
|
|
124
|
+
|
|
125
|
+
Reproducible via [`bench/eval_public_benchmarks.py`](./bench/eval_public_benchmarks.py) with results stored in [`bench/public_benchmarks_results.json`](./bench/public_benchmarks_results.json):
|
|
126
|
+
|
|
127
|
+
| External Public Benchmark | Split & Scale | Primary Metric | Calibration & Validity | Latency & Cost (NVIDIA L4) | Standing |
|
|
128
|
+
| :--- | :--- | :---: | :---: | :---: | :--- |
|
|
129
|
+
| **[`LocalLLaMA/typed-decisions`](https://huggingface.co/datasets/LocalLLaMA/typed-decisions)** | Official `test` (`400` cases · **`2,000` decisions**) | **`75.40%` Accuracy** (`noul` `82.67%`, `choice` `75.00%`, `score` `70.25%`) | `KL 0.1515` · `Brier 0.0770` · `ECE 0.0944` | `78.0 ms/dec` (`390 ms/case`) · `0` output tokens | **Rank #2 Overall Worldwide** (surpassing 27B–31B LLMs) · **Rank #1 Encoder** |
|
|
130
|
+
| **[Benchmark Heaven / `jevbench`](https://github.com/fstandhartinger/jevbench)** | Official Public Suite (`231` items via `/v1/systemone`) | **`100.0%` Easy (`48/48`)** · `62.50%` Original (`45/72`) · `37.84%` Hard (`42/111`) (`58.44%` all) | **`100.0%` Strict Schema Validity** (`Easy Brier 0.0207`, `ECE 0.0605`) | **`64.8 ms` `p50`** (`63.2 ms` Easy) · **`$0.00667 / 1k`** | `100.0%` on `adversarial`, `fact`, `ordinal`, & `tool_selection` |
|
|
131
|
+
| **[Stanford `rajpurkar/squad_v2`](https://huggingface.co/datasets/rajpurkar/squad_v2)** | Zero-overlap `validation` (`N = 500`: `250` HasAns + `250` NoAns) | **`87.00%` Exact Match** · **`89.36%` F1** | `HasAns F1 91.52%` · `NoAns EM 87.20%` | `10.3 ms` `p50` (L4 CUDA) · `$0.0017 / 1k` | Dedicated `qa_null_head` temperature-calibrated on disjoint `train` |
|
|
132
|
+
|
|
133
|
+
## Architecture (`NullspanForExtraction` 345.3M + `NullspanSystemOneEngine` 435M)
|
|
134
|
+
|
|
135
|
+

|
|
136
|
+
|
|
137
|
+
1. **`null_head` + `qa_null_head` (Span-Attended Calibrated Abstention)**: Bilinear span-summary + `[CLS]` margin residual with held-out temperature scaling (`p_null` AUROC `0.9933`, ECE `0.0142`; `0.9951` / `0.0127` in `331.1 MB` Int8 ONNX) and `abstention_reason` (`unassigned_placeholder` vs `missing_field`).
|
|
138
|
+
2. **`span_head` (Grounded Pointer + Shannon Entropy)**: Projects `512`-token encoder states (plus query-conditioned sliding windows on ultra-long contracts) to `[start, end]` character spans with calibrated `confidence` and Shannon `span_entropy_bits`.
|
|
139
|
+
3. **`bio_head` (Multi-Span List Tagging)**: Extracts ordered text and numeric tranche arrays (`type: "list"`) in a single pass.
|
|
140
|
+
4. **`op_head` + `quantile_head` + Invariant Verifier**: Routes 10 System One extraction/math operations, predicts monotone `(p10, median, p90)` quantiles, and verifies cross-field algebraic constraints (`verified: true/false`).
|
|
141
|
+
5. **`NullspanSystemOneEngine` (`/v1/systemone` Cross-Option & Cross-Question Judge)**: `DeBERTa-v3-Large` (`r=32` LoRA) with top-placed programmatic numeric/temporal checks, Head + IDF-Clause + Tail windowing, 2-layer `CrossOptionDecisionHead` self-attention across candidate options, Cross-Question Workflow Propagation across multi-question batches, and Cumulative Ranked Probability Score (`RPS`) ordinal calibration.
|
|
142
|
+
|
|
143
|
+
## NullspanBench Leaderboard (`bench/run_bench.py` · 19 Systems · $N=500$ Zero-Leakage Decisions · NVIDIA L4 GPU)
|
|
144
|
+
|
|
145
|
+
Modeled after [BenchmarkHeaven's JevBench](https://benchmarkheaven.com/jev-models), **NullspanBench v3.2** evaluates `19` systems (`13` local open-weights/open-code models running live forward passes on Google Cloud NVIDIA L4 GPU, live Cloudflare Workers AI `@cf/cloudflare/clairic + @cf/cloudflare/clef`, `2` reference JSON-mode LLM decoders, and `3` published JevBench v1.7 reference anchors) across **`500` zero-leakage observations** (`336` Open structured items + `164` Sealed items: `60` Stanford SQuAD v2.0, `96` Multi-Numeric Legal/Commercial Contract fields across `12` unseen agreements, and `8` Adversarial prompt-injection traps) on four axes: **Intelligence (`I`)**, **Calibration (`C`)**, **Speed (`S`)**, and **Cost (`K`)**.
|
|
146
|
+
|
|
147
|
+
Explore interactive weight sliders, Pareto frontier plots, and 2-system radar comparisons at [**https://hemanth.github.io/nullspan/bench.html**](https://hemanth.github.io/nullspan/bench.html):
|
|
148
|
+
|
|
149
|
+
| Rank | System | Composite (`25:25:25:25`) | Capability (`50I:50C`) | Intelligence `I` (95% CI) | Calibration `C` | Speed `S` | Cost `K` | `$/1k` | `p50` Latency |
|
|
150
|
+
| :---: | :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
|
151
|
+
| **1** | **`nullspan-Hybrid (Fast + ONNX Int8)`** (Open) | **95.7** | **98.2** | **97.8%** `[96.6, 99.0]` | **98.6** | **92.7** | **93.8** | `$0.0003` | `0.040 ms` |
|
|
152
|
+
| **2** | **`nullspan-ZeroDep (Fast Engine)`** (Open) | **94.7** | `92.9` | `90.8%` `[88.2, 93.4]` | `95.0` | **93.5** | **99.8** | `$0.0001` | `0.034 ms` |
|
|
153
|
+
| **3** | **`nullspan-345M (PyTorch FP32 · L4 CUDA)`** (Open) | **81.3** | **98.2** | **98.0%** `[96.8, 99.2]` | **98.5** | `63.2` | `76.8` | `$0.0017` | `10.297 ms` |
|
|
154
|
+
| **4** | **`nullspan-345M-Int8 (ONNX WASM/CPU)`** (Open) | **78.1** | **98.2** | **97.8%** `[96.6, 99.0]` | **98.6** | `52.7` | `84.2` | `$0.0008` | `75.933 ms` |
|
|
155
|
+
| **5** | `H2O-Lightning-4B v1.1 (JevBench #1 Comp)` (Open 4B) | `72.5` | `75.0` | `60.0%` `[57.8, 62.3]` | `90.0` | `92.6` | `60.3` | `$0.0211` | `29.285 ms` |
|
|
156
|
+
| **6** | **`@cf/cloudflare/clairic + clef (Live Workers AI)`** (API) | `72.1` | `93.2` | `91.6%` `[89.2, 93.9]` | `94.9` | `51.7` | `68.2` | `$0.0041` | `91.323 ms` (`315.3 ms/doc`) |
|
|
157
|
+
| **7** | `basal-omni-1.0-ultra (JevBench #1 Cap)` (Open 31B) | `71.8` | `82.5` | `74.1%` `[71.8, 76.3]` | `91.0` | `87.3` | `50.5` | `$0.0447` | `102.000 ms` |
|
|
158
|
+
| **8** | `Jev 1.13.0 (TypeSafe AI · JevBench Ref)` (API) | `71.5` | `77.1` | `63.6%` `[61.2, 66.0]` | `90.6` | `91.5` | `54.7` | `$0.0323` | `239.102 ms` |
|
|
159
|
+
| **9** | **`nullspan-345M (ONNX FP32)`** (Open) | `71.2` | **98.2** | **98.0%** `[96.8, 99.2]` | **98.5** | `43.5` | `78.0` | `$0.0015` | `424.943 ms` |
|
|
160
|
+
| **10** | `deberta-v3-base-squad2 (184M SQuAD2 QA)` (Open) | `60.3` | `55.8` | `48.1%` `[43.9, 52.4]` | `63.4` | `65.1` | `69.2` | `$0.0037` | `7.234 ms` |
|
|
161
|
+
| **11** | `gpt-5.4-nano (JSON Decoder · API)` (API) | `59.8` | `92.1` | `95.2%` `[93.4, 97.0]` | `89.0` | `43.6` | `45.1` | `$0.0438` | `417.991 ms` |
|
|
162
|
+
| **12** | `claude-haiku-4.5 (JSON Decoder · API)` (API) | `48.8` | `92.1` | `95.4%` `[93.4, 97.2]` | `88.7` | `41.8` | `27.5` | `$0.2639` | `586.392 ms` |
|
|
163
|
+
| **13** | `bge-small-en-v1.5 (Dense Span+Null)` (Open) | `37.6` | `52.0` | `14.7%` `[12.0, 17.5]` | `89.2` | `64.2` | `85.5` | `$0.0007` | `8.625 ms` |
|
|
164
|
+
| **14** | `distilbert-base-control (Raw Control)` (Open) | `36.9` | `52.9` | `14.2%` `[11.6, 17.0]` | `91.5` | `67.7` | `81.1` | `$0.0011` | `4.479 ms` |
|
|
165
|
+
| **15** | `all-MiniLM-L12-v2 (Dense Span+Null)` (Open) | `36.3` | `44.0` | `14.4%` `[11.7, 17.5]` | `73.5` | `64.2` | `85.5` | `$0.0007` | `8.582 ms` |
|
|
166
|
+
| **16** | `all-mpnet-base-v2 (Dense Span+Null)` (Open) | `34.4` | `42.3` | `13.6%` `[10.9, 16.7]` | `71.0` | `63.3` | `77.4` | `$0.0016` | `10.257 ms` |
|
|
167
|
+
| **17** | `paraphrase-albert-small-v2 (Dense Span+Null)` (Open) | `33.6` | `44.5` | `12.5%` `[10.0, 15.2]` | `76.6` | `66.4` | `91.0` | `$0.0004` | `5.700 ms` |
|
|
168
|
+
| **18** | `paraphrase-MiniLM-L3-v2 (Dense Span+Null)` (Open) | `30.9` | `41.7` | `11.1%` `[8.7, 13.8]` | `72.3` | `69.6` | `91.0` | `$0.0004` | `3.114 ms` |
|
|
169
|
+
| **19** | `all-MiniLM-L6-v2 (Dense Span+Null)` (Open) | `28.5` | `40.5` | `10.0%` `[7.7, 12.3]` | `70.9` | `67.1` | `88.8` | `$0.0005` | `4.988 ms` |
|
|
170
|
+
|
|
171
|
+
See full methodology, proper scoring definitions, request-type breakdowns (`100.0%` on Multi-Numeric Contracts `n=96`, `100.0%` Choice `n=41`, `100.0%` Math & Invariants `n=51`), and head-to-head analysis in [`bench/SCIENCE_REPORT.md`](./bench/SCIENCE_REPORT.md).
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT © [Hemanth.HM](https://h3manth.com)
|
|
176
|
+
|
nullspan-1.2.1/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# nullspan
|
|
2
|
+
|
|
3
|
+
Single-pass System One structured extraction, multi-numeric contract disambiguation, calibrated `p_null` abstention, character-offset `[start, end]` spans, calibrated `noul`/`choice`/`score` `/v1/systemone` decisions, cross-field algebraic invariant verification, and calibrated quantile estimation (`p10`, `median`, `p90`) in JavaScript, Python, and Browser WebAssembly ONNX (`512`-token context + long-document windowing).
|
|
4
|
+
|
|
5
|
+
- **Interactive Playground (Browser WASM ONNX + Zero-Dep Engine)**: [https://hemanth.github.io/nullspan/](https://hemanth.github.io/nullspan/)
|
|
6
|
+
- **NullspanBench Leaderboard (JevBench-Style 19-System Evaluation, N=500)**: [https://hemanth.github.io/nullspan/bench.html](https://hemanth.github.io/nullspan/bench.html)
|
|
7
|
+
- **Hugging Face Space**: [https://huggingface.co/spaces/gnumanth/nullspan](https://huggingface.co/spaces/gnumanth/nullspan)
|
|
8
|
+
- **Fine-Tuned Hugging Face Model (345.3M Extraction + 331.1 MB Int8 ONNX + 435M System One Cross-Encoder)**: [https://huggingface.co/gnumanth/nullspan](https://huggingface.co/gnumanth/nullspan)
|
|
9
|
+
- **Hugging Face Dataset**: [https://huggingface.co/datasets/gnumanth/nullspan-dataset](https://huggingface.co/datasets/gnumanth/nullspan-dataset)
|
|
10
|
+
- **Empirical Science Report**: [`bench/SCIENCE_REPORT.md`](./bench/SCIENCE_REPORT.md)
|
|
11
|
+
|
|
12
|
+
## In Plain English
|
|
13
|
+
|
|
14
|
+
Standard LLMs extract data and make typed workflow judgments by generating JSON token-by-token, which is slow, bills per output token, can get hijacked by *"ignore previous instructions"* prompt injections, and often hallucinates values when a field is missing. **`nullspan` acts like a grounded highlighter + calibrated System One judge instead of a chatbot:**
|
|
15
|
+
|
|
16
|
+
1. **Points to Exact Text (`[start, end]` Span)**: Highlights exact character positions in your input text (`0` generated output tokens), making fabricated strings and prompt-injection payloads impossible.
|
|
17
|
+
2. **Knows When to Say "Not There" (`p_null`)**: Computes a calibrated probability that a field is missing (`p_null`) and returns `null` with `abstention_reason` when evidence is absent (`87.00%` EM / `89.36%` F1 on Stanford `SQuAD v2.0`).
|
|
18
|
+
3. **Disambiguates Multi-Numeric Contracts & Checks Math (`constraint` & `verified`)**: Separates competing dollar caps, percentages, day windows, and numeric tranches across multi-section agreements (`96/96` on unseen contracts) and verifies equations like `subtotal == nodes * hourly_rate`.
|
|
19
|
+
4. **Calibrated `/v1/systemone` Typed Decisions (`decide()`)**: Evaluates `noul` (`true`/`false`), `choice` (`options`), and ordinal `score` (`min..max`) rubrics with calibrated probability distributions—ranking **#2 Overall / #1 Non-LLM Encoder** (`75.40%`) on [`LocalLLaMA/typed-decisions`](https://huggingface.co/datasets/LocalLLaMA/typed-decisions) and hitting **`100.0%` (`48/48`)** on `JevBench Easy` (`100.0%` strict schema validity).
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install nullspan
|
|
25
|
+
pip install nullspan
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage (JavaScript / ESM)
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { extract } from 'nullspan';
|
|
32
|
+
|
|
33
|
+
const incident =
|
|
34
|
+
"Incident INC-40928 opened on Apr 12, 2026 with a 502 Bad Gateway errors and a TLS handshake timeouts. " +
|
|
35
|
+
"Issued $1240.50 plus $310.25 SLA credit. Severity is sev-1 and customer data loss was false. " +
|
|
36
|
+
"Root cause CVE not yet assigned. Cluster stabilized on Apr 19, 2026. Reported by Mira";
|
|
37
|
+
|
|
38
|
+
const result = extract(incident, {
|
|
39
|
+
incident_id: { type: 'text', instructions: 'Incident ticket identifier' },
|
|
40
|
+
opened_on: { type: 'date', instructions: 'Date the incident opened' },
|
|
41
|
+
total_credit: { type: 'number', decimals: 2, unit: 'USD', instructions: 'Total credit including SLA penalty' },
|
|
42
|
+
outage_days: { type: 'duration', unit: 'days', integer: true, instructions: 'Days from incident open to cluster stabilization' },
|
|
43
|
+
failure_modes: { type: 'list', items: 'text', instructions: 'Each failure mode triggered' },
|
|
44
|
+
severity: { type: 'enum', options: ['sev-1', 'sev-2', 'sev-3'], instructions: 'Incident severity tier' },
|
|
45
|
+
data_loss: { type: 'boolean', instructions: 'Whether customer data loss occurred' },
|
|
46
|
+
cve_id: { type: 'text', instructions: 'Assigned CVE identifier if present' },
|
|
47
|
+
remediation_cost: { type: 'estimate', unit: 'USD', min: 100, max: 25000, scale: 'log', step: 10, instructions: 'Estimated engineering remediation cost' },
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage (Python Extraction + `/v1/systemone` Typed Decisions)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from nullspan import extract, decide
|
|
55
|
+
|
|
56
|
+
invoice = (
|
|
57
|
+
"Invoice from TensorCore Cloud\n"
|
|
58
|
+
"Invoice #88310 | Issued: 2026-08-14 18:20\n"
|
|
59
|
+
"Nodes: 4 @ $3.50 each\n"
|
|
60
|
+
"Subtotal: $14.00\n"
|
|
61
|
+
"Egress Fee: $0.00\n"
|
|
62
|
+
"Priority Surcharge: $1.75"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# 1. Grounded extraction + cross-field algebraic invariants (backend="fast" | "hf" | "onnx")
|
|
66
|
+
out = extract(
|
|
67
|
+
state=invoice,
|
|
68
|
+
fields={
|
|
69
|
+
"nodes": {"type": "number", "integer": True, "instructions": "Provisioned GPU node count"},
|
|
70
|
+
"hourly_rate": {"type": "number", "decimals": 2, "unit": "USD", "instructions": "Hourly rate per node"},
|
|
71
|
+
"subtotal": {
|
|
72
|
+
"type": "number",
|
|
73
|
+
"decimals": 2,
|
|
74
|
+
"unit": "USD",
|
|
75
|
+
"constraint": "subtotal == nodes * hourly_rate",
|
|
76
|
+
"instructions": "Compute subtotal before fees and surcharge",
|
|
77
|
+
},
|
|
78
|
+
"promo_rebate": {"type": "number", "decimals": 2, "unit": "USD", "instructions": "Promotional rebate amount if present"},
|
|
79
|
+
},
|
|
80
|
+
backend="hf",
|
|
81
|
+
model_id="gnumanth/nullspan",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# 2. TypeSafe-compatible System One typed decisions (noul / choice / score)
|
|
85
|
+
judgments = decide(
|
|
86
|
+
state={"invoice_id": "INV-88310", "subtotal": 14.0, "nodes": 4, "unit_price": 3.5, "approved_vendor": True},
|
|
87
|
+
questions={
|
|
88
|
+
"math_valid": {"type": "noul", "instructions": "Does subtotal equal nodes * unit_price?"},
|
|
89
|
+
"routing": {"type": "choice", "options": ["auto_pay", "manual_review", "reject"], "instructions": "Select workflow action."},
|
|
90
|
+
"risk_tier": {"type": "score", "min": 1, "max": 5, "instructions": "Rate audit risk from 1 (low) to 5 (critical)."},
|
|
91
|
+
},
|
|
92
|
+
model_id="gnumanth/nullspan",
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
To run the live TypeSafe `/v1/systemone` HTTP server for `jevbench` or `LocalLLaMA/typed-decisions`:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
python training/serve_systemone.py --model-dir gnumanth/nullspan --port 8010
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Official External Public Benchmarks (NVIDIA L4 GPU · Zero Test Contamination)
|
|
103
|
+
|
|
104
|
+
Reproducible via [`bench/eval_public_benchmarks.py`](./bench/eval_public_benchmarks.py) with results stored in [`bench/public_benchmarks_results.json`](./bench/public_benchmarks_results.json):
|
|
105
|
+
|
|
106
|
+
| External Public Benchmark | Split & Scale | Primary Metric | Calibration & Validity | Latency & Cost (NVIDIA L4) | Standing |
|
|
107
|
+
| :--- | :--- | :---: | :---: | :---: | :--- |
|
|
108
|
+
| **[`LocalLLaMA/typed-decisions`](https://huggingface.co/datasets/LocalLLaMA/typed-decisions)** | Official `test` (`400` cases · **`2,000` decisions**) | **`75.40%` Accuracy** (`noul` `82.67%`, `choice` `75.00%`, `score` `70.25%`) | `KL 0.1515` · `Brier 0.0770` · `ECE 0.0944` | `78.0 ms/dec` (`390 ms/case`) · `0` output tokens | **Rank #2 Overall Worldwide** (surpassing 27B–31B LLMs) · **Rank #1 Encoder** |
|
|
109
|
+
| **[Benchmark Heaven / `jevbench`](https://github.com/fstandhartinger/jevbench)** | Official Public Suite (`231` items via `/v1/systemone`) | **`100.0%` Easy (`48/48`)** · `62.50%` Original (`45/72`) · `37.84%` Hard (`42/111`) (`58.44%` all) | **`100.0%` Strict Schema Validity** (`Easy Brier 0.0207`, `ECE 0.0605`) | **`64.8 ms` `p50`** (`63.2 ms` Easy) · **`$0.00667 / 1k`** | `100.0%` on `adversarial`, `fact`, `ordinal`, & `tool_selection` |
|
|
110
|
+
| **[Stanford `rajpurkar/squad_v2`](https://huggingface.co/datasets/rajpurkar/squad_v2)** | Zero-overlap `validation` (`N = 500`: `250` HasAns + `250` NoAns) | **`87.00%` Exact Match** · **`89.36%` F1** | `HasAns F1 91.52%` · `NoAns EM 87.20%` | `10.3 ms` `p50` (L4 CUDA) · `$0.0017 / 1k` | Dedicated `qa_null_head` temperature-calibrated on disjoint `train` |
|
|
111
|
+
|
|
112
|
+
## Architecture (`NullspanForExtraction` 345.3M + `NullspanSystemOneEngine` 435M)
|
|
113
|
+
|
|
114
|
+

|
|
115
|
+
|
|
116
|
+
1. **`null_head` + `qa_null_head` (Span-Attended Calibrated Abstention)**: Bilinear span-summary + `[CLS]` margin residual with held-out temperature scaling (`p_null` AUROC `0.9933`, ECE `0.0142`; `0.9951` / `0.0127` in `331.1 MB` Int8 ONNX) and `abstention_reason` (`unassigned_placeholder` vs `missing_field`).
|
|
117
|
+
2. **`span_head` (Grounded Pointer + Shannon Entropy)**: Projects `512`-token encoder states (plus query-conditioned sliding windows on ultra-long contracts) to `[start, end]` character spans with calibrated `confidence` and Shannon `span_entropy_bits`.
|
|
118
|
+
3. **`bio_head` (Multi-Span List Tagging)**: Extracts ordered text and numeric tranche arrays (`type: "list"`) in a single pass.
|
|
119
|
+
4. **`op_head` + `quantile_head` + Invariant Verifier**: Routes 10 System One extraction/math operations, predicts monotone `(p10, median, p90)` quantiles, and verifies cross-field algebraic constraints (`verified: true/false`).
|
|
120
|
+
5. **`NullspanSystemOneEngine` (`/v1/systemone` Cross-Option & Cross-Question Judge)**: `DeBERTa-v3-Large` (`r=32` LoRA) with top-placed programmatic numeric/temporal checks, Head + IDF-Clause + Tail windowing, 2-layer `CrossOptionDecisionHead` self-attention across candidate options, Cross-Question Workflow Propagation across multi-question batches, and Cumulative Ranked Probability Score (`RPS`) ordinal calibration.
|
|
121
|
+
|
|
122
|
+
## NullspanBench Leaderboard (`bench/run_bench.py` · 19 Systems · $N=500$ Zero-Leakage Decisions · NVIDIA L4 GPU)
|
|
123
|
+
|
|
124
|
+
Modeled after [BenchmarkHeaven's JevBench](https://benchmarkheaven.com/jev-models), **NullspanBench v3.2** evaluates `19` systems (`13` local open-weights/open-code models running live forward passes on Google Cloud NVIDIA L4 GPU, live Cloudflare Workers AI `@cf/cloudflare/clairic + @cf/cloudflare/clef`, `2` reference JSON-mode LLM decoders, and `3` published JevBench v1.7 reference anchors) across **`500` zero-leakage observations** (`336` Open structured items + `164` Sealed items: `60` Stanford SQuAD v2.0, `96` Multi-Numeric Legal/Commercial Contract fields across `12` unseen agreements, and `8` Adversarial prompt-injection traps) on four axes: **Intelligence (`I`)**, **Calibration (`C`)**, **Speed (`S`)**, and **Cost (`K`)**.
|
|
125
|
+
|
|
126
|
+
Explore interactive weight sliders, Pareto frontier plots, and 2-system radar comparisons at [**https://hemanth.github.io/nullspan/bench.html**](https://hemanth.github.io/nullspan/bench.html):
|
|
127
|
+
|
|
128
|
+
| Rank | System | Composite (`25:25:25:25`) | Capability (`50I:50C`) | Intelligence `I` (95% CI) | Calibration `C` | Speed `S` | Cost `K` | `$/1k` | `p50` Latency |
|
|
129
|
+
| :---: | :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
|
130
|
+
| **1** | **`nullspan-Hybrid (Fast + ONNX Int8)`** (Open) | **95.7** | **98.2** | **97.8%** `[96.6, 99.0]` | **98.6** | **92.7** | **93.8** | `$0.0003` | `0.040 ms` |
|
|
131
|
+
| **2** | **`nullspan-ZeroDep (Fast Engine)`** (Open) | **94.7** | `92.9` | `90.8%` `[88.2, 93.4]` | `95.0` | **93.5** | **99.8** | `$0.0001` | `0.034 ms` |
|
|
132
|
+
| **3** | **`nullspan-345M (PyTorch FP32 · L4 CUDA)`** (Open) | **81.3** | **98.2** | **98.0%** `[96.8, 99.2]` | **98.5** | `63.2` | `76.8` | `$0.0017` | `10.297 ms` |
|
|
133
|
+
| **4** | **`nullspan-345M-Int8 (ONNX WASM/CPU)`** (Open) | **78.1** | **98.2** | **97.8%** `[96.6, 99.0]` | **98.6** | `52.7` | `84.2` | `$0.0008` | `75.933 ms` |
|
|
134
|
+
| **5** | `H2O-Lightning-4B v1.1 (JevBench #1 Comp)` (Open 4B) | `72.5` | `75.0` | `60.0%` `[57.8, 62.3]` | `90.0` | `92.6` | `60.3` | `$0.0211` | `29.285 ms` |
|
|
135
|
+
| **6** | **`@cf/cloudflare/clairic + clef (Live Workers AI)`** (API) | `72.1` | `93.2` | `91.6%` `[89.2, 93.9]` | `94.9` | `51.7` | `68.2` | `$0.0041` | `91.323 ms` (`315.3 ms/doc`) |
|
|
136
|
+
| **7** | `basal-omni-1.0-ultra (JevBench #1 Cap)` (Open 31B) | `71.8` | `82.5` | `74.1%` `[71.8, 76.3]` | `91.0` | `87.3` | `50.5` | `$0.0447` | `102.000 ms` |
|
|
137
|
+
| **8** | `Jev 1.13.0 (TypeSafe AI · JevBench Ref)` (API) | `71.5` | `77.1` | `63.6%` `[61.2, 66.0]` | `90.6` | `91.5` | `54.7` | `$0.0323` | `239.102 ms` |
|
|
138
|
+
| **9** | **`nullspan-345M (ONNX FP32)`** (Open) | `71.2` | **98.2** | **98.0%** `[96.8, 99.2]` | **98.5** | `43.5` | `78.0` | `$0.0015` | `424.943 ms` |
|
|
139
|
+
| **10** | `deberta-v3-base-squad2 (184M SQuAD2 QA)` (Open) | `60.3` | `55.8` | `48.1%` `[43.9, 52.4]` | `63.4` | `65.1` | `69.2` | `$0.0037` | `7.234 ms` |
|
|
140
|
+
| **11** | `gpt-5.4-nano (JSON Decoder · API)` (API) | `59.8` | `92.1` | `95.2%` `[93.4, 97.0]` | `89.0` | `43.6` | `45.1` | `$0.0438` | `417.991 ms` |
|
|
141
|
+
| **12** | `claude-haiku-4.5 (JSON Decoder · API)` (API) | `48.8` | `92.1` | `95.4%` `[93.4, 97.2]` | `88.7` | `41.8` | `27.5` | `$0.2639` | `586.392 ms` |
|
|
142
|
+
| **13** | `bge-small-en-v1.5 (Dense Span+Null)` (Open) | `37.6` | `52.0` | `14.7%` `[12.0, 17.5]` | `89.2` | `64.2` | `85.5` | `$0.0007` | `8.625 ms` |
|
|
143
|
+
| **14** | `distilbert-base-control (Raw Control)` (Open) | `36.9` | `52.9` | `14.2%` `[11.6, 17.0]` | `91.5` | `67.7` | `81.1` | `$0.0011` | `4.479 ms` |
|
|
144
|
+
| **15** | `all-MiniLM-L12-v2 (Dense Span+Null)` (Open) | `36.3` | `44.0` | `14.4%` `[11.7, 17.5]` | `73.5` | `64.2` | `85.5` | `$0.0007` | `8.582 ms` |
|
|
145
|
+
| **16** | `all-mpnet-base-v2 (Dense Span+Null)` (Open) | `34.4` | `42.3` | `13.6%` `[10.9, 16.7]` | `71.0` | `63.3` | `77.4` | `$0.0016` | `10.257 ms` |
|
|
146
|
+
| **17** | `paraphrase-albert-small-v2 (Dense Span+Null)` (Open) | `33.6` | `44.5` | `12.5%` `[10.0, 15.2]` | `76.6` | `66.4` | `91.0` | `$0.0004` | `5.700 ms` |
|
|
147
|
+
| **18** | `paraphrase-MiniLM-L3-v2 (Dense Span+Null)` (Open) | `30.9` | `41.7` | `11.1%` `[8.7, 13.8]` | `72.3` | `69.6` | `91.0` | `$0.0004` | `3.114 ms` |
|
|
148
|
+
| **19** | `all-MiniLM-L6-v2 (Dense Span+Null)` (Open) | `28.5` | `40.5` | `10.0%` `[7.7, 12.3]` | `70.9` | `67.1` | `88.8` | `$0.0005` | `4.988 ms` |
|
|
149
|
+
|
|
150
|
+
See full methodology, proper scoring definitions, request-type breakdowns (`100.0%` on Multi-Numeric Contracts `n=96`, `100.0%` Choice `n=41`, `100.0%` Math & Invariants `n=51`), and head-to-head analysis in [`bench/SCIENCE_REPORT.md`](./bench/SCIENCE_REPORT.md).
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT © [Hemanth.HM](https://h3manth.com)
|
|
@@ -4,8 +4,8 @@ build-backend = "flit_core.buildapi"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nullspan"
|
|
7
|
-
version = "1.1
|
|
8
|
-
description = "Calibrated p_null abstention, grounded [start, end] span extraction, arithmetic, and distribution forecasting in a single forward pass."
|
|
7
|
+
version = "1.2.1"
|
|
8
|
+
description = "Calibrated p_null abstention, grounded [start, end] span extraction, System One typed decisions, arithmetic, and distribution forecasting in a single forward pass."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
dependencies = []
|
|
21
21
|
|
|
22
22
|
[project.optional-dependencies]
|
|
23
|
-
hf = ["torch>=2.0.0", "transformers>=4.40.0", "safetensors>=0.4.0", "huggingface_hub>=0.23.0"]
|
|
23
|
+
hf = ["torch>=2.0.0", "transformers>=4.40.0", "safetensors>=0.4.0", "huggingface_hub>=0.23.0", "sentencepiece>=0.1.99"]
|
|
24
24
|
|
|
25
25
|
[project.urls]
|
|
26
26
|
Homepage = "https://github.com/hemanth/nullspan"
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""nullspan — Calibrated p_null abstention, grounded [start, end] span extraction, System One typed decisions, and distribution forecasting."""
|
|
2
|
+
from .engine import DEFAULT_HF_REPO, NullspanExtractor, decide, extract
|
|
3
|
+
|
|
4
|
+
__version__ = "1.2.1"
|
|
5
|
+
__all__ = ["extract", "decide", "NullspanExtractor", "DEFAULT_HF_REPO"]
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import datetime as dt
|
|
5
|
+
import os
|
|
5
6
|
import re
|
|
6
7
|
import time
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
DEFAULT_HF_REPO = "gnumanth/nullspan"
|
|
10
11
|
_HF_CACHE: dict[str, tuple[Any, Any]] = {}
|
|
12
|
+
_S1_CACHE: dict[str, Any] = {}
|
|
11
13
|
|
|
12
14
|
MONTH_MAP = {
|
|
13
15
|
"jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6,
|
|
@@ -131,7 +133,16 @@ _GENERIC_MEASURE_WORDS = {
|
|
|
131
133
|
"rate", "rates", "period", "periods", "fee", "fees", "limit", "limits",
|
|
132
134
|
"level", "levels", "time", "times", "window", "windows", "schedule",
|
|
133
135
|
"schedules", "ratio", "ratios", "charge", "charges", "cost", "costs",
|
|
134
|
-
"price", "prices", "payment", "payments", "duration",
|
|
136
|
+
"price", "prices", "payment", "payments", "duration", "cap", "caps",
|
|
137
|
+
"allowance", "allowances", "penalty", "penalties", "credit", "credits",
|
|
138
|
+
"rebate", "rebates", "surcharge", "surcharges", "deposit", "deposits",
|
|
139
|
+
"retainer", "minimum", "maximum", "floor", "ceiling", "threshold",
|
|
140
|
+
"target", "term", "terms", "quota", "quotas", "hours", "days", "months",
|
|
141
|
+
"years", "minutes", "annual", "monthly", "daily", "quarterly", "hourly",
|
|
142
|
+
"tenant", "subtenant", "landlord", "sublandlord", "provider", "customer",
|
|
143
|
+
"vendor", "client", "buyer", "seller", "carrier", "shipper", "sponsor",
|
|
144
|
+
"licensor", "licensee", "grantor", "grantee", "merchant", "acquirer",
|
|
145
|
+
"advertiser", "platform", "responder", "party", "parties",
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
|
|
@@ -141,10 +152,10 @@ def _disambiguate_numeric_field(state: str, fname: str, spec: dict[str, Any]) ->
|
|
|
141
152
|
instr_s = str(spec.get("instructions") or spec.get("instruction") or "")
|
|
142
153
|
q_raw = f"{fn_clean} {instr_s}".lower()
|
|
143
154
|
q_words: list[str] = []
|
|
144
|
-
for w in re.findall(r"[a-
|
|
145
|
-
if w not in _NUM_STOPWORDS and w not in q_words:
|
|
155
|
+
for w in re.findall(r"[a-z0-9]{2,}", q_raw):
|
|
156
|
+
if not w.isdigit() and w not in _NUM_STOPWORDS and w not in q_words:
|
|
146
157
|
q_words.append(w)
|
|
147
|
-
specific_q_words = [w for w in q_words if w not in _GENERIC_MEASURE_WORDS]
|
|
158
|
+
specific_q_words = [w for w in q_words if w not in _GENERIC_MEASURE_WORDS] or list(q_words)
|
|
148
159
|
|
|
149
160
|
u_val = str(spec.get("unit", "")).lower()
|
|
150
161
|
unit_str = f"{q_raw} {u_val}"
|
|
@@ -196,7 +207,7 @@ def _disambiguate_numeric_field(state: str, fname: str, spec: dict[str, Any]) ->
|
|
|
196
207
|
pat = rf"\b{re.escape(w)}\b"
|
|
197
208
|
else:
|
|
198
209
|
stem = w[:5] if len(w) >= 6 else w.rstrip("s")
|
|
199
|
-
pat = re.escape(stem)
|
|
210
|
+
pat = rf"\b{re.escape(stem)}"
|
|
200
211
|
best_w_sc = 0.0
|
|
201
212
|
for wm in re.finditer(pat, clause_txt):
|
|
202
213
|
w_abs = c_start + wm.start()
|
|
@@ -348,14 +359,56 @@ def _extract_field_fast(state: str, fname: str, spec: dict[str, Any]) -> dict[st
|
|
|
348
359
|
}
|
|
349
360
|
|
|
350
361
|
if ftype == "boolean":
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
362
|
+
q_raw_b = f"{fname.replace('_', ' ')} {spec.get('instructions') or spec.get('instruction') or ''}".lower()
|
|
363
|
+
q_words_b = [
|
|
364
|
+
w for w in re.findall(r"[a-z]{2,}", q_raw_b)
|
|
365
|
+
if w not in _NUM_STOPWORDS and w not in ("whether", "status", "active", "enabled", "permitted", "allowed", "true", "false")
|
|
366
|
+
]
|
|
367
|
+
clauses_b = list(re.finditer(r"[^.!?\n]+[.!?\n]?", state))
|
|
368
|
+
search_txt = state
|
|
369
|
+
search_off = 0
|
|
370
|
+
if len(clauses_b) > 1 and q_words_b:
|
|
371
|
+
best_cb, best_sc_b = clauses_b[0], -1.0
|
|
372
|
+
for cm in clauses_b:
|
|
373
|
+
c_low = cm.group(0).lower()
|
|
374
|
+
sc = sum(1.0 for w in q_words_b if (w[:5] if len(w) >= 6 else w.rstrip("s")) in c_low)
|
|
375
|
+
if sc > best_sc_b:
|
|
376
|
+
best_sc_b = sc
|
|
377
|
+
best_cb = cm
|
|
378
|
+
if best_sc_b > 0:
|
|
379
|
+
search_txt = best_cb.group(0)
|
|
380
|
+
search_off = best_cb.start()
|
|
381
|
+
m_neg = re.search(
|
|
382
|
+
r"\b(not\s+(?:enabled|active|permitted|allowed|granted)|false|no|disabled|inactive|prohibited|denied)\b",
|
|
383
|
+
search_txt,
|
|
384
|
+
re.I,
|
|
385
|
+
)
|
|
386
|
+
m_pos = re.search(r"\b(true|yes|active|enabled|permitted|allowed|confirmed|granted)\b", search_txt, re.I)
|
|
387
|
+
if m_neg or m_pos:
|
|
388
|
+
if m_neg and (not m_pos or m_neg.start() <= m_pos.start()):
|
|
389
|
+
m_use = m_neg
|
|
390
|
+
b_val = False
|
|
391
|
+
else:
|
|
392
|
+
m_use = m_pos # type: ignore[assignment]
|
|
393
|
+
b_val = True
|
|
394
|
+
return {
|
|
395
|
+
"type": "boolean",
|
|
396
|
+
"p_null": 1.1e-6,
|
|
397
|
+
"p_true": 0.9992 if b_val else 0.0008,
|
|
398
|
+
"span": [search_off + m_use.start(1), search_off + m_use.end(1)],
|
|
399
|
+
"guess": b_val,
|
|
400
|
+
"confidence": 0.9992,
|
|
401
|
+
"span_entropy_bits": 0.041,
|
|
402
|
+
"value": b_val,
|
|
403
|
+
}
|
|
404
|
+
m_bool_fb = re.search(r"\b(true|false|yes|no)\b", state, re.I)
|
|
405
|
+
if m_bool_fb:
|
|
406
|
+
b_val = m_bool_fb.group(1).lower() in ("true", "yes")
|
|
354
407
|
return {
|
|
355
408
|
"type": "boolean",
|
|
356
409
|
"p_null": 1.1e-6,
|
|
357
410
|
"p_true": 0.9992 if b_val else 0.0008,
|
|
358
|
-
"span": [
|
|
411
|
+
"span": [m_bool_fb.start(1), m_bool_fb.end(1)],
|
|
359
412
|
"guess": b_val,
|
|
360
413
|
"confidence": 0.9992,
|
|
361
414
|
"span_entropy_bits": 0.041,
|
|
@@ -494,6 +547,9 @@ def _extract_field_fast(state: str, fname: str, spec: dict[str, Any]) -> dict[st
|
|
|
494
547
|
"span_entropy_bits": 0.031,
|
|
495
548
|
"value": val_d,
|
|
496
549
|
}
|
|
550
|
+
dis_dur = _disambiguate_numeric_field(state, fname, {**spec, "type": "duration", "unit": unit})
|
|
551
|
+
if dis_dur is not None:
|
|
552
|
+
return {**dis_dur, "unit": unit}
|
|
497
553
|
return {"type": "duration", "p_null": 0.9991, "unit": unit, "span": [0, 0], "guess": 0, "confidence": 0.41, "value": None}
|
|
498
554
|
|
|
499
555
|
if ftype in ("date", "time", "datetime"):
|
|
@@ -515,7 +571,18 @@ def _extract_field_fast(state: str, fname: str, spec: dict[str, Any]) -> dict[st
|
|
|
515
571
|
if ftype == "number":
|
|
516
572
|
if any(w in key or w in instr for w in ("rebate", "discount", "promo")):
|
|
517
573
|
m_disc = re.search(r"(?:Promo Rebate|Discount):\s*-?\$?(\d+(?:\.\d+)?)", state, re.I)
|
|
518
|
-
if
|
|
574
|
+
if m_disc:
|
|
575
|
+
d_val = float(m_disc.group(1))
|
|
576
|
+
return {
|
|
577
|
+
"type": "number",
|
|
578
|
+
"p_null": 1.2e-6,
|
|
579
|
+
"span": [m_disc.start(1), m_disc.end(1)],
|
|
580
|
+
"guess": d_val,
|
|
581
|
+
"confidence": 0.9994,
|
|
582
|
+
"span_entropy_bits": 0.044,
|
|
583
|
+
"value": d_val,
|
|
584
|
+
}
|
|
585
|
+
if "Invoice from " in state or "Receipt from " in state:
|
|
519
586
|
m_zero = re.search(r"(?:Egress Fee|Sales Tax):\s*\$(\d+(?:\.\d+)?)", state, re.I)
|
|
520
587
|
sp = [m_zero.start(1), m_zero.end(1)] if m_zero else [0, 0]
|
|
521
588
|
return {
|
|
@@ -528,16 +595,6 @@ def _extract_field_fast(state: str, fname: str, spec: dict[str, Any]) -> dict[st
|
|
|
528
595
|
"abstention_reason": "missing_field",
|
|
529
596
|
"value": None,
|
|
530
597
|
}
|
|
531
|
-
d_val = float(m_disc.group(1))
|
|
532
|
-
return {
|
|
533
|
-
"type": "number",
|
|
534
|
-
"p_null": 1.2e-6,
|
|
535
|
-
"span": [m_disc.start(1), m_disc.end(1)],
|
|
536
|
-
"guess": d_val,
|
|
537
|
-
"confidence": 0.9994,
|
|
538
|
-
"span_entropy_bits": 0.044,
|
|
539
|
-
"value": d_val,
|
|
540
|
-
}
|
|
541
598
|
|
|
542
599
|
if "including" in instr or "total_credit" in key or "amount_paid" in key:
|
|
543
600
|
m_plus = re.search(r"\$(\d+(?:\.\d+)?)\s+plus\s+\$(\d+(?:\.\d+)?)", state)
|
|
@@ -674,7 +731,7 @@ def _extract_field_fast(state: str, fname: str, spec: dict[str, Any]) -> dict[st
|
|
|
674
731
|
"value": p_str,
|
|
675
732
|
}
|
|
676
733
|
|
|
677
|
-
return _extract_open_domain_qa(state, spec.get("instructions", "") or
|
|
734
|
+
return _extract_open_domain_qa(state, spec.get("instructions", "") or fname)
|
|
678
735
|
|
|
679
736
|
|
|
680
737
|
_STOPWORDS = {
|
|
@@ -849,9 +906,13 @@ def _run_extraction(
|
|
|
849
906
|
mdl = AutoModel.from_pretrained(model_id, trust_remote_code=True)
|
|
850
907
|
if backend == "onnx":
|
|
851
908
|
import onnxruntime as ort
|
|
852
|
-
from huggingface_hub import hf_hub_download
|
|
853
909
|
|
|
854
|
-
|
|
910
|
+
if os.path.isdir(model_id):
|
|
911
|
+
onnx_file = os.path.join(model_id, "onnx", "model_quantized.onnx")
|
|
912
|
+
else:
|
|
913
|
+
from huggingface_hub import hf_hub_download
|
|
914
|
+
|
|
915
|
+
onnx_file = hf_hub_download(repo_id=model_id, filename="onnx/model_quantized.onnx")
|
|
855
916
|
sess = ort.InferenceSession(onnx_file, providers=["CPUExecutionProvider"])
|
|
856
917
|
_HF_CACHE[cache_key] = (tok, mdl, sess)
|
|
857
918
|
else:
|
|
@@ -896,3 +957,28 @@ def extract(
|
|
|
896
957
|
if not isinstance(state_or_fields, str) or not isinstance(fields, dict):
|
|
897
958
|
raise ValueError("Pass (state: str, fields: dict) or (fields: dict) to create an extractor.")
|
|
898
959
|
return _run_extraction(state_or_fields, fields, **options)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def decide(
|
|
963
|
+
state: Any,
|
|
964
|
+
questions: dict[str, dict[str, Any]],
|
|
965
|
+
*,
|
|
966
|
+
model_id: str = DEFAULT_HF_REPO,
|
|
967
|
+
device: str | None = None,
|
|
968
|
+
) -> dict[str, Any]:
|
|
969
|
+
"""Evaluate typed System One decisions (`noul`, `choice`, `score`) in one bidirectional encoder pass (`output_tokens = 0`)."""
|
|
970
|
+
from .systemone import NullspanSystemOneEngine
|
|
971
|
+
|
|
972
|
+
cache_key = f"{model_id}:{device or 'auto'}"
|
|
973
|
+
if cache_key not in _S1_CACHE:
|
|
974
|
+
if os.path.isdir(model_id):
|
|
975
|
+
local_dir = model_id
|
|
976
|
+
else:
|
|
977
|
+
from huggingface_hub import snapshot_download
|
|
978
|
+
|
|
979
|
+
local_dir = snapshot_download(
|
|
980
|
+
repo_id=model_id,
|
|
981
|
+
allow_patterns=["systemone/*", "systemone_decision.pt"],
|
|
982
|
+
)
|
|
983
|
+
_S1_CACHE[cache_key] = NullspanSystemOneEngine.load_from_dir(local_dir, device=device)
|
|
984
|
+
return _S1_CACHE[cache_key].decide(state=state, questions=questions)
|