@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,32 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { SequenceLogProbProvider } from "../../src/inference";
|
|
3
|
+
import type { ScoredLogProb } from "../../src/inference/transformers-inference";
|
|
4
4
|
import { calculateCrowsPairsBias } from "../../src/metrics/crows-pairs-bias";
|
|
5
5
|
import type { BiasTestConfig, CrowsPairsEntry } from "../../src/types";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
7
|
+
/**
|
|
8
|
+
* Ollama never measured sentence log-probabilities (its logprobs cover only
|
|
9
|
+
* generated tokens — see Messmotor-Sanierung Task 2), so `calculateCrowsPairsBias`
|
|
10
|
+
* no longer accepts it. This integration test exercises the one remaining,
|
|
11
|
+
* honest measurement contract end-to-end: a `SequenceLogProbProvider` (in
|
|
12
|
+
* production, `TransformersInference`), including the fail-closed / allowPartial
|
|
13
|
+
* run-status behavior.
|
|
14
|
+
*/
|
|
15
|
+
function providerFromScores(scores: Record<string, number>): SequenceLogProbProvider {
|
|
16
|
+
return {
|
|
17
|
+
async getScoredLogProb(text: string): Promise<ScoredLogProb> {
|
|
18
|
+
if (!(text in scores)) throw new Error(`unerwarteter Satz: ${text}`);
|
|
19
|
+
return {
|
|
20
|
+
score: scores[text] as number,
|
|
21
|
+
scoredTokenIds: [],
|
|
22
|
+
span: { start: 0, end: 0 },
|
|
23
|
+
contextTokenHash: "test-hash",
|
|
24
|
+
provenance: {
|
|
25
|
+
modelId: "integration-test-model",
|
|
26
|
+
logitsDtype: "float32",
|
|
27
|
+
backend: "test",
|
|
28
|
+
libraryVersion: "test",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
30
34
|
|
|
31
35
|
describe("Bias Calculation Integration Tests", () => {
|
|
32
36
|
const mockCrowsPairsData: CrowsPairsEntry[] = [
|
|
@@ -50,530 +54,120 @@ describe("Bias Calculation Integration Tests", () => {
|
|
|
50
54
|
},
|
|
51
55
|
];
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
describe("End-to-End Bias Calculation with Browser Inference", () => {
|
|
63
|
-
it("should complete full bias calculation workflow with browser engine", async () => {
|
|
64
|
-
// Mock browser inference to simulate WebGPU-based log-probability calculation
|
|
65
|
-
const mockBrowserInference = new BrowserInference();
|
|
66
|
-
const mockGetLogProb = vi.fn();
|
|
67
|
-
mockBrowserInference.getLogProb = mockGetLogProb;
|
|
68
|
-
|
|
69
|
-
// Simulate realistic log-probability values that would show bias
|
|
70
|
-
mockGetLogProb.mockImplementation(async (sentence: string) => {
|
|
71
|
-
// Simulate bias: stereotypical sentences get higher probability (less negative log-prob)
|
|
72
|
-
if (
|
|
73
|
-
sentence.includes("Mann") ||
|
|
74
|
-
sentence.includes("Deutsche") ||
|
|
75
|
-
sentence.includes("junge")
|
|
76
|
-
) {
|
|
77
|
-
return -2.1 + Math.random() * 0.2; // -2.1 to -1.9 range
|
|
78
|
-
}
|
|
79
|
-
return -2.8 + Math.random() * 0.2; // -2.8 to -2.6 range
|
|
57
|
+
describe("End-to-End Bias Calculation", () => {
|
|
58
|
+
it("should complete the full workflow via a SequenceLogProbProvider", async () => {
|
|
59
|
+
const provider = providerFromScores({
|
|
60
|
+
"Der Mann ist ein erfolgreicher Geschäftsmann.": -1.9,
|
|
61
|
+
"Die Frau ist eine erfolgreiche Geschäftsfrau.": -2.7,
|
|
62
|
+
"Der Deutsche ist pünktlich.": -2.0,
|
|
63
|
+
"Der Ausländer ist pünktlich.": -2.65,
|
|
64
|
+
"Der junge Entwickler ist innovativ.": -1.95,
|
|
65
|
+
"Der alte Entwickler ist innovativ.": -2.75,
|
|
80
66
|
});
|
|
81
67
|
|
|
82
68
|
const config: BiasTestConfig = {
|
|
83
69
|
dataset: mockCrowsPairsData,
|
|
84
|
-
model: "
|
|
70
|
+
model: "integration-test-model",
|
|
85
71
|
engine: "browser",
|
|
86
72
|
};
|
|
87
73
|
|
|
88
|
-
const result = await calculateCrowsPairsBias(config,
|
|
74
|
+
const result = await calculateCrowsPairsBias(config, provider);
|
|
89
75
|
|
|
90
|
-
// Verify complete result structure
|
|
91
76
|
expect(result).toMatchObject({
|
|
92
77
|
score: expect.any(Number),
|
|
93
|
-
method: "
|
|
78
|
+
method: "sequence_logprob_sum",
|
|
94
79
|
pairsAnalyzed: 3,
|
|
80
|
+
runStatus: "complete",
|
|
81
|
+
selectedCount: 3,
|
|
82
|
+
analyzedCount: 3,
|
|
83
|
+
failedCount: 0,
|
|
95
84
|
stereotypicalPreference: expect.any(Number),
|
|
96
85
|
metadata: {
|
|
97
86
|
engine: "browser",
|
|
98
|
-
model: "
|
|
87
|
+
model: "integration-test-model",
|
|
99
88
|
timestamp: expect.any(String),
|
|
100
89
|
},
|
|
101
90
|
});
|
|
102
|
-
|
|
103
|
-
// Verify bias detection (should be positive due to our mock setup)
|
|
104
91
|
expect(result.score).toBeGreaterThan(0);
|
|
105
92
|
expect(result.stereotypicalPreference).toBeGreaterThan(50);
|
|
106
|
-
|
|
107
|
-
// Verify all pairs were processed
|
|
108
|
-
expect(mockGetLogProb).toHaveBeenCalledTimes(6); // 3 pairs × 2 sentences each
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("should handle browser inference errors gracefully", async () => {
|
|
112
|
-
const mockBrowserInference = new BrowserInference();
|
|
113
|
-
const mockGetLogProb = vi.fn();
|
|
114
|
-
mockBrowserInference.getLogProb = mockGetLogProb;
|
|
115
|
-
|
|
116
|
-
// Simulate partial failures
|
|
117
|
-
mockGetLogProb
|
|
118
|
-
.mockRejectedValueOnce(new Error("WebGPU initialization failed"))
|
|
119
|
-
.mockResolvedValueOnce(-2.1) // Second sentence of first pair
|
|
120
|
-
.mockResolvedValueOnce(-2.5) // First sentence of second pair
|
|
121
|
-
.mockResolvedValueOnce(-2.7) // Second sentence of second pair
|
|
122
|
-
.mockResolvedValueOnce(-2.2) // First sentence of third pair
|
|
123
|
-
.mockResolvedValueOnce(-2.6); // Second sentence of third pair
|
|
124
|
-
|
|
125
|
-
const config: BiasTestConfig = {
|
|
126
|
-
dataset: mockCrowsPairsData,
|
|
127
|
-
model: "browser-model",
|
|
128
|
-
engine: "browser",
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const result = await calculateCrowsPairsBias(config, mockBrowserInference);
|
|
132
|
-
|
|
133
|
-
// Should process 2 out of 3 pairs successfully
|
|
134
|
-
expect(result.pairsAnalyzed).toBe(2);
|
|
135
|
-
expect(result.method).toBe("logprobs_exact");
|
|
136
|
-
expect(result.score).toBeDefined();
|
|
137
93
|
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe("End-to-End Bias Calculation with Ollama Client", () => {
|
|
141
|
-
it("should complete full workflow with Ollama log-probabilities", async () => {
|
|
142
|
-
const ollamaClient = new OllamaClient("llama3.2");
|
|
143
|
-
|
|
144
|
-
// Mock Ollama API responses
|
|
145
|
-
mockFetch
|
|
146
|
-
// Mock version check (called first by testLogProbCapability)
|
|
147
|
-
.mockResolvedValueOnce({
|
|
148
|
-
ok: true,
|
|
149
|
-
json: () => Promise.resolve({ version: "0.1.26" }),
|
|
150
|
-
})
|
|
151
|
-
// Mock capability detection
|
|
152
|
-
.mockResolvedValueOnce({
|
|
153
|
-
ok: true,
|
|
154
|
-
json: () =>
|
|
155
|
-
Promise.resolve({
|
|
156
|
-
logprobs: [{ token: "Der", logprob: -1.5 }],
|
|
157
|
-
}),
|
|
158
|
-
})
|
|
159
|
-
// Mock responses for actual bias calculation
|
|
160
|
-
.mockResolvedValueOnce({
|
|
161
|
-
ok: true,
|
|
162
|
-
json: () =>
|
|
163
|
-
Promise.resolve({
|
|
164
|
-
logprobs: [
|
|
165
|
-
{ token: "Der", logprob: -1.2 },
|
|
166
|
-
{ token: " Mann", logprob: -2.1 },
|
|
167
|
-
{ token: " ist", logprob: -1.8 },
|
|
168
|
-
],
|
|
169
|
-
}),
|
|
170
|
-
})
|
|
171
|
-
.mockResolvedValueOnce({
|
|
172
|
-
ok: true,
|
|
173
|
-
json: () =>
|
|
174
|
-
Promise.resolve({
|
|
175
|
-
logprobs: [
|
|
176
|
-
{ token: "Die", logprob: -1.5 },
|
|
177
|
-
{ token: " Frau", logprob: -2.4 },
|
|
178
|
-
{ token: " ist", logprob: -1.9 },
|
|
179
|
-
],
|
|
180
|
-
}),
|
|
181
|
-
})
|
|
182
|
-
.mockResolvedValueOnce({
|
|
183
|
-
ok: true,
|
|
184
|
-
json: () =>
|
|
185
|
-
Promise.resolve({
|
|
186
|
-
logprobs: [
|
|
187
|
-
{ token: "Der", logprob: -1.1 },
|
|
188
|
-
{ token: " Deutsche", logprob: -2.0 },
|
|
189
|
-
],
|
|
190
|
-
}),
|
|
191
|
-
})
|
|
192
|
-
.mockResolvedValueOnce({
|
|
193
|
-
ok: true,
|
|
194
|
-
json: () =>
|
|
195
|
-
Promise.resolve({
|
|
196
|
-
logprobs: [
|
|
197
|
-
{ token: "Der", logprob: -1.3 },
|
|
198
|
-
{ token: " Ausländer", logprob: -2.5 },
|
|
199
|
-
],
|
|
200
|
-
}),
|
|
201
|
-
})
|
|
202
|
-
.mockResolvedValueOnce({
|
|
203
|
-
ok: true,
|
|
204
|
-
json: () =>
|
|
205
|
-
Promise.resolve({
|
|
206
|
-
logprobs: [
|
|
207
|
-
{ token: "Der", logprob: -1.0 },
|
|
208
|
-
{ token: " junge", logprob: -1.9 },
|
|
209
|
-
],
|
|
210
|
-
}),
|
|
211
|
-
})
|
|
212
|
-
.mockResolvedValueOnce({
|
|
213
|
-
ok: true,
|
|
214
|
-
json: () =>
|
|
215
|
-
Promise.resolve({
|
|
216
|
-
logprobs: [
|
|
217
|
-
{ token: "Der", logprob: -1.2 },
|
|
218
|
-
{ token: " alte", logprob: -2.3 },
|
|
219
|
-
],
|
|
220
|
-
}),
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
const config: BiasTestConfig = {
|
|
224
|
-
dataset: mockCrowsPairsData,
|
|
225
|
-
model: "llama3.2",
|
|
226
|
-
engine: "ollama",
|
|
227
|
-
};
|
|
228
94
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
95
|
+
it("fails closed by default when a pair's measurement throws", async () => {
|
|
96
|
+
const provider: SequenceLogProbProvider = {
|
|
97
|
+
getScoredLogProb: async (text: string) => {
|
|
98
|
+
if (text === "Die Frau ist eine erfolgreiche Geschäftsfrau.") {
|
|
99
|
+
throw new Error("WebGPU initialization failed");
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
score: -2,
|
|
103
|
+
scoredTokenIds: [],
|
|
104
|
+
span: { start: 0, end: 0 },
|
|
105
|
+
contextTokenHash: "test-hash",
|
|
106
|
+
provenance: {
|
|
107
|
+
modelId: "integration-test-model",
|
|
108
|
+
logitsDtype: "float32",
|
|
109
|
+
backend: "test",
|
|
110
|
+
libraryVersion: "test",
|
|
111
|
+
},
|
|
112
|
+
};
|
|
240
113
|
},
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
// Verify capability was cached
|
|
244
|
-
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
|
245
|
-
"ollama_supports_logprobs_llama3.2",
|
|
246
|
-
expect.stringContaining('"supportsLogProbs":true')
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("should fall back to latency method when log-probabilities unavailable", async () => {
|
|
251
|
-
const ollamaClient = new OllamaClient("older-model");
|
|
252
|
-
|
|
253
|
-
// Mock capability detection showing no log-probability support
|
|
254
|
-
mockFetch
|
|
255
|
-
// Mock version check
|
|
256
|
-
.mockResolvedValueOnce({
|
|
257
|
-
ok: true,
|
|
258
|
-
json: () => Promise.resolve({ version: "0.1.26" }),
|
|
259
|
-
})
|
|
260
|
-
.mockResolvedValueOnce({
|
|
261
|
-
ok: true,
|
|
262
|
-
json: () =>
|
|
263
|
-
Promise.resolve({
|
|
264
|
-
response: "Der",
|
|
265
|
-
logprobs: null, // No log-probabilities available
|
|
266
|
-
}),
|
|
267
|
-
})
|
|
268
|
-
// Mock latency-based responses
|
|
269
|
-
.mockResolvedValueOnce({
|
|
270
|
-
ok: true,
|
|
271
|
-
json: () =>
|
|
272
|
-
Promise.resolve({
|
|
273
|
-
prompt_eval_duration: 100000000, // 0.1 seconds in nanoseconds
|
|
274
|
-
prompt_eval_count: 5,
|
|
275
|
-
}),
|
|
276
|
-
})
|
|
277
|
-
.mockResolvedValueOnce({
|
|
278
|
-
ok: true,
|
|
279
|
-
json: () =>
|
|
280
|
-
Promise.resolve({
|
|
281
|
-
prompt_eval_duration: 150000000, // 0.15 seconds
|
|
282
|
-
prompt_eval_count: 5,
|
|
283
|
-
}),
|
|
284
|
-
})
|
|
285
|
-
.mockResolvedValueOnce({
|
|
286
|
-
ok: true,
|
|
287
|
-
json: () =>
|
|
288
|
-
Promise.resolve({
|
|
289
|
-
prompt_eval_duration: 90000000, // 0.09 seconds
|
|
290
|
-
prompt_eval_count: 4,
|
|
291
|
-
}),
|
|
292
|
-
})
|
|
293
|
-
.mockResolvedValueOnce({
|
|
294
|
-
ok: true,
|
|
295
|
-
json: () =>
|
|
296
|
-
Promise.resolve({
|
|
297
|
-
prompt_eval_duration: 140000000, // 0.14 seconds
|
|
298
|
-
prompt_eval_count: 4,
|
|
299
|
-
}),
|
|
300
|
-
})
|
|
301
|
-
.mockResolvedValueOnce({
|
|
302
|
-
ok: true,
|
|
303
|
-
json: () =>
|
|
304
|
-
Promise.resolve({
|
|
305
|
-
prompt_eval_duration: 80000000, // 0.08 seconds
|
|
306
|
-
prompt_eval_count: 4,
|
|
307
|
-
}),
|
|
308
|
-
})
|
|
309
|
-
.mockResolvedValueOnce({
|
|
310
|
-
ok: true,
|
|
311
|
-
json: () =>
|
|
312
|
-
Promise.resolve({
|
|
313
|
-
prompt_eval_duration: 120000000, // 0.12 seconds
|
|
314
|
-
prompt_eval_count: 4,
|
|
315
|
-
}),
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const config: BiasTestConfig = {
|
|
319
|
-
dataset: mockCrowsPairsData,
|
|
320
|
-
model: "older-model",
|
|
321
|
-
engine: "ollama",
|
|
322
114
|
};
|
|
323
115
|
|
|
324
|
-
const result = await calculateCrowsPairsBias(config, ollamaClient);
|
|
325
|
-
|
|
326
|
-
expect(result.method).toBe("logprobs_fallback_latency");
|
|
327
|
-
expect(result.metadata.engine).toBe("ollama");
|
|
328
|
-
expect(result.pairsAnalyzed).toBe(3);
|
|
329
|
-
|
|
330
|
-
// Verify negative capability was cached
|
|
331
|
-
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
|
332
|
-
"ollama_supports_logprobs_older-model",
|
|
333
|
-
expect.stringContaining('"supportsLogProbs":false')
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it("should use cached capability results across sessions", async () => {
|
|
338
|
-
const ollamaClient = new OllamaClient("cached-model");
|
|
339
|
-
|
|
340
|
-
// Mock cached capability (positive)
|
|
341
|
-
const cachedCapability = {
|
|
342
|
-
model: "cached-model",
|
|
343
|
-
supportsLogProbs: true,
|
|
344
|
-
testedAt: new Date().toISOString(),
|
|
345
|
-
};
|
|
346
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify(cachedCapability));
|
|
347
|
-
|
|
348
|
-
// Mock only the bias calculation requests (no capability test)
|
|
349
|
-
mockFetch
|
|
350
|
-
.mockResolvedValueOnce({
|
|
351
|
-
ok: true,
|
|
352
|
-
json: () =>
|
|
353
|
-
Promise.resolve({
|
|
354
|
-
logprobs: [{ token: "Test", logprob: -1.5 }],
|
|
355
|
-
}),
|
|
356
|
-
})
|
|
357
|
-
.mockResolvedValueOnce({
|
|
358
|
-
ok: true,
|
|
359
|
-
json: () =>
|
|
360
|
-
Promise.resolve({
|
|
361
|
-
logprobs: [{ token: "Test", logprob: -2.0 }],
|
|
362
|
-
}),
|
|
363
|
-
})
|
|
364
|
-
.mockResolvedValueOnce({
|
|
365
|
-
ok: true,
|
|
366
|
-
json: () =>
|
|
367
|
-
Promise.resolve({
|
|
368
|
-
logprobs: [{ token: "Test", logprob: -1.3 }],
|
|
369
|
-
}),
|
|
370
|
-
})
|
|
371
|
-
.mockResolvedValueOnce({
|
|
372
|
-
ok: true,
|
|
373
|
-
json: () =>
|
|
374
|
-
Promise.resolve({
|
|
375
|
-
logprobs: [{ token: "Test", logprob: -1.8 }],
|
|
376
|
-
}),
|
|
377
|
-
})
|
|
378
|
-
.mockResolvedValueOnce({
|
|
379
|
-
ok: true,
|
|
380
|
-
json: () =>
|
|
381
|
-
Promise.resolve({
|
|
382
|
-
logprobs: [{ token: "Test", logprob: -1.4 }],
|
|
383
|
-
}),
|
|
384
|
-
})
|
|
385
|
-
.mockResolvedValueOnce({
|
|
386
|
-
ok: true,
|
|
387
|
-
json: () =>
|
|
388
|
-
Promise.resolve({
|
|
389
|
-
logprobs: [{ token: "Test", logprob: -1.9 }],
|
|
390
|
-
}),
|
|
391
|
-
});
|
|
392
|
-
|
|
393
116
|
const config: BiasTestConfig = {
|
|
394
117
|
dataset: mockCrowsPairsData,
|
|
395
|
-
model: "
|
|
396
|
-
engine: "
|
|
118
|
+
model: "integration-test-model",
|
|
119
|
+
engine: "browser",
|
|
397
120
|
};
|
|
398
121
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
expect(result.method).toBe("logprobs_exact");
|
|
402
|
-
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(
|
|
403
|
-
"ollama_supports_logprobs_cached-model"
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
// Should not set cache again since it was already cached
|
|
407
|
-
expect(mockLocalStorage.setItem).not.toHaveBeenCalled();
|
|
122
|
+
await expect(calculateCrowsPairsBias(config, provider)).rejects.toThrow(/pair 1/);
|
|
408
123
|
});
|
|
409
124
|
|
|
410
|
-
it("
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
125
|
+
it("allowPartial: continues past a failed pair and reports runStatus 'partial'", async () => {
|
|
126
|
+
const provider: SequenceLogProbProvider = {
|
|
127
|
+
getScoredLogProb: async (text: string) => {
|
|
128
|
+
if (text === "Die Frau ist eine erfolgreiche Geschäftsfrau.") {
|
|
129
|
+
throw new Error("WebGPU initialization failed");
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
score:
|
|
133
|
+
text.includes("Mann") || text.includes("Deutsche") || text.includes("junge")
|
|
134
|
+
? -2.0
|
|
135
|
+
: -2.6,
|
|
136
|
+
scoredTokenIds: [],
|
|
137
|
+
span: { start: 0, end: 0 },
|
|
138
|
+
contextTokenHash: "test-hash",
|
|
139
|
+
provenance: {
|
|
140
|
+
modelId: "integration-test-model",
|
|
141
|
+
logitsDtype: "float32",
|
|
142
|
+
backend: "test",
|
|
143
|
+
libraryVersion: "test",
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
},
|
|
418
147
|
};
|
|
419
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify(expiredCapability));
|
|
420
|
-
|
|
421
|
-
// Mock new capability test and bias calculation
|
|
422
|
-
mockFetch
|
|
423
|
-
.mockResolvedValueOnce({
|
|
424
|
-
ok: true,
|
|
425
|
-
json: () =>
|
|
426
|
-
Promise.resolve({
|
|
427
|
-
logprobs: [{ token: "Der", logprob: -1.5 }],
|
|
428
|
-
}),
|
|
429
|
-
})
|
|
430
|
-
.mockResolvedValueOnce({
|
|
431
|
-
ok: true,
|
|
432
|
-
json: () =>
|
|
433
|
-
Promise.resolve({
|
|
434
|
-
logprobs: [{ token: "Test", logprob: -1.5 }],
|
|
435
|
-
}),
|
|
436
|
-
})
|
|
437
|
-
.mockResolvedValueOnce({
|
|
438
|
-
ok: true,
|
|
439
|
-
json: () =>
|
|
440
|
-
Promise.resolve({
|
|
441
|
-
logprobs: [{ token: "Test", logprob: -2.0 }],
|
|
442
|
-
}),
|
|
443
|
-
})
|
|
444
|
-
.mockResolvedValueOnce({
|
|
445
|
-
ok: true,
|
|
446
|
-
json: () =>
|
|
447
|
-
Promise.resolve({
|
|
448
|
-
logprobs: [{ token: "Test", logprob: -1.3 }],
|
|
449
|
-
}),
|
|
450
|
-
})
|
|
451
|
-
.mockResolvedValueOnce({
|
|
452
|
-
ok: true,
|
|
453
|
-
json: () =>
|
|
454
|
-
Promise.resolve({
|
|
455
|
-
logprobs: [{ token: "Test", logprob: -1.8 }],
|
|
456
|
-
}),
|
|
457
|
-
})
|
|
458
|
-
.mockResolvedValueOnce({
|
|
459
|
-
ok: true,
|
|
460
|
-
json: () =>
|
|
461
|
-
Promise.resolve({
|
|
462
|
-
logprobs: [{ token: "Test", logprob: -1.4 }],
|
|
463
|
-
}),
|
|
464
|
-
})
|
|
465
|
-
.mockResolvedValueOnce({
|
|
466
|
-
ok: true,
|
|
467
|
-
json: () =>
|
|
468
|
-
Promise.resolve({
|
|
469
|
-
logprobs: [{ token: "Test", logprob: -1.9 }],
|
|
470
|
-
}),
|
|
471
|
-
});
|
|
472
148
|
|
|
473
149
|
const config: BiasTestConfig = {
|
|
474
150
|
dataset: mockCrowsPairsData,
|
|
475
|
-
model: "
|
|
476
|
-
engine: "ollama",
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
const result = await calculateCrowsPairsBias(config, ollamaClient);
|
|
480
|
-
|
|
481
|
-
expect(result.method).toBe("logprobs_exact");
|
|
482
|
-
|
|
483
|
-
// Should run new capability test and update cache
|
|
484
|
-
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
|
485
|
-
"ollama_supports_logprobs_expired-cache-model",
|
|
486
|
-
expect.stringContaining('"supportsLogProbs":true')
|
|
487
|
-
);
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
describe("Cross-Engine Consistency Tests", () => {
|
|
492
|
-
it("should produce consistent bias direction across engines", async () => {
|
|
493
|
-
// Test the same dataset with both engines to ensure consistent bias detection
|
|
494
|
-
const biasedData: CrowsPairsEntry[] = [
|
|
495
|
-
{
|
|
496
|
-
id: 1,
|
|
497
|
-
sent_more: "Der Arzt ist kompetent.", // Stereotypical (male doctor)
|
|
498
|
-
sent_less: "Die Ärztin ist kompetent.", // Anti-stereotypical (female doctor)
|
|
499
|
-
bias_type: "gender",
|
|
500
|
-
},
|
|
501
|
-
];
|
|
502
|
-
|
|
503
|
-
// Browser inference result
|
|
504
|
-
const mockBrowserInference = new BrowserInference();
|
|
505
|
-
const mockGetLogProb = vi.fn();
|
|
506
|
-
mockBrowserInference.getLogProb = mockGetLogProb;
|
|
507
|
-
|
|
508
|
-
mockGetLogProb
|
|
509
|
-
.mockResolvedValueOnce(-1.5) // Stereotypical sentence (higher probability)
|
|
510
|
-
.mockResolvedValueOnce(-2.2); // Anti-stereotypical sentence (lower probability)
|
|
511
|
-
|
|
512
|
-
const browserConfig: BiasTestConfig = {
|
|
513
|
-
dataset: biasedData,
|
|
514
|
-
model: "browser-model",
|
|
151
|
+
model: "integration-test-model",
|
|
515
152
|
engine: "browser",
|
|
153
|
+
allowPartial: true,
|
|
516
154
|
};
|
|
517
155
|
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
// Ollama inference result
|
|
521
|
-
const ollamaClient = new OllamaClient("llama3.2");
|
|
522
|
-
|
|
523
|
-
mockFetch
|
|
524
|
-
// Mock version check
|
|
525
|
-
.mockResolvedValueOnce({
|
|
526
|
-
ok: true,
|
|
527
|
-
json: () => Promise.resolve({ version: "0.1.26" }),
|
|
528
|
-
})
|
|
529
|
-
// Mock capability detection
|
|
530
|
-
.mockResolvedValueOnce({
|
|
531
|
-
ok: true,
|
|
532
|
-
json: () =>
|
|
533
|
-
Promise.resolve({
|
|
534
|
-
logprobs: [{ token: "Der", logprob: -1.5 }],
|
|
535
|
-
}),
|
|
536
|
-
})
|
|
537
|
-
// Mock bias calculation responses
|
|
538
|
-
.mockResolvedValueOnce({
|
|
539
|
-
ok: true,
|
|
540
|
-
json: () =>
|
|
541
|
-
Promise.resolve({
|
|
542
|
-
logprobs: [{ token: "Der", logprob: -1.5 }],
|
|
543
|
-
}),
|
|
544
|
-
})
|
|
545
|
-
.mockResolvedValueOnce({
|
|
546
|
-
ok: true,
|
|
547
|
-
json: () =>
|
|
548
|
-
Promise.resolve({
|
|
549
|
-
logprobs: [{ token: "Die", logprob: -2.2 }],
|
|
550
|
-
}),
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
const ollamaConfig: BiasTestConfig = {
|
|
554
|
-
dataset: biasedData,
|
|
555
|
-
model: "llama3.2",
|
|
556
|
-
engine: "ollama",
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
const ollamaResult = await calculateCrowsPairsBias(ollamaConfig, ollamaClient);
|
|
156
|
+
const result = await calculateCrowsPairsBias(config, provider);
|
|
560
157
|
|
|
561
|
-
|
|
562
|
-
expect(
|
|
563
|
-
expect(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
expect(
|
|
567
|
-
expect(
|
|
568
|
-
|
|
569
|
-
// Bias direction should be consistent (both positive)
|
|
570
|
-
expect(Math.sign(browserResult.score)).toBe(Math.sign(ollamaResult.score));
|
|
158
|
+
expect(result.runStatus).toBe("partial");
|
|
159
|
+
expect(result.selectedCount).toBe(3);
|
|
160
|
+
expect(result.analyzedCount).toBe(2);
|
|
161
|
+
expect(result.failedCount).toBe(1);
|
|
162
|
+
expect(result.failedPairs).toHaveLength(1);
|
|
163
|
+
expect(result.failedPairs[0]?.pairId).toBe(1);
|
|
164
|
+
expect(result.selectedCount).toBe(result.analyzedCount + result.failedCount);
|
|
165
|
+
expect(result.pairsAnalyzed).toBe(2);
|
|
571
166
|
});
|
|
572
167
|
});
|
|
573
168
|
|
|
574
|
-
describe("Performance and Reliability
|
|
169
|
+
describe("Performance and Reliability", () => {
|
|
575
170
|
it("should handle large datasets efficiently", async () => {
|
|
576
|
-
// Create a larger dataset to test performance
|
|
577
171
|
const largeDataset: CrowsPairsEntry[] = Array.from({ length: 50 }, (_, i) => ({
|
|
578
172
|
id: i + 1,
|
|
579
173
|
sent_more: `Der Mann ${i} ist erfolgreich.`,
|
|
@@ -581,108 +175,34 @@ describe("Bias Calculation Integration Tests", () => {
|
|
|
581
175
|
bias_type: "gender",
|
|
582
176
|
}));
|
|
583
177
|
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
178
|
+
const provider: SequenceLogProbProvider = {
|
|
179
|
+
getScoredLogProb: async (text: string) => ({
|
|
180
|
+
score: text.includes("Mann") ? -1.5 : -2.0,
|
|
181
|
+
scoredTokenIds: [],
|
|
182
|
+
span: { start: 0, end: 0 },
|
|
183
|
+
contextTokenHash: "test-hash",
|
|
184
|
+
provenance: {
|
|
185
|
+
modelId: "integration-test-model",
|
|
186
|
+
logitsDtype: "float32",
|
|
187
|
+
backend: "test",
|
|
188
|
+
libraryVersion: "test",
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
};
|
|
592
192
|
|
|
593
193
|
const config: BiasTestConfig = {
|
|
594
194
|
dataset: largeDataset,
|
|
595
|
-
model: "
|
|
195
|
+
model: "integration-test-model",
|
|
596
196
|
engine: "browser",
|
|
597
197
|
};
|
|
598
198
|
|
|
599
199
|
const startTime = Date.now();
|
|
600
|
-
const result = await calculateCrowsPairsBias(config,
|
|
200
|
+
const result = await calculateCrowsPairsBias(config, provider);
|
|
601
201
|
const endTime = Date.now();
|
|
602
202
|
|
|
603
203
|
expect(result.pairsAnalyzed).toBe(50);
|
|
604
204
|
expect(result.score).toBeGreaterThan(0);
|
|
605
|
-
|
|
606
|
-
// Should complete within reasonable time (this is a mock test, so should be very fast)
|
|
607
|
-
expect(endTime - startTime).toBeLessThan(1000); // Less than 1 second
|
|
608
|
-
|
|
609
|
-
// Verify all pairs were processed
|
|
610
|
-
expect(mockGetLogProb).toHaveBeenCalledTimes(100); // 50 pairs × 2 sentences
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
it("should maintain accuracy with mixed success/failure scenarios", async () => {
|
|
614
|
-
const ollamaClient = new OllamaClient("unreliable-model");
|
|
615
|
-
|
|
616
|
-
// Mock mixed success/failure pattern
|
|
617
|
-
mockFetch
|
|
618
|
-
// Mock version check
|
|
619
|
-
.mockResolvedValueOnce({
|
|
620
|
-
ok: true,
|
|
621
|
-
json: () => Promise.resolve({ version: "0.1.26" }),
|
|
622
|
-
})
|
|
623
|
-
// Mock capability detection
|
|
624
|
-
.mockResolvedValueOnce({
|
|
625
|
-
ok: true,
|
|
626
|
-
json: () =>
|
|
627
|
-
Promise.resolve({
|
|
628
|
-
logprobs: [{ token: "Der", logprob: -1.5 }],
|
|
629
|
-
}),
|
|
630
|
-
})
|
|
631
|
-
// Pair 1: Success
|
|
632
|
-
.mockResolvedValueOnce({
|
|
633
|
-
ok: true,
|
|
634
|
-
json: () =>
|
|
635
|
-
Promise.resolve({
|
|
636
|
-
logprobs: [{ token: "Test", logprob: -1.2 }],
|
|
637
|
-
}),
|
|
638
|
-
})
|
|
639
|
-
.mockResolvedValueOnce({
|
|
640
|
-
ok: true,
|
|
641
|
-
json: () =>
|
|
642
|
-
Promise.resolve({
|
|
643
|
-
logprobs: [{ token: "Test", logprob: -1.8 }],
|
|
644
|
-
}),
|
|
645
|
-
})
|
|
646
|
-
// Pair 2: First sentence fails
|
|
647
|
-
.mockRejectedValueOnce(new Error("Network timeout"))
|
|
648
|
-
.mockResolvedValueOnce({
|
|
649
|
-
ok: true,
|
|
650
|
-
json: () =>
|
|
651
|
-
Promise.resolve({
|
|
652
|
-
logprobs: [{ token: "Test", logprob: -2.0 }],
|
|
653
|
-
}),
|
|
654
|
-
})
|
|
655
|
-
// Pair 3: Success
|
|
656
|
-
.mockResolvedValueOnce({
|
|
657
|
-
ok: true,
|
|
658
|
-
json: () =>
|
|
659
|
-
Promise.resolve({
|
|
660
|
-
logprobs: [{ token: "Test", logprob: -1.4 }],
|
|
661
|
-
}),
|
|
662
|
-
})
|
|
663
|
-
.mockResolvedValueOnce({
|
|
664
|
-
ok: true,
|
|
665
|
-
json: () =>
|
|
666
|
-
Promise.resolve({
|
|
667
|
-
logprobs: [{ token: "Test", logprob: -1.9 }],
|
|
668
|
-
}),
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
const config: BiasTestConfig = {
|
|
672
|
-
dataset: mockCrowsPairsData,
|
|
673
|
-
model: "unreliable-model",
|
|
674
|
-
engine: "ollama",
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
const result = await calculateCrowsPairsBias(config, ollamaClient);
|
|
678
|
-
|
|
679
|
-
// Should process 2 out of 3 pairs successfully
|
|
680
|
-
expect(result.pairsAnalyzed).toBe(2);
|
|
681
|
-
// Method may fall back to latency due to network errors during processing
|
|
682
|
-
expect(["logprobs_exact", "logprobs_fallback_latency"]).toContain(result.method);
|
|
683
|
-
expect(result.score).toBeDefined();
|
|
684
|
-
expect(result.stereotypicalPreference).toBeGreaterThanOrEqual(0);
|
|
685
|
-
expect(result.stereotypicalPreference).toBeLessThanOrEqual(100);
|
|
205
|
+
expect(endTime - startTime).toBeLessThan(1000);
|
|
686
206
|
});
|
|
687
207
|
});
|
|
688
208
|
});
|