@euconform/core 1.0.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/.turbo/turbo-build.log +4 -0
- package/LICENSE +21 -0
- package/dist/fairness-metrics.d.ts +65 -0
- package/dist/fairness-metrics.d.ts.map +1 -0
- package/dist/fairness-metrics.js +394 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/inference/browser-inference.d.ts +60 -0
- package/dist/inference/browser-inference.d.ts.map +1 -0
- package/dist/inference/browser-inference.js +221 -0
- package/dist/inference/capability-detection.d.ts +183 -0
- package/dist/inference/capability-detection.d.ts.map +1 -0
- package/dist/inference/capability-detection.js +552 -0
- package/dist/inference/index.d.ts +43 -0
- package/dist/inference/index.d.ts.map +1 -0
- package/dist/inference/index.js +97 -0
- package/dist/inference/inference-factory.d.ts +73 -0
- package/dist/inference/inference-factory.d.ts.map +1 -0
- package/dist/inference/inference-factory.js +175 -0
- package/dist/inference/ollama-client.d.ts +114 -0
- package/dist/inference/ollama-client.d.ts.map +1 -0
- package/dist/inference/ollama-client.js +386 -0
- package/dist/legal-checks/annex-iv-report.d.ts +131 -0
- package/dist/legal-checks/annex-iv-report.d.ts.map +1 -0
- package/dist/legal-checks/annex-iv-report.js +62 -0
- package/dist/legal-checks/bias-metrics.d.ts +162 -0
- package/dist/legal-checks/bias-metrics.d.ts.map +1 -0
- package/dist/legal-checks/bias-metrics.js +408 -0
- package/dist/legal-checks/data-governance.d.ts +30 -0
- package/dist/legal-checks/data-governance.d.ts.map +1 -0
- package/dist/legal-checks/data-governance.js +99 -0
- package/dist/legal-checks/human-oversight-logging.d.ts +27 -0
- package/dist/legal-checks/human-oversight-logging.d.ts.map +1 -0
- package/dist/legal-checks/human-oversight-logging.js +106 -0
- package/dist/legal-checks/index.d.ts +13 -0
- package/dist/legal-checks/index.d.ts.map +1 -0
- package/dist/legal-checks/index.js +6 -0
- package/dist/legal-checks/risk-classifier.d.ts +35 -0
- package/dist/legal-checks/risk-classifier.d.ts.map +1 -0
- package/dist/legal-checks/risk-classifier.js +234 -0
- package/dist/legal-checks/robustness-transparency.d.ts +45 -0
- package/dist/legal-checks/robustness-transparency.d.ts.map +1 -0
- package/dist/legal-checks/robustness-transparency.js +111 -0
- package/dist/metrics/crows-pairs-bias.d.ts +41 -0
- package/dist/metrics/crows-pairs-bias.d.ts.map +1 -0
- package/dist/metrics/crows-pairs-bias.js +172 -0
- package/dist/metrics/index.d.ts +6 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +5 -0
- package/dist/metrics/stereotype-bias.d.ts +89 -0
- package/dist/metrics/stereotype-bias.d.ts.map +1 -0
- package/dist/metrics/stereotype-bias.js +157 -0
- package/dist/risk-engine.d.ts +23 -0
- package/dist/risk-engine.d.ts.map +1 -0
- package/dist/risk-engine.js +451 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/package.json +28 -0
- package/src/fairness-metrics.ts +498 -0
- package/src/index.ts +150 -0
- package/src/inference/browser-inference.ts +258 -0
- package/src/inference/capability-detection.ts +714 -0
- package/src/inference/index.ts +156 -0
- package/src/inference/inference-factory.ts +222 -0
- package/src/inference/ollama-client.ts +514 -0
- package/src/inference/transformers.d.ts +62 -0
- package/src/legal-checks/annex-iv-report.ts +157 -0
- package/src/legal-checks/bias-metrics.ts +590 -0
- package/src/legal-checks/data-governance.ts +148 -0
- package/src/legal-checks/human-oversight-logging.ts +135 -0
- package/src/legal-checks/index.ts +55 -0
- package/src/legal-checks/risk-classifier.ts +285 -0
- package/src/legal-checks/robustness-transparency.ts +162 -0
- package/src/metrics/crows-pairs-bias.ts +245 -0
- package/src/metrics/index.ts +22 -0
- package/src/metrics/stereotype-bias.ts +267 -0
- package/src/risk-engine.ts +525 -0
- package/src/types.ts +154 -0
- package/tests/annex-iv-report.test.ts +323 -0
- package/tests/crows-pairs-bias.test.ts +342 -0
- package/tests/fairness-metrics.test.ts +187 -0
- package/tests/integration/bias-calculation.integration.test.ts +688 -0
- package/tests/legal-checks.test.ts +53 -0
- package/tests/ollama-client.test.ts +427 -0
- package/tests/risk-engine.test.ts +149 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-based inference using transformers.js
|
|
3
|
+
* For small models (≤3B params) that can run via WebGPU
|
|
4
|
+
*
|
|
5
|
+
* STRICT MODE: Real inference only. No demo data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// We need to use 'any' for transformers objects because the types are not fully exported
|
|
9
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
10
|
+
|
|
11
|
+
export interface BrowserModel {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
size: string;
|
|
15
|
+
supported: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Supported models for browser inference
|
|
19
|
+
export const BROWSER_MODELS: BrowserModel[] = [
|
|
20
|
+
{
|
|
21
|
+
id: "Xenova/phi-3-mini-4k-instruct",
|
|
22
|
+
name: "Phi-3 Mini",
|
|
23
|
+
size: "3.8B",
|
|
24
|
+
supported: true,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "Xenova/Phi-3.5-mini-instruct",
|
|
28
|
+
name: "Phi-3.5 Mini",
|
|
29
|
+
size: "3.8B",
|
|
30
|
+
supported: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "Xenova/gpt2",
|
|
34
|
+
name: "GPT-2",
|
|
35
|
+
size: "124M",
|
|
36
|
+
supported: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "Xenova/distilgpt2",
|
|
40
|
+
name: "DistilGPT-2",
|
|
41
|
+
size: "82M",
|
|
42
|
+
supported: true,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if WebGPU is available in the browser
|
|
48
|
+
*/
|
|
49
|
+
export async function checkWebGPU(): Promise<boolean> {
|
|
50
|
+
if (typeof navigator === "undefined") return false;
|
|
51
|
+
if (typeof window === "undefined") return false;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Check for WebGPU support
|
|
55
|
+
const gpu = (navigator as Navigator & { gpu?: unknown }).gpu;
|
|
56
|
+
if (!gpu) return false;
|
|
57
|
+
// Try to get adapter
|
|
58
|
+
const adapter = await (gpu as { requestAdapter: () => Promise<unknown> }).requestAdapter();
|
|
59
|
+
return adapter !== null;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if transformers.js is available
|
|
67
|
+
*/
|
|
68
|
+
export async function checkTransformersAvailable(): Promise<boolean> {
|
|
69
|
+
try {
|
|
70
|
+
// Try dynamic import - this will fail if package not installed
|
|
71
|
+
await import("@xenova/transformers");
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Browser inference engine using transformers.js
|
|
80
|
+
* STRICT MODE: Throws error if package not installed.
|
|
81
|
+
*/
|
|
82
|
+
export class BrowserInference {
|
|
83
|
+
public readonly modelId: string;
|
|
84
|
+
private model: unknown = null;
|
|
85
|
+
private tokenizer: unknown = null;
|
|
86
|
+
private pipeline: unknown = null; // Keep pipeline for generation
|
|
87
|
+
private loadingPromise: Promise<void> | null = null;
|
|
88
|
+
private loadError: Error | null = null;
|
|
89
|
+
private statusCallback: ((status: string) => void) | null = null;
|
|
90
|
+
|
|
91
|
+
constructor(
|
|
92
|
+
modelId = "Xenova/distilgpt2",
|
|
93
|
+
statusCallback: ((status: string) => void) | null = null
|
|
94
|
+
) {
|
|
95
|
+
this.modelId = modelId;
|
|
96
|
+
this.statusCallback = statusCallback;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if browser inference is available
|
|
101
|
+
*/
|
|
102
|
+
async isAvailable(): Promise<boolean> {
|
|
103
|
+
const hasTransformers = await checkTransformersAvailable();
|
|
104
|
+
return hasTransformers;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Load the model (lazy loading)
|
|
109
|
+
* Handles concurrency with loadingPromise
|
|
110
|
+
*/
|
|
111
|
+
async load(): Promise<void> {
|
|
112
|
+
if (this.model && this.tokenizer) return;
|
|
113
|
+
if (this.loadingPromise) return this.loadingPromise;
|
|
114
|
+
if (this.loadError) throw this.loadError;
|
|
115
|
+
|
|
116
|
+
this.loadingPromise = this.performLoad().finally(() => {
|
|
117
|
+
this.loadingPromise = null;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return this.loadingPromise;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async performLoad() {
|
|
124
|
+
if (this.statusCallback) this.statusCallback("loading");
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const { AutoTokenizer, AutoModelForCausalLM, pipeline } = await import(
|
|
128
|
+
"@xenova/transformers"
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const hasWebGPU = await checkWebGPU();
|
|
132
|
+
const device = hasWebGPU ? "webgpu" : "cpu";
|
|
133
|
+
|
|
134
|
+
if (this.statusCallback) this.statusCallback("loading-tokenizer");
|
|
135
|
+
this.tokenizer = await AutoTokenizer.from_pretrained(this.modelId);
|
|
136
|
+
|
|
137
|
+
if (this.statusCallback) this.statusCallback("loading-model");
|
|
138
|
+
// biome-ignore lint/suspicious/noExplicitAny: library internal
|
|
139
|
+
this.model = await (AutoModelForCausalLM as any).from_pretrained(this.modelId, {
|
|
140
|
+
device,
|
|
141
|
+
quantized: true,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (this.statusCallback) this.statusCallback("loading-pipeline");
|
|
145
|
+
// biome-ignore lint/suspicious/noExplicitAny: library internal
|
|
146
|
+
this.pipeline = await (pipeline as any)("text-generation", this.modelId, {
|
|
147
|
+
device,
|
|
148
|
+
model: this.model,
|
|
149
|
+
tokenizer: this.tokenizer,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (this.statusCallback) this.statusCallback("ready");
|
|
153
|
+
} catch (err) {
|
|
154
|
+
this.loadError = err instanceof Error ? err : new Error(String(err));
|
|
155
|
+
if (this.statusCallback) this.statusCallback("error");
|
|
156
|
+
throw this.loadError;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Generate text
|
|
162
|
+
*/
|
|
163
|
+
async generate(prompt: string, maxTokens = 50): Promise<string> {
|
|
164
|
+
await this.load();
|
|
165
|
+
|
|
166
|
+
if (!this.pipeline) {
|
|
167
|
+
throw new Error("Model not loaded");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// biome-ignore lint/suspicious/noExplicitAny: library internal
|
|
171
|
+
const result = await (this.pipeline as any)(prompt, {
|
|
172
|
+
max_new_tokens: maxTokens,
|
|
173
|
+
temperature: 0.7,
|
|
174
|
+
do_sample: true,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return result[0]?.generated_text || "";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Calculate Log-Probability for a given text
|
|
182
|
+
* This is the "Gold Standard" for bias testing.
|
|
183
|
+
* We calculate the loss (negative log likelihood) of the text.
|
|
184
|
+
*/
|
|
185
|
+
async getLogProb(text: string): Promise<number> {
|
|
186
|
+
await this.load();
|
|
187
|
+
|
|
188
|
+
if (!this.tokenizer || !this.model) {
|
|
189
|
+
throw new Error("Model not loaded");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// biome-ignore lint/suspicious/noExplicitAny: internal transformers.js types
|
|
193
|
+
const inputs = await (this.tokenizer as any)(text, { return_tensors: "pt" });
|
|
194
|
+
const inputIds = inputs.input_ids;
|
|
195
|
+
|
|
196
|
+
// biome-ignore lint/suspicious/noExplicitAny: internal transformers.js types
|
|
197
|
+
const outputs = await (this.model as any)(inputs);
|
|
198
|
+
const logits = outputs.logits;
|
|
199
|
+
|
|
200
|
+
// 3. Compute Log Likelihood
|
|
201
|
+
// We want to calculate P(text) = P(t1) * P(t2|t1) * ...
|
|
202
|
+
// LogProb = sum( log( P(ti | t_<i) ) )
|
|
203
|
+
|
|
204
|
+
// logits shape: [batch_size, seq_len, vocab_size]
|
|
205
|
+
// inputIds shape: [batch_size, seq_len]
|
|
206
|
+
// We are interested in the probability of the *next* token given previous tokens.
|
|
207
|
+
// So we look at logits[0, i, :] to predict inputIds[0, i+1]
|
|
208
|
+
|
|
209
|
+
const data = logits.data; // Float32Array
|
|
210
|
+
const dims = logits.dims; // [batch, seq, vocab]
|
|
211
|
+
const vocabSize = dims[2];
|
|
212
|
+
const seqLen = dims[1];
|
|
213
|
+
|
|
214
|
+
let totalLogProb = 0;
|
|
215
|
+
|
|
216
|
+
// Helper to get logit value
|
|
217
|
+
const getLogit = (seqIdx: number, vocabIdx: number) => {
|
|
218
|
+
return data[seqIdx * vocabSize + vocabIdx];
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Helper for softmax/logsoftmax
|
|
222
|
+
// We only need the log_prob of the target token
|
|
223
|
+
// log_softmax(x_i) = x_i - log(sum(exp(x_j)))
|
|
224
|
+
// To avoid overflow: x_i - max(x) - log(sum(exp(x_j - max(x))))
|
|
225
|
+
|
|
226
|
+
for (let i = 0; i < seqLen - 1; i++) {
|
|
227
|
+
const targetTokenId = Number(inputIds.data[i + 1]);
|
|
228
|
+
|
|
229
|
+
// Calculate max logit for numerical stability
|
|
230
|
+
let maxLogit = Number.NEGATIVE_INFINITY;
|
|
231
|
+
for (let j = 0; j < vocabSize; j++) {
|
|
232
|
+
const val = getLogit(i, j);
|
|
233
|
+
if (val > maxLogit) maxLogit = val;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Calculate sum of exp
|
|
237
|
+
let sumExp = 0;
|
|
238
|
+
for (let j = 0; j < vocabSize; j++) {
|
|
239
|
+
sumExp += Math.exp(getLogit(i, j) - maxLogit);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const logSumExp = maxLogit + Math.log(sumExp);
|
|
243
|
+
const targetLogit = getLogit(i, targetTokenId);
|
|
244
|
+
|
|
245
|
+
const logProb = targetLogit - logSumExp;
|
|
246
|
+
totalLogProb += logProb;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return totalLogProb;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get list of supported browser models
|
|
254
|
+
*/
|
|
255
|
+
static getSupportedModels(): BrowserModel[] {
|
|
256
|
+
return BROWSER_MODELS.filter((m) => m.supported);
|
|
257
|
+
}
|
|
258
|
+
}
|