gheim 0.1.4 → 0.1.5

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.
@@ -58,7 +58,8 @@ __export(exports_src, {
58
58
  LocalDetector: () => LocalDetector,
59
59
  LABEL_TO_TAG: () => LABEL_TO_TAG,
60
60
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
61
- CompositeDetector: () => CompositeDetector
61
+ CompositeDetector: () => CompositeDetector,
62
+ CalibratedDetector: () => CalibratedDetector
62
63
  });
63
64
  module.exports = __toCommonJS(exports_src);
64
65
 
@@ -842,10 +843,281 @@ class CompositeDetector {
842
843
  return out;
843
844
  }
844
845
  }
846
+ // src/detectors/calibrated.ts
847
+ var DEFAULT_MODEL3 = "joelbarmettler/gheim-ch-560m";
848
+ var DEFAULT_CHUNK_TOKENS2 = 510;
849
+ var DEFAULT_OVERLAP_TOKENS2 = 32;
850
+ function trimSpan2(text, start, end) {
851
+ while (start < end && /\s/.test(text[start] ?? ""))
852
+ start++;
853
+ while (end > start && /\s/.test(text[end - 1] ?? ""))
854
+ end--;
855
+ return [start, end];
856
+ }
857
+
858
+ class CalibratedDetector {
859
+ model;
860
+ oBias;
861
+ device;
862
+ dtype;
863
+ chunkTokens;
864
+ chunkOverlapTokens;
865
+ trimWhitespace;
866
+ actualDevice = null;
867
+ _model = null;
868
+ _tokenizer = null;
869
+ _id2label = {};
870
+ _oId = 0;
871
+ _loadPromise = null;
872
+ _onProgress;
873
+ constructor(opts = {}) {
874
+ this.model = opts.model ?? DEFAULT_MODEL3;
875
+ this.oBias = opts.oBias ?? 0.5;
876
+ this.device = opts.device ?? "auto";
877
+ this.dtype = opts.dtype ?? "fp32";
878
+ this.chunkTokens = opts.chunkTokens ?? DEFAULT_CHUNK_TOKENS2;
879
+ this.chunkOverlapTokens = opts.chunkOverlapTokens ?? DEFAULT_OVERLAP_TOKENS2;
880
+ this.trimWhitespace = opts.trimWhitespace ?? true;
881
+ this._onProgress = opts.onProgress;
882
+ if (this.chunkOverlapTokens >= this.chunkTokens) {
883
+ throw new Error(`chunkOverlapTokens (${this.chunkOverlapTokens}) must be smaller than ` + `chunkTokens (${this.chunkTokens})`);
884
+ }
885
+ }
886
+ async load() {
887
+ if (this._model)
888
+ return;
889
+ if (this._loadPromise)
890
+ return this._loadPromise;
891
+ this._loadPromise = (async () => {
892
+ const mod = await loadTransformersJs2();
893
+ let preferredDevice = this.device;
894
+ if (this.device === "auto") {
895
+ const nav = typeof navigator !== "undefined" ? navigator : null;
896
+ preferredDevice = "wasm";
897
+ if (nav?.gpu?.requestAdapter) {
898
+ try {
899
+ const adapter = await nav.gpu.requestAdapter();
900
+ if (adapter)
901
+ preferredDevice = "webgpu";
902
+ } catch {}
903
+ }
904
+ }
905
+ const tokenizer = await mod.AutoTokenizer.from_pretrained(this.model);
906
+ const model = await mod.AutoModelForTokenClassification.from_pretrained(this.model, {
907
+ device: preferredDevice,
908
+ dtype: this.dtype,
909
+ progress_callback: this._onProgress ? (raw) => this._emitProgress(raw, preferredDevice) : undefined
910
+ });
911
+ this._tokenizer = tokenizer;
912
+ this._model = model;
913
+ this._id2label = model.config.id2label;
914
+ const label2id = model.config.label2id;
915
+ this._oId = label2id?.["O"] ?? 0;
916
+ this.actualDevice = preferredDevice;
917
+ })();
918
+ return this._loadPromise;
919
+ }
920
+ async detect(text, opts) {
921
+ if (opts?.model && opts.model !== this.model) {
922
+ throw new Error(`CalibratedDetector was constructed with model='${this.model}'; ` + `per-call model override ('${opts.model}') not supported.`);
923
+ }
924
+ if (!text)
925
+ return [];
926
+ await this.load();
927
+ return this._detectChunked(text);
928
+ }
929
+ async _detectChunked(text) {
930
+ if (text.length <= 1500) {
931
+ return await this._detectWindow(text, 0);
932
+ }
933
+ const stride = 1500 - 200;
934
+ const seen = new Set;
935
+ const out = [];
936
+ for (let s = 0;s < text.length; s += stride) {
937
+ const e = Math.min(s + 1500, text.length);
938
+ const sub = text.slice(s, e);
939
+ const subSpans = await this._detectWindow(sub, s);
940
+ for (const sp of subSpans) {
941
+ const key = `${sp.start}|${sp.end}|${sp.label}`;
942
+ if (seen.has(key))
943
+ continue;
944
+ seen.add(key);
945
+ out.push(sp);
946
+ }
947
+ if (e >= text.length)
948
+ break;
949
+ }
950
+ out.sort((a, b) => a.start - b.start || a.end - b.end);
951
+ return out;
952
+ }
953
+ async _detectWindow(text, charOffset) {
954
+ const tokenizer = this._tokenizer;
955
+ const model = this._model;
956
+ const enc = await tokenizer([text], {
957
+ padding: true,
958
+ truncation: true,
959
+ max_length: this.chunkTokens + 2
960
+ });
961
+ const inputs = {};
962
+ if (enc.input_ids)
963
+ inputs["input_ids"] = enc.input_ids;
964
+ if (enc.attention_mask)
965
+ inputs["attention_mask"] = enc.attention_mask;
966
+ const out = await model(inputs);
967
+ const logits = readLogits(out.logits);
968
+ if (this.oBias !== 0) {
969
+ for (let t = 0;t < logits.length; t++) {
970
+ const row = logits[t];
971
+ if (row === undefined)
972
+ continue;
973
+ const cur = row[this._oId];
974
+ if (cur !== undefined) {
975
+ row[this._oId] = cur - this.oBias;
976
+ }
977
+ }
978
+ }
979
+ const inputIds = readInputIds(enc.input_ids);
980
+ const tokenLabels = [];
981
+ for (let t = 0;t < logits.length; t++) {
982
+ const row = logits[t];
983
+ const id = inputIds[t];
984
+ if (!row || id === undefined)
985
+ continue;
986
+ let maxIdx = 0;
987
+ let maxVal = row[0];
988
+ for (let i = 1;i < row.length; i++) {
989
+ if (row[i] > maxVal) {
990
+ maxVal = row[i];
991
+ maxIdx = i;
992
+ }
993
+ }
994
+ tokenLabels.push({ id, label: this._id2label[maxIdx] ?? "O" });
995
+ }
996
+ const spans = [];
997
+ let cursor = 0;
998
+ let groupIds = [];
999
+ let groupCat = null;
1000
+ const flush = () => {
1001
+ if (groupCat === null || groupIds.length === 0)
1002
+ return;
1003
+ const surface = normalizeDecoded(tokenizer.decode(groupIds, { skip_special_tokens: true }));
1004
+ groupIds = [];
1005
+ const cat = groupCat;
1006
+ groupCat = null;
1007
+ if (!surface)
1008
+ return;
1009
+ const idx = text.indexOf(surface, cursor);
1010
+ if (idx === -1)
1011
+ return;
1012
+ let s = idx;
1013
+ let e = idx + surface.length;
1014
+ cursor = e;
1015
+ if (this.trimWhitespace)
1016
+ [s, e] = trimSpan2(text, s, e);
1017
+ if (s >= e)
1018
+ return;
1019
+ spans.push({
1020
+ start: s + charOffset,
1021
+ end: e + charOffset,
1022
+ label: cat,
1023
+ text: text.slice(s, e),
1024
+ score: 1
1025
+ });
1026
+ };
1027
+ for (const { id, label } of tokenLabels) {
1028
+ const cat = label === "O" ? null : label.split("-", 2)[1] ?? null;
1029
+ if (cat === groupCat && cat !== null) {
1030
+ groupIds.push(id);
1031
+ } else {
1032
+ flush();
1033
+ if (cat !== null) {
1034
+ groupIds.push(id);
1035
+ groupCat = cat;
1036
+ }
1037
+ }
1038
+ }
1039
+ flush();
1040
+ return spans;
1041
+ }
1042
+ _emitProgress(raw, device) {
1043
+ if (!this._onProgress)
1044
+ return;
1045
+ const event = {
1046
+ phase: raw.status ?? "progress",
1047
+ device
1048
+ };
1049
+ if (raw.file)
1050
+ event.file = raw.file;
1051
+ if (raw.status === "progress") {
1052
+ if (raw.loaded != null)
1053
+ event.loadedBytes = raw.loaded;
1054
+ if (raw.total != null)
1055
+ event.totalBytes = raw.total;
1056
+ if (raw.progress != null) {
1057
+ const norm = raw.progress > 1 ? raw.progress / 100 : raw.progress;
1058
+ event.fraction = Math.max(0, Math.min(1, norm));
1059
+ }
1060
+ }
1061
+ this._onProgress(event);
1062
+ }
1063
+ }
1064
+ async function loadTransformersJs2() {
1065
+ try {
1066
+ return await import("@huggingface/transformers");
1067
+ } catch {
1068
+ throw new Error("CalibratedDetector requires `@huggingface/transformers`. Install with: bun add @huggingface/transformers");
1069
+ }
1070
+ }
1071
+ function readInputIds(tensor) {
1072
+ if (!tensor)
1073
+ return [];
1074
+ if (typeof tensor.tolist === "function") {
1075
+ const list = tensor.tolist();
1076
+ if (Array.isArray(list) && Array.isArray(list[0])) {
1077
+ return list[0].map((x) => Number(x));
1078
+ }
1079
+ if (Array.isArray(list))
1080
+ return list.map((x) => Number(x));
1081
+ }
1082
+ const dims = tensor.dims;
1083
+ const seq = dims[dims.length - 1] ?? 0;
1084
+ const data = tensor.data;
1085
+ const out = [];
1086
+ for (let i = 0;i < seq; i++)
1087
+ out.push(Number(data[i]));
1088
+ return out;
1089
+ }
1090
+ function normalizeDecoded(s) {
1091
+ return s.replace(/##/g, "").trim();
1092
+ }
1093
+ function readLogits(tensor) {
1094
+ if (typeof tensor.tolist === "function") {
1095
+ const list = tensor.tolist();
1096
+ if (Array.isArray(list) && Array.isArray(list[0]) && Array.isArray(list[0][0])) {
1097
+ return list[0];
1098
+ }
1099
+ if (Array.isArray(list) && Array.isArray(list[0])) {
1100
+ return list;
1101
+ }
1102
+ }
1103
+ const dims = tensor.dims;
1104
+ const seq = dims[dims.length - 2] ?? 0;
1105
+ const cls = dims[dims.length - 1] ?? 0;
1106
+ const data = tensor.data;
1107
+ const out = [];
1108
+ for (let t = 0;t < seq; t++) {
1109
+ const row = [];
1110
+ for (let c = 0;c < cls; c++) {
1111
+ row.push(Number(data[t * cls + c]));
1112
+ }
1113
+ out.push(row);
1114
+ }
1115
+ return out;
1116
+ }
845
1117
  // src/detectors/index.ts
846
1118
  function defaultDetector() {
847
1119
  const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
848
- return hasKey ? new RemoteDetector : new LocalDetector;
1120
+ return hasKey ? new RemoteDetector : new CalibratedDetector;
849
1121
  }
850
1122
  // src/plain.ts
851
1123
  function resolve(session, detector) {
@@ -896,4 +1168,4 @@ async function* deanonymizeStream(chunks, session) {
896
1168
  }
897
1169
 
898
1170
  // src/index.ts
899
- var VERSION = "0.1.4";
1171
+ var VERSION = "0.1.5";
@@ -751,10 +751,281 @@ class CompositeDetector {
751
751
  return out;
752
752
  }
753
753
  }
754
+ // src/detectors/calibrated.ts
755
+ var DEFAULT_MODEL3 = "joelbarmettler/gheim-ch-560m";
756
+ var DEFAULT_CHUNK_TOKENS2 = 510;
757
+ var DEFAULT_OVERLAP_TOKENS2 = 32;
758
+ function trimSpan2(text, start, end) {
759
+ while (start < end && /\s/.test(text[start] ?? ""))
760
+ start++;
761
+ while (end > start && /\s/.test(text[end - 1] ?? ""))
762
+ end--;
763
+ return [start, end];
764
+ }
765
+
766
+ class CalibratedDetector {
767
+ model;
768
+ oBias;
769
+ device;
770
+ dtype;
771
+ chunkTokens;
772
+ chunkOverlapTokens;
773
+ trimWhitespace;
774
+ actualDevice = null;
775
+ _model = null;
776
+ _tokenizer = null;
777
+ _id2label = {};
778
+ _oId = 0;
779
+ _loadPromise = null;
780
+ _onProgress;
781
+ constructor(opts = {}) {
782
+ this.model = opts.model ?? DEFAULT_MODEL3;
783
+ this.oBias = opts.oBias ?? 0.5;
784
+ this.device = opts.device ?? "auto";
785
+ this.dtype = opts.dtype ?? "fp32";
786
+ this.chunkTokens = opts.chunkTokens ?? DEFAULT_CHUNK_TOKENS2;
787
+ this.chunkOverlapTokens = opts.chunkOverlapTokens ?? DEFAULT_OVERLAP_TOKENS2;
788
+ this.trimWhitespace = opts.trimWhitespace ?? true;
789
+ this._onProgress = opts.onProgress;
790
+ if (this.chunkOverlapTokens >= this.chunkTokens) {
791
+ throw new Error(`chunkOverlapTokens (${this.chunkOverlapTokens}) must be smaller than ` + `chunkTokens (${this.chunkTokens})`);
792
+ }
793
+ }
794
+ async load() {
795
+ if (this._model)
796
+ return;
797
+ if (this._loadPromise)
798
+ return this._loadPromise;
799
+ this._loadPromise = (async () => {
800
+ const mod = await loadTransformersJs2();
801
+ let preferredDevice = this.device;
802
+ if (this.device === "auto") {
803
+ const nav = typeof navigator !== "undefined" ? navigator : null;
804
+ preferredDevice = "wasm";
805
+ if (nav?.gpu?.requestAdapter) {
806
+ try {
807
+ const adapter = await nav.gpu.requestAdapter();
808
+ if (adapter)
809
+ preferredDevice = "webgpu";
810
+ } catch {}
811
+ }
812
+ }
813
+ const tokenizer = await mod.AutoTokenizer.from_pretrained(this.model);
814
+ const model = await mod.AutoModelForTokenClassification.from_pretrained(this.model, {
815
+ device: preferredDevice,
816
+ dtype: this.dtype,
817
+ progress_callback: this._onProgress ? (raw) => this._emitProgress(raw, preferredDevice) : undefined
818
+ });
819
+ this._tokenizer = tokenizer;
820
+ this._model = model;
821
+ this._id2label = model.config.id2label;
822
+ const label2id = model.config.label2id;
823
+ this._oId = label2id?.["O"] ?? 0;
824
+ this.actualDevice = preferredDevice;
825
+ })();
826
+ return this._loadPromise;
827
+ }
828
+ async detect(text, opts) {
829
+ if (opts?.model && opts.model !== this.model) {
830
+ throw new Error(`CalibratedDetector was constructed with model='${this.model}'; ` + `per-call model override ('${opts.model}') not supported.`);
831
+ }
832
+ if (!text)
833
+ return [];
834
+ await this.load();
835
+ return this._detectChunked(text);
836
+ }
837
+ async _detectChunked(text) {
838
+ if (text.length <= 1500) {
839
+ return await this._detectWindow(text, 0);
840
+ }
841
+ const stride = 1500 - 200;
842
+ const seen = new Set;
843
+ const out = [];
844
+ for (let s = 0;s < text.length; s += stride) {
845
+ const e = Math.min(s + 1500, text.length);
846
+ const sub = text.slice(s, e);
847
+ const subSpans = await this._detectWindow(sub, s);
848
+ for (const sp of subSpans) {
849
+ const key = `${sp.start}|${sp.end}|${sp.label}`;
850
+ if (seen.has(key))
851
+ continue;
852
+ seen.add(key);
853
+ out.push(sp);
854
+ }
855
+ if (e >= text.length)
856
+ break;
857
+ }
858
+ out.sort((a, b) => a.start - b.start || a.end - b.end);
859
+ return out;
860
+ }
861
+ async _detectWindow(text, charOffset) {
862
+ const tokenizer = this._tokenizer;
863
+ const model = this._model;
864
+ const enc = await tokenizer([text], {
865
+ padding: true,
866
+ truncation: true,
867
+ max_length: this.chunkTokens + 2
868
+ });
869
+ const inputs = {};
870
+ if (enc.input_ids)
871
+ inputs["input_ids"] = enc.input_ids;
872
+ if (enc.attention_mask)
873
+ inputs["attention_mask"] = enc.attention_mask;
874
+ const out = await model(inputs);
875
+ const logits = readLogits(out.logits);
876
+ if (this.oBias !== 0) {
877
+ for (let t = 0;t < logits.length; t++) {
878
+ const row = logits[t];
879
+ if (row === undefined)
880
+ continue;
881
+ const cur = row[this._oId];
882
+ if (cur !== undefined) {
883
+ row[this._oId] = cur - this.oBias;
884
+ }
885
+ }
886
+ }
887
+ const inputIds = readInputIds(enc.input_ids);
888
+ const tokenLabels = [];
889
+ for (let t = 0;t < logits.length; t++) {
890
+ const row = logits[t];
891
+ const id = inputIds[t];
892
+ if (!row || id === undefined)
893
+ continue;
894
+ let maxIdx = 0;
895
+ let maxVal = row[0];
896
+ for (let i = 1;i < row.length; i++) {
897
+ if (row[i] > maxVal) {
898
+ maxVal = row[i];
899
+ maxIdx = i;
900
+ }
901
+ }
902
+ tokenLabels.push({ id, label: this._id2label[maxIdx] ?? "O" });
903
+ }
904
+ const spans = [];
905
+ let cursor = 0;
906
+ let groupIds = [];
907
+ let groupCat = null;
908
+ const flush = () => {
909
+ if (groupCat === null || groupIds.length === 0)
910
+ return;
911
+ const surface = normalizeDecoded(tokenizer.decode(groupIds, { skip_special_tokens: true }));
912
+ groupIds = [];
913
+ const cat = groupCat;
914
+ groupCat = null;
915
+ if (!surface)
916
+ return;
917
+ const idx = text.indexOf(surface, cursor);
918
+ if (idx === -1)
919
+ return;
920
+ let s = idx;
921
+ let e = idx + surface.length;
922
+ cursor = e;
923
+ if (this.trimWhitespace)
924
+ [s, e] = trimSpan2(text, s, e);
925
+ if (s >= e)
926
+ return;
927
+ spans.push({
928
+ start: s + charOffset,
929
+ end: e + charOffset,
930
+ label: cat,
931
+ text: text.slice(s, e),
932
+ score: 1
933
+ });
934
+ };
935
+ for (const { id, label } of tokenLabels) {
936
+ const cat = label === "O" ? null : label.split("-", 2)[1] ?? null;
937
+ if (cat === groupCat && cat !== null) {
938
+ groupIds.push(id);
939
+ } else {
940
+ flush();
941
+ if (cat !== null) {
942
+ groupIds.push(id);
943
+ groupCat = cat;
944
+ }
945
+ }
946
+ }
947
+ flush();
948
+ return spans;
949
+ }
950
+ _emitProgress(raw, device) {
951
+ if (!this._onProgress)
952
+ return;
953
+ const event = {
954
+ phase: raw.status ?? "progress",
955
+ device
956
+ };
957
+ if (raw.file)
958
+ event.file = raw.file;
959
+ if (raw.status === "progress") {
960
+ if (raw.loaded != null)
961
+ event.loadedBytes = raw.loaded;
962
+ if (raw.total != null)
963
+ event.totalBytes = raw.total;
964
+ if (raw.progress != null) {
965
+ const norm = raw.progress > 1 ? raw.progress / 100 : raw.progress;
966
+ event.fraction = Math.max(0, Math.min(1, norm));
967
+ }
968
+ }
969
+ this._onProgress(event);
970
+ }
971
+ }
972
+ async function loadTransformersJs2() {
973
+ try {
974
+ return await import("@huggingface/transformers");
975
+ } catch {
976
+ throw new Error("CalibratedDetector requires `@huggingface/transformers`. Install with: bun add @huggingface/transformers");
977
+ }
978
+ }
979
+ function readInputIds(tensor) {
980
+ if (!tensor)
981
+ return [];
982
+ if (typeof tensor.tolist === "function") {
983
+ const list = tensor.tolist();
984
+ if (Array.isArray(list) && Array.isArray(list[0])) {
985
+ return list[0].map((x) => Number(x));
986
+ }
987
+ if (Array.isArray(list))
988
+ return list.map((x) => Number(x));
989
+ }
990
+ const dims = tensor.dims;
991
+ const seq = dims[dims.length - 1] ?? 0;
992
+ const data = tensor.data;
993
+ const out = [];
994
+ for (let i = 0;i < seq; i++)
995
+ out.push(Number(data[i]));
996
+ return out;
997
+ }
998
+ function normalizeDecoded(s) {
999
+ return s.replace(/##/g, "").trim();
1000
+ }
1001
+ function readLogits(tensor) {
1002
+ if (typeof tensor.tolist === "function") {
1003
+ const list = tensor.tolist();
1004
+ if (Array.isArray(list) && Array.isArray(list[0]) && Array.isArray(list[0][0])) {
1005
+ return list[0];
1006
+ }
1007
+ if (Array.isArray(list) && Array.isArray(list[0])) {
1008
+ return list;
1009
+ }
1010
+ }
1011
+ const dims = tensor.dims;
1012
+ const seq = dims[dims.length - 2] ?? 0;
1013
+ const cls = dims[dims.length - 1] ?? 0;
1014
+ const data = tensor.data;
1015
+ const out = [];
1016
+ for (let t = 0;t < seq; t++) {
1017
+ const row = [];
1018
+ for (let c = 0;c < cls; c++) {
1019
+ row.push(Number(data[t * cls + c]));
1020
+ }
1021
+ out.push(row);
1022
+ }
1023
+ return out;
1024
+ }
754
1025
  // src/detectors/index.ts
755
1026
  function defaultDetector() {
756
1027
  const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
757
- return hasKey ? new RemoteDetector : new LocalDetector;
1028
+ return hasKey ? new RemoteDetector : new CalibratedDetector;
758
1029
  }
759
1030
 
760
1031
  // src/core/session.ts
package/dist/esm/index.js CHANGED
@@ -786,10 +786,281 @@ class CompositeDetector {
786
786
  return out;
787
787
  }
788
788
  }
789
+ // src/detectors/calibrated.ts
790
+ var DEFAULT_MODEL3 = "joelbarmettler/gheim-ch-560m";
791
+ var DEFAULT_CHUNK_TOKENS2 = 510;
792
+ var DEFAULT_OVERLAP_TOKENS2 = 32;
793
+ function trimSpan2(text, start, end) {
794
+ while (start < end && /\s/.test(text[start] ?? ""))
795
+ start++;
796
+ while (end > start && /\s/.test(text[end - 1] ?? ""))
797
+ end--;
798
+ return [start, end];
799
+ }
800
+
801
+ class CalibratedDetector {
802
+ model;
803
+ oBias;
804
+ device;
805
+ dtype;
806
+ chunkTokens;
807
+ chunkOverlapTokens;
808
+ trimWhitespace;
809
+ actualDevice = null;
810
+ _model = null;
811
+ _tokenizer = null;
812
+ _id2label = {};
813
+ _oId = 0;
814
+ _loadPromise = null;
815
+ _onProgress;
816
+ constructor(opts = {}) {
817
+ this.model = opts.model ?? DEFAULT_MODEL3;
818
+ this.oBias = opts.oBias ?? 0.5;
819
+ this.device = opts.device ?? "auto";
820
+ this.dtype = opts.dtype ?? "fp32";
821
+ this.chunkTokens = opts.chunkTokens ?? DEFAULT_CHUNK_TOKENS2;
822
+ this.chunkOverlapTokens = opts.chunkOverlapTokens ?? DEFAULT_OVERLAP_TOKENS2;
823
+ this.trimWhitespace = opts.trimWhitespace ?? true;
824
+ this._onProgress = opts.onProgress;
825
+ if (this.chunkOverlapTokens >= this.chunkTokens) {
826
+ throw new Error(`chunkOverlapTokens (${this.chunkOverlapTokens}) must be smaller than ` + `chunkTokens (${this.chunkTokens})`);
827
+ }
828
+ }
829
+ async load() {
830
+ if (this._model)
831
+ return;
832
+ if (this._loadPromise)
833
+ return this._loadPromise;
834
+ this._loadPromise = (async () => {
835
+ const mod = await loadTransformersJs2();
836
+ let preferredDevice = this.device;
837
+ if (this.device === "auto") {
838
+ const nav = typeof navigator !== "undefined" ? navigator : null;
839
+ preferredDevice = "wasm";
840
+ if (nav?.gpu?.requestAdapter) {
841
+ try {
842
+ const adapter = await nav.gpu.requestAdapter();
843
+ if (adapter)
844
+ preferredDevice = "webgpu";
845
+ } catch {}
846
+ }
847
+ }
848
+ const tokenizer = await mod.AutoTokenizer.from_pretrained(this.model);
849
+ const model = await mod.AutoModelForTokenClassification.from_pretrained(this.model, {
850
+ device: preferredDevice,
851
+ dtype: this.dtype,
852
+ progress_callback: this._onProgress ? (raw) => this._emitProgress(raw, preferredDevice) : undefined
853
+ });
854
+ this._tokenizer = tokenizer;
855
+ this._model = model;
856
+ this._id2label = model.config.id2label;
857
+ const label2id = model.config.label2id;
858
+ this._oId = label2id?.["O"] ?? 0;
859
+ this.actualDevice = preferredDevice;
860
+ })();
861
+ return this._loadPromise;
862
+ }
863
+ async detect(text, opts) {
864
+ if (opts?.model && opts.model !== this.model) {
865
+ throw new Error(`CalibratedDetector was constructed with model='${this.model}'; ` + `per-call model override ('${opts.model}') not supported.`);
866
+ }
867
+ if (!text)
868
+ return [];
869
+ await this.load();
870
+ return this._detectChunked(text);
871
+ }
872
+ async _detectChunked(text) {
873
+ if (text.length <= 1500) {
874
+ return await this._detectWindow(text, 0);
875
+ }
876
+ const stride = 1500 - 200;
877
+ const seen = new Set;
878
+ const out = [];
879
+ for (let s = 0;s < text.length; s += stride) {
880
+ const e = Math.min(s + 1500, text.length);
881
+ const sub = text.slice(s, e);
882
+ const subSpans = await this._detectWindow(sub, s);
883
+ for (const sp of subSpans) {
884
+ const key = `${sp.start}|${sp.end}|${sp.label}`;
885
+ if (seen.has(key))
886
+ continue;
887
+ seen.add(key);
888
+ out.push(sp);
889
+ }
890
+ if (e >= text.length)
891
+ break;
892
+ }
893
+ out.sort((a, b) => a.start - b.start || a.end - b.end);
894
+ return out;
895
+ }
896
+ async _detectWindow(text, charOffset) {
897
+ const tokenizer = this._tokenizer;
898
+ const model = this._model;
899
+ const enc = await tokenizer([text], {
900
+ padding: true,
901
+ truncation: true,
902
+ max_length: this.chunkTokens + 2
903
+ });
904
+ const inputs = {};
905
+ if (enc.input_ids)
906
+ inputs["input_ids"] = enc.input_ids;
907
+ if (enc.attention_mask)
908
+ inputs["attention_mask"] = enc.attention_mask;
909
+ const out = await model(inputs);
910
+ const logits = readLogits(out.logits);
911
+ if (this.oBias !== 0) {
912
+ for (let t = 0;t < logits.length; t++) {
913
+ const row = logits[t];
914
+ if (row === undefined)
915
+ continue;
916
+ const cur = row[this._oId];
917
+ if (cur !== undefined) {
918
+ row[this._oId] = cur - this.oBias;
919
+ }
920
+ }
921
+ }
922
+ const inputIds = readInputIds(enc.input_ids);
923
+ const tokenLabels = [];
924
+ for (let t = 0;t < logits.length; t++) {
925
+ const row = logits[t];
926
+ const id = inputIds[t];
927
+ if (!row || id === undefined)
928
+ continue;
929
+ let maxIdx = 0;
930
+ let maxVal = row[0];
931
+ for (let i = 1;i < row.length; i++) {
932
+ if (row[i] > maxVal) {
933
+ maxVal = row[i];
934
+ maxIdx = i;
935
+ }
936
+ }
937
+ tokenLabels.push({ id, label: this._id2label[maxIdx] ?? "O" });
938
+ }
939
+ const spans = [];
940
+ let cursor = 0;
941
+ let groupIds = [];
942
+ let groupCat = null;
943
+ const flush = () => {
944
+ if (groupCat === null || groupIds.length === 0)
945
+ return;
946
+ const surface = normalizeDecoded(tokenizer.decode(groupIds, { skip_special_tokens: true }));
947
+ groupIds = [];
948
+ const cat = groupCat;
949
+ groupCat = null;
950
+ if (!surface)
951
+ return;
952
+ const idx = text.indexOf(surface, cursor);
953
+ if (idx === -1)
954
+ return;
955
+ let s = idx;
956
+ let e = idx + surface.length;
957
+ cursor = e;
958
+ if (this.trimWhitespace)
959
+ [s, e] = trimSpan2(text, s, e);
960
+ if (s >= e)
961
+ return;
962
+ spans.push({
963
+ start: s + charOffset,
964
+ end: e + charOffset,
965
+ label: cat,
966
+ text: text.slice(s, e),
967
+ score: 1
968
+ });
969
+ };
970
+ for (const { id, label } of tokenLabels) {
971
+ const cat = label === "O" ? null : label.split("-", 2)[1] ?? null;
972
+ if (cat === groupCat && cat !== null) {
973
+ groupIds.push(id);
974
+ } else {
975
+ flush();
976
+ if (cat !== null) {
977
+ groupIds.push(id);
978
+ groupCat = cat;
979
+ }
980
+ }
981
+ }
982
+ flush();
983
+ return spans;
984
+ }
985
+ _emitProgress(raw, device) {
986
+ if (!this._onProgress)
987
+ return;
988
+ const event = {
989
+ phase: raw.status ?? "progress",
990
+ device
991
+ };
992
+ if (raw.file)
993
+ event.file = raw.file;
994
+ if (raw.status === "progress") {
995
+ if (raw.loaded != null)
996
+ event.loadedBytes = raw.loaded;
997
+ if (raw.total != null)
998
+ event.totalBytes = raw.total;
999
+ if (raw.progress != null) {
1000
+ const norm = raw.progress > 1 ? raw.progress / 100 : raw.progress;
1001
+ event.fraction = Math.max(0, Math.min(1, norm));
1002
+ }
1003
+ }
1004
+ this._onProgress(event);
1005
+ }
1006
+ }
1007
+ async function loadTransformersJs2() {
1008
+ try {
1009
+ return await import("@huggingface/transformers");
1010
+ } catch {
1011
+ throw new Error("CalibratedDetector requires `@huggingface/transformers`. Install with: bun add @huggingface/transformers");
1012
+ }
1013
+ }
1014
+ function readInputIds(tensor) {
1015
+ if (!tensor)
1016
+ return [];
1017
+ if (typeof tensor.tolist === "function") {
1018
+ const list = tensor.tolist();
1019
+ if (Array.isArray(list) && Array.isArray(list[0])) {
1020
+ return list[0].map((x) => Number(x));
1021
+ }
1022
+ if (Array.isArray(list))
1023
+ return list.map((x) => Number(x));
1024
+ }
1025
+ const dims = tensor.dims;
1026
+ const seq = dims[dims.length - 1] ?? 0;
1027
+ const data = tensor.data;
1028
+ const out = [];
1029
+ for (let i = 0;i < seq; i++)
1030
+ out.push(Number(data[i]));
1031
+ return out;
1032
+ }
1033
+ function normalizeDecoded(s) {
1034
+ return s.replace(/##/g, "").trim();
1035
+ }
1036
+ function readLogits(tensor) {
1037
+ if (typeof tensor.tolist === "function") {
1038
+ const list = tensor.tolist();
1039
+ if (Array.isArray(list) && Array.isArray(list[0]) && Array.isArray(list[0][0])) {
1040
+ return list[0];
1041
+ }
1042
+ if (Array.isArray(list) && Array.isArray(list[0])) {
1043
+ return list;
1044
+ }
1045
+ }
1046
+ const dims = tensor.dims;
1047
+ const seq = dims[dims.length - 2] ?? 0;
1048
+ const cls = dims[dims.length - 1] ?? 0;
1049
+ const data = tensor.data;
1050
+ const out = [];
1051
+ for (let t = 0;t < seq; t++) {
1052
+ const row = [];
1053
+ for (let c = 0;c < cls; c++) {
1054
+ row.push(Number(data[t * cls + c]));
1055
+ }
1056
+ out.push(row);
1057
+ }
1058
+ return out;
1059
+ }
789
1060
  // src/detectors/index.ts
790
1061
  function defaultDetector() {
791
1062
  const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
792
- return hasKey ? new RemoteDetector : new LocalDetector;
1063
+ return hasKey ? new RemoteDetector : new CalibratedDetector;
793
1064
  }
794
1065
  // src/plain.ts
795
1066
  function resolve(session, detector) {
@@ -840,7 +1111,7 @@ async function* deanonymizeStream(chunks, session) {
840
1111
  }
841
1112
 
842
1113
  // src/index.ts
843
- var VERSION = "0.1.4";
1114
+ var VERSION = "0.1.5";
844
1115
  export {
845
1116
  renderSentinel,
846
1117
  parseSentinel,
@@ -861,5 +1132,6 @@ export {
861
1132
  LocalDetector,
862
1133
  LABEL_TO_TAG,
863
1134
  DEFAULT_BASE_URL,
864
- CompositeDetector
1135
+ CompositeDetector,
1136
+ CalibratedDetector
865
1137
  };
@@ -708,10 +708,281 @@ class CompositeDetector {
708
708
  return out;
709
709
  }
710
710
  }
711
+ // src/detectors/calibrated.ts
712
+ var DEFAULT_MODEL3 = "joelbarmettler/gheim-ch-560m";
713
+ var DEFAULT_CHUNK_TOKENS2 = 510;
714
+ var DEFAULT_OVERLAP_TOKENS2 = 32;
715
+ function trimSpan2(text, start, end) {
716
+ while (start < end && /\s/.test(text[start] ?? ""))
717
+ start++;
718
+ while (end > start && /\s/.test(text[end - 1] ?? ""))
719
+ end--;
720
+ return [start, end];
721
+ }
722
+
723
+ class CalibratedDetector {
724
+ model;
725
+ oBias;
726
+ device;
727
+ dtype;
728
+ chunkTokens;
729
+ chunkOverlapTokens;
730
+ trimWhitespace;
731
+ actualDevice = null;
732
+ _model = null;
733
+ _tokenizer = null;
734
+ _id2label = {};
735
+ _oId = 0;
736
+ _loadPromise = null;
737
+ _onProgress;
738
+ constructor(opts = {}) {
739
+ this.model = opts.model ?? DEFAULT_MODEL3;
740
+ this.oBias = opts.oBias ?? 0.5;
741
+ this.device = opts.device ?? "auto";
742
+ this.dtype = opts.dtype ?? "fp32";
743
+ this.chunkTokens = opts.chunkTokens ?? DEFAULT_CHUNK_TOKENS2;
744
+ this.chunkOverlapTokens = opts.chunkOverlapTokens ?? DEFAULT_OVERLAP_TOKENS2;
745
+ this.trimWhitespace = opts.trimWhitespace ?? true;
746
+ this._onProgress = opts.onProgress;
747
+ if (this.chunkOverlapTokens >= this.chunkTokens) {
748
+ throw new Error(`chunkOverlapTokens (${this.chunkOverlapTokens}) must be smaller than ` + `chunkTokens (${this.chunkTokens})`);
749
+ }
750
+ }
751
+ async load() {
752
+ if (this._model)
753
+ return;
754
+ if (this._loadPromise)
755
+ return this._loadPromise;
756
+ this._loadPromise = (async () => {
757
+ const mod = await loadTransformersJs2();
758
+ let preferredDevice = this.device;
759
+ if (this.device === "auto") {
760
+ const nav = typeof navigator !== "undefined" ? navigator : null;
761
+ preferredDevice = "wasm";
762
+ if (nav?.gpu?.requestAdapter) {
763
+ try {
764
+ const adapter = await nav.gpu.requestAdapter();
765
+ if (adapter)
766
+ preferredDevice = "webgpu";
767
+ } catch {}
768
+ }
769
+ }
770
+ const tokenizer = await mod.AutoTokenizer.from_pretrained(this.model);
771
+ const model = await mod.AutoModelForTokenClassification.from_pretrained(this.model, {
772
+ device: preferredDevice,
773
+ dtype: this.dtype,
774
+ progress_callback: this._onProgress ? (raw) => this._emitProgress(raw, preferredDevice) : undefined
775
+ });
776
+ this._tokenizer = tokenizer;
777
+ this._model = model;
778
+ this._id2label = model.config.id2label;
779
+ const label2id = model.config.label2id;
780
+ this._oId = label2id?.["O"] ?? 0;
781
+ this.actualDevice = preferredDevice;
782
+ })();
783
+ return this._loadPromise;
784
+ }
785
+ async detect(text, opts) {
786
+ if (opts?.model && opts.model !== this.model) {
787
+ throw new Error(`CalibratedDetector was constructed with model='${this.model}'; ` + `per-call model override ('${opts.model}') not supported.`);
788
+ }
789
+ if (!text)
790
+ return [];
791
+ await this.load();
792
+ return this._detectChunked(text);
793
+ }
794
+ async _detectChunked(text) {
795
+ if (text.length <= 1500) {
796
+ return await this._detectWindow(text, 0);
797
+ }
798
+ const stride = 1500 - 200;
799
+ const seen = new Set;
800
+ const out = [];
801
+ for (let s = 0;s < text.length; s += stride) {
802
+ const e = Math.min(s + 1500, text.length);
803
+ const sub = text.slice(s, e);
804
+ const subSpans = await this._detectWindow(sub, s);
805
+ for (const sp of subSpans) {
806
+ const key = `${sp.start}|${sp.end}|${sp.label}`;
807
+ if (seen.has(key))
808
+ continue;
809
+ seen.add(key);
810
+ out.push(sp);
811
+ }
812
+ if (e >= text.length)
813
+ break;
814
+ }
815
+ out.sort((a, b) => a.start - b.start || a.end - b.end);
816
+ return out;
817
+ }
818
+ async _detectWindow(text, charOffset) {
819
+ const tokenizer = this._tokenizer;
820
+ const model = this._model;
821
+ const enc = await tokenizer([text], {
822
+ padding: true,
823
+ truncation: true,
824
+ max_length: this.chunkTokens + 2
825
+ });
826
+ const inputs = {};
827
+ if (enc.input_ids)
828
+ inputs["input_ids"] = enc.input_ids;
829
+ if (enc.attention_mask)
830
+ inputs["attention_mask"] = enc.attention_mask;
831
+ const out = await model(inputs);
832
+ const logits = readLogits(out.logits);
833
+ if (this.oBias !== 0) {
834
+ for (let t = 0;t < logits.length; t++) {
835
+ const row = logits[t];
836
+ if (row === undefined)
837
+ continue;
838
+ const cur = row[this._oId];
839
+ if (cur !== undefined) {
840
+ row[this._oId] = cur - this.oBias;
841
+ }
842
+ }
843
+ }
844
+ const inputIds = readInputIds(enc.input_ids);
845
+ const tokenLabels = [];
846
+ for (let t = 0;t < logits.length; t++) {
847
+ const row = logits[t];
848
+ const id = inputIds[t];
849
+ if (!row || id === undefined)
850
+ continue;
851
+ let maxIdx = 0;
852
+ let maxVal = row[0];
853
+ for (let i = 1;i < row.length; i++) {
854
+ if (row[i] > maxVal) {
855
+ maxVal = row[i];
856
+ maxIdx = i;
857
+ }
858
+ }
859
+ tokenLabels.push({ id, label: this._id2label[maxIdx] ?? "O" });
860
+ }
861
+ const spans = [];
862
+ let cursor = 0;
863
+ let groupIds = [];
864
+ let groupCat = null;
865
+ const flush = () => {
866
+ if (groupCat === null || groupIds.length === 0)
867
+ return;
868
+ const surface = normalizeDecoded(tokenizer.decode(groupIds, { skip_special_tokens: true }));
869
+ groupIds = [];
870
+ const cat = groupCat;
871
+ groupCat = null;
872
+ if (!surface)
873
+ return;
874
+ const idx = text.indexOf(surface, cursor);
875
+ if (idx === -1)
876
+ return;
877
+ let s = idx;
878
+ let e = idx + surface.length;
879
+ cursor = e;
880
+ if (this.trimWhitespace)
881
+ [s, e] = trimSpan2(text, s, e);
882
+ if (s >= e)
883
+ return;
884
+ spans.push({
885
+ start: s + charOffset,
886
+ end: e + charOffset,
887
+ label: cat,
888
+ text: text.slice(s, e),
889
+ score: 1
890
+ });
891
+ };
892
+ for (const { id, label } of tokenLabels) {
893
+ const cat = label === "O" ? null : label.split("-", 2)[1] ?? null;
894
+ if (cat === groupCat && cat !== null) {
895
+ groupIds.push(id);
896
+ } else {
897
+ flush();
898
+ if (cat !== null) {
899
+ groupIds.push(id);
900
+ groupCat = cat;
901
+ }
902
+ }
903
+ }
904
+ flush();
905
+ return spans;
906
+ }
907
+ _emitProgress(raw, device) {
908
+ if (!this._onProgress)
909
+ return;
910
+ const event = {
911
+ phase: raw.status ?? "progress",
912
+ device
913
+ };
914
+ if (raw.file)
915
+ event.file = raw.file;
916
+ if (raw.status === "progress") {
917
+ if (raw.loaded != null)
918
+ event.loadedBytes = raw.loaded;
919
+ if (raw.total != null)
920
+ event.totalBytes = raw.total;
921
+ if (raw.progress != null) {
922
+ const norm = raw.progress > 1 ? raw.progress / 100 : raw.progress;
923
+ event.fraction = Math.max(0, Math.min(1, norm));
924
+ }
925
+ }
926
+ this._onProgress(event);
927
+ }
928
+ }
929
+ async function loadTransformersJs2() {
930
+ try {
931
+ return await import("@huggingface/transformers");
932
+ } catch {
933
+ throw new Error("CalibratedDetector requires `@huggingface/transformers`. Install with: bun add @huggingface/transformers");
934
+ }
935
+ }
936
+ function readInputIds(tensor) {
937
+ if (!tensor)
938
+ return [];
939
+ if (typeof tensor.tolist === "function") {
940
+ const list = tensor.tolist();
941
+ if (Array.isArray(list) && Array.isArray(list[0])) {
942
+ return list[0].map((x) => Number(x));
943
+ }
944
+ if (Array.isArray(list))
945
+ return list.map((x) => Number(x));
946
+ }
947
+ const dims = tensor.dims;
948
+ const seq = dims[dims.length - 1] ?? 0;
949
+ const data = tensor.data;
950
+ const out = [];
951
+ for (let i = 0;i < seq; i++)
952
+ out.push(Number(data[i]));
953
+ return out;
954
+ }
955
+ function normalizeDecoded(s) {
956
+ return s.replace(/##/g, "").trim();
957
+ }
958
+ function readLogits(tensor) {
959
+ if (typeof tensor.tolist === "function") {
960
+ const list = tensor.tolist();
961
+ if (Array.isArray(list) && Array.isArray(list[0]) && Array.isArray(list[0][0])) {
962
+ return list[0];
963
+ }
964
+ if (Array.isArray(list) && Array.isArray(list[0])) {
965
+ return list;
966
+ }
967
+ }
968
+ const dims = tensor.dims;
969
+ const seq = dims[dims.length - 2] ?? 0;
970
+ const cls = dims[dims.length - 1] ?? 0;
971
+ const data = tensor.data;
972
+ const out = [];
973
+ for (let t = 0;t < seq; t++) {
974
+ const row = [];
975
+ for (let c = 0;c < cls; c++) {
976
+ row.push(Number(data[t * cls + c]));
977
+ }
978
+ out.push(row);
979
+ }
980
+ return out;
981
+ }
711
982
  // src/detectors/index.ts
712
983
  function defaultDetector() {
713
984
  const hasKey = typeof process !== "undefined" && Boolean(process.env?.GHEIM_API_KEY);
714
- return hasKey ? new RemoteDetector : new LocalDetector;
985
+ return hasKey ? new RemoteDetector : new CalibratedDetector;
715
986
  }
716
987
 
717
988
  // src/core/session.ts
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Calibrated detector — fixes the v1 model's "single-category mode"
3
+ * bug at inference time by subtracting a constant `oBias` from the O
4
+ * class logit before argmax.
5
+ *
6
+ * Empirical justification (see eval/calibration_sweep.json): on a
7
+ * 237-case multi-entity pathology suite the v1 model emits literally
8
+ * zero non-O tokens for "Müller" / "März" when an IBAN follows them —
9
+ * but the per-token softmax shows person at 47% vs O at 51%. A small
10
+ * additive bias on O flips those marginal cases without inducing false
11
+ * positives on PII-free text.
12
+ *
13
+ * Sweet spot: oBias=0.5:
14
+ * - pathology full-coverage 81% → 94.5% (+13.5pp)
15
+ * - test_v1 strict-F1 0.872 → 0.868 (−0.4pp)
16
+ * - ~0.05 extra FP per chunk on real text
17
+ *
18
+ * `defaultDetector()` returns this with oBias=0.5, so users get the
19
+ * better behavior automatically.
20
+ */
21
+ import type { Detector, Span } from "./base.ts";
22
+ import type { LocalDetectorLoadEvent } from "./local.ts";
23
+ /** Constructor options for {@link CalibratedDetector}. */
24
+ export interface CalibratedDetectorOptions {
25
+ /** HuggingFace model id (must be a token-classification head). */
26
+ model?: string;
27
+ /**
28
+ * Constant subtracted from the O-class logit before argmax. Default
29
+ * `0.5` is the Pareto-clean point per the full-scale bias sweep
30
+ * (eval/calibration_sweep.json):
31
+ *
32
+ * - oBias=0.0 uncalibrated (matches the raw model)
33
+ * - oBias=0.5 recovers ~95% of multi-entity pathology cases for
34
+ * ~0.4pp test_v1 strict-F1 cost
35
+ * - oBias=1.0 more aggressive: 96.6% pathology, 1.1pp test_v1 cost
36
+ * - oBias=1.5+ trades too much precision; not recommended
37
+ */
38
+ oBias?: number;
39
+ /** Backend selector. See {@link LocalDetectorOptions.device}. */
40
+ device?: string;
41
+ /** Quantization dtype: "fp32" | "fp16" | "q8" | "q4" | "auto". */
42
+ dtype?: string;
43
+ /** Sliding-window size in model tokens (default 510). */
44
+ chunkTokens?: number;
45
+ /** Overlap between consecutive windows (default 32). */
46
+ chunkOverlapTokens?: number;
47
+ /** Strip leading/trailing whitespace from each emitted span. Default true. */
48
+ trimWhitespace?: boolean;
49
+ /** Optional progress callback during model load (mirrors LocalDetector). */
50
+ onProgress?: (event: LocalDetectorLoadEvent) => void;
51
+ }
52
+ /** CalibratedDetector — local model + O-logit bias. */
53
+ export declare class CalibratedDetector implements Detector {
54
+ readonly model: string;
55
+ readonly oBias: number;
56
+ readonly device: string;
57
+ readonly dtype: string;
58
+ readonly chunkTokens: number;
59
+ readonly chunkOverlapTokens: number;
60
+ readonly trimWhitespace: boolean;
61
+ /** Backend actually used after `load()` resolves; `null` before. */
62
+ actualDevice: string | null;
63
+ private _model;
64
+ private _tokenizer;
65
+ private _id2label;
66
+ private _oId;
67
+ private _loadPromise;
68
+ private readonly _onProgress?;
69
+ constructor(opts?: CalibratedDetectorOptions);
70
+ load(): Promise<void>;
71
+ detect(text: string, opts?: {
72
+ model?: string;
73
+ }): Promise<Span[]>;
74
+ private _detectChunked;
75
+ private _detectWindow;
76
+ private _emitProgress;
77
+ }
78
+ //# sourceMappingURL=calibrated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calibrated.d.ts","sourceRoot":"","sources":["../../../src/detectors/calibrated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAMzD,0DAA0D;AAC1D,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AA4CD,uDAAuD;AACvD,qBAAa,kBAAmB,YAAW,QAAQ;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,oEAAoE;IACpE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IACnC,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAA0C;gBAE3D,IAAI,GAAE,yBAA8B;IAiB1C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCrB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YAYxD,cAAc;YA0Bd,aAAa;IA6F3B,OAAO,CAAC,aAAa;CAiBtB"}
@@ -5,7 +5,14 @@ export { LocalDetector } from "./local.ts";
5
5
  export type { LocalDetectorOptions, LocalDetectorLoadEvent } from "./local.ts";
6
6
  export { CompositeDetector } from "./composite.ts";
7
7
  export type { CompositeDetectorOptions } from "./composite.ts";
8
+ export { CalibratedDetector } from "./calibrated.ts";
9
+ export type { CalibratedDetectorOptions } from "./calibrated.ts";
8
10
  import type { Detector } from "./base.ts";
9
- /** Pick a reasonable default: remote if `GHEIM_API_KEY` is set in env, else local. */
11
+ /** Pick a reasonable default: remote when `GHEIM_API_KEY` is set in env,
12
+ * else a calibrated local model.
13
+ *
14
+ * The calibrated default (`oBias=0.5`) recovers ~95% of the
15
+ * multi-entity-letter cases that the raw v1 model fails on, with
16
+ * ~0.4pp test_v1 strict-F1 cost. See eval/calibration_sweep.json. */
10
17
  export declare function defaultDetector(): Detector;
11
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/detectors/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,aAAa,IAAI,oBAAoB,EACrC,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAI1C,sFAAsF;AACtF,wBAAgB,eAAe,IAAI,QAAQ,CAI1C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/detectors/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,aAAa,IAAI,oBAAoB,EACrC,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,YAAY,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAEjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAI1C;;;;;sEAKsE;AACtE,wBAAgB,eAAe,IAAI,QAAQ,CAI1C"}
@@ -13,11 +13,11 @@ export { LABEL_TO_TAG, SENTINEL_RE, isPossibleSentinelPrefix, labelTag, parseSen
13
13
  export type { Sentinel } from "./core/sentinels.ts";
14
14
  export { StreamDeanonymizer } from "./core/stream.ts";
15
15
  export type { Detector, Span } from "./detectors/base.ts";
16
- export { DEFAULT_BASE_URL, REMOTE_DEFAULT_MODEL, RemoteDetector, LocalDetector, CompositeDetector, defaultDetector, } from "./detectors/index.ts";
17
- export type { RemoteDetectorOptions, LocalDetectorOptions, LocalDetectorLoadEvent, CompositeDetectorOptions, } from "./detectors/index.ts";
16
+ export { DEFAULT_BASE_URL, REMOTE_DEFAULT_MODEL, RemoteDetector, LocalDetector, CompositeDetector, CalibratedDetector, defaultDetector, } from "./detectors/index.ts";
17
+ export type { RemoteDetectorOptions, LocalDetectorOptions, LocalDetectorLoadEvent, CompositeDetectorOptions, CalibratedDetectorOptions, } from "./detectors/index.ts";
18
18
  export { anonymizeMessages, anonymizeText, deanonymizeStream, deanonymizeText, } from "./plain.ts";
19
19
  export type { ChatMessage } from "./plain.ts";
20
- export declare const VERSION = "0.1.4";
20
+ export declare const VERSION = "0.1.5";
21
21
  export { mergeAdjacent } from "./core/session.ts";
22
22
  export type { MergedSpan } from "./core/session.ts";
23
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EACL,YAAY,EACZ,WAAW,EACX,wBAAwB,EACxB,QAAQ,EACR,aAAa,EACb,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EACL,YAAY,EACZ,WAAW,EACX,wBAAwB,EACxB,QAAQ,EACR,aAAa,EACb,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gheim",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "PII round-trip for LLM APIs: anonymize before the request, de-anonymize the stream on the way back.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Joel Barmettler <joel.barmettler@gmail.com>",