Spaces:
Paused
Paused
CrispStrobe commited on
Commit ·
03b99dd
1
Parent(s): 88466b5
feat: implement private model caching for HF Hub and add manual mappings for Qwen/IONOS models
Browse files- data/providers.json +0 -0
- scripts/fetch-providers.js +55 -22
data/providers.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/fetch-providers.js
CHANGED
|
@@ -53,7 +53,7 @@ function updateProviderModels(providers, providerName, models) {
|
|
| 53 |
return false;
|
| 54 |
}
|
| 55 |
|
| 56 |
-
// Smart merge: preserve existing metadata (size_b, hf_id, capabilities) if missing in new data
|
| 57 |
const existingMap = new Map((provider.models || []).map(m => [m.name, m]));
|
| 58 |
|
| 59 |
provider.models = models.map(newModel => {
|
|
@@ -66,6 +66,7 @@ function updateProviderModels(providers, providerName, models) {
|
|
| 66 |
// But preserve these if newModel doesn't have them
|
| 67 |
size_b: newModel.size_b || existing.size_b,
|
| 68 |
hf_id: newModel.hf_id || existing.hf_id,
|
|
|
|
| 69 |
capabilities: (newModel.capabilities && newModel.capabilities.length > 0)
|
| 70 |
? newModel.capabilities
|
| 71 |
: existing.capabilities,
|
|
@@ -112,7 +113,7 @@ function findOrMatch(modelName, orIndex) {
|
|
| 112 |
for (const entry of orIndex) {
|
| 113 |
if (entry.norm === n) return entry;
|
| 114 |
}
|
| 115 |
-
// 2. Provider model name starts with OR model part
|
| 116 |
let best = null;
|
| 117 |
let bestLen = 0;
|
| 118 |
for (const entry of orIndex) {
|
|
@@ -122,12 +123,11 @@ function findOrMatch(modelName, orIndex) {
|
|
| 122 |
}
|
| 123 |
}
|
| 124 |
if (best) return best;
|
| 125 |
-
// 3. OR model part starts with provider name
|
| 126 |
for (const entry of orIndex) {
|
| 127 |
if (entry.norm.startsWith(n + ' ')) return entry;
|
| 128 |
}
|
| 129 |
// 4. OR model norm contains provider name as a contiguous word sequence.
|
| 130 |
-
// Handles short display names like "Sonnet 4.6" matching inside "claude sonnet 4 6".
|
| 131 |
if (n.length >= 5) {
|
| 132 |
let bestC = null, bestCLen = Infinity;
|
| 133 |
for (const entry of orIndex) {
|
|
@@ -139,8 +139,7 @@ function findOrMatch(modelName, orIndex) {
|
|
| 139 |
}
|
| 140 |
if (bestC) return bestC;
|
| 141 |
}
|
| 142 |
-
// 5. All tokens of provider name appear in OR norm
|
| 143 |
-
// e.g. "Sonnet 3.7" → tokens ["sonnet","3","7"] match inside "claude 3 7 sonnet 20250219".
|
| 144 |
const tokens = n.split(' ');
|
| 145 |
if (tokens.length >= 2 && n.length >= 7) {
|
| 146 |
let bestT = null, bestTLen = Infinity;
|
|
@@ -157,23 +156,30 @@ function findOrMatch(modelName, orIndex) {
|
|
| 157 |
|
| 158 |
// Fetch total_parameters from Hugging Face Hub API (Metadata)
|
| 159 |
async function fetchHFSize(hfId) {
|
| 160 |
-
if (!hfId || hfId.includes(' ') || !hfId.includes('/')) return
|
| 161 |
const token = process.env.HF_TOKEN;
|
| 162 |
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
| 163 |
try {
|
|
|
|
| 164 |
const data = await getJson(`https://huggingface.co/api/models/${hfId}`, { headers, retries: 1 });
|
|
|
|
| 165 |
// Check various common metadata locations for total parameters
|
| 166 |
let params = data.safetensors?.total || data.config?.total_parameters || data.config?.model_type_params;
|
| 167 |
if (!params && data.cardData?.model_details?.parameters) {
|
| 168 |
const match = data.cardData.model_details.parameters.match(/([\d.]+)\s*[Bb]/);
|
| 169 |
if (match) params = parseFloat(match[1]) * 1_000_000_000;
|
| 170 |
}
|
| 171 |
-
|
|
|
|
|
|
|
| 172 |
const b = params / 1_000_000_000;
|
| 173 |
// Keep 2 decimals for small models (<1B), 1 decimal for others
|
| 174 |
-
|
|
|
|
| 175 |
} catch (e) {
|
| 176 |
-
|
|
|
|
|
|
|
| 177 |
}
|
| 178 |
}
|
| 179 |
|
|
@@ -186,6 +192,19 @@ const MANUAL_HF_ID_MAP = {
|
|
| 186 |
'whisper large v3': 'openai/whisper-large-v3',
|
| 187 |
'step 3 5 flash': 'stepfun-ai/Step-3.5-Flash',
|
| 188 |
'bge m3': 'BAAI/bge-m3',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
};
|
| 190 |
|
| 191 |
const MANUAL_SIZE_MAP = {
|
|
@@ -282,6 +301,7 @@ async function propagateExtraData(data) {
|
|
| 282 |
}
|
| 283 |
// Crucial: inherit hf_id to enable Hub API fallback below
|
| 284 |
if (!model.hf_id && match.hf_id) model.hf_id = match.hf_id;
|
|
|
|
| 285 |
}
|
| 286 |
}
|
| 287 |
|
|
@@ -293,7 +313,8 @@ async function propagateExtraData(data) {
|
|
| 293 |
}
|
| 294 |
|
| 295 |
// 6. QUEUE: Still missing size? Try Hub API metadata lookup
|
| 296 |
-
|
|
|
|
| 297 |
hfLookupQueue.push(model);
|
| 298 |
}
|
| 299 |
}
|
|
@@ -303,18 +324,26 @@ async function propagateExtraData(data) {
|
|
| 303 |
const uniqueIds = [...new Set(hfLookupQueue.map(m => m.hf_id || m.name).filter(id => id.includes('/')))].slice(0, 200);
|
| 304 |
if (uniqueIds.length > 0) {
|
| 305 |
console.log(`\n HF Hub: technical metadata inspection for ${uniqueIds.length} models...`);
|
| 306 |
-
const
|
| 307 |
|
| 308 |
// Process sequentially with small delay to avoid 429 rate limits
|
| 309 |
for (let i = 0; i < uniqueIds.length; i++) {
|
| 310 |
const id = uniqueIds[i];
|
| 311 |
process.stdout.write(` [${i + 1}/${uniqueIds.length}] ${id.padEnd(50)} `);
|
| 312 |
-
const
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
|
|
|
| 316 |
} else {
|
| 317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
}
|
| 319 |
await new Promise(r => setTimeout(r, 50)); // Tiny delay
|
| 320 |
}
|
|
@@ -322,17 +351,21 @@ async function propagateExtraData(data) {
|
|
| 322 |
for (const model of hfLookupQueue) {
|
| 323 |
if (!model.size_b) {
|
| 324 |
const id = model.hf_id || model.name;
|
| 325 |
-
const
|
| 326 |
-
if (
|
| 327 |
-
|
| 328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
}
|
| 330 |
}
|
| 331 |
}
|
| 332 |
console.log(` ✓ Total ${hfSizeFetched} new sizes from HF metadata`);
|
| 333 |
}
|
| 334 |
|
| 335 |
-
|
| 336 |
if (autoTagged > 0) console.log(`Auto-tagged ${autoTagged} image-gen/embedding models.`);
|
| 337 |
if (propagatedCaps > 0) console.log(`Propagated capabilities to ${propagatedCaps} models.`);
|
| 338 |
if (propagatedSize + hfSizeFetched > 0) console.log(`Enriched size data for ${propagatedSize + hfSizeFetched} models.`);
|
|
|
|
| 53 |
return false;
|
| 54 |
}
|
| 55 |
|
| 56 |
+
// Smart merge: preserve existing metadata (size_b, hf_id, capabilities, hf_private) if missing in new data
|
| 57 |
const existingMap = new Map((provider.models || []).map(m => [m.name, m]));
|
| 58 |
|
| 59 |
provider.models = models.map(newModel => {
|
|
|
|
| 66 |
// But preserve these if newModel doesn't have them
|
| 67 |
size_b: newModel.size_b || existing.size_b,
|
| 68 |
hf_id: newModel.hf_id || existing.hf_id,
|
| 69 |
+
hf_private: newModel.hf_private ?? existing.hf_private,
|
| 70 |
capabilities: (newModel.capabilities && newModel.capabilities.length > 0)
|
| 71 |
? newModel.capabilities
|
| 72 |
: existing.capabilities,
|
|
|
|
| 113 |
for (const entry of orIndex) {
|
| 114 |
if (entry.norm === n) return entry;
|
| 115 |
}
|
| 116 |
+
// 2. Provider model name starts with OR model part
|
| 117 |
let best = null;
|
| 118 |
let bestLen = 0;
|
| 119 |
for (const entry of orIndex) {
|
|
|
|
| 123 |
}
|
| 124 |
}
|
| 125 |
if (best) return best;
|
| 126 |
+
// 3. OR model part starts with provider name
|
| 127 |
for (const entry of orIndex) {
|
| 128 |
if (entry.norm.startsWith(n + ' ')) return entry;
|
| 129 |
}
|
| 130 |
// 4. OR model norm contains provider name as a contiguous word sequence.
|
|
|
|
| 131 |
if (n.length >= 5) {
|
| 132 |
let bestC = null, bestCLen = Infinity;
|
| 133 |
for (const entry of orIndex) {
|
|
|
|
| 139 |
}
|
| 140 |
if (bestC) return bestC;
|
| 141 |
}
|
| 142 |
+
// 5. All tokens of provider name appear in OR norm.
|
|
|
|
| 143 |
const tokens = n.split(' ');
|
| 144 |
if (tokens.length >= 2 && n.length >= 7) {
|
| 145 |
let bestT = null, bestTLen = Infinity;
|
|
|
|
| 156 |
|
| 157 |
// Fetch total_parameters from Hugging Face Hub API (Metadata)
|
| 158 |
async function fetchHFSize(hfId) {
|
| 159 |
+
if (!hfId || hfId.includes(' ') || !hfId.includes('/')) return { error: 'Invalid ID' };
|
| 160 |
const token = process.env.HF_TOKEN;
|
| 161 |
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
| 162 |
try {
|
| 163 |
+
// Limit to 1 retry for technical metadata lookups
|
| 164 |
const data = await getJson(`https://huggingface.co/api/models/${hfId}`, { headers, retries: 1 });
|
| 165 |
+
|
| 166 |
// Check various common metadata locations for total parameters
|
| 167 |
let params = data.safetensors?.total || data.config?.total_parameters || data.config?.model_type_params;
|
| 168 |
if (!params && data.cardData?.model_details?.parameters) {
|
| 169 |
const match = data.cardData.model_details.parameters.match(/([\d.]+)\s*[Bb]/);
|
| 170 |
if (match) params = parseFloat(match[1]) * 1_000_000_000;
|
| 171 |
}
|
| 172 |
+
|
| 173 |
+
if (!params) return { error: 'No parameter data in Hub metadata' };
|
| 174 |
+
|
| 175 |
const b = params / 1_000_000_000;
|
| 176 |
// Keep 2 decimals for small models (<1B), 1 decimal for others
|
| 177 |
+
const size = b < 1 ? Math.round(b * 100) / 100 : Math.round(b * 10) / 10;
|
| 178 |
+
return { size };
|
| 179 |
} catch (e) {
|
| 180 |
+
// Flag as private if we get 401 (unauthorized) or 404 (not found - often private/aliased)
|
| 181 |
+
const isPrivate = e.message.includes('401') || e.message.includes('404');
|
| 182 |
+
return { error: e.message, private: isPrivate };
|
| 183 |
}
|
| 184 |
}
|
| 185 |
|
|
|
|
| 192 |
'whisper large v3': 'openai/whisper-large-v3',
|
| 193 |
'step 3 5 flash': 'stepfun-ai/Step-3.5-Flash',
|
| 194 |
'bge m3': 'BAAI/bge-m3',
|
| 195 |
+
'lightonocr 2': 'lightonai/LightOnOCR-2-1B',
|
| 196 |
+
'flux 1 schnell': 'black-forest-labs/FLUX.1-schnell',
|
| 197 |
+
'paraphrase multilingual mpnet base v2': 'sentence-transformers/paraphrase-multilingual-mpnet-base-v2',
|
| 198 |
+
'bge large en v1 5': 'BAAI/bge-large-en-v1.5',
|
| 199 |
+
'bge multilingual gemma2': 'BAAI/bge-multilingual-gemma2',
|
| 200 |
+
'photomaker v2': 'TencentARC/PhotoMaker-V2',
|
| 201 |
+
'flux schnell': 'black-forest-labs/FLUX.1-schnell',
|
| 202 |
+
// Qwen mappings
|
| 203 |
+
'qwen3 coder flash': 'Qwen/Qwen2.5-Coder-7B-Instruct', // Counterpart mapping
|
| 204 |
+
'qwen3 coder plus': 'Qwen/Qwen2.5-Coder-32B-Instruct',
|
| 205 |
+
'qwen 3 5 flash': 'Qwen/Qwen2.5-7B-Instruct',
|
| 206 |
+
'qwen vl plus': 'Qwen/Qwen2-VL-7B-Instruct',
|
| 207 |
+
'qwen vl max': 'Qwen/Qwen2-VL-72B-Instruct',
|
| 208 |
};
|
| 209 |
|
| 210 |
const MANUAL_SIZE_MAP = {
|
|
|
|
| 301 |
}
|
| 302 |
// Crucial: inherit hf_id to enable Hub API fallback below
|
| 303 |
if (!model.hf_id && match.hf_id) model.hf_id = match.hf_id;
|
| 304 |
+
if (model.hf_private === undefined && match.hf_private !== undefined) model.hf_private = match.hf_private;
|
| 305 |
}
|
| 306 |
}
|
| 307 |
|
|
|
|
| 313 |
}
|
| 314 |
|
| 315 |
// 6. QUEUE: Still missing size? Try Hub API metadata lookup
|
| 316 |
+
// Skip models that we've previously marked as private/unauthorized
|
| 317 |
+
if (!model.size_b && !model.hf_private && (model.name.includes('/') || model.hf_id)) {
|
| 318 |
hfLookupQueue.push(model);
|
| 319 |
}
|
| 320 |
}
|
|
|
|
| 324 |
const uniqueIds = [...new Set(hfLookupQueue.map(m => m.hf_id || m.name).filter(id => id.includes('/')))].slice(0, 200);
|
| 325 |
if (uniqueIds.length > 0) {
|
| 326 |
console.log(`\n HF Hub: technical metadata inspection for ${uniqueIds.length} models...`);
|
| 327 |
+
const idToResult = new Map();
|
| 328 |
|
| 329 |
// Process sequentially with small delay to avoid 429 rate limits
|
| 330 |
for (let i = 0; i < uniqueIds.length; i++) {
|
| 331 |
const id = uniqueIds[i];
|
| 332 |
process.stdout.write(` [${i + 1}/${uniqueIds.length}] ${id.padEnd(50)} `);
|
| 333 |
+
const result = await fetchHFSize(id);
|
| 334 |
+
|
| 335 |
+
if (result.size) {
|
| 336 |
+
idToResult.set(id, result);
|
| 337 |
+
process.stdout.write(`✓ ${result.size}B\n`);
|
| 338 |
} else {
|
| 339 |
+
idToResult.set(id, result);
|
| 340 |
+
process.stdout.write(`✗ ${result.error || 'Unknown Error'}\n`);
|
| 341 |
+
|
| 342 |
+
// CIRCUIT BREAKER: Stop if we hit a rate limit (429)
|
| 343 |
+
if (result.error && result.error.includes('429')) {
|
| 344 |
+
console.warn('\n ⚠ HIT RATE LIMIT (429) - Stopping further HF lookups for this run.');
|
| 345 |
+
break;
|
| 346 |
+
}
|
| 347 |
}
|
| 348 |
await new Promise(r => setTimeout(r, 50)); // Tiny delay
|
| 349 |
}
|
|
|
|
| 351 |
for (const model of hfLookupQueue) {
|
| 352 |
if (!model.size_b) {
|
| 353 |
const id = model.hf_id || model.name;
|
| 354 |
+
const result = idToResult.get(id);
|
| 355 |
+
if (result) {
|
| 356 |
+
if (result.size) {
|
| 357 |
+
model.size_b = result.size;
|
| 358 |
+
hfSizeFetched++;
|
| 359 |
+
}
|
| 360 |
+
if (result.private) {
|
| 361 |
+
model.hf_private = true;
|
| 362 |
+
}
|
| 363 |
}
|
| 364 |
}
|
| 365 |
}
|
| 366 |
console.log(` ✓ Total ${hfSizeFetched} new sizes from HF metadata`);
|
| 367 |
}
|
| 368 |
|
|
|
|
| 369 |
if (autoTagged > 0) console.log(`Auto-tagged ${autoTagged} image-gen/embedding models.`);
|
| 370 |
if (propagatedCaps > 0) console.log(`Propagated capabilities to ${propagatedCaps} models.`);
|
| 371 |
if (propagatedSize + hfSizeFetched > 0) console.log(`Enriched size data for ${propagatedSize + hfSizeFetched} models.`);
|