@euconform/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE +21 -0
- package/dist/fairness-metrics.d.ts +65 -0
- package/dist/fairness-metrics.d.ts.map +1 -0
- package/dist/fairness-metrics.js +394 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/inference/browser-inference.d.ts +60 -0
- package/dist/inference/browser-inference.d.ts.map +1 -0
- package/dist/inference/browser-inference.js +221 -0
- package/dist/inference/capability-detection.d.ts +183 -0
- package/dist/inference/capability-detection.d.ts.map +1 -0
- package/dist/inference/capability-detection.js +552 -0
- package/dist/inference/index.d.ts +43 -0
- package/dist/inference/index.d.ts.map +1 -0
- package/dist/inference/index.js +97 -0
- package/dist/inference/inference-factory.d.ts +73 -0
- package/dist/inference/inference-factory.d.ts.map +1 -0
- package/dist/inference/inference-factory.js +175 -0
- package/dist/inference/ollama-client.d.ts +114 -0
- package/dist/inference/ollama-client.d.ts.map +1 -0
- package/dist/inference/ollama-client.js +386 -0
- package/dist/legal-checks/annex-iv-report.d.ts +131 -0
- package/dist/legal-checks/annex-iv-report.d.ts.map +1 -0
- package/dist/legal-checks/annex-iv-report.js +62 -0
- package/dist/legal-checks/bias-metrics.d.ts +162 -0
- package/dist/legal-checks/bias-metrics.d.ts.map +1 -0
- package/dist/legal-checks/bias-metrics.js +408 -0
- package/dist/legal-checks/data-governance.d.ts +30 -0
- package/dist/legal-checks/data-governance.d.ts.map +1 -0
- package/dist/legal-checks/data-governance.js +99 -0
- package/dist/legal-checks/human-oversight-logging.d.ts +27 -0
- package/dist/legal-checks/human-oversight-logging.d.ts.map +1 -0
- package/dist/legal-checks/human-oversight-logging.js +106 -0
- package/dist/legal-checks/index.d.ts +13 -0
- package/dist/legal-checks/index.d.ts.map +1 -0
- package/dist/legal-checks/index.js +6 -0
- package/dist/legal-checks/risk-classifier.d.ts +35 -0
- package/dist/legal-checks/risk-classifier.d.ts.map +1 -0
- package/dist/legal-checks/risk-classifier.js +234 -0
- package/dist/legal-checks/robustness-transparency.d.ts +45 -0
- package/dist/legal-checks/robustness-transparency.d.ts.map +1 -0
- package/dist/legal-checks/robustness-transparency.js +111 -0
- package/dist/metrics/crows-pairs-bias.d.ts +41 -0
- package/dist/metrics/crows-pairs-bias.d.ts.map +1 -0
- package/dist/metrics/crows-pairs-bias.js +172 -0
- package/dist/metrics/index.d.ts +6 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +5 -0
- package/dist/metrics/stereotype-bias.d.ts +89 -0
- package/dist/metrics/stereotype-bias.d.ts.map +1 -0
- package/dist/metrics/stereotype-bias.js +157 -0
- package/dist/risk-engine.d.ts +23 -0
- package/dist/risk-engine.d.ts.map +1 -0
- package/dist/risk-engine.js +451 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/package.json +28 -0
- package/src/fairness-metrics.ts +498 -0
- package/src/index.ts +150 -0
- package/src/inference/browser-inference.ts +258 -0
- package/src/inference/capability-detection.ts +714 -0
- package/src/inference/index.ts +156 -0
- package/src/inference/inference-factory.ts +222 -0
- package/src/inference/ollama-client.ts +514 -0
- package/src/inference/transformers.d.ts +62 -0
- package/src/legal-checks/annex-iv-report.ts +157 -0
- package/src/legal-checks/bias-metrics.ts +590 -0
- package/src/legal-checks/data-governance.ts +148 -0
- package/src/legal-checks/human-oversight-logging.ts +135 -0
- package/src/legal-checks/index.ts +55 -0
- package/src/legal-checks/risk-classifier.ts +285 -0
- package/src/legal-checks/robustness-transparency.ts +162 -0
- package/src/metrics/crows-pairs-bias.ts +245 -0
- package/src/metrics/index.ts +22 -0
- package/src/metrics/stereotype-bias.ts +267 -0
- package/src/risk-engine.ts +525 -0
- package/src/types.ts +154 -0
- package/tests/annex-iv-report.test.ts +323 -0
- package/tests/crows-pairs-bias.test.ts +342 -0
- package/tests/fairness-metrics.test.ts +187 -0
- package/tests/integration/bias-calculation.integration.test.ts +688 -0
- package/tests/legal-checks.test.ts +53 -0
- package/tests/ollama-client.test.ts +427 -0
- package/tests/risk-engine.test.ts +149 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Capability Detection Service
|
|
3
|
+
* Real-time detection of model capabilities with smart caching
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Implementation of capability detection service
|
|
7
|
+
*/
|
|
8
|
+
export class DefaultCapabilityDetectionService {
|
|
9
|
+
cache = new Map();
|
|
10
|
+
CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
11
|
+
CACHE_KEY_PREFIX = "euconform_capability_";
|
|
12
|
+
constructor() {
|
|
13
|
+
this.loadCacheFromStorage();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Detect capabilities for all available models
|
|
17
|
+
* Includes browser inference and parallel Ollama model testing
|
|
18
|
+
*/
|
|
19
|
+
async detectAllCapabilities() {
|
|
20
|
+
const capabilities = [];
|
|
21
|
+
try {
|
|
22
|
+
// Import browser models dynamically
|
|
23
|
+
const { BROWSER_MODELS } = await import("./browser-inference");
|
|
24
|
+
for (const model of BROWSER_MODELS) {
|
|
25
|
+
if (!model.supported)
|
|
26
|
+
continue;
|
|
27
|
+
capabilities.push({
|
|
28
|
+
modelId: model.id,
|
|
29
|
+
engine: "browser",
|
|
30
|
+
method: "logprobs_exact",
|
|
31
|
+
status: "available",
|
|
32
|
+
recommended: true,
|
|
33
|
+
lastTested: new Date().toISOString(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.warn("Failed to load browser models:", error);
|
|
39
|
+
// Fallback if import fails
|
|
40
|
+
capabilities.push({
|
|
41
|
+
modelId: "Xenova/distilgpt2",
|
|
42
|
+
engine: "browser",
|
|
43
|
+
method: "logprobs_exact",
|
|
44
|
+
status: "available",
|
|
45
|
+
recommended: true,
|
|
46
|
+
lastTested: new Date().toISOString(),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
// Import Ollama functions dynamically to avoid issues if not available
|
|
51
|
+
const { detectOllama, listOllamaModels } = await import("./ollama-client");
|
|
52
|
+
// Check if Ollama is available
|
|
53
|
+
const ollamaAvailable = await detectOllama();
|
|
54
|
+
if (ollamaAvailable) {
|
|
55
|
+
// Get list of available Ollama models
|
|
56
|
+
const ollamaModels = await listOllamaModels();
|
|
57
|
+
// Detect capabilities for all Ollama models in parallel
|
|
58
|
+
const detectionPromises = ollamaModels.map((model) => this.detectModelCapability(model.name, "ollama"));
|
|
59
|
+
const ollamaCapabilities = await Promise.allSettled(detectionPromises);
|
|
60
|
+
// Process results and add successful detections
|
|
61
|
+
ollamaCapabilities.forEach((result, index) => {
|
|
62
|
+
if (result.status === "fulfilled") {
|
|
63
|
+
capabilities.push(result.value);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Add error capability for failed detections
|
|
67
|
+
const model = ollamaModels[index];
|
|
68
|
+
if (model) {
|
|
69
|
+
capabilities.push({
|
|
70
|
+
modelId: model.name,
|
|
71
|
+
engine: "ollama",
|
|
72
|
+
method: "logprobs_fallback_latency",
|
|
73
|
+
status: "error",
|
|
74
|
+
error: result.reason?.message || "Detection failed",
|
|
75
|
+
lastTested: new Date().toISOString(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.warn("Failed to detect Ollama capabilities:", error);
|
|
84
|
+
}
|
|
85
|
+
// Sort capabilities by recommendation score
|
|
86
|
+
return this.sortByRecommendation(capabilities);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Detect capability for a specific model
|
|
90
|
+
* Uses cache-first strategy with live fallback
|
|
91
|
+
*/
|
|
92
|
+
async detectModelCapability(modelId, engine) {
|
|
93
|
+
// Check cache first
|
|
94
|
+
const cached = this.getCachedCapability(modelId);
|
|
95
|
+
if (cached && this.isCacheValid(cached)) {
|
|
96
|
+
return cached.capability;
|
|
97
|
+
}
|
|
98
|
+
// Browser inference always supports exact log-probabilities
|
|
99
|
+
if (engine === "browser") {
|
|
100
|
+
const capability = {
|
|
101
|
+
modelId,
|
|
102
|
+
engine: "browser",
|
|
103
|
+
method: "logprobs_exact",
|
|
104
|
+
status: "available",
|
|
105
|
+
recommended: true,
|
|
106
|
+
lastTested: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
this.setCachedCapability(capability);
|
|
109
|
+
return capability;
|
|
110
|
+
}
|
|
111
|
+
// Detect Ollama model capability
|
|
112
|
+
try {
|
|
113
|
+
const { OllamaClient } = await import("./ollama-client");
|
|
114
|
+
const client = new OllamaClient(modelId);
|
|
115
|
+
// Test if model supports log-probabilities
|
|
116
|
+
const supportsLogProbs = await client.detectLogProbSupport();
|
|
117
|
+
const capability = {
|
|
118
|
+
modelId,
|
|
119
|
+
engine: "ollama",
|
|
120
|
+
method: supportsLogProbs ? "logprobs_exact" : "logprobs_fallback_latency",
|
|
121
|
+
status: "available",
|
|
122
|
+
lastTested: new Date().toISOString(),
|
|
123
|
+
};
|
|
124
|
+
this.setCachedCapability(capability);
|
|
125
|
+
return capability;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
const capability = {
|
|
129
|
+
modelId,
|
|
130
|
+
engine: "ollama",
|
|
131
|
+
method: "logprobs_fallback_latency",
|
|
132
|
+
status: "error",
|
|
133
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
134
|
+
lastTested: new Date().toISOString(),
|
|
135
|
+
};
|
|
136
|
+
// Cache error results for shorter duration to allow retry
|
|
137
|
+
this.setCachedCapability(capability, 5 * 60 * 1000); // 5 minutes for errors
|
|
138
|
+
return capability;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get cached capability result
|
|
143
|
+
*/
|
|
144
|
+
getCachedCapability(modelId) {
|
|
145
|
+
const cached = this.cache.get(modelId);
|
|
146
|
+
if (cached && this.isCacheValid(cached)) {
|
|
147
|
+
return cached;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Cache capability result with optional custom duration
|
|
153
|
+
*/
|
|
154
|
+
setCachedCapability(capability, durationMs) {
|
|
155
|
+
const duration = durationMs || this.CACHE_DURATION_MS;
|
|
156
|
+
const now = new Date();
|
|
157
|
+
const expiresAt = new Date(now.getTime() + duration);
|
|
158
|
+
const cacheEntry = {
|
|
159
|
+
modelId: capability.modelId,
|
|
160
|
+
capability,
|
|
161
|
+
cachedAt: now.toISOString(),
|
|
162
|
+
expiresAt: expiresAt.toISOString(),
|
|
163
|
+
};
|
|
164
|
+
// Store in memory cache
|
|
165
|
+
this.cache.set(capability.modelId, cacheEntry);
|
|
166
|
+
// Persist to localStorage
|
|
167
|
+
this.saveCacheToStorage(capability.modelId, cacheEntry);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Clear all cached capabilities
|
|
171
|
+
*/
|
|
172
|
+
clearCache() {
|
|
173
|
+
this.cache.clear();
|
|
174
|
+
// Clear from localStorage
|
|
175
|
+
try {
|
|
176
|
+
const keys = Object.keys(localStorage);
|
|
177
|
+
for (const key of keys) {
|
|
178
|
+
if (key.startsWith(this.CACHE_KEY_PREFIX)) {
|
|
179
|
+
localStorage.removeItem(key);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
console.warn("Failed to clear capability cache from localStorage:", error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Refresh all capabilities by clearing cache and re-detecting
|
|
189
|
+
*/
|
|
190
|
+
async refreshCapabilities() {
|
|
191
|
+
this.clearCache();
|
|
192
|
+
return this.detectAllCapabilities();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Check if cache entry is still valid
|
|
196
|
+
*/
|
|
197
|
+
isCacheValid(cacheEntry) {
|
|
198
|
+
const now = new Date();
|
|
199
|
+
const expiresAt = new Date(cacheEntry.expiresAt);
|
|
200
|
+
return now < expiresAt;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Load cache from localStorage
|
|
204
|
+
*/
|
|
205
|
+
loadCacheFromStorage() {
|
|
206
|
+
try {
|
|
207
|
+
const keys = Object.keys(localStorage);
|
|
208
|
+
for (const key of keys) {
|
|
209
|
+
if (key.startsWith(this.CACHE_KEY_PREFIX)) {
|
|
210
|
+
const cached = localStorage.getItem(key);
|
|
211
|
+
if (cached) {
|
|
212
|
+
const cacheEntry = JSON.parse(cached);
|
|
213
|
+
if (this.isCacheValid(cacheEntry)) {
|
|
214
|
+
const modelId = key.replace(this.CACHE_KEY_PREFIX, "");
|
|
215
|
+
this.cache.set(modelId, cacheEntry);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Remove expired cache entries
|
|
219
|
+
localStorage.removeItem(key);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.warn("Failed to load capability cache from localStorage:", error);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Save cache entry to localStorage
|
|
231
|
+
*/
|
|
232
|
+
saveCacheToStorage(modelId, cacheEntry) {
|
|
233
|
+
try {
|
|
234
|
+
const key = `${this.CACHE_KEY_PREFIX}${modelId}`;
|
|
235
|
+
localStorage.setItem(key, JSON.stringify(cacheEntry));
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.warn("Failed to save capability cache to localStorage:", error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Sort capabilities by recommendation score
|
|
243
|
+
*/
|
|
244
|
+
sortByRecommendation(capabilities) {
|
|
245
|
+
return capabilities.sort((a, b) => {
|
|
246
|
+
const scoreA = this.getRecommendationScore(a);
|
|
247
|
+
const scoreB = this.getRecommendationScore(b);
|
|
248
|
+
return scoreB - scoreA;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Calculate recommendation score for a capability
|
|
253
|
+
*/
|
|
254
|
+
getRecommendationScore(capability) {
|
|
255
|
+
if (capability.status !== "available")
|
|
256
|
+
return 0;
|
|
257
|
+
let score = 0;
|
|
258
|
+
// Browser inference gets highest score
|
|
259
|
+
if (capability.engine === "browser")
|
|
260
|
+
score += 100;
|
|
261
|
+
// Exact methods get bonus
|
|
262
|
+
if (capability.method === "logprobs_exact")
|
|
263
|
+
score += 50;
|
|
264
|
+
// Recommended models get bonus
|
|
265
|
+
if (capability.recommended)
|
|
266
|
+
score += 25;
|
|
267
|
+
return score;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Enhanced capability detection service with timeout and retry logic
|
|
272
|
+
*/
|
|
273
|
+
export class EnhancedCapabilityDetectionService extends DefaultCapabilityDetectionService {
|
|
274
|
+
DETECTION_TIMEOUT_MS = 5000; // 5 seconds
|
|
275
|
+
MAX_RETRIES = 3;
|
|
276
|
+
RETRY_DELAY_BASE_MS = 1000; // Base delay for exponential backoff
|
|
277
|
+
/**
|
|
278
|
+
* Detect model capability with timeout and retry logic
|
|
279
|
+
*/
|
|
280
|
+
async detectModelCapability(modelId, engine) {
|
|
281
|
+
// Check cache first
|
|
282
|
+
const cached = this.getCachedCapability(modelId);
|
|
283
|
+
if (cached && this.isCacheValid(cached)) {
|
|
284
|
+
return cached.capability;
|
|
285
|
+
}
|
|
286
|
+
// Browser inference always supports exact log-probabilities
|
|
287
|
+
if (engine === "browser") {
|
|
288
|
+
const capability = {
|
|
289
|
+
modelId,
|
|
290
|
+
engine: "browser",
|
|
291
|
+
method: "logprobs_exact",
|
|
292
|
+
status: "available",
|
|
293
|
+
recommended: true,
|
|
294
|
+
lastTested: new Date().toISOString(),
|
|
295
|
+
};
|
|
296
|
+
this.setCachedCapability(capability);
|
|
297
|
+
return capability;
|
|
298
|
+
}
|
|
299
|
+
// Detect Ollama model capability with timeout and retry
|
|
300
|
+
return this.detectOllamaCapabilityWithRetry(modelId);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Detect Ollama capability with retry logic and exponential backoff
|
|
304
|
+
*/
|
|
305
|
+
async detectOllamaCapabilityWithRetry(modelId) {
|
|
306
|
+
let lastError = null;
|
|
307
|
+
for (let attempt = 0; attempt < this.MAX_RETRIES; attempt++) {
|
|
308
|
+
try {
|
|
309
|
+
return await this.detectOllamaCapabilityWithTimeout(modelId);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
313
|
+
// Don't retry on certain errors
|
|
314
|
+
if (this.isNonRetryableError(lastError)) {
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
// Wait before retry with exponential backoff
|
|
318
|
+
if (attempt < this.MAX_RETRIES - 1) {
|
|
319
|
+
const delay = this.RETRY_DELAY_BASE_MS * 2 ** attempt;
|
|
320
|
+
await this.sleep(delay);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// All retries failed, return error capability
|
|
325
|
+
const errorMessage = this.getErrorMessage(lastError);
|
|
326
|
+
const capability = {
|
|
327
|
+
modelId,
|
|
328
|
+
engine: "ollama",
|
|
329
|
+
method: "logprobs_fallback_latency",
|
|
330
|
+
status: this.isOllamaUnavailableError(lastError) ? "unavailable" : "error",
|
|
331
|
+
error: errorMessage,
|
|
332
|
+
lastTested: new Date().toISOString(),
|
|
333
|
+
};
|
|
334
|
+
// Cache error results for shorter duration
|
|
335
|
+
this.setCachedCapability(capability, 5 * 60 * 1000); // 5 minutes for errors
|
|
336
|
+
return capability;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Detect Ollama capability with timeout
|
|
340
|
+
*/
|
|
341
|
+
async detectOllamaCapabilityWithTimeout(modelId) {
|
|
342
|
+
let timeoutId;
|
|
343
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
344
|
+
timeoutId = setTimeout(() => {
|
|
345
|
+
reject(new Error(`Detection timeout after ${this.DETECTION_TIMEOUT_MS}ms`));
|
|
346
|
+
}, this.DETECTION_TIMEOUT_MS);
|
|
347
|
+
});
|
|
348
|
+
const detectionPromise = (async () => {
|
|
349
|
+
const { OllamaClient, detectOllama } = await import("./ollama-client");
|
|
350
|
+
// First check if Ollama is available
|
|
351
|
+
const ollamaAvailable = await detectOllama();
|
|
352
|
+
if (!ollamaAvailable) {
|
|
353
|
+
throw new Error("Ollama service is not available");
|
|
354
|
+
}
|
|
355
|
+
const client = new OllamaClient(modelId);
|
|
356
|
+
// Test if model supports log-probabilities
|
|
357
|
+
const supportsLogProbs = await client.detectLogProbSupport();
|
|
358
|
+
const capability = {
|
|
359
|
+
modelId,
|
|
360
|
+
engine: "ollama",
|
|
361
|
+
method: supportsLogProbs ? "logprobs_exact" : "logprobs_fallback_latency",
|
|
362
|
+
status: "available",
|
|
363
|
+
lastTested: new Date().toISOString(),
|
|
364
|
+
};
|
|
365
|
+
this.setCachedCapability(capability);
|
|
366
|
+
return capability;
|
|
367
|
+
})();
|
|
368
|
+
try {
|
|
369
|
+
return await Promise.race([detectionPromise, timeoutPromise]);
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
if (timeoutId)
|
|
373
|
+
clearTimeout(timeoutId);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Enhanced detectAllCapabilities with better error handling
|
|
378
|
+
*/
|
|
379
|
+
async detectAllCapabilities() {
|
|
380
|
+
const capabilities = [];
|
|
381
|
+
// Add all supported browser models dynamically
|
|
382
|
+
try {
|
|
383
|
+
const { BROWSER_MODELS } = await import("./browser-inference");
|
|
384
|
+
for (const model of BROWSER_MODELS) {
|
|
385
|
+
if (!model.supported)
|
|
386
|
+
continue;
|
|
387
|
+
capabilities.push({
|
|
388
|
+
modelId: model.id,
|
|
389
|
+
engine: "browser",
|
|
390
|
+
method: "logprobs_exact",
|
|
391
|
+
status: "available",
|
|
392
|
+
recommended: true,
|
|
393
|
+
lastTested: new Date().toISOString(),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
console.warn("Failed to load browser models:", error);
|
|
399
|
+
// Fallback if import fails
|
|
400
|
+
capabilities.push({
|
|
401
|
+
modelId: "Xenova/distilgpt2",
|
|
402
|
+
engine: "browser",
|
|
403
|
+
method: "logprobs_exact",
|
|
404
|
+
status: "available",
|
|
405
|
+
recommended: true,
|
|
406
|
+
lastTested: new Date().toISOString(),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
// Import Ollama functions dynamically
|
|
411
|
+
const { detectOllama, listOllamaModels } = await import("./ollama-client");
|
|
412
|
+
// Check if Ollama is available with timeout
|
|
413
|
+
const ollamaAvailable = await this.withTimeout(detectOllama(), 2000, // 2 second timeout for Ollama availability check
|
|
414
|
+
"Ollama availability check timeout");
|
|
415
|
+
if (ollamaAvailable) {
|
|
416
|
+
// Get list of available Ollama models with timeout
|
|
417
|
+
const ollamaModels = await this.withTimeout(listOllamaModels(), 5000, // 5 second timeout for model listing
|
|
418
|
+
"Ollama model listing timeout");
|
|
419
|
+
if (ollamaModels.length === 0) {
|
|
420
|
+
console.warn("No Ollama models found");
|
|
421
|
+
return capabilities;
|
|
422
|
+
}
|
|
423
|
+
// Detect capabilities for all Ollama models in parallel
|
|
424
|
+
const detectionPromises = ollamaModels.map((model) => this.detectModelCapability(model.name, "ollama"));
|
|
425
|
+
const ollamaCapabilities = await Promise.allSettled(detectionPromises);
|
|
426
|
+
// Process results and add all capabilities (including errors)
|
|
427
|
+
ollamaCapabilities.forEach((result, index) => {
|
|
428
|
+
if (result.status === "fulfilled") {
|
|
429
|
+
capabilities.push(result.value);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// Add error capability for failed detections
|
|
433
|
+
const model = ollamaModels[index];
|
|
434
|
+
if (model) {
|
|
435
|
+
const errorMessage = result.reason?.message || "Detection failed";
|
|
436
|
+
capabilities.push({
|
|
437
|
+
modelId: model.name,
|
|
438
|
+
engine: "ollama",
|
|
439
|
+
method: "logprobs_fallback_latency",
|
|
440
|
+
status: "error",
|
|
441
|
+
error: errorMessage,
|
|
442
|
+
lastTested: new Date().toISOString(),
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
console.info("Ollama is not available, only browser inference will be offered");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
console.warn("Failed to detect Ollama capabilities:", error);
|
|
454
|
+
// Don't throw - we still have browser inference available
|
|
455
|
+
}
|
|
456
|
+
// Sort capabilities by recommendation score
|
|
457
|
+
return this.sortByRecommendation(capabilities);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Utility function to add timeout to any promise
|
|
461
|
+
*/
|
|
462
|
+
withTimeout(promise, timeoutMs, errorMessage) {
|
|
463
|
+
return new Promise((resolve, reject) => {
|
|
464
|
+
const timeoutId = setTimeout(() => {
|
|
465
|
+
reject(new Error(errorMessage));
|
|
466
|
+
}, timeoutMs);
|
|
467
|
+
promise
|
|
468
|
+
.then((result) => {
|
|
469
|
+
clearTimeout(timeoutId);
|
|
470
|
+
resolve(result);
|
|
471
|
+
})
|
|
472
|
+
.catch((error) => {
|
|
473
|
+
clearTimeout(timeoutId);
|
|
474
|
+
reject(error);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Sleep utility for retry delays
|
|
480
|
+
*/
|
|
481
|
+
sleep(ms) {
|
|
482
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Check if error should not be retried
|
|
486
|
+
*/
|
|
487
|
+
isNonRetryableError(error) {
|
|
488
|
+
const message = error.message.toLowerCase();
|
|
489
|
+
return (message.includes("not found") ||
|
|
490
|
+
(message.includes("model") && message.includes("not available")) ||
|
|
491
|
+
message.includes("permission denied") ||
|
|
492
|
+
message.includes("unauthorized"));
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Check if error indicates Ollama is unavailable
|
|
496
|
+
*/
|
|
497
|
+
isOllamaUnavailableError(error) {
|
|
498
|
+
if (!error)
|
|
499
|
+
return false;
|
|
500
|
+
const message = error.message.toLowerCase();
|
|
501
|
+
return (message.includes("ollama service is not available") ||
|
|
502
|
+
message.includes("connection refused") ||
|
|
503
|
+
message.includes("network error") ||
|
|
504
|
+
message.includes("fetch failed"));
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Get user-friendly error message
|
|
508
|
+
*/
|
|
509
|
+
getErrorMessage(error) {
|
|
510
|
+
if (!error)
|
|
511
|
+
return "Unknown error";
|
|
512
|
+
const message = error.message;
|
|
513
|
+
// Timeout errors
|
|
514
|
+
if (message.includes("timeout")) {
|
|
515
|
+
return "Model detection timed out. The model may be slow to respond.";
|
|
516
|
+
}
|
|
517
|
+
// Ollama unavailable
|
|
518
|
+
if (this.isOllamaUnavailableError(error)) {
|
|
519
|
+
return "Ollama service is not running. Please start Ollama to use local models.";
|
|
520
|
+
}
|
|
521
|
+
// Model not found
|
|
522
|
+
if (message.includes("not found")) {
|
|
523
|
+
return "Model not found. Please ensure the model is installed in Ollama.";
|
|
524
|
+
}
|
|
525
|
+
// Network errors
|
|
526
|
+
if (message.includes("network") || message.includes("fetch")) {
|
|
527
|
+
return "Network error. Please check your connection and try again.";
|
|
528
|
+
}
|
|
529
|
+
// Generic error
|
|
530
|
+
return `Detection failed: ${message}`;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Check if cache entry is still valid (override to handle version tracking)
|
|
534
|
+
*/
|
|
535
|
+
isCacheValid(cacheEntry) {
|
|
536
|
+
const now = new Date();
|
|
537
|
+
const expiresAt = new Date(cacheEntry.expiresAt);
|
|
538
|
+
// Check expiration
|
|
539
|
+
if (now >= expiresAt) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
// TODO: Add Ollama version checking when available
|
|
543
|
+
// For now, we rely on time-based expiration
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Create a capability detection service instance
|
|
549
|
+
*/
|
|
550
|
+
export function createCapabilityDetectionService() {
|
|
551
|
+
return new EnhancedCapabilityDetectionService();
|
|
552
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inference Factory
|
|
3
|
+
* Auto-detects available inference engines and provides unified interface
|
|
4
|
+
*/
|
|
5
|
+
import { type BrowserModel } from "./browser-inference";
|
|
6
|
+
import { type OllamaModel } from "./ollama-client";
|
|
7
|
+
export type InferenceEngine = "ollama" | "browser" | "demo";
|
|
8
|
+
export interface InferenceCapabilities {
|
|
9
|
+
ollama: {
|
|
10
|
+
available: boolean;
|
|
11
|
+
models: OllamaModel[];
|
|
12
|
+
};
|
|
13
|
+
browser: {
|
|
14
|
+
available: boolean;
|
|
15
|
+
webgpu: boolean;
|
|
16
|
+
models: BrowserModel[];
|
|
17
|
+
};
|
|
18
|
+
recommended: InferenceEngine;
|
|
19
|
+
}
|
|
20
|
+
export interface InferenceClient {
|
|
21
|
+
engine: InferenceEngine;
|
|
22
|
+
modelId: string;
|
|
23
|
+
generate(prompt: string): Promise<string>;
|
|
24
|
+
getLogProb(prompt: string): Promise<number>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Detect all available inference capabilities
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectCapabilities(): Promise<InferenceCapabilities>;
|
|
30
|
+
/**
|
|
31
|
+
* Create an inference client for the specified engine
|
|
32
|
+
*/
|
|
33
|
+
export declare function createInferenceClient(engine: InferenceEngine, modelId: string, statusCallback?: (status: string) => void): InferenceClient;
|
|
34
|
+
/**
|
|
35
|
+
* Auto-select the best available inference client
|
|
36
|
+
*/
|
|
37
|
+
export declare function createAutoInferenceClient(preferredModel?: string): Promise<InferenceClient>;
|
|
38
|
+
export { detectOllama, listOllamaModels, OllamaClient } from "./ollama-client";
|
|
39
|
+
export { BrowserInference, checkWebGPU, BROWSER_MODELS } from "./browser-inference";
|
|
40
|
+
export { InferenceFactory, createInferenceFactory, detectBestMethodForConfig, type InferenceResult, } from "./inference-factory";
|
|
41
|
+
export { createCapabilityDetectionService, DefaultCapabilityDetectionService, EnhancedCapabilityDetectionService, type CapabilityDetectionService, type ModelCapability, type ModelSelectionState, type CapabilityCacheEntry, type DetectionProgress, type UserPreferences, type EnhancedModelSelectionState, type CapabilityDetectionStatus, } from "./capability-detection";
|
|
42
|
+
export type { OllamaModel, BrowserModel };
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/inference/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAgB,KAAK,WAAW,EAAkC,MAAM,iBAAiB,CAAC;AAEjG,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QACN,SAAS,EAAE,OAAO,CAAC;QACnB,MAAM,EAAE,WAAW,EAAE,CAAC;KACvB,CAAC;IACF,OAAO,EAAE;QACP,SAAS,EAAE,OAAO,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,EAAE,YAAY,EAAE,CAAC;KACxB,CAAC;IACF,WAAW,EAAE,eAAe,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,qBAAqB,CAAC,CA2BzE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GACxC,eAAe,CAqCjB;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAejG;AAGD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,eAAe,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gCAAgC,EAChC,iCAAiC,EACjC,kCAAkC,EAClC,KAAK,0BAA0B,EAC/B,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,GAC/B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inference Factory
|
|
3
|
+
* Auto-detects available inference engines and provides unified interface
|
|
4
|
+
*/
|
|
5
|
+
import { BROWSER_MODELS, BrowserInference, checkWebGPU, } from "./browser-inference";
|
|
6
|
+
import { OllamaClient, detectOllama, listOllamaModels } from "./ollama-client";
|
|
7
|
+
/**
|
|
8
|
+
* Detect all available inference capabilities
|
|
9
|
+
*/
|
|
10
|
+
export async function detectCapabilities() {
|
|
11
|
+
const [ollamaAvailable, webgpuAvailable, ollamaModels] = await Promise.all([
|
|
12
|
+
detectOllama(),
|
|
13
|
+
checkWebGPU(),
|
|
14
|
+
detectOllama().then((ok) => (ok ? listOllamaModels() : [])),
|
|
15
|
+
]);
|
|
16
|
+
// Determine recommended engine
|
|
17
|
+
let recommended = "demo";
|
|
18
|
+
if (ollamaAvailable && ollamaModels.length > 0) {
|
|
19
|
+
recommended = "ollama";
|
|
20
|
+
}
|
|
21
|
+
else if (webgpuAvailable) {
|
|
22
|
+
recommended = "browser";
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
ollama: {
|
|
26
|
+
available: ollamaAvailable,
|
|
27
|
+
models: ollamaModels,
|
|
28
|
+
},
|
|
29
|
+
browser: {
|
|
30
|
+
available: webgpuAvailable || true, // CPU fallback always available
|
|
31
|
+
webgpu: webgpuAvailable,
|
|
32
|
+
models: BROWSER_MODELS,
|
|
33
|
+
},
|
|
34
|
+
recommended,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create an inference client for the specified engine
|
|
39
|
+
*/
|
|
40
|
+
export function createInferenceClient(engine, modelId, statusCallback) {
|
|
41
|
+
switch (engine) {
|
|
42
|
+
case "ollama": {
|
|
43
|
+
const ollamaClient = new OllamaClient(modelId);
|
|
44
|
+
return {
|
|
45
|
+
engine: "ollama",
|
|
46
|
+
modelId,
|
|
47
|
+
generate: (prompt) => ollamaClient.generate(prompt),
|
|
48
|
+
getLogProb: (prompt) => ollamaClient.getLogProb(prompt),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
case "browser": {
|
|
52
|
+
const browserClient = new BrowserInference(modelId, statusCallback);
|
|
53
|
+
return {
|
|
54
|
+
engine: "browser",
|
|
55
|
+
modelId,
|
|
56
|
+
generate: (prompt) => browserClient.generate(prompt),
|
|
57
|
+
getLogProb: (prompt) => browserClient.getLogProb(prompt),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
// Demo mode returns simulated results
|
|
62
|
+
return {
|
|
63
|
+
engine: "demo",
|
|
64
|
+
modelId: "demo-model",
|
|
65
|
+
generate: async (prompt) => {
|
|
66
|
+
await new Promise((r) => setTimeout(r, 500)); // Simulate latency
|
|
67
|
+
return `[Demo] Response to: ${prompt.slice(0, 50)}...`;
|
|
68
|
+
},
|
|
69
|
+
getLogProb: async () => {
|
|
70
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
71
|
+
// Return random value between -5 and 0 for demo
|
|
72
|
+
return -Math.random() * 5;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Auto-select the best available inference client
|
|
79
|
+
*/
|
|
80
|
+
export async function createAutoInferenceClient(preferredModel) {
|
|
81
|
+
const capabilities = await detectCapabilities();
|
|
82
|
+
if (capabilities.recommended === "ollama") {
|
|
83
|
+
// Use preferred model if available, otherwise first model
|
|
84
|
+
const model = preferredModel || capabilities.ollama.models[0]?.name || "llama3.2:3b";
|
|
85
|
+
return createInferenceClient("ollama", model);
|
|
86
|
+
}
|
|
87
|
+
if (capabilities.recommended === "browser") {
|
|
88
|
+
const model = preferredModel || BROWSER_MODELS[0]?.id || "Xenova/distilgpt2";
|
|
89
|
+
return createInferenceClient("browser", model);
|
|
90
|
+
}
|
|
91
|
+
return createInferenceClient("demo", "demo-model");
|
|
92
|
+
}
|
|
93
|
+
// Re-export types and utilities
|
|
94
|
+
export { detectOllama, listOllamaModels, OllamaClient } from "./ollama-client";
|
|
95
|
+
export { BrowserInference, checkWebGPU, BROWSER_MODELS } from "./browser-inference";
|
|
96
|
+
export { InferenceFactory, createInferenceFactory, detectBestMethodForConfig, } from "./inference-factory";
|
|
97
|
+
export { createCapabilityDetectionService, DefaultCapabilityDetectionService, EnhancedCapabilityDetectionService, } from "./capability-detection";
|