@mailwoman/neural-web 0.1.0

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.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @mailwoman/neural-web
2
+
3
+ Browser-side mailwoman neural runtime — drop-in for [`@mailwoman/neural`](https://www.npmjs.com/package/@mailwoman/neural) when targeting a static-asset deploy. Pairs the existing SentencePiece tokenizer + BIO decoder with an [`onnxruntime-web`](https://www.npmjs.com/package/onnxruntime-web) inference path (WebGPU primary, WASM SIMD fallback).
4
+
5
+ Path B of the demo plan — see [sister-software/mailwoman#98](https://github.com/sister-software/mailwoman/issues/98).
6
+
7
+ ## Status
8
+
9
+ **v0.1.0 — scaffold + end-to-end smoke test.** `WebOnnxRunner` implements the `NeuralRunner` interface and is composable into `NeuralAddressClassifier` exactly like `OnnxRunner` from `@mailwoman/neural`. Test suite runs the real `@mailwoman/neural-weights-en-us` model through the WASM execution provider in Node — WebGPU is the production-time fast path but isn't testable in Node.
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import { loadNeuralClassifierFromUrls } from "@mailwoman/neural-web"
15
+
16
+ const classifier = await loadNeuralClassifierFromUrls({
17
+ modelUrl: "/static/mailwoman/model.onnx",
18
+ tokenizerUrl: "/static/mailwoman/tokenizer.model",
19
+ runner: {
20
+ // Optional. If your bundler doesn't put ort .wasm files in the default location,
21
+ // point this at where you serve them.
22
+ wasmPathsRoot: "/static/ort/",
23
+ },
24
+ })
25
+
26
+ const tree = await classifier.parse("123 Main St, Springfield, IL 62704")
27
+ console.log(tree.roots)
28
+ ```
29
+
30
+ For lower-level control, use `WebOnnxRunner` directly:
31
+
32
+ ```ts
33
+ import { WebOnnxRunner, MailwomanTokenizer, NeuralAddressClassifier } from "@mailwoman/neural-web"
34
+
35
+ const runner = await WebOnnxRunner.fromUrl("/static/mailwoman/model.onnx", { useWebGpu: true })
36
+ const tokenizer = await MailwomanTokenizer.loadFromBase64(/* base64 of tokenizer.model */)
37
+ const classifier = new NeuralAddressClassifier({ tokenizer, runner })
38
+ ```
39
+
40
+ ## Execution provider strategy
41
+
42
+ `WebOnnxRunner` attempts WebGPU first (10× faster than WASM on supported devices — Chromium 113+, Safari Tech Preview, hardware-dependent). If the WebGPU probe fails (no adapter, browser doesn't expose it, etc.), it transparently falls back to the WASM execution provider. Set `useWebGpu: false` to skip the probe entirely — useful in test environments where the failure path adds latency.
43
+
44
+ ## Bundling
45
+
46
+ This package ships compiled TypeScript only. The `onnxruntime-web` runtime ships its own `.wasm` assets — your bundler needs to serve them. The package's defaults point at a CDN; production deploys typically self-host:
47
+
48
+ ```ts
49
+ import { loadNeuralClassifierFromUrls } from "@mailwoman/neural-web"
50
+
51
+ // Copy node_modules/onnxruntime-web/dist/*.wasm into your /public dir during build,
52
+ // then point the runner at them:
53
+ const classifier = await loadNeuralClassifierFromUrls({
54
+ modelUrl: "/static/mailwoman/model.onnx",
55
+ tokenizerUrl: "/static/mailwoman/tokenizer.model",
56
+ runner: { wasmPathsRoot: "/ort-wasm/" },
57
+ })
58
+ ```
59
+
60
+ ## Why not extend `@mailwoman/neural` directly?
61
+
62
+ `@mailwoman/neural` depends on `onnxruntime-node`, which ships native binaries and breaks in a browser bundle. The classifier surface itself is runtime-agnostic — it only needs a `NeuralRunner` (a structural interface with `infer(ids): Promise<InferResult>`). Splitting the runner lets both implementations co-exist without forcing browser bundlers to dead-code-eliminate native code.
63
+
64
+ ## License
65
+
66
+ AGPL-3.0-only.
package/out/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+ export { defaultGazetteerLexiconUrl, loadNeuralClassifierFromUrls, type LoadFromUrlsOpts } from "./loader.js";
7
+ export { DEFAULT_FIXED_SEQ_LEN, WebOnnxRunner, type WebOnnxRunnerOpts } from "./web-onnx-runner.js";
8
+ export { MailwomanTokenizer, NeuralAddressClassifier, type NeuralAddressClassifierConfig, type NeuralRunner, } from "@mailwoman/neural/browser";
9
+ export type { InferResult } from "@mailwoman/neural/browser";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC7G,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAKnG,OAAO,EACN,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,6BAA6B,EAClC,KAAK,YAAY,GACjB,MAAM,2BAA2B,CAAA;AAClC,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA"}
package/out/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+ export { defaultGazetteerLexiconUrl, loadNeuralClassifierFromUrls } from "./loader.js";
7
+ export { DEFAULT_FIXED_SEQ_LEN, WebOnnxRunner } from "./web-onnx-runner.js";
8
+ // Re-export the public neural surface so callers don't need both packages on the typed path.
9
+ // Pull from the browser-safe entry — the default entry would drag onnxruntime-node + node:fs
10
+ // into the bundle graph via classifier.ts's transitive imports.
11
+ export { MailwomanTokenizer, NeuralAddressClassifier, } from "@mailwoman/neural/browser";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,0BAA0B,EAAE,4BAA4B,EAAyB,MAAM,aAAa,CAAA;AAC7G,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAA0B,MAAM,sBAAsB,CAAA;AAEnG,6FAA6F;AAC7F,6FAA6F;AAC7F,gEAAgE;AAChE,OAAO,EACN,kBAAkB,EAClB,uBAAuB,GAGvB,MAAM,2BAA2B,CAAA"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Browser-side loader that pairs the existing `MailwomanTokenizer` (whose `loadFromBase64` path is
7
+ * already browser-safe — it doesn't touch Node fs) with a fresh `WebOnnxRunner`, and returns a
8
+ * ready-to-use `NeuralAddressClassifier`.
9
+ *
10
+ * V1 strategy: fetch both `model.onnx` and `tokenizer.model` over HTTP from caller-provided URLs
11
+ * (typically pointing at the same static-asset bundle that ships the resolver's slim WOF DB). The
12
+ * neural weights package `@mailwoman/neural-weights-en-us` is the canonical source of those two
13
+ * files; for a static deploy, copy them into the public bundle and pass the resulting URLs.
14
+ */
15
+ import { NeuralAddressClassifier, type NeuralAddressClassifierConfig } from "@mailwoman/neural/browser";
16
+ import { type WebOnnxRunnerDiagnostics, type WebOnnxRunnerOpts } from "./web-onnx-runner.js";
17
+ export type { WebOnnxRunnerDiagnostics };
18
+ export interface LoadResult {
19
+ classifier: NeuralAddressClassifier;
20
+ diagnostics: WebOnnxRunnerDiagnostics | null;
21
+ /**
22
+ * Labels actually applied to the classifier. `null` when no model-card was provided or its
23
+ * `labels` field was missing — the classifier fell back to its built-in default (Stage 2).
24
+ */
25
+ labels: readonly string[] | null;
26
+ /**
27
+ * The parsed postcode-anchor lookup (postcode → posterior + centroid), when anchor binaries were
28
+ * loaded. Exposed so consumers (the demo's anchor-centroid map fallback) can reuse the SAME
29
+ * artifact the model channel feeds from — WOF ships placeholder (0,0) for ~22% of US postcodes;
30
+ * this lookup has a real centroid for every covered ZIP.
31
+ */
32
+ postcodeAnchorLookup?: import("@mailwoman/neural").AnchorLookup;
33
+ }
34
+ export interface LoadFromUrlsOpts {
35
+ /** URL to the ONNX model file (e.g. `/static/mailwoman/model.onnx`). */
36
+ modelUrl: string;
37
+ /** URL to the SentencePiece tokenizer model (e.g. `/static/mailwoman/tokenizer.model`). */
38
+ tokenizerUrl: string;
39
+ /**
40
+ * URL to `model-card.json`. When provided, its `labels` field is threaded into the classifier so
41
+ * post-Stage-2 bundles (33-label Stage 3 and beyond) decode correctly. Skip for legacy bundles
42
+ * whose cards predate the `labels` field — the loader falls back to the built-in Stage 2
43
+ * default.
44
+ *
45
+ * Required for any v0.6.x+ bundle: without it the classifier builds a 21×21 transition mask while
46
+ * the model emits 33 logits and viterbi crashes with "Cannot read properties of undefined".
47
+ */
48
+ modelCardUrl?: string;
49
+ /** Runner options (WebGPU toggle, fixed sequence length, WASM path override). */
50
+ runner?: WebOnnxRunnerOpts;
51
+ /**
52
+ * URLs to one or more PCB1 postcode binaries (`postcode-<cc>.bin`). For anchor-trained models
53
+ * (#239/#240) these are decoded + merged into the postcode→anchor lookup the classifier feeds at
54
+ * inference, so the demo runs the model with the anchor on. Pass the locales the model handles
55
+ * (e.g. US + DE). Omit for plain models — the runner then feeds the anchor-off identity.
56
+ */
57
+ postcodeBinaryUrls?: readonly string[];
58
+ /**
59
+ * URL to the gazetteer-anchor lexicon JSON (`anchor-lexicon-v1.json`, #464 — the in-repo source
60
+ * is `data/gazetteer/anchor-lexicon-v1.json`). Gazetteer-trained models (v4.2.0+, whose ONNX
61
+ * declares the `gazetteer_features`/`gazetteer_confidence` inputs) REQUIRE this clue at
62
+ * inference: running them on the zero-filled fallback is the measured train/inference mismatch
63
+ * that wrecks segmentation ("the zero-fill trap", CONTRIBUTING_MODEL_WORK.mdx eval invariants).
64
+ *
65
+ * Defaults to `anchor-lexicon-v1.json` next to `modelUrl`. A fetch miss (404 etc.) does NOT throw
66
+ * — older bundles never shipped the file — but if the loaded model turns out to be
67
+ * gazetteer-trained the loader logs a loud `console.error` naming the missing file and the model
68
+ * runs gazetteer-off (structurally valid, quality-degraded). Pass `null` to skip the fetch
69
+ * entirely.
70
+ */
71
+ gazetteerLexiconUrl?: string | null;
72
+ /**
73
+ * Channel choreography (#464, v0.9.13 postcode fix): zero the gazetteer clue on pieces adjacent
74
+ * to a postcode-anchor hit. Defaults to TRUE — it pairs with the train-time half on every
75
+ * gazetteer-trained bundle (v4.2.0+) and is inert when either channel is absent.
76
+ */
77
+ suppressGazetteerNearPostcode?: boolean;
78
+ /**
79
+ * Address-system conventions mode (#511 Tier A, v4.3.0+). Defaults to `"auto"` (read the model's
80
+ * locale head when exported; inert on bundles without `locale_logits`). Pass a `SystemCode` to
81
+ * pin, or `null` to disable.
82
+ */
83
+ addressSystemConventions?: NeuralAddressClassifierConfig["addressSystemConventions"] | null;
84
+ /**
85
+ * Span bridge (v4.4.0 declared behavior): merge same-tag spans split at intra-token punctuation
86
+ * ("P.O. Box"). Defaults to TRUE per the v4.4.0 ship config (model-card.json: po_box 60.4
87
+ * without, 89.1 with). Pass false to disable for pre-bridge bundles where gate parity matters.
88
+ */
89
+ bridgePunctuationGaps?: boolean;
90
+ /** Optional fetch override. Defaults to `globalThis.fetch`. */
91
+ fetchImpl?: typeof fetch;
92
+ }
93
+ /**
94
+ * Default location of the gazetteer-anchor lexicon: `anchor-lexicon-v1.json` as a sibling of the
95
+ * model file. Matches how release bundles lay out their version directory (model.onnx,
96
+ * tokenizer.model, model-card.json, postcode-*.bin, anchor-lexicon-v1.json side by side).
97
+ */
98
+ export declare function defaultGazetteerLexiconUrl(modelUrl: string): string;
99
+ /**
100
+ * Convenience factory: fetch model + tokenizer, build the runner, return a classifier. The
101
+ * tokenizer is loaded via the existing `loadFromBase64` path so this file shares zero Node-only
102
+ * code with `@mailwoman/neural/classifier`'s `loadFromWeights`.
103
+ *
104
+ * The classifier is constructed with the v4.4.0 ship config by default (gazetteer lexicon +
105
+ * postcode anchor when their assets resolve, `suppressGazetteerNearPostcode: true`,
106
+ * `addressSystemConventions: "auto"`, `bridgePunctuationGaps: true`) — every knob is inert on
107
+ * bundles that predate the corresponding channel, so older versions keep decoding unchanged.
108
+ */
109
+ export declare function loadNeuralClassifierFromUrls(opts: LoadFromUrlsOpts): Promise<LoadResult>;
110
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAIN,uBAAuB,EACvB,KAAK,6BAA6B,EAGlC,MAAM,2BAA2B,CAAA;AAElC,OAAO,EAAiB,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAE3G,YAAY,EAAE,wBAAwB,EAAE,CAAA;AAExC,MAAM,WAAW,UAAU;IAC1B,UAAU,EAAE,uBAAuB,CAAA;IACnC,WAAW,EAAE,wBAAwB,GAAG,IAAI,CAAA;IAC5C;;;OAGG;IACH,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;IAChC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,mBAAmB,EAAE,YAAY,CAAA;CAC/D;AAED,MAAM,WAAW,gBAAgB;IAChC,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,YAAY,EAAE,MAAM,CAAA;IACpB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC;;;;;;;;;;;;OAYG;IACH,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC;;;;OAIG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAA;IACvC;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,6BAA6B,CAAC,0BAA0B,CAAC,GAAG,IAAI,CAAA;IAC3F;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACxB;AA6BD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAInE;AAED;;;;;;;;;GASG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA8C9F"}
package/out/loader.js ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Browser-side loader that pairs the existing `MailwomanTokenizer` (whose `loadFromBase64` path is
7
+ * already browser-safe — it doesn't touch Node fs) with a fresh `WebOnnxRunner`, and returns a
8
+ * ready-to-use `NeuralAddressClassifier`.
9
+ *
10
+ * V1 strategy: fetch both `model.onnx` and `tokenizer.model` over HTTP from caller-provided URLs
11
+ * (typically pointing at the same static-asset bundle that ships the resolver's slim WOF DB). The
12
+ * neural weights package `@mailwoman/neural-weights-en-us` is the canonical source of those two
13
+ * files; for a static deploy, copy them into the public bundle and pass the resulting URLs.
14
+ */
15
+ import { MailwomanTokenizer, NeuralAddressClassifier, parseGazetteerLexicon, PostcodeBinaryResolver, } from "@mailwoman/neural/browser";
16
+ import { WebOnnxRunner } from "./web-onnx-runner.js";
17
+ /** Merge per-binary anchor lookups: union the country posteriors per postcode, mean the centroids. */
18
+ function mergeAnchorLookups(lookups) {
19
+ if (lookups.length === 1)
20
+ return lookups[0];
21
+ const merged = new Map();
22
+ for (const lookup of lookups) {
23
+ for (const [postcode, entry] of lookup) {
24
+ const existing = merged.get(postcode);
25
+ if (!existing) {
26
+ merged.set(postcode, { posterior: { ...entry.posterior }, lat: entry.lat, lon: entry.lon });
27
+ continue;
28
+ }
29
+ for (const country of Object.keys(entry.posterior))
30
+ existing.posterior[country] = 1;
31
+ // Average a real centroid in; ignore (0,0) placeholders.
32
+ if (entry.lat !== 0 || entry.lon !== 0) {
33
+ if (existing.lat === 0 && existing.lon === 0) {
34
+ existing.lat = entry.lat;
35
+ existing.lon = entry.lon;
36
+ }
37
+ else {
38
+ existing.lat = (existing.lat + entry.lat) / 2;
39
+ existing.lon = (existing.lon + entry.lon) / 2;
40
+ }
41
+ }
42
+ }
43
+ }
44
+ return merged;
45
+ }
46
+ /**
47
+ * Default location of the gazetteer-anchor lexicon: `anchor-lexicon-v1.json` as a sibling of the
48
+ * model file. Matches how release bundles lay out their version directory (model.onnx,
49
+ * tokenizer.model, model-card.json, postcode-*.bin, anchor-lexicon-v1.json side by side).
50
+ */
51
+ export function defaultGazetteerLexiconUrl(modelUrl) {
52
+ // Swap the final path segment — string surgery rather than `new URL()` so relative model URLs
53
+ // ("/static/mailwoman/model.onnx") stay relative.
54
+ return modelUrl.replace(/[^/]*$/, "anchor-lexicon-v1.json");
55
+ }
56
+ /**
57
+ * Convenience factory: fetch model + tokenizer, build the runner, return a classifier. The
58
+ * tokenizer is loaded via the existing `loadFromBase64` path so this file shares zero Node-only
59
+ * code with `@mailwoman/neural/classifier`'s `loadFromWeights`.
60
+ *
61
+ * The classifier is constructed with the v4.4.0 ship config by default (gazetteer lexicon +
62
+ * postcode anchor when their assets resolve, `suppressGazetteerNearPostcode: true`,
63
+ * `addressSystemConventions: "auto"`, `bridgePunctuationGaps: true`) — every knob is inert on
64
+ * bundles that predate the corresponding channel, so older versions keep decoding unchanged.
65
+ */
66
+ export async function loadNeuralClassifierFromUrls(opts) {
67
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
68
+ if (!fetchImpl) {
69
+ throw new Error("no fetch implementation available — pass fetchImpl in non-fetch environments");
70
+ }
71
+ const gazetteerLexiconUrl = opts.gazetteerLexiconUrl === null ? null : (opts.gazetteerLexiconUrl ?? defaultGazetteerLexiconUrl(opts.modelUrl));
72
+ const [modelBytes, tokenizerBytes, labels, gazetteerLexicon] = await Promise.all([
73
+ fetchBytes(opts.modelUrl, fetchImpl),
74
+ fetchBytes(opts.tokenizerUrl, fetchImpl),
75
+ opts.modelCardUrl ? fetchLabelsFromModelCard(opts.modelCardUrl, fetchImpl) : Promise.resolve(null),
76
+ gazetteerLexiconUrl ? fetchGazetteerLexicon(gazetteerLexiconUrl, fetchImpl) : Promise.resolve(null),
77
+ ]);
78
+ const [tokenizer, runner, postcodeAnchorLookup] = await Promise.all([
79
+ MailwomanTokenizer.loadFromBase64(toBase64(tokenizerBytes)),
80
+ WebOnnxRunner.fromBytes(modelBytes, opts.runner),
81
+ opts.postcodeBinaryUrls?.length
82
+ ? Promise.all(opts.postcodeBinaryUrls.map(async (url) => new PostcodeBinaryResolver(await fetchBytes(url, fetchImpl)).toAnchorLookup())).then(mergeAnchorLookups)
83
+ : Promise.resolve(undefined),
84
+ ]);
85
+ const conventions = opts.addressSystemConventions === null ? undefined : (opts.addressSystemConventions ?? "auto");
86
+ const classifier = new NeuralAddressClassifier({
87
+ tokenizer,
88
+ runner,
89
+ ...(labels ? { labels } : {}),
90
+ ...(postcodeAnchorLookup ? { postcodeAnchorLookup } : {}),
91
+ ...(gazetteerLexicon ? { gazetteerLexicon } : {}),
92
+ suppressGazetteerNearPostcode: opts.suppressGazetteerNearPostcode ?? true,
93
+ ...(conventions ? { addressSystemConventions: conventions } : {}),
94
+ bridgePunctuationGaps: opts.bridgePunctuationGaps ?? true,
95
+ });
96
+ await runner.infer([0]);
97
+ warnOnUnfedTrainedChannels(runner, {
98
+ gazetteerLexicon,
99
+ gazetteerLexiconUrl,
100
+ postcodeAnchorLookup,
101
+ });
102
+ return { classifier, diagnostics: runner.diagnostics, labels };
103
+ }
104
+ /**
105
+ * Loud degrade (#464): the warmup `infer([0])` above forced session creation, so the graph's
106
+ * declared inputs are now known. A gazetteer/anchor-TRAINED model running on the zero-filled
107
+ * fallback is a measured failure mode (train/inference mismatch — "the zero-fill trap"), not a
108
+ * quality-neutral default; without this check the only symptom would be silently degraded parses.
109
+ * (Pre-fix, the symptom was worse still: ORT's cryptic `input 'gazetteer_features' is missing in
110
+ * 'feeds'`.) The loader still returns a working classifier — structural fallback, loud console.
111
+ */
112
+ function warnOnUnfedTrainedChannels(runner, fed) {
113
+ const inputNames = runner.inputNames;
114
+ if (!inputNames)
115
+ return;
116
+ if (inputNames.includes("gazetteer_features") && !fed.gazetteerLexicon) {
117
+ console.error("[mailwoman/neural-web] This model is gazetteer-anchor-trained (its ONNX declares `gazetteer_features`) " +
118
+ "but no gazetteer lexicon was loaded" +
119
+ (fed.gazetteerLexiconUrl
120
+ ? ` — \`anchor-lexicon-v1.json\` could not be fetched from ${fed.gazetteerLexiconUrl}. ` +
121
+ "Upload the lexicon next to model.onnx, or pass `gazetteerLexiconUrl` explicitly."
122
+ : " — `gazetteerLexiconUrl` was explicitly disabled (null). ") +
123
+ " Running with zero-filled gazetteer clues: parses will be degraded (train/inference mismatch).");
124
+ }
125
+ if (inputNames.includes("anchor_features") && !fed.postcodeAnchorLookup) {
126
+ console.error("[mailwoman/neural-web] This model is postcode-anchor-trained (its ONNX declares `anchor_features`) " +
127
+ "but no `postcodeBinaryUrls` were provided (postcode-<cc>.bin). " +
128
+ "Running with zero-filled anchor features: the anchor-off identity, degraded vs the ship config.");
129
+ }
130
+ }
131
+ /**
132
+ * Fetch + parse `anchor-lexicon-v1.json`. A missing file (404 or network failure) returns null —
133
+ * the caller decides whether that matters (it does iff the model declares the gazetteer inputs; see
134
+ * `warnOnUnfedTrainedChannels`). A PRESENT-but-malformed lexicon throws loudly via
135
+ * `parseGazetteerLexicon`'s validation — never silently zero-fill off bad data.
136
+ */
137
+ async function fetchGazetteerLexicon(url, fetchImpl) {
138
+ let res;
139
+ try {
140
+ res = await fetchImpl(url);
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ if (!res.ok)
146
+ return null;
147
+ return parseGazetteerLexicon((await res.json()));
148
+ }
149
+ /**
150
+ * Browser-side analogue of `weights.readLabelsFromModelCard`. Same shape contract: returns the
151
+ * `labels` array only when the card has a non-empty string array, throws on a present-but-malformed
152
+ * field, returns `null` when the field is simply absent (legacy pre-v0.4.0 card).
153
+ *
154
+ * A 404 on the model-card itself is treated as "no card provided" — we tolerate older bundles that
155
+ * shipped without one and let the classifier fall back to its compile-time default.
156
+ */
157
+ async function fetchLabelsFromModelCard(url, fetchImpl) {
158
+ const res = await fetchImpl(url);
159
+ if (!res.ok) {
160
+ if (res.status === 404)
161
+ return null;
162
+ throw new Error(`fetch ${url} failed: ${res.status} ${res.statusText}`);
163
+ }
164
+ const parsed = (await res.json());
165
+ const labels = parsed.labels;
166
+ if (labels === undefined)
167
+ return null;
168
+ if (!Array.isArray(labels) || labels.length === 0 || !labels.every((l) => typeof l === "string")) {
169
+ throw new Error(`model-card at ${url} has a malformed \`labels\` field — ` +
170
+ `expected a non-empty array of strings, got ${JSON.stringify(labels)}.`);
171
+ }
172
+ return Object.freeze(labels.slice());
173
+ }
174
+ async function fetchBytes(url, fetchImpl) {
175
+ const res = await fetchImpl(url);
176
+ if (!res.ok)
177
+ throw new Error(`fetch ${url} failed: ${res.status} ${res.statusText}`);
178
+ return new Uint8Array(await res.arrayBuffer());
179
+ }
180
+ /**
181
+ * Base64-encode a Uint8Array. Browsers + Node 18+ both have `btoa(String.fromCharCode(...))` but
182
+ * String.fromCharCode chokes on long arrays (call-stack overflow on a few MB of bytes). The chunked
183
+ * loop avoids that — kept here rather than imported because both browser and Node need it and
184
+ * adding a dep for ~5 lines is silly.
185
+ */
186
+ function toBase64(bytes) {
187
+ const chunkSize = 0x8000;
188
+ let binary = "";
189
+ for (let i = 0; i < bytes.length; i += chunkSize) {
190
+ const chunk = bytes.subarray(i, i + chunkSize);
191
+ binary += String.fromCharCode(...chunk);
192
+ }
193
+ if (typeof btoa === "function")
194
+ return btoa(binary);
195
+ // Node: Buffer is the lower-friction path; the lazy import keeps the file from pulling in
196
+ // node:buffer when bundlers are statically analyzing browser entries.
197
+ return Buffer.from(binary, "binary").toString("base64");
198
+ }
199
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Browser ONNX inference wrapper. Implements the same `NeuralRunner` contract `@mailwoman/neural`'s
7
+ * classifier consumes, but backed by `onnxruntime-web` (WASM + optional WebGPU) instead of
8
+ * `onnxruntime-node`.
9
+ *
10
+ * Execution provider strategy:
11
+ *
12
+ * - Try WebGPU first when `useWebGpu !== false`. ~10× faster than WASM on supported devices, but
13
+ * availability depends on browser (Chromium 113+, Safari Tech Preview) AND hardware. The
14
+ * runtime surfaces a clean error when WebGPU is unavailable, so the constructor falls back to
15
+ * WASM automatically.
16
+ * - WASM (SIMD when available) is the universal fallback. ~2× slower than WebGPU on the same model
17
+ * but works everywhere onnxruntime-web does — including in Node, which is how the test
18
+ * harness exercises this file.
19
+ *
20
+ * Tensor shape + I/O contract matches `OnnxRunner` exactly: fixed-length int64 inputs, padded with
21
+ * zeros + attention_mask, output is a `logits` tensor of shape `[batch, seq, num_labels]`. See
22
+ * `@mailwoman/neural/onnx-runner` for the full export contract this file mirrors.
23
+ */
24
+ import { type InferResult, type NeuralRunner } from "@mailwoman/neural/browser";
25
+ export interface WebOnnxRunnerOpts {
26
+ /**
27
+ * Try the WebGPU execution provider first. Defaults to true. Set false to skip the WebGPU probe —
28
+ * useful in test environments where WebGPU isn't available and the probe failure adds latency.
29
+ */
30
+ useWebGpu?: boolean;
31
+ /**
32
+ * Fixed sequence length the model expects. Matches `OnnxRunner.DEFAULT_FIXED_SEQ_LEN` (128) by
33
+ * default. Re-quantized models can override.
34
+ */
35
+ fixedSeqLen?: number;
36
+ /**
37
+ * Optional override for where onnxruntime-web should load its `.wasm` assets from. Defaults to
38
+ * the package's CDN paths; bundlers usually want to point this at a self-hosted copy.
39
+ *
40
+ * Example: `setWasmPaths("/static/ort/")` and put the .wasm files at /static/ort/.
41
+ */
42
+ wasmPathsRoot?: string;
43
+ }
44
+ export declare const DEFAULT_FIXED_SEQ_LEN = 128;
45
+ export interface WebOnnxRunnerDiagnostics {
46
+ backend: "webgpu" | "wasm";
47
+ modelBytes: number;
48
+ }
49
+ export declare class WebOnnxRunner implements NeuralRunner {
50
+ #private;
51
+ private readonly modelBytes;
52
+ private readonly opts;
53
+ readonly fixedSeqLen: number;
54
+ diagnostics: WebOnnxRunnerDiagnostics | null;
55
+ private constructor();
56
+ /** Construct from already-fetched model bytes. */
57
+ static fromBytes(modelBytes: Uint8Array, opts?: WebOnnxRunnerOpts): Promise<WebOnnxRunner>;
58
+ /** Fetch the model from a URL and construct. */
59
+ static fromUrl(modelUrl: string, opts?: WebOnnxRunnerOpts): Promise<WebOnnxRunner>;
60
+ /**
61
+ * Names of the inputs the loaded ONNX graph declares. `null` until the session has been created
62
+ * (first `infer()` call). Lets callers (e.g. the neural-web loader) detect
63
+ * anchor/gazetteer-trained models and warn loudly when the corresponding feature source wasn't
64
+ * provided — running such a model on the zero-filled fallback is the measured train/inference
65
+ * mismatch ("the zero-fill trap"), not a quality-neutral degrade.
66
+ */
67
+ get inputNames(): readonly string[] | null;
68
+ infer(tokenIds: number[], anchor?: {
69
+ features: ReadonlyArray<ReadonlyArray<number>>;
70
+ confidence: ReadonlyArray<number>;
71
+ }, gazetteer?: {
72
+ features: ReadonlyArray<ReadonlyArray<number>>;
73
+ confidence: ReadonlyArray<number>;
74
+ }): Promise<InferResult>;
75
+ }
76
+ //# sourceMappingURL=web-onnx-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-onnx-runner.d.ts","sourceRoot":"","sources":["../web-onnx-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAGN,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,MAAM,2BAA2B,CAAA;AAGlC,MAAM,WAAW,iBAAiB;IACjC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAUxC,MAAM,WAAW,wBAAwB;IACxC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,qBAAa,aAAc,YAAW,YAAY;;IAOhD,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPtB,SAAgB,WAAW,EAAE,MAAM,CAAA;IAC5B,WAAW,EAAE,wBAAwB,GAAG,IAAI,CAAO;IAI1D,OAAO;IAOP,kDAAkD;WACrC,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC;IAMpG,gDAAgD;WACnC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC;IAqC5F;;;;;;OAMG;IACH,IAAI,UAAU,IAAI,SAAS,MAAM,EAAE,GAAG,IAAI,CAEzC;IAEK,KAAK,CACV,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;QAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;KAAE,EAC9F,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;QAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;KAAE,GAC/F,OAAO,CAAC,WAAW,CAAC;CAoFvB"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Browser ONNX inference wrapper. Implements the same `NeuralRunner` contract `@mailwoman/neural`'s
7
+ * classifier consumes, but backed by `onnxruntime-web` (WASM + optional WebGPU) instead of
8
+ * `onnxruntime-node`.
9
+ *
10
+ * Execution provider strategy:
11
+ *
12
+ * - Try WebGPU first when `useWebGpu !== false`. ~10× faster than WASM on supported devices, but
13
+ * availability depends on browser (Chromium 113+, Safari Tech Preview) AND hardware. The
14
+ * runtime surfaces a clean error when WebGPU is unavailable, so the constructor falls back to
15
+ * WASM automatically.
16
+ * - WASM (SIMD when available) is the universal fallback. ~2× slower than WebGPU on the same model
17
+ * but works everywhere onnxruntime-web does — including in Node, which is how the test
18
+ * harness exercises this file.
19
+ *
20
+ * Tensor shape + I/O contract matches `OnnxRunner` exactly: fixed-length int64 inputs, padded with
21
+ * zeros + attention_mask, output is a `logits` tensor of shape `[batch, seq, num_labels]`. See
22
+ * `@mailwoman/neural/onnx-runner` for the full export contract this file mirrors.
23
+ */
24
+ import { ANCHOR_FEATURE_DIM, GAZETTEER_FEATURE_DIM, } from "@mailwoman/neural/browser";
25
+ import * as ort from "onnxruntime-web/webgpu";
26
+ export const DEFAULT_FIXED_SEQ_LEN = 128;
27
+ /** Apply `wasmPathsRoot` once at module init. Safe to call multiple times. */
28
+ function configureWasmPaths(root) {
29
+ if (!root)
30
+ return;
31
+ // onnxruntime-web ships this on `ort.env.wasm`. We assign directly rather than calling
32
+ // `setWasmPaths` so it works across the slightly different shapes the typings have had.
33
+ ort.env.wasm.wasmPaths = root;
34
+ }
35
+ export class WebOnnxRunner {
36
+ modelBytes;
37
+ opts;
38
+ fixedSeqLen;
39
+ diagnostics = null;
40
+ #session = null;
41
+ #loadPromise = null;
42
+ constructor(modelBytes, opts) {
43
+ this.modelBytes = modelBytes;
44
+ this.opts = opts;
45
+ this.fixedSeqLen = opts.fixedSeqLen ?? DEFAULT_FIXED_SEQ_LEN;
46
+ }
47
+ /** Construct from already-fetched model bytes. */
48
+ static async fromBytes(modelBytes, opts = {}) {
49
+ configureWasmPaths(opts.wasmPathsRoot);
50
+ const runner = new WebOnnxRunner(modelBytes, opts);
51
+ return runner;
52
+ }
53
+ /** Fetch the model from a URL and construct. */
54
+ static async fromUrl(modelUrl, opts = {}) {
55
+ const res = await fetch(modelUrl);
56
+ if (!res.ok)
57
+ throw new Error(`fetch ${modelUrl} failed: ${res.status} ${res.statusText}`);
58
+ const bytes = new Uint8Array(await res.arrayBuffer());
59
+ return WebOnnxRunner.fromBytes(bytes, opts);
60
+ }
61
+ async #ensureSession() {
62
+ if (this.#session)
63
+ return this.#session;
64
+ if (!this.#loadPromise) {
65
+ this.#loadPromise = (async () => {
66
+ const wantWebGpu = this.opts.useWebGpu !== false;
67
+ if (wantWebGpu) {
68
+ try {
69
+ const session = await ort.InferenceSession.create(this.modelBytes, {
70
+ executionProviders: ["webgpu", "wasm"],
71
+ graphOptimizationLevel: "all",
72
+ });
73
+ this.#session = session;
74
+ this.diagnostics = { backend: "webgpu", modelBytes: this.modelBytes.byteLength };
75
+ return session;
76
+ }
77
+ catch {
78
+ // WebGPU probe failed — fall through to WASM
79
+ }
80
+ }
81
+ const session = await ort.InferenceSession.create(this.modelBytes, {
82
+ executionProviders: ["wasm"],
83
+ graphOptimizationLevel: "all",
84
+ });
85
+ this.#session = session;
86
+ this.diagnostics = { backend: "wasm", modelBytes: this.modelBytes.byteLength };
87
+ return session;
88
+ })();
89
+ }
90
+ return this.#loadPromise;
91
+ }
92
+ /**
93
+ * Names of the inputs the loaded ONNX graph declares. `null` until the session has been created
94
+ * (first `infer()` call). Lets callers (e.g. the neural-web loader) detect
95
+ * anchor/gazetteer-trained models and warn loudly when the corresponding feature source wasn't
96
+ * provided — running such a model on the zero-filled fallback is the measured train/inference
97
+ * mismatch ("the zero-fill trap"), not a quality-neutral degrade.
98
+ */
99
+ get inputNames() {
100
+ return this.#session?.inputNames ?? null;
101
+ }
102
+ async infer(tokenIds, anchor, gazetteer) {
103
+ const session = await this.#ensureSession();
104
+ const seqLen = Math.min(tokenIds.length, this.fixedSeqLen);
105
+ const padded = new BigInt64Array(this.fixedSeqLen);
106
+ const mask = new BigInt64Array(this.fixedSeqLen);
107
+ for (let i = 0; i < seqLen; i++) {
108
+ padded[i] = BigInt(tokenIds[i]);
109
+ mask[i] = 1n;
110
+ }
111
+ const feeds = {
112
+ input_ids: new ort.Tensor("int64", padded, [1, this.fixedSeqLen]),
113
+ attention_mask: new ort.Tensor("int64", mask, [1, this.fixedSeqLen]),
114
+ };
115
+ // Anchor channel (#239/#240) — mirror of the node OnnxRunner. Feed the per-piece anchor when the
116
+ // caller supplies it; otherwise, for anchor-trained models (whose ONNX declares the inputs as
117
+ // mandatory), feed zeros — the confidence=0 identity / anchor-off path. Without this the session
118
+ // throws on the missing required inputs.
119
+ if (anchor) {
120
+ const dim = anchor.features[0]?.length ?? 0;
121
+ const af = new Float32Array(this.fixedSeqLen * dim);
122
+ const ac = new Float32Array(this.fixedSeqLen);
123
+ for (let i = 0; i < seqLen; i++) {
124
+ ac[i] = anchor.confidence[i] ?? 0;
125
+ const row = anchor.features[i];
126
+ if (row)
127
+ for (let d = 0; d < dim; d++)
128
+ af[i * dim + d] = row[d] ?? 0;
129
+ }
130
+ feeds.anchor_features = new ort.Tensor("float32", af, [1, this.fixedSeqLen, dim]);
131
+ feeds.anchor_confidence = new ort.Tensor("float32", ac, [1, this.fixedSeqLen]);
132
+ }
133
+ else if (session.inputNames.includes("anchor_features")) {
134
+ feeds.anchor_features = new ort.Tensor("float32", new Float32Array(this.fixedSeqLen * ANCHOR_FEATURE_DIM), [
135
+ 1,
136
+ this.fixedSeqLen,
137
+ ANCHOR_FEATURE_DIM,
138
+ ]);
139
+ feeds.anchor_confidence = new ort.Tensor("float32", new Float32Array(this.fixedSeqLen), [1, this.fixedSeqLen]);
140
+ }
141
+ // Gazetteer-anchor channel (#464) — mirror of the node OnnxRunner. Feed the per-piece clue when
142
+ // the caller supplies it AND the graph declares the inputs; for gazetteer-trained models with no
143
+ // clue data, feed zeros (the confidence=0 identity — a structural fallback only; see the loader's
144
+ // loud warning) so the session doesn't throw `input 'gazetteer_features' is missing in 'feeds'`.
145
+ if (gazetteer && session.inputNames.includes("gazetteer_features")) {
146
+ const dim = gazetteer.features[0]?.length ?? 0;
147
+ const gf = new Float32Array(this.fixedSeqLen * dim);
148
+ const gc = new Float32Array(this.fixedSeqLen);
149
+ for (let i = 0; i < seqLen; i++) {
150
+ gc[i] = gazetteer.confidence[i] ?? 0;
151
+ const row = gazetteer.features[i];
152
+ if (row)
153
+ for (let d = 0; d < dim; d++)
154
+ gf[i * dim + d] = row[d] ?? 0;
155
+ }
156
+ feeds.gazetteer_features = new ort.Tensor("float32", gf, [1, this.fixedSeqLen, dim]);
157
+ feeds.gazetteer_confidence = new ort.Tensor("float32", gc, [1, this.fixedSeqLen]);
158
+ }
159
+ else if (session.inputNames.includes("gazetteer_features")) {
160
+ feeds.gazetteer_features = new ort.Tensor("float32", new Float32Array(this.fixedSeqLen * GAZETTEER_FEATURE_DIM), [
161
+ 1,
162
+ this.fixedSeqLen,
163
+ GAZETTEER_FEATURE_DIM,
164
+ ]);
165
+ feeds.gazetteer_confidence = new ort.Tensor("float32", new Float32Array(this.fixedSeqLen), [1, this.fixedSeqLen]);
166
+ }
167
+ const output = await session.run(feeds);
168
+ const logitsTensor = output["logits"];
169
+ if (!logitsTensor)
170
+ throw new Error("ONNX model did not return a `logits` output");
171
+ const data = logitsTensor.data;
172
+ const [, , numLabels] = logitsTensor.dims;
173
+ const logits = [];
174
+ for (let t = 0; t < seqLen; t++) {
175
+ const row = new Array(numLabels);
176
+ const base = t * numLabels;
177
+ for (let l = 0; l < numLabels; l++)
178
+ row[l] = data[base + l];
179
+ logits.push(row);
180
+ }
181
+ // Locale head (#511 Tier A): present on v1.1.0+ exports (shipped v4.3.0+), absent before.
182
+ // Mirrors the node OnnxRunner — `addressSystemConventions: "auto"` depends on this surfacing.
183
+ const localeTensor = output["locale_logits"];
184
+ const localeLogits = localeTensor ? Array.from(localeTensor.data) : undefined;
185
+ return { logits, numLabels, ...(localeLogits ? { localeLogits } : {}) };
186
+ }
187
+ }
188
+ //# sourceMappingURL=web-onnx-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-onnx-runner.js","sourceRoot":"","sources":["../web-onnx-runner.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@mailwoman/neural-web",
3
+ "version": "0.1.0",
4
+ "description": "Browser-side mailwoman neural runtime. Pairs the existing tokenizer + decoder with an onnxruntime-web inference path (WebGPU primary, WASM SIMD fallback). Drop-in for @mailwoman/neural when targeting a static-asset deploy (Phase B of the demo plan).",
5
+ "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/sister-software/mailwoman.git",
9
+ "directory": "neural-web"
10
+ },
11
+ "type": "module",
12
+ "exports": {
13
+ "./package.json": "./package.json",
14
+ ".": "./out/index.js"
15
+ },
16
+ "dependencies": {
17
+ "@mailwoman/core": "4.14.0",
18
+ "@mailwoman/neural": "4.14.0",
19
+ "onnxruntime-web": "1.25.0-dev.20260327-722743c0e2"
20
+ },
21
+ "files": [
22
+ "out/**/*.js",
23
+ "out/**/*.js.map",
24
+ "out/**/*.d.ts",
25
+ "out/**/*.d.ts.map",
26
+ "README.md"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }