@euconform/core 1.4.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.
Files changed (179) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +46 -0
  3. package/dist/datasets/crows-pairs.d.ts +18 -0
  4. package/dist/datasets/crows-pairs.d.ts.map +1 -1
  5. package/dist/datasets/crows-pairs.js +58 -0
  6. package/dist/evidence/bias-comparison-markdown.d.ts +11 -0
  7. package/dist/evidence/bias-comparison-markdown.d.ts.map +1 -0
  8. package/dist/evidence/bias-comparison-markdown.js +120 -0
  9. package/dist/evidence/bias-comparison.d.ts +133 -0
  10. package/dist/evidence/bias-comparison.d.ts.map +1 -0
  11. package/dist/evidence/bias-comparison.js +274 -0
  12. package/dist/evidence/bias-statistics.d.ts +72 -0
  13. package/dist/evidence/bias-statistics.d.ts.map +1 -0
  14. package/dist/evidence/bias-statistics.js +122 -0
  15. package/dist/evidence/bias-v2-semantics.d.ts +19 -0
  16. package/dist/evidence/bias-v2-semantics.d.ts.map +1 -0
  17. package/dist/evidence/bias-v2-semantics.js +133 -0
  18. package/dist/evidence/browser.d.ts +18 -0
  19. package/dist/evidence/browser.d.ts.map +1 -0
  20. package/dist/evidence/browser.js +14 -0
  21. package/dist/evidence/bundle.d.ts +1 -0
  22. package/dist/evidence/bundle.d.ts.map +1 -1
  23. package/dist/evidence/bundle.js +2 -0
  24. package/dist/evidence/index.d.ts +9 -2
  25. package/dist/evidence/index.d.ts.map +1 -1
  26. package/dist/evidence/index.js +5 -1
  27. package/dist/evidence/output.d.ts.map +1 -1
  28. package/dist/evidence/output.js +1 -0
  29. package/dist/evidence/types.d.ts +183 -3
  30. package/dist/evidence/types.d.ts.map +1 -1
  31. package/dist/evidence/validate-bias-v2.d.ts +31 -0
  32. package/dist/evidence/validate-bias-v2.d.ts.map +1 -0
  33. package/dist/evidence/validate-bias-v2.js +40 -0
  34. package/dist/evidence/validate.d.ts +29 -2
  35. package/dist/evidence/validate.d.ts.map +1 -1
  36. package/dist/evidence/validate.js +96 -11
  37. package/dist/fairness-metrics.d.ts +15 -11
  38. package/dist/fairness-metrics.d.ts.map +1 -1
  39. package/dist/fairness-metrics.js +59 -183
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/inference/browser-inference.d.ts +6 -57
  44. package/dist/inference/browser-inference.d.ts.map +1 -1
  45. package/dist/inference/browser-inference.js +6 -219
  46. package/dist/inference/capability-detection.d.ts +9 -2
  47. package/dist/inference/capability-detection.d.ts.map +1 -1
  48. package/dist/inference/capability-detection.js +18 -16
  49. package/dist/inference/index.d.ts +15 -2
  50. package/dist/inference/index.d.ts.map +1 -1
  51. package/dist/inference/index.js +12 -3
  52. package/dist/inference/inference-factory.d.ts +6 -3
  53. package/dist/inference/inference-factory.d.ts.map +1 -1
  54. package/dist/inference/inference-factory.js +7 -26
  55. package/dist/inference/ollama-client.d.ts +29 -0
  56. package/dist/inference/ollama-client.d.ts.map +1 -1
  57. package/dist/inference/ollama-client.js +29 -0
  58. package/dist/inference/scored-log-prob-types.d.ts +64 -0
  59. package/dist/inference/scored-log-prob-types.d.ts.map +1 -0
  60. package/dist/inference/scored-log-prob-types.js +12 -0
  61. package/dist/inference/sequence-logprob.d.ts +34 -0
  62. package/dist/inference/sequence-logprob.d.ts.map +1 -0
  63. package/dist/inference/sequence-logprob.js +87 -0
  64. package/dist/inference/template-scoring.d.ts +124 -0
  65. package/dist/inference/template-scoring.d.ts.map +1 -0
  66. package/dist/inference/template-scoring.js +164 -0
  67. package/dist/inference/transformers-inference.d.ts +124 -0
  68. package/dist/inference/transformers-inference.d.ts.map +1 -0
  69. package/dist/inference/transformers-inference.js +392 -0
  70. package/dist/legal-checks/bias-metrics.d.ts +5 -7
  71. package/dist/legal-checks/bias-metrics.d.ts.map +1 -1
  72. package/dist/legal-checks/bias-metrics.js +8 -101
  73. package/dist/metrics/crows-pairs-bias.d.ts +17 -9
  74. package/dist/metrics/crows-pairs-bias.d.ts.map +1 -1
  75. package/dist/metrics/crows-pairs-bias.js +147 -59
  76. package/dist/metrics/stereotype-bias.d.ts +13 -5
  77. package/dist/metrics/stereotype-bias.d.ts.map +1 -1
  78. package/dist/metrics/stereotype-bias.js +16 -99
  79. package/dist/sbom/component-mapper.d.ts +26 -0
  80. package/dist/sbom/component-mapper.d.ts.map +1 -0
  81. package/dist/sbom/component-mapper.js +83 -0
  82. package/dist/sbom/cyclonedx-import.d.ts +34 -0
  83. package/dist/sbom/cyclonedx-import.d.ts.map +1 -0
  84. package/dist/sbom/cyclonedx-import.js +131 -0
  85. package/dist/sbom/index.d.ts +6 -0
  86. package/dist/sbom/index.d.ts.map +1 -0
  87. package/dist/sbom/index.js +4 -0
  88. package/dist/sbom/known-packages.d.ts +13 -0
  89. package/dist/sbom/known-packages.d.ts.map +1 -0
  90. package/dist/sbom/known-packages.js +129 -0
  91. package/dist/sbom/purl.d.ts +14 -0
  92. package/dist/sbom/purl.d.ts.map +1 -0
  93. package/dist/sbom/purl.js +56 -0
  94. package/dist/sbom/types.d.ts +39 -0
  95. package/dist/sbom/types.d.ts.map +1 -0
  96. package/dist/sbom/types.js +5 -0
  97. package/dist/scanner/detectors/ai-framework-detector.js +1 -1
  98. package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -1
  99. package/dist/scanner/detectors/local-inference-detector.js +1 -1
  100. package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -1
  101. package/dist/scanner/detectors/package-json-detector.js +1 -0
  102. package/dist/types.d.ts +103 -2
  103. package/dist/types.d.ts.map +1 -1
  104. package/dist/validation/index.d.ts +2 -0
  105. package/dist/validation/index.d.ts.map +1 -0
  106. package/dist/validation/index.js +1 -0
  107. package/dist/validation/schema-validator.d.ts +18 -0
  108. package/dist/validation/schema-validator.d.ts.map +1 -0
  109. package/dist/validation/schema-validator.js +105 -0
  110. package/package.json +9 -3
  111. package/scripts/seed-logprob-fixture.mjs +179 -0
  112. package/scripts/ts-extensionless-resolve-hook.mjs +26 -0
  113. package/src/datasets/crows-pairs.ts +74 -0
  114. package/src/evidence/bias-comparison-markdown.ts +162 -0
  115. package/src/evidence/bias-comparison.ts +432 -0
  116. package/src/evidence/bias-statistics.ts +180 -0
  117. package/src/evidence/bias-v2-semantics.ts +214 -0
  118. package/src/evidence/browser.ts +35 -0
  119. package/src/evidence/bundle.ts +2 -0
  120. package/src/evidence/index.ts +31 -0
  121. package/src/evidence/output.ts +1 -0
  122. package/src/evidence/types.ts +213 -3
  123. package/src/evidence/validate-bias-v2.ts +47 -0
  124. package/src/evidence/validate.ts +151 -17
  125. package/src/fairness-metrics.ts +79 -222
  126. package/src/index.ts +7 -0
  127. package/src/inference/browser-inference.ts +14 -256
  128. package/src/inference/capability-detection.ts +32 -18
  129. package/src/inference/index.ts +33 -5
  130. package/src/inference/inference-factory.ts +9 -30
  131. package/src/inference/ollama-client.ts +29 -0
  132. package/src/inference/scored-log-prob-types.ts +66 -0
  133. package/src/inference/sequence-logprob.ts +136 -0
  134. package/src/inference/template-scoring.ts +248 -0
  135. package/src/inference/transformers-inference.ts +494 -0
  136. package/src/legal-checks/bias-metrics.ts +20 -118
  137. package/src/metrics/crows-pairs-bias.ts +180 -83
  138. package/src/metrics/stereotype-bias.ts +27 -119
  139. package/src/sbom/component-mapper.ts +107 -0
  140. package/src/sbom/cyclonedx-import.ts +198 -0
  141. package/src/sbom/index.ts +12 -0
  142. package/src/sbom/known-packages.ts +142 -0
  143. package/src/sbom/purl.ts +66 -0
  144. package/src/sbom/types.ts +34 -0
  145. package/src/scanner/detectors/ai-framework-detector.ts +1 -1
  146. package/src/scanner/detectors/local-inference-detector.ts +2 -1
  147. package/src/scanner/detectors/package-json-detector.ts +1 -0
  148. package/src/types.ts +108 -2
  149. package/src/validation/index.ts +7 -0
  150. package/src/validation/schema-validator.ts +147 -0
  151. package/tests/annex-iv-report.test.ts +1 -1
  152. package/tests/capability-detection.test.ts +57 -0
  153. package/tests/crows-pairs-bias.test.ts +286 -186
  154. package/tests/custom-dataset.test.ts +80 -0
  155. package/tests/evidence/bias-artifact-v2.test.ts +135 -0
  156. package/tests/evidence/bias-artifact.test.ts +78 -0
  157. package/tests/evidence/bias-comparison.test.ts +397 -0
  158. package/tests/evidence/bias-statistics.test.ts +191 -0
  159. package/tests/evidence/external-observation.test.ts +167 -0
  160. package/tests/evidence/spec-schemas.test.ts +35 -2
  161. package/tests/evidence/validate-bias-v2.test.ts +180 -0
  162. package/tests/fairness-metrics.test.ts +77 -1
  163. package/tests/fixtures/cyclonedx-minimal.json +13 -0
  164. package/tests/fixtures/cyclonedx-mixed.json +17 -0
  165. package/tests/fixtures/cyclonedx-ml-bom.json +25 -0
  166. package/tests/inference/browser-inference-scoring.test.ts +159 -0
  167. package/tests/inference/fixtures/logprob-golden.json +69 -0
  168. package/tests/inference/gemma-template.integration.test.ts +81 -0
  169. package/tests/inference/logprob-golden.integration.test.ts +128 -0
  170. package/tests/inference/synthetic-logits.test.ts +188 -0
  171. package/tests/inference/template-scoring.test.ts +249 -0
  172. package/tests/inference/transformers-inference-template-mode.test.ts +214 -0
  173. package/tests/integration/bias-calculation.integration.test.ts +119 -599
  174. package/tests/sbom/component-mapper.test.ts +258 -0
  175. package/tests/sbom/cyclonedx-import.test.ts +329 -0
  176. package/tests/sbom/purl.test.ts +102 -0
  177. package/tests/stilled-ollama-paths.test.ts +76 -0
  178. package/tests/validation/schema-validator.test.ts +219 -0
  179. package/src/inference/transformers.d.ts +0 -62
@@ -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 = "detecting" | "available" | "unavailable" | "error";
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: "available",
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: "available",
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
- // Browser inference always supports exact log-probabilities
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: "available",
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
- if (capability.status !== "available") return 0;
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
- // Browser inference always supports exact log-probabilities
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: "available",
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: "available",
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: "available",
549
+ status: "unverified",
535
550
  recommended: true,
536
- lastTested: new Date().toISOString(),
537
551
  });
538
552
  }
539
553
 
@@ -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 "./browser-inference";
12
- import { OllamaClient, type OllamaModel, detectOllama, listOllamaModels } from "./ollama-client";
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 BrowserInference(modelId, statusCallback);
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 { BrowserInference, checkWebGPU, BROWSER_MODELS } from "./browser-inference";
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
- * Create a bias calculation with automatic method detection
46
- * Returns InferenceResult with both data and method metadata
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
- config: BiasTestConfig
51
+ _config: BiasTestConfig
50
52
  ): Promise<InferenceResult<CrowsPairsBiasResult>> {
51
- const { engine, model } = config;
52
-
53
- this.validateConfiguration(config);
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
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Pure, offline-testable log-probability scorer for autoregressive language models.
3
+ *
4
+ * Scoring convention (binding):
5
+ * - A span [start, end) is half-open over token positions of the FULL fed sequence
6
+ * (i.e. including any context tokens such as a prepended BOS).
7
+ * - The token AT position j is scored using the logits AT position j-1 (the model's
8
+ * prediction distribution for "what comes next" is produced one position before the
9
+ * token it predicts).
10
+ * - The result is a SUM of log-probabilities, not a mean.
11
+ * - batch_size must be exactly 1; anything else is a caller error and throws.
12
+ */
13
+
14
+ /** Minimal shape this module needs from an onnxruntime-style Tensor. */
15
+ export interface LogitsTensorLike {
16
+ /** Flattened [batch, seqLen, vocabSize] logits. Must be Float32Array or Float64Array. */
17
+ data: Float32Array | Float64Array;
18
+ /** [batchSize, seqLen, vocabSize] */
19
+ dims: readonly number[];
20
+ }
21
+
22
+ /** Half-open token-position span [start, end). */
23
+ export interface Span {
24
+ start: number;
25
+ end: number;
26
+ }
27
+
28
+ function assertSupportedDtype(data: LogitsTensorLike["data"]): void {
29
+ if (!(data instanceof Float32Array) && !(data instanceof Float64Array)) {
30
+ const ctorName =
31
+ (data as { constructor?: { name?: string } })?.constructor?.name ?? typeof data;
32
+ throw new Error(
33
+ `sumSequenceLogProb: unsupported logits dtype, expected Float32Array or Float64Array, got ${ctorName}`
34
+ );
35
+ }
36
+ }
37
+
38
+ function assertBatchAndDims(
39
+ dims: readonly number[]
40
+ ): [batchSize: number, seqLen: number, vocabSize: number] {
41
+ if (dims.length !== 3) {
42
+ throw new Error(
43
+ `sumSequenceLogProb: expected logits dims [batch, seqLen, vocabSize], got [${dims.join(", ")}]`
44
+ );
45
+ }
46
+ const batchSize = dims[0] ?? 0;
47
+ const seqLen = dims[1] ?? 0;
48
+ const vocabSize = dims[2] ?? 0;
49
+ if (batchSize !== 1) {
50
+ throw new Error(`sumSequenceLogProb: batch_size must be 1, got ${batchSize}`);
51
+ }
52
+ return [batchSize, seqLen, vocabSize];
53
+ }
54
+
55
+ function assertSpanWithinBounds(span: Span, seqLen: number): void {
56
+ const { start, end } = span;
57
+ if (
58
+ !Number.isInteger(start) ||
59
+ !Number.isInteger(end) ||
60
+ start < 1 ||
61
+ end > seqLen ||
62
+ start >= end
63
+ ) {
64
+ throw new Error(
65
+ `sumSequenceLogProb: invalid span [${start}, ${end}) for seqLen ${seqLen}; span must satisfy integer 1 <= start < end <= seqLen`
66
+ );
67
+ }
68
+ }
69
+
70
+ function logSumExpRow(
71
+ data: LogitsTensorLike["data"],
72
+ rowStart: number,
73
+ vocabSize: number,
74
+ contextLabel: string
75
+ ): { maxLogit: number; logSumExp: number } {
76
+ let maxLogit = Number.NEGATIVE_INFINITY;
77
+ for (let v = 0; v < vocabSize; v++) {
78
+ const val = data[rowStart + v] ?? Number.NaN;
79
+ if (!Number.isFinite(val)) {
80
+ throw new Error(
81
+ `sumSequenceLogProb: non-finite logit at ${contextLabel}, vocab index ${v} (value: ${val})`
82
+ );
83
+ }
84
+ if (val > maxLogit) maxLogit = val;
85
+ }
86
+ let sumExp = 0;
87
+ for (let v = 0; v < vocabSize; v++) {
88
+ const val = data[rowStart + v] ?? Number.NaN;
89
+ sumExp += Math.exp(val - maxLogit);
90
+ }
91
+ return { maxLogit, logSumExp: maxLogit + Math.log(sumExp) };
92
+ }
93
+
94
+ /**
95
+ * Sum the log-probabilities of the tokens in `tokenIds` over the half-open span
96
+ * [span.start, span.end), where the token at position j is scored from the logits
97
+ * distribution at position j-1.
98
+ *
99
+ * Throws on: unsupported dtype, wrong dims shape, batch_size !== 1, tokenIds/seqLen
100
+ * mismatch, out-of-bounds span, out-of-vocab token ids, or non-finite logits.
101
+ */
102
+ export function sumSequenceLogProb(
103
+ logits: LogitsTensorLike,
104
+ tokenIds: ArrayLike<number | bigint>,
105
+ span: Span
106
+ ): number {
107
+ assertSupportedDtype(logits.data);
108
+ const [, seqLen, vocabSize] = assertBatchAndDims(logits.dims);
109
+
110
+ if (tokenIds.length !== seqLen) {
111
+ throw new Error(
112
+ `sumSequenceLogProb: tokenIds length (${tokenIds.length}) does not match logits seqLen (${seqLen})`
113
+ );
114
+ }
115
+ assertSpanWithinBounds(span, seqLen);
116
+
117
+ const { data } = logits;
118
+ let total = 0;
119
+
120
+ for (let j = span.start; j < span.end; j++) {
121
+ const targetId = Number(tokenIds[j]);
122
+ if (!Number.isInteger(targetId) || targetId < 0 || targetId >= vocabSize) {
123
+ throw new Error(
124
+ `sumSequenceLogProb: token id ${targetId} at position ${j} out of vocab range [0, ${vocabSize})`
125
+ );
126
+ }
127
+
128
+ const pos = j - 1;
129
+ const rowStart = pos * vocabSize;
130
+ const { logSumExp } = logSumExpRow(data, rowStart, vocabSize, `position ${pos}`);
131
+ const targetLogit = data[rowStart + targetId] ?? Number.NaN;
132
+ total += targetLogit - logSumExp;
133
+ }
134
+
135
+ return total;
136
+ }