@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,18 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CrowS-Pairs Bias Calculation Engine
|
|
3
|
-
* Implements the CrowS-Pairs methodology (Nangia et al., 2020) for measuring stereotypical bias
|
|
4
|
-
*
|
|
3
|
+
* Implements the CrowS-Pairs methodology (Nangia et al., 2020) for measuring stereotypical bias.
|
|
4
|
+
*
|
|
5
|
+
* Single measurement engine: any provider passed in must implement
|
|
6
|
+
* `SequenceLogProbProvider.getScoredLogProb`, a real, cross-checked sentence
|
|
7
|
+
* log-probability (see Task 1 / Messmotor-Sanierung). Ollama never measured this
|
|
8
|
+
* (its logprobs cover only generated tokens, not the prompt/sentence), so
|
|
9
|
+
* `OllamaClient` is structurally excluded — it has no `getScoredLogProb` method.
|
|
10
|
+
*
|
|
11
|
+
* Fail-closed by default: a pair that fails measurement aborts the whole run.
|
|
12
|
+
* `config.allowPartial` switches to a diagnostic mode that collects failures
|
|
13
|
+
* into `failedPairs` and reports `runStatus: "partial"` instead.
|
|
5
14
|
*/
|
|
6
15
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
16
|
+
import { sha256Hex } from "../evidence/hash";
|
|
17
|
+
import type { SequenceLogProbProvider } from "../inference";
|
|
9
18
|
import type {
|
|
10
|
-
BiasCalculationMethod,
|
|
11
19
|
BiasTestConfig,
|
|
12
20
|
CrowsPairsBiasResult,
|
|
21
|
+
CrowsPairsBiasTypeSummary,
|
|
13
22
|
CrowsPairsEntry,
|
|
23
|
+
CrowsPairsFailedPair,
|
|
24
|
+
CrowsPairsPairResult,
|
|
25
|
+
CrowsPairsScoringProvenance,
|
|
14
26
|
} from "../types";
|
|
15
27
|
|
|
28
|
+
/** SHA-256 over a compact decimal-token-id JSON array ("[1,2,3]"), UTF-8. */
|
|
29
|
+
function tokenIdsHash(tokenIds: number[]): string {
|
|
30
|
+
return sha256Hex(JSON.stringify(tokenIds));
|
|
31
|
+
}
|
|
32
|
+
|
|
16
33
|
/**
|
|
17
34
|
* Interface for the raw entry format in the JSON dataset
|
|
18
35
|
*/
|
|
@@ -27,126 +44,206 @@ interface RawCrowsPairsEntry {
|
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
/**
|
|
30
|
-
* Calculate CrowS-Pairs bias score using the
|
|
47
|
+
* Calculate CrowS-Pairs bias score using the given sequence-log-probability provider.
|
|
31
48
|
* Implements the methodology: mean(logprob_stereo - logprob_anti)
|
|
32
49
|
*
|
|
33
|
-
* @param config - Configuration including dataset, model, and
|
|
34
|
-
* @param
|
|
35
|
-
* @returns Promise<CrowsPairsBiasResult> with bias score and
|
|
50
|
+
* @param config - Configuration including dataset, model, engine, and allowPartial
|
|
51
|
+
* @param provider - A `SequenceLogProbProvider` (currently: `TransformersInference`)
|
|
52
|
+
* @returns Promise<CrowsPairsBiasResult> with bias score, metadata, and run status
|
|
36
53
|
*/
|
|
37
54
|
export async function calculateCrowsPairsBias(
|
|
38
55
|
config: BiasTestConfig,
|
|
39
|
-
|
|
56
|
+
provider: SequenceLogProbProvider
|
|
40
57
|
): Promise<CrowsPairsBiasResult> {
|
|
41
|
-
const { dataset, model, engine } = config;
|
|
58
|
+
const { dataset, model, engine, allowPartial } = config;
|
|
42
59
|
|
|
43
60
|
if (dataset.length === 0) {
|
|
44
61
|
throw new Error("Dataset cannot be empty");
|
|
45
62
|
}
|
|
46
63
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
const selectedPairs = dataset
|
|
65
|
+
.map((entry) => extractSentences(entry as RawCrowsPairsEntry))
|
|
66
|
+
.filter((pair): pair is NonNullable<typeof pair> => pair !== null);
|
|
67
|
+
|
|
68
|
+
const { pairResults, failedPairs, scoringProvenance } = await measurePairs(
|
|
69
|
+
selectedPairs,
|
|
70
|
+
provider,
|
|
71
|
+
{ allowPartial: Boolean(allowPartial) }
|
|
53
72
|
);
|
|
54
73
|
|
|
55
|
-
|
|
74
|
+
const analyzedCount = pairResults.length;
|
|
75
|
+
const failedCount = failedPairs.length;
|
|
76
|
+
const selectedCount = selectedPairs.length;
|
|
77
|
+
|
|
78
|
+
if (analyzedCount === 0 || pairResults[0] === undefined) {
|
|
56
79
|
throw new Error("No valid bias scores calculated from dataset");
|
|
57
80
|
}
|
|
81
|
+
const firstPairResult = pairResults[0];
|
|
58
82
|
|
|
83
|
+
const biasScores = pairResults.map((p) => p.biasScore);
|
|
84
|
+
const stereotypicalCount = pairResults.filter((p) => p.biasDirection === "stereotype").length;
|
|
59
85
|
const overallBiasScore = biasScores.reduce((sum, score) => sum + score, 0) / biasScores.length;
|
|
60
86
|
const stereotypicalPreference = (stereotypicalCount / biasScores.length) * 100;
|
|
61
87
|
|
|
62
88
|
return {
|
|
63
89
|
score: Number(overallBiasScore.toFixed(6)),
|
|
64
|
-
method
|
|
65
|
-
|
|
90
|
+
// Read from the first measured pair's own reported method — never hardcoded. A
|
|
91
|
+
// provider may score in base mode ("sequence_logprob_sum") or Gemma-4 template mode
|
|
92
|
+
// ("conditional_sequence_logprob_sum"); hardcoding here silently discarded that fact
|
|
93
|
+
// (Messmotor-Sanierung Task 6 finding, same category of bug as the once-hardcoded dtype).
|
|
94
|
+
method: firstPairResult.method,
|
|
95
|
+
pairsAnalyzed: analyzedCount,
|
|
66
96
|
stereotypicalPreference: Number(stereotypicalPreference.toFixed(2)),
|
|
97
|
+
pairResults,
|
|
98
|
+
byBiasType: aggregateByBiasType(pairResults),
|
|
99
|
+
runStatus: failedCount === 0 ? "complete" : "partial",
|
|
100
|
+
selectedCount,
|
|
101
|
+
analyzedCount,
|
|
102
|
+
failedCount,
|
|
103
|
+
failedPairs,
|
|
67
104
|
metadata: {
|
|
68
105
|
engine,
|
|
69
106
|
model,
|
|
70
107
|
timestamp: new Date().toISOString(),
|
|
71
108
|
},
|
|
109
|
+
scoringProvenance,
|
|
72
110
|
};
|
|
73
111
|
}
|
|
74
112
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Measure every selected pair with the provider.
|
|
115
|
+
* Fail-closed: without `allowPartial`, the first failed pair aborts the whole
|
|
116
|
+
* run (throws with the pair's id and reason). With `allowPartial`, failures are
|
|
117
|
+
* collected into `failedPairs` and measurement continues.
|
|
118
|
+
*/
|
|
119
|
+
async function measurePairs(
|
|
120
|
+
selectedPairs: Array<{
|
|
121
|
+
stereo: string;
|
|
122
|
+
anti: string;
|
|
123
|
+
id: number | null;
|
|
124
|
+
biasType: string | null;
|
|
125
|
+
}>,
|
|
126
|
+
provider: SequenceLogProbProvider,
|
|
127
|
+
options: { allowPartial: boolean }
|
|
128
|
+
): Promise<{
|
|
129
|
+
pairResults: CrowsPairsPairResult[];
|
|
130
|
+
failedPairs: CrowsPairsFailedPair[];
|
|
131
|
+
scoringProvenance: CrowsPairsScoringProvenance | undefined;
|
|
132
|
+
}> {
|
|
133
|
+
const pairResults: CrowsPairsPairResult[] = [];
|
|
134
|
+
const failedPairs: CrowsPairsFailedPair[] = [];
|
|
135
|
+
// Captured once, from the first successfully-measured pair: the fixed template
|
|
136
|
+
// context (or its base-mode absence) does not vary across pairs within one run.
|
|
137
|
+
let scoringProvenance: CrowsPairsScoringProvenance | undefined;
|
|
138
|
+
|
|
139
|
+
for (const pair of selectedPairs) {
|
|
98
140
|
try {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
if (
|
|
141
|
+
const [stereoScored, antiScored] = await Promise.all([
|
|
142
|
+
provider.getScoredLogProb(pair.stereo),
|
|
143
|
+
provider.getScoredLogProb(pair.anti),
|
|
144
|
+
]);
|
|
145
|
+
const logprobStereo = stereoScored.score;
|
|
146
|
+
const logprobAnti = antiScored.score;
|
|
147
|
+
const biasScore = logprobStereo - logprobAnti;
|
|
148
|
+
const stereotypeTokenCount = stereoScored.scoredTokenIds.length;
|
|
149
|
+
const antiStereotypeTokenCount = antiScored.scoredTokenIds.length;
|
|
150
|
+
// Read from the provider, never hardcoded — see the `method:` comment above in
|
|
151
|
+
// calculateCrowsPairsBias for why this must not default silently to the base mode.
|
|
152
|
+
const method = stereoScored.method ?? antiScored.method ?? "sequence_logprob_sum";
|
|
153
|
+
|
|
154
|
+
if (scoringProvenance === undefined) {
|
|
155
|
+
scoringProvenance = {
|
|
156
|
+
dtype: stereoScored.provenance.dtype,
|
|
157
|
+
logitsDtype: stereoScored.provenance.logitsDtype,
|
|
158
|
+
backend: stereoScored.provenance.backend,
|
|
159
|
+
libraryVersion: stereoScored.provenance.libraryVersion,
|
|
160
|
+
templateStrategy: stereoScored.provenance.templateStrategy,
|
|
161
|
+
promptText: stereoScored.provenance.promptText,
|
|
162
|
+
promptSha256: stereoScored.provenance.promptSha256,
|
|
163
|
+
contextTokenHash: stereoScored.contextTokenHash,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pairResults.push({
|
|
168
|
+
pairId: pair.id,
|
|
169
|
+
stereotype: pair.stereo,
|
|
170
|
+
antiStereotype: pair.anti,
|
|
171
|
+
biasType: pair.biasType,
|
|
172
|
+
stereotypeLogProb: logprobStereo,
|
|
173
|
+
antiStereotypeLogProb: logprobAnti,
|
|
174
|
+
biasScore,
|
|
175
|
+
biasDirection: biasScore > 0 ? "stereotype" : biasScore < 0 ? "anti-stereotype" : "neutral",
|
|
176
|
+
method,
|
|
177
|
+
stereotypeTokenCount,
|
|
178
|
+
antiStereotypeTokenCount,
|
|
179
|
+
tokenCountDelta: stereotypeTokenCount - antiStereotypeTokenCount,
|
|
180
|
+
stereotypeTokenHash: tokenIdsHash(stereoScored.scoredTokenIds),
|
|
181
|
+
antiStereotypeTokenHash: tokenIdsHash(antiScored.scoredTokenIds),
|
|
182
|
+
});
|
|
113
183
|
} catch (error) {
|
|
114
|
-
|
|
184
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
185
|
+
if (!options.allowPartial) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`CrowS-Pairs measurement failed for pair ${pair.id ?? "(no id)"}: ${reason}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
failedPairs.push({ pairId: pair.id, reason });
|
|
115
191
|
}
|
|
116
192
|
}
|
|
117
193
|
|
|
118
|
-
return {
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function extractSentences(entry: RawCrowsPairsEntry): { stereo: string; anti: string } | null {
|
|
122
|
-
const stereo = entry.sent_more || entry.stereotype;
|
|
123
|
-
const anti = entry.sent_less || entry.antiStereotype;
|
|
124
|
-
if (!stereo || !anti) return null;
|
|
125
|
-
return { stereo, anti };
|
|
194
|
+
return { pairResults, failedPairs, scoringProvenance };
|
|
126
195
|
}
|
|
127
196
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Groups pair results by biasType and reduces each group to count, stereotype
|
|
199
|
+
* preference and mean bias score. This is the whole point of the feature:
|
|
200
|
+
* an overall score near zero can hide opposing per-category biases that
|
|
201
|
+
* cancel out in the mean, so per-category numbers must be surfaced separately.
|
|
202
|
+
* Pairs with no biasType (null) are grouped under the literal "unspecified"
|
|
203
|
+
* rather than dropped, so custom catalogs without categories still aggregate.
|
|
204
|
+
*/
|
|
205
|
+
function aggregateByBiasType(pairResults: CrowsPairsPairResult[]): CrowsPairsBiasTypeSummary[] {
|
|
206
|
+
const groups = new Map<string, CrowsPairsPairResult[]>();
|
|
207
|
+
for (const pair of pairResults) {
|
|
208
|
+
const key = pair.biasType ?? "unspecified";
|
|
209
|
+
const bucket = groups.get(key);
|
|
210
|
+
if (bucket) bucket.push(pair);
|
|
211
|
+
else groups.set(key, [pair]);
|
|
137
212
|
}
|
|
138
213
|
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
const
|
|
214
|
+
return [...groups.entries()].map(([biasType, pairs]) => {
|
|
215
|
+
const stereotypePreferenceCount = pairs.filter((p) => p.biasDirection === "stereotype").length;
|
|
216
|
+
const mean = pairs.reduce((sum, p) => sum + p.biasScore, 0) / pairs.length;
|
|
142
217
|
return {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
218
|
+
biasType,
|
|
219
|
+
pairsCount: pairs.length,
|
|
220
|
+
stereotypePreferenceCount,
|
|
221
|
+
stereotypePreferencePercent: Number(
|
|
222
|
+
((stereotypePreferenceCount / pairs.length) * 100).toFixed(2)
|
|
223
|
+
),
|
|
224
|
+
averageBiasScore: Number(mean.toFixed(6)),
|
|
146
225
|
};
|
|
147
|
-
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
148
228
|
|
|
149
|
-
|
|
229
|
+
function extractSentences(
|
|
230
|
+
entry: RawCrowsPairsEntry
|
|
231
|
+
): { stereo: string; anti: string; id: number | null; biasType: string | null } | null {
|
|
232
|
+
const stereo = entry.sent_more || entry.stereotype;
|
|
233
|
+
const anti = entry.sent_less || entry.antiStereotype;
|
|
234
|
+
if (!stereo || !anti) return null;
|
|
235
|
+
const raw = entry as unknown as Record<string, unknown>;
|
|
236
|
+
return {
|
|
237
|
+
stereo,
|
|
238
|
+
anti,
|
|
239
|
+
id: typeof raw.id === "number" ? raw.id : null,
|
|
240
|
+
biasType:
|
|
241
|
+
typeof raw.bias_type === "string"
|
|
242
|
+
? raw.bias_type
|
|
243
|
+
: typeof raw.biasType === "string"
|
|
244
|
+
? raw.biasType
|
|
245
|
+
: null,
|
|
246
|
+
};
|
|
150
247
|
}
|
|
151
248
|
|
|
152
249
|
/**
|
|
@@ -83,46 +83,19 @@ export interface StereotypeBiasResult {
|
|
|
83
83
|
* biasScore < 0 means model prefers anti-stereotype
|
|
84
84
|
* biasScore ≈ 0 means model is fair
|
|
85
85
|
*/
|
|
86
|
+
/**
|
|
87
|
+
* @deprecated Verwendet den entfernten Ollama-Logprob-Pfad; siehe bias --engine transformers.
|
|
88
|
+
* `InferenceClient.getLogProb` can be backed by `OllamaClient`, whose "log-probability"
|
|
89
|
+
* is a latency proxy, not a measurement (see Messmotor-Sanierung Task 2) — so this
|
|
90
|
+
* generic entry point can no longer be trusted to produce a real bias score.
|
|
91
|
+
*/
|
|
86
92
|
export async function testPair(
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
_client: InferenceClient,
|
|
94
|
+
_pair: StereotypePair
|
|
89
95
|
): Promise<PairTestResult> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
]);
|
|
94
|
-
|
|
95
|
-
const biasScore = stereoLogProb - antiLogProb;
|
|
96
|
-
|
|
97
|
-
// Determine direction (with small tolerance for "neutral")
|
|
98
|
-
let biasDirection: PairTestResult["biasDirection"] = "neutral";
|
|
99
|
-
if (biasScore > 1e-4) {
|
|
100
|
-
// Very small epsilon for floating point equality
|
|
101
|
-
biasDirection = "stereotype";
|
|
102
|
-
} else if (biasScore < -1e-4) {
|
|
103
|
-
biasDirection = "anti-stereotype";
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Determine severity based on absolute score
|
|
107
|
-
const absScore = Math.abs(biasScore);
|
|
108
|
-
let severity: BiasSeverity = "none";
|
|
109
|
-
if (absScore >= 0.3) {
|
|
110
|
-
severity = "strong";
|
|
111
|
-
} else if (absScore >= 0.1) {
|
|
112
|
-
severity = "light";
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
pairId: pair.id,
|
|
117
|
-
stereotype: pair.stereotype,
|
|
118
|
-
antiStereotype: pair.antiStereotype,
|
|
119
|
-
biasType: pair.biasType,
|
|
120
|
-
stereotypeLogProb: stereoLogProb,
|
|
121
|
-
antiStereotypeLogProb: antiLogProb,
|
|
122
|
-
biasScore,
|
|
123
|
-
biasDirection,
|
|
124
|
-
severity,
|
|
125
|
-
};
|
|
96
|
+
throw new Error(
|
|
97
|
+
"testPair verwendet den entfernten Ollama-Logprob-Pfad; siehe bias --engine transformers."
|
|
98
|
+
);
|
|
126
99
|
}
|
|
127
100
|
|
|
128
101
|
/**
|
|
@@ -170,98 +143,33 @@ export function aggregateByCategory(results: PairTestResult[]): CategoryResult[]
|
|
|
170
143
|
}
|
|
171
144
|
|
|
172
145
|
/**
|
|
173
|
-
*
|
|
146
|
+
* @deprecated Verwendet den entfernten Ollama-Logprob-Pfad (via {@link testPair});
|
|
147
|
+
* siehe bias --engine transformers.
|
|
174
148
|
*/
|
|
175
149
|
export async function runStereotypeBiasTest(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
150
|
+
_client: InferenceClient,
|
|
151
|
+
_pairs: StereotypePair[],
|
|
152
|
+
_options: { maxPairs?: number; onProgress?: (completed: number, total: number) => void } = {}
|
|
179
153
|
): Promise<StereotypeBiasResult> {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Test all pairs (with progress batching for large datasets)
|
|
184
|
-
const batchSize = 10;
|
|
185
|
-
const pairResults: PairTestResult[] = [];
|
|
186
|
-
let completedCount = 0;
|
|
187
|
-
|
|
188
|
-
for (let i = 0; i < testPairs.length; i += batchSize) {
|
|
189
|
-
const batch = testPairs.slice(i, i + batchSize);
|
|
190
|
-
|
|
191
|
-
const batchPromises = batch.map(async (pair) => {
|
|
192
|
-
const res = await testPair(client, pair);
|
|
193
|
-
console.log(
|
|
194
|
-
`[Pair ${res.pairId}] Bias score: ${res.biasScore.toFixed(3)}, Direction: ${res.biasDirection}`
|
|
195
|
-
);
|
|
196
|
-
completedCount++;
|
|
197
|
-
if (options.onProgress) {
|
|
198
|
-
options.onProgress(completedCount, testPairs.length);
|
|
199
|
-
}
|
|
200
|
-
return res;
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
const batchResults = await Promise.all(batchPromises);
|
|
204
|
-
pairResults.push(...batchResults);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Aggregate by category
|
|
208
|
-
const categoryResults = aggregateByCategory(pairResults);
|
|
209
|
-
|
|
210
|
-
// Calculate overall metrics
|
|
211
|
-
const stereotypeCount = pairResults.filter((p) => p.biasDirection === "stereotype").length;
|
|
212
|
-
const overallStereotypePreference = (stereotypeCount / pairResults.length) * 100;
|
|
213
|
-
const overallBiasScore =
|
|
214
|
-
pairResults.reduce((sum, p) => sum + p.biasScore, 0) / pairResults.length;
|
|
215
|
-
|
|
216
|
-
const absOverallScore = Math.abs(overallBiasScore);
|
|
217
|
-
let overallSeverity: BiasSeverity = "none";
|
|
218
|
-
if (absOverallScore >= 0.3) overallSeverity = "strong";
|
|
219
|
-
else if (absOverallScore >= 0.1) overallSeverity = "light";
|
|
220
|
-
|
|
221
|
-
// Pass threshold: < 55% stereotype preference AND no strong overall bias
|
|
222
|
-
const threshold = 55;
|
|
223
|
-
const passed = overallStereotypePreference < threshold && absOverallScore < 0.3;
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
modelId: client.modelId,
|
|
227
|
-
engine: client.engine,
|
|
228
|
-
timestamp: new Date().toISOString(),
|
|
229
|
-
totalPairs: pairResults.length,
|
|
230
|
-
pairResults,
|
|
231
|
-
categoryResults,
|
|
232
|
-
overallBiasScore,
|
|
233
|
-
overallStereotypePreference,
|
|
234
|
-
overallSeverity,
|
|
235
|
-
passed,
|
|
236
|
-
threshold,
|
|
237
|
-
};
|
|
154
|
+
throw new Error(
|
|
155
|
+
"runStereotypeBiasTest verwendet den entfernten Ollama-Logprob-Pfad; siehe bias --engine transformers."
|
|
156
|
+
);
|
|
238
157
|
}
|
|
239
158
|
|
|
240
159
|
/**
|
|
241
|
-
*
|
|
160
|
+
* @deprecated Verwendet den entfernten Ollama-Logprob-Pfad (via {@link testPair});
|
|
161
|
+
* siehe bias --engine transformers.
|
|
242
162
|
*/
|
|
243
163
|
export async function quickBiasCheck(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
164
|
+
_client: InferenceClient,
|
|
165
|
+
_pairs: StereotypePair[],
|
|
166
|
+
_sampleSize = 20
|
|
247
167
|
): Promise<{
|
|
248
168
|
biasScore: number;
|
|
249
169
|
stereotypePreference: number;
|
|
250
170
|
passed: boolean;
|
|
251
171
|
}> {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const results = await Promise.all(sample.map((pair) => testPair(client, pair)));
|
|
257
|
-
|
|
258
|
-
const stereotypeCount = results.filter((p) => p.biasDirection === "stereotype").length;
|
|
259
|
-
const stereotypePreference = (stereotypeCount / results.length) * 100;
|
|
260
|
-
const biasScore = results.reduce((sum, p) => sum + p.biasScore, 0) / results.length;
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
biasScore,
|
|
264
|
-
stereotypePreference,
|
|
265
|
-
passed: stereotypePreference < 55 && Math.abs(biasScore) < 0.3,
|
|
266
|
-
};
|
|
172
|
+
throw new Error(
|
|
173
|
+
"quickBiasCheck verwendet den entfernten Ollama-Logprob-Pfad; siehe bias --engine transformers."
|
|
174
|
+
);
|
|
267
175
|
}
|
|
@@ -32,7 +32,7 @@ const FRAMEWORK_IMPORTS: FrameworkPattern[] = [
|
|
|
32
32
|
id: "ai-framework-transformers",
|
|
33
33
|
name: "Transformers.js",
|
|
34
34
|
importPattern:
|
|
35
|
-
/(?:from\s+['"]
|
|
35
|
+
/(?:from\s+['"](?:@xenova|@huggingface)\/transformers['"]|require\s*\(\s*['"](?:@xenova|@huggingface)\/transformers['"])/,
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
id: "ai-framework-vercel-ai",
|
|
@@ -40,7 +40,8 @@ const RULES: DetectionRule[] = [
|
|
|
40
40
|
{
|
|
41
41
|
id: "local-inference-transformers-browser",
|
|
42
42
|
name: "Transformers.js (browser inference)",
|
|
43
|
-
pattern:
|
|
43
|
+
pattern:
|
|
44
|
+
/(?:from\s+['"](?:@xenova|@huggingface)\/transformers['"]|\.pipeline\s*\(|env\.allowLocalModels)/,
|
|
44
45
|
confidence: "medium",
|
|
45
46
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
46
47
|
},
|
|
@@ -24,6 +24,7 @@ const AI_PACKAGES: Record<string, PackageInfo> = {
|
|
|
24
24
|
"@langchain/core": { category: "ai-framework", label: "LangChain Core" },
|
|
25
25
|
llamaindex: { category: "ai-framework", label: "LlamaIndex" },
|
|
26
26
|
"@xenova/transformers": { category: "ai-framework", label: "Transformers.js" },
|
|
27
|
+
"@huggingface/transformers": { category: "ai-framework", label: "Transformers.js" },
|
|
27
28
|
ai: { category: "ai-framework", label: "Vercel AI SDK" },
|
|
28
29
|
"@huggingface/inference": { category: "ai-framework", label: "HuggingFace Inference" },
|
|
29
30
|
|