@euconform/core 1.5.0 → 1.6.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 +1 -1
- package/CHANGELOG.md +34 -0
- package/dist/datasets/crows-pairs.d.ts +18 -0
- package/dist/datasets/crows-pairs.d.ts.map +1 -1
- package/dist/datasets/crows-pairs.js +58 -0
- package/dist/evidence/bias-comparison-markdown.d.ts +11 -0
- package/dist/evidence/bias-comparison-markdown.d.ts.map +1 -0
- package/dist/evidence/bias-comparison-markdown.js +120 -0
- package/dist/evidence/bias-comparison.d.ts +133 -0
- package/dist/evidence/bias-comparison.d.ts.map +1 -0
- package/dist/evidence/bias-comparison.js +274 -0
- package/dist/evidence/bias-statistics.d.ts +72 -0
- package/dist/evidence/bias-statistics.d.ts.map +1 -0
- package/dist/evidence/bias-statistics.js +122 -0
- package/dist/evidence/bias-v2-semantics.d.ts +19 -0
- package/dist/evidence/bias-v2-semantics.d.ts.map +1 -0
- package/dist/evidence/bias-v2-semantics.js +133 -0
- package/dist/evidence/browser.d.ts +18 -0
- package/dist/evidence/browser.d.ts.map +1 -0
- package/dist/evidence/browser.js +14 -0
- package/dist/evidence/bundle.d.ts +1 -0
- package/dist/evidence/bundle.d.ts.map +1 -1
- package/dist/evidence/bundle.js +2 -0
- package/dist/evidence/index.d.ts +9 -2
- package/dist/evidence/index.d.ts.map +1 -1
- package/dist/evidence/index.js +5 -1
- package/dist/evidence/types.d.ts +175 -1
- package/dist/evidence/types.d.ts.map +1 -1
- package/dist/evidence/validate-bias-v2.d.ts +31 -0
- package/dist/evidence/validate-bias-v2.d.ts.map +1 -0
- package/dist/evidence/validate-bias-v2.js +40 -0
- package/dist/evidence/validate.d.ts +29 -2
- package/dist/evidence/validate.d.ts.map +1 -1
- package/dist/evidence/validate.js +90 -8
- package/dist/fairness-metrics.d.ts +15 -11
- package/dist/fairness-metrics.d.ts.map +1 -1
- package/dist/fairness-metrics.js +59 -183
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/inference/browser-inference.d.ts +6 -57
- package/dist/inference/browser-inference.d.ts.map +1 -1
- package/dist/inference/browser-inference.js +6 -219
- package/dist/inference/capability-detection.d.ts +9 -2
- package/dist/inference/capability-detection.d.ts.map +1 -1
- package/dist/inference/capability-detection.js +18 -16
- package/dist/inference/index.d.ts +15 -2
- package/dist/inference/index.d.ts.map +1 -1
- package/dist/inference/index.js +12 -3
- package/dist/inference/inference-factory.d.ts +6 -3
- package/dist/inference/inference-factory.d.ts.map +1 -1
- package/dist/inference/inference-factory.js +7 -26
- package/dist/inference/ollama-client.d.ts +29 -0
- package/dist/inference/ollama-client.d.ts.map +1 -1
- package/dist/inference/ollama-client.js +29 -0
- package/dist/inference/scored-log-prob-types.d.ts +64 -0
- package/dist/inference/scored-log-prob-types.d.ts.map +1 -0
- package/dist/inference/scored-log-prob-types.js +12 -0
- package/dist/inference/sequence-logprob.d.ts +34 -0
- package/dist/inference/sequence-logprob.d.ts.map +1 -0
- package/dist/inference/sequence-logprob.js +87 -0
- package/dist/inference/template-scoring.d.ts +124 -0
- package/dist/inference/template-scoring.d.ts.map +1 -0
- package/dist/inference/template-scoring.js +164 -0
- package/dist/inference/transformers-inference.d.ts +124 -0
- package/dist/inference/transformers-inference.d.ts.map +1 -0
- package/dist/inference/transformers-inference.js +392 -0
- package/dist/legal-checks/bias-metrics.d.ts +5 -7
- package/dist/legal-checks/bias-metrics.d.ts.map +1 -1
- package/dist/legal-checks/bias-metrics.js +8 -101
- package/dist/metrics/crows-pairs-bias.d.ts +17 -9
- package/dist/metrics/crows-pairs-bias.d.ts.map +1 -1
- package/dist/metrics/crows-pairs-bias.js +147 -59
- package/dist/metrics/stereotype-bias.d.ts +13 -5
- package/dist/metrics/stereotype-bias.d.ts.map +1 -1
- package/dist/metrics/stereotype-bias.js +16 -99
- package/dist/scanner/detectors/ai-framework-detector.js +1 -1
- package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -1
- package/dist/scanner/detectors/local-inference-detector.js +1 -1
- package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -1
- package/dist/scanner/detectors/package-json-detector.js +1 -0
- package/dist/types.d.ts +103 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.d.ts.map +1 -1
- package/dist/validation/schema-validator.js +6 -0
- package/package.json +4 -2
- package/scripts/seed-logprob-fixture.mjs +179 -0
- package/scripts/ts-extensionless-resolve-hook.mjs +26 -0
- package/src/datasets/crows-pairs.ts +74 -0
- package/src/evidence/bias-comparison-markdown.ts +162 -0
- package/src/evidence/bias-comparison.ts +432 -0
- package/src/evidence/bias-statistics.ts +180 -0
- package/src/evidence/bias-v2-semantics.ts +214 -0
- package/src/evidence/browser.ts +35 -0
- package/src/evidence/bundle.ts +2 -0
- package/src/evidence/index.ts +31 -0
- package/src/evidence/types.ts +204 -1
- package/src/evidence/validate-bias-v2.ts +47 -0
- package/src/evidence/validate.ts +140 -13
- package/src/fairness-metrics.ts +79 -222
- package/src/index.ts +7 -0
- package/src/inference/browser-inference.ts +14 -256
- package/src/inference/capability-detection.ts +32 -18
- package/src/inference/index.ts +33 -5
- package/src/inference/inference-factory.ts +9 -30
- package/src/inference/ollama-client.ts +29 -0
- package/src/inference/scored-log-prob-types.ts +66 -0
- package/src/inference/sequence-logprob.ts +136 -0
- package/src/inference/template-scoring.ts +248 -0
- package/src/inference/transformers-inference.ts +494 -0
- package/src/legal-checks/bias-metrics.ts +20 -118
- package/src/metrics/crows-pairs-bias.ts +180 -83
- package/src/metrics/stereotype-bias.ts +27 -119
- package/src/scanner/detectors/ai-framework-detector.ts +1 -1
- package/src/scanner/detectors/local-inference-detector.ts +2 -1
- package/src/scanner/detectors/package-json-detector.ts +1 -0
- package/src/types.ts +108 -2
- package/src/validation/schema-validator.ts +15 -1
- package/tests/annex-iv-report.test.ts +1 -1
- package/tests/capability-detection.test.ts +57 -0
- package/tests/crows-pairs-bias.test.ts +286 -186
- package/tests/custom-dataset.test.ts +80 -0
- package/tests/evidence/bias-artifact-v2.test.ts +135 -0
- package/tests/evidence/bias-artifact.test.ts +78 -0
- package/tests/evidence/bias-comparison.test.ts +397 -0
- package/tests/evidence/bias-statistics.test.ts +191 -0
- package/tests/evidence/external-observation.test.ts +167 -0
- package/tests/evidence/spec-schemas.test.ts +35 -2
- package/tests/evidence/validate-bias-v2.test.ts +180 -0
- package/tests/fairness-metrics.test.ts +77 -1
- package/tests/inference/browser-inference-scoring.test.ts +159 -0
- package/tests/inference/fixtures/logprob-golden.json +69 -0
- package/tests/inference/gemma-template.integration.test.ts +81 -0
- package/tests/inference/logprob-golden.integration.test.ts +128 -0
- package/tests/inference/synthetic-logits.test.ts +188 -0
- package/tests/inference/template-scoring.test.ts +249 -0
- package/tests/inference/transformers-inference-template-mode.test.ts +214 -0
- package/tests/integration/bias-calculation.integration.test.ts +119 -599
- package/tests/stilled-ollama-paths.test.ts +76 -0
- package/src/inference/transformers.d.ts +0 -62
|
@@ -1,258 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* @deprecated Kept for backward compatibility (apps/web, existing tests import
|
|
3
|
+
* `BrowserInference` from this path). The canonical implementation now lives in
|
|
4
|
+
* `transformers-inference.ts` under the name `TransformersInference` — see
|
|
5
|
+
* Messmotor-Sanierung Task 2. New code should import `TransformersInference`
|
|
6
|
+
* from "./transformers-inference" directly.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
}
|
|
8
|
+
export {
|
|
9
|
+
TransformersInference as BrowserInference,
|
|
10
|
+
type BrowserModel,
|
|
11
|
+
BROWSER_MODELS,
|
|
12
|
+
checkWebGPU,
|
|
13
|
+
checkTransformersAvailable,
|
|
14
|
+
type ScoredLogProb,
|
|
15
|
+
type ScoredLogProbProvenance,
|
|
16
|
+
} from "./transformers-inference";
|
|
@@ -6,9 +6,21 @@
|
|
|
6
6
|
import type { BiasCalculationMethod } from "../types";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Detection status for model capabilities
|
|
9
|
+
* Detection status for model capabilities.
|
|
10
|
+
*
|
|
11
|
+
* "unverified": the model is declared supported (e.g. it's in the static
|
|
12
|
+
* BROWSER_MODELS list) but has NOT actually been loaded/tested — no request
|
|
13
|
+
* was made, no capability was measured. Distinct from "available", which
|
|
14
|
+
* means a real detection/measurement round confirmed it. Do not report
|
|
15
|
+
* "available" for a model that was never tested (see Messmotor-Sanierung
|
|
16
|
+
* Task 2): the runtime status is what a consumer reads, not source comments.
|
|
10
17
|
*/
|
|
11
|
-
export type CapabilityDetectionStatus =
|
|
18
|
+
export type CapabilityDetectionStatus =
|
|
19
|
+
| "detecting"
|
|
20
|
+
| "available"
|
|
21
|
+
| "unverified"
|
|
22
|
+
| "unavailable"
|
|
23
|
+
| "error";
|
|
12
24
|
|
|
13
25
|
/**
|
|
14
26
|
* Model capability information
|
|
@@ -105,15 +117,19 @@ export class DefaultCapabilityDetectionService implements CapabilityDetectionSer
|
|
|
105
117
|
// Import browser models dynamically
|
|
106
118
|
const { BROWSER_MODELS } = await import("./browser-inference");
|
|
107
119
|
|
|
120
|
+
// These entries are declared from the static BROWSER_MODELS list, not
|
|
121
|
+
// measured — no model has actually been loaded/tested at this point.
|
|
122
|
+
// status: "unverified" (not "available") reflects that honestly; no
|
|
123
|
+
// lastTested since no test ran. A real per-model verification step is
|
|
124
|
+
// future work, not this task's scope.
|
|
108
125
|
for (const model of BROWSER_MODELS) {
|
|
109
126
|
if (!model.supported) continue;
|
|
110
127
|
capabilities.push({
|
|
111
128
|
modelId: model.id,
|
|
112
129
|
engine: "browser",
|
|
113
130
|
method: "logprobs_exact",
|
|
114
|
-
status: "
|
|
131
|
+
status: "unverified",
|
|
115
132
|
recommended: true,
|
|
116
|
-
lastTested: new Date().toISOString(),
|
|
117
133
|
});
|
|
118
134
|
}
|
|
119
135
|
} catch (error) {
|
|
@@ -123,9 +139,8 @@ export class DefaultCapabilityDetectionService implements CapabilityDetectionSer
|
|
|
123
139
|
modelId: "Xenova/distilgpt2",
|
|
124
140
|
engine: "browser",
|
|
125
141
|
method: "logprobs_exact",
|
|
126
|
-
status: "
|
|
142
|
+
status: "unverified",
|
|
127
143
|
recommended: true,
|
|
128
|
-
lastTested: new Date().toISOString(),
|
|
129
144
|
});
|
|
130
145
|
}
|
|
131
146
|
|
|
@@ -189,15 +204,14 @@ export class DefaultCapabilityDetectionService implements CapabilityDetectionSer
|
|
|
189
204
|
return cached.capability;
|
|
190
205
|
}
|
|
191
206
|
|
|
192
|
-
//
|
|
207
|
+
// Declared, not measured — no model has been loaded/tested here.
|
|
193
208
|
if (engine === "browser") {
|
|
194
209
|
const capability: ModelCapability = {
|
|
195
210
|
modelId,
|
|
196
211
|
engine: "browser",
|
|
197
212
|
method: "logprobs_exact",
|
|
198
|
-
status: "
|
|
213
|
+
status: "unverified",
|
|
199
214
|
recommended: true,
|
|
200
|
-
lastTested: new Date().toISOString(),
|
|
201
215
|
};
|
|
202
216
|
|
|
203
217
|
this.setCachedCapability(capability);
|
|
@@ -360,7 +374,9 @@ export class DefaultCapabilityDetectionService implements CapabilityDetectionSer
|
|
|
360
374
|
* Calculate recommendation score for a capability
|
|
361
375
|
*/
|
|
362
376
|
protected getRecommendationScore(capability: ModelCapability): number {
|
|
363
|
-
|
|
377
|
+
// "unverified" is a usable, declared-supported model (see the type doc) —
|
|
378
|
+
// only truly broken/unknown states (unavailable, error, detecting) score 0.
|
|
379
|
+
if (capability.status !== "available" && capability.status !== "unverified") return 0;
|
|
364
380
|
|
|
365
381
|
let score = 0;
|
|
366
382
|
|
|
@@ -398,15 +414,14 @@ export class EnhancedCapabilityDetectionService extends DefaultCapabilityDetecti
|
|
|
398
414
|
return cached.capability;
|
|
399
415
|
}
|
|
400
416
|
|
|
401
|
-
//
|
|
417
|
+
// Declared, not measured — no model has been loaded/tested here.
|
|
402
418
|
if (engine === "browser") {
|
|
403
419
|
const capability: ModelCapability = {
|
|
404
420
|
modelId,
|
|
405
421
|
engine: "browser",
|
|
406
422
|
method: "logprobs_exact",
|
|
407
|
-
status: "
|
|
423
|
+
status: "unverified",
|
|
408
424
|
recommended: true,
|
|
409
|
-
lastTested: new Date().toISOString(),
|
|
410
425
|
};
|
|
411
426
|
|
|
412
427
|
this.setCachedCapability(capability);
|
|
@@ -509,7 +524,8 @@ export class EnhancedCapabilityDetectionService extends DefaultCapabilityDetecti
|
|
|
509
524
|
async detectAllCapabilities(): Promise<ModelCapability[]> {
|
|
510
525
|
const capabilities: ModelCapability[] = [];
|
|
511
526
|
|
|
512
|
-
// Add all supported browser models dynamically
|
|
527
|
+
// Add all supported browser models dynamically. Declared, not measured —
|
|
528
|
+
// no model has actually been loaded/tested at this point.
|
|
513
529
|
try {
|
|
514
530
|
const { BROWSER_MODELS } = await import("./browser-inference");
|
|
515
531
|
|
|
@@ -519,9 +535,8 @@ export class EnhancedCapabilityDetectionService extends DefaultCapabilityDetecti
|
|
|
519
535
|
modelId: model.id,
|
|
520
536
|
engine: "browser",
|
|
521
537
|
method: "logprobs_exact",
|
|
522
|
-
status: "
|
|
538
|
+
status: "unverified",
|
|
523
539
|
recommended: true,
|
|
524
|
-
lastTested: new Date().toISOString(),
|
|
525
540
|
});
|
|
526
541
|
}
|
|
527
542
|
} catch (error) {
|
|
@@ -531,9 +546,8 @@ export class EnhancedCapabilityDetectionService extends DefaultCapabilityDetecti
|
|
|
531
546
|
modelId: "Xenova/distilgpt2",
|
|
532
547
|
engine: "browser",
|
|
533
548
|
method: "logprobs_exact",
|
|
534
|
-
status: "
|
|
549
|
+
status: "unverified",
|
|
535
550
|
recommended: true,
|
|
536
|
-
lastTested: new Date().toISOString(),
|
|
537
551
|
});
|
|
538
552
|
}
|
|
539
553
|
|
package/src/inference/index.ts
CHANGED
|
@@ -3,13 +3,26 @@
|
|
|
3
3
|
* Auto-detects available inference engines and provides unified interface
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { OllamaClient, type OllamaModel, detectOllama, listOllamaModels } from "./ollama-client";
|
|
6
7
|
import {
|
|
7
8
|
BROWSER_MODELS,
|
|
8
|
-
BrowserInference,
|
|
9
9
|
type BrowserModel,
|
|
10
|
+
type ScoredLogProb,
|
|
11
|
+
TransformersInference,
|
|
10
12
|
checkWebGPU,
|
|
11
|
-
} from "./
|
|
12
|
-
|
|
13
|
+
} from "./transformers-inference";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The one contract a bias-measurement engine must satisfy: a real, cross-checked
|
|
17
|
+
* sentence log-probability (see Task 1 / Messmotor-Sanierung). `OllamaClient`
|
|
18
|
+
* deliberately does NOT implement this — Ollama's logprobs only cover generated
|
|
19
|
+
* tokens, never prompt/sentence log-probability, so it cannot honestly produce a
|
|
20
|
+
* `ScoredLogProb`. TypeScript's structural typing then locks it out of any API
|
|
21
|
+
* that requires this interface (e.g. `calculateCrowsPairsBias`).
|
|
22
|
+
*/
|
|
23
|
+
export interface SequenceLogProbProvider {
|
|
24
|
+
getScoredLogProb(text: string): Promise<ScoredLogProb>;
|
|
25
|
+
}
|
|
13
26
|
|
|
14
27
|
export type InferenceEngine = "ollama" | "browser" | "demo";
|
|
15
28
|
|
|
@@ -85,7 +98,7 @@ export function createInferenceClient(
|
|
|
85
98
|
}
|
|
86
99
|
|
|
87
100
|
case "browser": {
|
|
88
|
-
const browserClient = new
|
|
101
|
+
const browserClient = new TransformersInference(modelId, statusCallback);
|
|
89
102
|
return {
|
|
90
103
|
engine: "browser",
|
|
91
104
|
modelId,
|
|
@@ -138,7 +151,22 @@ export {
|
|
|
138
151
|
OllamaClient,
|
|
139
152
|
type CapabilityCache,
|
|
140
153
|
} from "./ollama-client";
|
|
141
|
-
export {
|
|
154
|
+
export {
|
|
155
|
+
TransformersInference,
|
|
156
|
+
checkWebGPU,
|
|
157
|
+
BROWSER_MODELS,
|
|
158
|
+
type ScoredLogProb,
|
|
159
|
+
type ScoredLogProbProvenance,
|
|
160
|
+
} from "./transformers-inference";
|
|
161
|
+
// Architecture detection for the Gemma-4 template-mode scorer, re-exported so callers that
|
|
162
|
+
// must pick a dtype BEFORE constructing `TransformersInference` (e.g. the CLI's
|
|
163
|
+
// `run-bias-test.ts`, which needs to choose "q4" over "fp32" for conditional-generation
|
|
164
|
+
// models — fp32 Gemma-4 weights were measured and rejected as impractically large in
|
|
165
|
+
// Messmotor-Sanierung Task 5) can reuse the same detection `TransformersInference` uses
|
|
166
|
+
// internally, instead of duplicating the architecture check.
|
|
167
|
+
export { isConditionalGenerationConfig, type MinimalModelConfig } from "./template-scoring";
|
|
168
|
+
// Backward-compat alias: existing consumers (apps/web, tests) import `BrowserInference`.
|
|
169
|
+
export { BrowserInference } from "./browser-inference";
|
|
142
170
|
export {
|
|
143
171
|
InferenceFactory,
|
|
144
172
|
createInferenceFactory,
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Provides unified interface for bias calculations across engines with automatic method detection
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { calculateCrowsPairsBias } from "../metrics/crows-pairs-bias";
|
|
7
6
|
import type { BiasCalculationMethod, BiasTestConfig, CrowsPairsBiasResult } from "../types";
|
|
8
7
|
import { BrowserInference } from "./browser-inference";
|
|
9
8
|
import type { ModelCapability } from "./capability-detection";
|
|
@@ -42,38 +41,18 @@ export class InferenceFactory {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* @deprecated Uses the removed Ollama logprob path: this method auto-selected
|
|
45
|
+
* between browser and Ollama engines via `getInferenceClient`, which no longer
|
|
46
|
+
* measures anything honest for Ollama (see Messmotor-Sanierung Task 2). The
|
|
47
|
+
* auto-selection logic itself needs a real redesign, not a throw-or-convert
|
|
48
|
+
* fix — see bias --engine transformers for the supported path.
|
|
47
49
|
*/
|
|
48
50
|
async createBiasCalculation(
|
|
49
|
-
|
|
51
|
+
_config: BiasTestConfig
|
|
50
52
|
): Promise<InferenceResult<CrowsPairsBiasResult>> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const inferenceClient = await this.getInferenceClient(engine, model);
|
|
56
|
-
let _detectedMethod: BiasCalculationMethod;
|
|
57
|
-
|
|
58
|
-
if (engine === "browser") {
|
|
59
|
-
_detectedMethod = "logprobs_exact";
|
|
60
|
-
} else {
|
|
61
|
-
// Use capability method if available, otherwise detect
|
|
62
|
-
_detectedMethod =
|
|
63
|
-
this.selectedCapability?.method ?? (await this.detectBestMethod(inferenceClient));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Perform bias calculation
|
|
67
|
-
const biasResult = await calculateCrowsPairsBias(config, inferenceClient);
|
|
68
|
-
|
|
69
|
-
// Return result with metadata including capability information
|
|
70
|
-
return {
|
|
71
|
-
data: biasResult,
|
|
72
|
-
method: biasResult.method, // Use the method determined during calculation
|
|
73
|
-
engine,
|
|
74
|
-
model,
|
|
75
|
-
capability: this.selectedCapability || undefined,
|
|
76
|
-
};
|
|
53
|
+
throw new Error(
|
|
54
|
+
"InferenceFactory.createBiasCalculation verwendet den entfernten Ollama-Logprob-Pfad; siehe bias --engine transformers."
|
|
55
|
+
);
|
|
77
56
|
}
|
|
78
57
|
|
|
79
58
|
/**
|
|
@@ -270,6 +270,9 @@ export class OllamaClient {
|
|
|
270
270
|
return response.response;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
/**
|
|
274
|
+
* @deprecated Not a sentence log-probability — see `getLogProbWithFallback`.
|
|
275
|
+
*/
|
|
273
276
|
async getLogProb(prompt: string): Promise<number> {
|
|
274
277
|
// Use the enhanced getLogProbWithFallback method that supports both exact and fallback methods
|
|
275
278
|
const result = await this.getLogProbWithFallback(prompt);
|
|
@@ -281,6 +284,12 @@ export class OllamaClient {
|
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
/**
|
|
287
|
+
* @deprecated Part of the removed Ollama bias-measurement path (Messmotor-Sanierung
|
|
288
|
+
* Task 2): Ollama's logprobs cover only generated tokens, never the prompt/sentence,
|
|
289
|
+
* so this "capability" check never verified a real measurement. Kept only because
|
|
290
|
+
* `scan`'s non-bias paths and the capability-cache tests still reference it — not
|
|
291
|
+
* a foundation to build on. See bias --engine transformers for the real path.
|
|
292
|
+
*
|
|
284
293
|
* Test if the current model supports log-probabilities
|
|
285
294
|
* Sends a test request with "Der" prompt to detect capability
|
|
286
295
|
*/
|
|
@@ -405,6 +414,9 @@ export class OllamaClient {
|
|
|
405
414
|
}
|
|
406
415
|
|
|
407
416
|
/**
|
|
417
|
+
* @deprecated Part of the removed Ollama bias-measurement path (see
|
|
418
|
+
* `testLogProbCapability`); no longer used by `calculateCrowsPairsBias`.
|
|
419
|
+
*
|
|
408
420
|
* Detect if the current model supports log-probabilities
|
|
409
421
|
* Checks cache first, then runs test if needed
|
|
410
422
|
*/
|
|
@@ -429,6 +441,10 @@ export class OllamaClient {
|
|
|
429
441
|
return supports;
|
|
430
442
|
}
|
|
431
443
|
|
|
444
|
+
/**
|
|
445
|
+
* @deprecated Part of the removed Ollama bias-measurement path; see
|
|
446
|
+
* `testLogProbCapability`. Not a real sentence log-probability.
|
|
447
|
+
*/
|
|
432
448
|
private async tryExactLogProbs(
|
|
433
449
|
prompt: string
|
|
434
450
|
): Promise<{ logprob: number; method: BiasCalculationMethod } | null> {
|
|
@@ -477,6 +493,11 @@ export class OllamaClient {
|
|
|
477
493
|
}
|
|
478
494
|
}
|
|
479
495
|
|
|
496
|
+
/**
|
|
497
|
+
* @deprecated Part of the removed Ollama bias-measurement path; this is a
|
|
498
|
+
* latency proxy, not a probability — the exact thing Messmotor-Sanierung
|
|
499
|
+
* removes from the bias measurement path. Do not build on this.
|
|
500
|
+
*/
|
|
480
501
|
private async latencyFallback(
|
|
481
502
|
prompt: string
|
|
482
503
|
): Promise<{ logprob: number; method: BiasCalculationMethod }> {
|
|
@@ -519,6 +540,14 @@ export class OllamaClient {
|
|
|
519
540
|
}
|
|
520
541
|
|
|
521
542
|
/**
|
|
543
|
+
* @deprecated Removed from the bias-measurement path (Messmotor-Sanierung Task 2):
|
|
544
|
+
* Ollama returns logprobs only for generated tokens, never for the prompt/sentence
|
|
545
|
+
* being scored, so this never measured a sentence log-probability — even the
|
|
546
|
+
* "exact" branch (`tryExactLogProbs`) is not what its name implies, and the
|
|
547
|
+
* fallback is a latency proxy. Verified: r(sentence length, logP)=0.019 where a
|
|
548
|
+
* real sentence-probability measure shows −0.96. Use `TransformersInference.
|
|
549
|
+
* getScoredLogProb` (via `bias --engine transformers`) instead.
|
|
550
|
+
*
|
|
522
551
|
* Get log-probability with automatic fallback to latency method
|
|
523
552
|
* Returns both the calculated value and the method used
|
|
524
553
|
*/
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared result types for `SequenceLogProbProvider.getScoredLogProb`.
|
|
3
|
+
*
|
|
4
|
+
* Pulled out of `transformers-inference.ts` (Messmotor-Sanierung Task 5) so that
|
|
5
|
+
* `template-scoring.ts` (the Gemma-4 template-mode scorer) and `transformers-inference.ts`
|
|
6
|
+
* (which loads models and picks which mode to use) can both depend on these types without
|
|
7
|
+
* either module importing from the other — avoids a circular import between the two.
|
|
8
|
+
*
|
|
9
|
+
* `transformers-inference.ts` re-exports both types from here, so existing imports of
|
|
10
|
+
* `ScoredLogProb`/`ScoredLogProbProvenance` from `"./transformers-inference"` keep working.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { SequenceLogProbMethod } from "../types";
|
|
14
|
+
import type { Span } from "./sequence-logprob";
|
|
15
|
+
|
|
16
|
+
/** Provenance metadata attached to a scored log-probability result. */
|
|
17
|
+
export interface ScoredLogProbProvenance {
|
|
18
|
+
modelId: string;
|
|
19
|
+
/**
|
|
20
|
+
* The CONFIGURED weight precision passed to `from_pretrained` (e.g. "q4", "fp32", "q8") —
|
|
21
|
+
* NOT the output tensor's reported dtype (see `logitsDtype` for that; the two are
|
|
22
|
+
* different facts and must not be conflated). Task 1 measured 1.3-11.9% score deviation
|
|
23
|
+
* between quantized and fp32 weights on the same model/sentences, so this field is
|
|
24
|
+
* ergebnisrelevant: an artifact that cannot say which weight precision produced a score
|
|
25
|
+
* claims a precision it does not have (Messmotor-Sanierung Task 5 review finding).
|
|
26
|
+
*/
|
|
27
|
+
dtype: string;
|
|
28
|
+
/**
|
|
29
|
+
* The OUTPUT LOGITS TENSOR's reported `.type` (e.g. "float32"). The Task 0 spike found
|
|
30
|
+
* this stays "float32" even for q4-quantized weights ("logits.data ist Float32Array trotz
|
|
31
|
+
* q4-Gewichten") — so this field does NOT tell you the weight precision; use `dtype` for
|
|
32
|
+
* that. Kept because it is still a real, independently useful fact (confirms no silent
|
|
33
|
+
* downcast in the output itself).
|
|
34
|
+
*/
|
|
35
|
+
logitsDtype: string;
|
|
36
|
+
backend: string;
|
|
37
|
+
libraryVersion: string;
|
|
38
|
+
/**
|
|
39
|
+
* "none" for the base autoregressive mode (Task 1); "fixed_template_span" for the
|
|
40
|
+
* instruction-tuned template mode (Task 5), which conditions scoring on a fixed
|
|
41
|
+
* user-turn prompt. Optional so pre-Task-5 test doubles that predate this field
|
|
42
|
+
* (e.g. hand-built `ScoredLogProb` literals in other packages' tests) stay valid;
|
|
43
|
+
* every real measurement path sets it explicitly.
|
|
44
|
+
*/
|
|
45
|
+
templateStrategy?: "none" | "fixed_template_span";
|
|
46
|
+
/** Present only when templateStrategy is "fixed_template_span": the verbatim fixed
|
|
47
|
+
* context string fed before the sentence — recomputable by any third party. */
|
|
48
|
+
promptText?: string;
|
|
49
|
+
/** SHA-256 of `promptText`, present only when templateStrategy is "fixed_template_span". */
|
|
50
|
+
promptSha256?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Full result of scoring a sentence with `getScoredLogProb`. */
|
|
54
|
+
export interface ScoredLogProb {
|
|
55
|
+
score: number;
|
|
56
|
+
scoredTokenIds: number[];
|
|
57
|
+
span: Span;
|
|
58
|
+
contextTokenHash: string;
|
|
59
|
+
provenance: ScoredLogProbProvenance;
|
|
60
|
+
/**
|
|
61
|
+
* How the score was accumulated (Task 3's `SequenceLogProbMethod`). Optional for the
|
|
62
|
+
* same backward-compatibility reason as `provenance.templateStrategy`; every real
|
|
63
|
+
* measurement path sets it explicitly.
|
|
64
|
+
*/
|
|
65
|
+
method?: SequenceLogProbMethod;
|
|
66
|
+
}
|