llm-checker 3.5.12 → 3.5.13
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/README.md +83 -17
- package/bin/cli.js +40 -0
- package/bin/enhanced_cli.js +360 -33
- package/package.json +2 -1
- package/src/ai/model-selector.js +47 -16
- package/src/ai/multi-objective-selector.js +55 -9
- package/src/data/model-database.js +92 -1
- package/src/data/seed/README.md +8 -0
- package/src/data/seed/models.db +0 -0
- package/src/hardware/backends/rocm-detector.js +469 -68
- package/src/hardware/unified-detector.js +39 -5
- package/src/index.js +40 -7
- package/src/models/ai-check-selector.js +27 -2
- package/src/models/deterministic-selector.js +80 -7
- package/src/ollama/client.js +121 -0
- package/src/ollama/enhanced-scraper.js +40 -26
- package/src/ollama/native-scraper.js +52 -27
- package/src/ui/cli-theme.js +139 -24
- package/src/ui/interactive-panel.js +1 -18
- package/src/utils/verbose-progress.js +144 -187
package/bin/enhanced_cli.js
CHANGED
|
@@ -4,6 +4,7 @@ const chalk = require('chalk');
|
|
|
4
4
|
const ora = require('ora');
|
|
5
5
|
const { table } = require('table');
|
|
6
6
|
const os = require('os');
|
|
7
|
+
const readline = require('readline');
|
|
7
8
|
const { spawn } = require('child_process');
|
|
8
9
|
// LLMChecker is loaded lazily to avoid slow systeminformation init
|
|
9
10
|
let _LLMChecker = null;
|
|
@@ -49,6 +50,7 @@ const {
|
|
|
49
50
|
buildComplianceReport,
|
|
50
51
|
serializeComplianceReport
|
|
51
52
|
} = require('../src/policy/audit-reporter');
|
|
53
|
+
const { estimateTokenSpeedFromHardware } = require('../src/utils/token-speed-estimator');
|
|
52
54
|
const { renderCommandHeader, renderPersistentBanner } = require('../src/ui/cli-theme');
|
|
53
55
|
const { launchInteractivePanel } = require('../src/ui/interactive-panel');
|
|
54
56
|
const policyManager = new PolicyManager();
|
|
@@ -983,6 +985,12 @@ function displaySystemInfo(hardware, analysis) {
|
|
|
983
985
|
const gpuColor = hardware.gpu.dedicated ? chalk.green : chalk.hex('#FFA500');
|
|
984
986
|
const integratedList = formatGpuInventoryList(hardware.gpu.integratedGpuModels || hardware.summary?.integratedGpuModels);
|
|
985
987
|
const dedicatedList = formatGpuInventoryList(hardware.gpu.dedicatedGpuModels || hardware.summary?.dedicatedGpuModels);
|
|
988
|
+
const integratedSharedMemory = hardware.gpu.sharedMemory || hardware.summary?.integratedSharedMemory || 0;
|
|
989
|
+
const vramDisplay = !hardware.gpu.dedicated && integratedSharedMemory > 0
|
|
990
|
+
? `${integratedSharedMemory}GB shared`
|
|
991
|
+
: (hardware.gpu.vram === 0 && hardware.gpu.model && hardware.gpu.model.toLowerCase().includes('apple')
|
|
992
|
+
? 'Unified Memory'
|
|
993
|
+
: `${hardware.gpu.vram || 'N/A'}GB`);
|
|
986
994
|
|
|
987
995
|
const lines = [
|
|
988
996
|
`${chalk.cyan('CPU:')} ${cpuColor(hardware.cpu.brand)} ${chalk.gray(`(${hardware.cpu.cores} cores, ${hardware.cpu.speed}GHz)`)}`,
|
|
@@ -990,7 +998,7 @@ function displaySystemInfo(hardware, analysis) {
|
|
|
990
998
|
`${chalk.cyan('RAM:')} ${ramColor(hardware.memory.total + 'GB')}`,
|
|
991
999
|
`${chalk.cyan('GPU:')} ${gpuColor(hardware.gpu.model || 'Not detected')}`,
|
|
992
1000
|
`${chalk.cyan('Backend:')} ${chalk.white(getBackendLabelForDisplay(hardware))}`,
|
|
993
|
-
`${chalk.cyan('VRAM:')} ${
|
|
1001
|
+
`${chalk.cyan('VRAM:')} ${vramDisplay}${hardware.gpu.dedicated ? chalk.green(' (Dedicated)') : chalk.hex('#FFA500')(' (Integrated)')}`,
|
|
994
1002
|
`${chalk.cyan('Dedicated GPUs:')} ${chalk.green(dedicatedList)}`,
|
|
995
1003
|
`${chalk.cyan('Integrated GPUs:')} ${chalk.hex('#FFA500')(integratedList)}`,
|
|
996
1004
|
];
|
|
@@ -1426,6 +1434,266 @@ function displayCalibratedRoutingDecision(commandName, calibratedPolicy, routeDe
|
|
|
1426
1434
|
console.log(chalk.blue('╰'));
|
|
1427
1435
|
}
|
|
1428
1436
|
|
|
1437
|
+
function parseAiRunModelSizeB(value) {
|
|
1438
|
+
const match = String(value || '').match(/(\d+(?:\.\d+)?)\s*([kmb])\+?/i);
|
|
1439
|
+
if (!match) return null;
|
|
1440
|
+
|
|
1441
|
+
const amount = Number(match[1]);
|
|
1442
|
+
if (!Number.isFinite(amount) || amount <= 0) return null;
|
|
1443
|
+
|
|
1444
|
+
const unit = match[2].toLowerCase();
|
|
1445
|
+
if (unit === 'b') return amount;
|
|
1446
|
+
if (unit === 'm') return amount / 1000;
|
|
1447
|
+
if (unit === 'k') return amount / 1_000_000;
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function normalizeAiRunModelName(value) {
|
|
1452
|
+
return String(value || '')
|
|
1453
|
+
.trim()
|
|
1454
|
+
.toLowerCase()
|
|
1455
|
+
.replace(/:latest$/, '');
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function findAiRunLocalModel(localModels = [], modelName = '') {
|
|
1459
|
+
const target = normalizeAiRunModelName(modelName);
|
|
1460
|
+
if (!target) return null;
|
|
1461
|
+
|
|
1462
|
+
return localModels.find((model) => {
|
|
1463
|
+
const name = normalizeAiRunModelName(model.name || model.model);
|
|
1464
|
+
if (!name) return false;
|
|
1465
|
+
return name === target || name.includes(target) || target.includes(name);
|
|
1466
|
+
}) || null;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function resolveAiRunModelSizeB(modelName, aiSelector, localModel = null) {
|
|
1470
|
+
const localParameterSize = localModel?.details?.parameter_size || localModel?.size;
|
|
1471
|
+
const parsedLocalSize = parseAiRunModelSizeB(localParameterSize);
|
|
1472
|
+
if (parsedLocalSize) return parsedLocalSize;
|
|
1473
|
+
|
|
1474
|
+
const parsedNameSize = parseAiRunModelSizeB(modelName);
|
|
1475
|
+
if (parsedNameSize) return parsedNameSize;
|
|
1476
|
+
|
|
1477
|
+
if (aiSelector && typeof aiSelector.estimateModelSize === 'function') {
|
|
1478
|
+
const selectorSize = Number(aiSelector.estimateModelSize(modelName));
|
|
1479
|
+
if (Number.isFinite(selectorSize) && selectorSize > 0) return selectorSize;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
return 7;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function formatAiRunNumber(value, decimals = 1) {
|
|
1486
|
+
const number = Number(value);
|
|
1487
|
+
if (!Number.isFinite(number)) return 'N/A';
|
|
1488
|
+
return number.toFixed(decimals).replace(/\.0$/, '');
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function estimateAiRunWorkingSetGB(modelSizeB, localModel = null) {
|
|
1492
|
+
const fileSizeGB = Number(localModel?.fileSizeGB) || 0;
|
|
1493
|
+
const parameterEstimateGB = (Number(modelSizeB) * 0.75) + 2;
|
|
1494
|
+
if (fileSizeGB > 0) {
|
|
1495
|
+
return Math.max(fileSizeGB * 1.15, parameterEstimateGB * 0.85);
|
|
1496
|
+
}
|
|
1497
|
+
return parameterEstimateGB;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function formatAiRunHardwareSummary(systemInfo = {}) {
|
|
1501
|
+
const cpuBrand = systemInfo.cpu?.brand || systemInfo.cpu?.model || 'CPU';
|
|
1502
|
+
const cores = systemInfo.cpu?.cores ? ` (${systemInfo.cpu.cores} cores)` : '';
|
|
1503
|
+
const memory = systemInfo.memory?.total ? `${systemInfo.memory.total}GB RAM` : 'RAM unknown';
|
|
1504
|
+
const gpu = systemInfo.gpu?.model || 'GPU not detected';
|
|
1505
|
+
return `${cpuBrand}${cores}, ${memory}, ${gpu}`;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function formatAiRunMethod(method = '') {
|
|
1509
|
+
return String(method || 'selector')
|
|
1510
|
+
.replace(/[_-]+/g, ' ')
|
|
1511
|
+
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function formatAiRunReason(reason = '') {
|
|
1515
|
+
const text = String(reason || '').replace(/\s+/g, ' ').trim();
|
|
1516
|
+
if (!text) return 'Selected from local model compatibility scoring.';
|
|
1517
|
+
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
function formatAiRunMeasuredSpeed(benchmark = null) {
|
|
1521
|
+
if (!benchmark) return null;
|
|
1522
|
+
if (!benchmark.success) {
|
|
1523
|
+
return `not available (${benchmark.error || 'benchmark failed'})`;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const parts = [];
|
|
1527
|
+
if (Number(benchmark.evalTokensPerSecond) > 0) {
|
|
1528
|
+
parts.push(`${formatAiRunNumber(benchmark.evalTokensPerSecond)} eval t/s`);
|
|
1529
|
+
}
|
|
1530
|
+
if (Number(benchmark.endToEndTokensPerSecond) > 0) {
|
|
1531
|
+
parts.push(`${formatAiRunNumber(benchmark.endToEndTokensPerSecond)} end-to-end t/s`);
|
|
1532
|
+
}
|
|
1533
|
+
if (parts.length === 0 && Number(benchmark.tokensPerSecond) > 0) {
|
|
1534
|
+
parts.push(`${formatAiRunNumber(benchmark.tokensPerSecond)} t/s`);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
const generated = Number(benchmark.tokensGenerated) > 0
|
|
1538
|
+
? `, ${benchmark.tokensGenerated} tokens`
|
|
1539
|
+
: '';
|
|
1540
|
+
return `${parts.join(', ')}${generated}`;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
function displayAiRunReference({ result, systemInfo, taskHint, candidateModels, localModels, aiSelector, benchmark }) {
|
|
1544
|
+
const localModel = findAiRunLocalModel(localModels, result.bestModel);
|
|
1545
|
+
const modelSizeB = resolveAiRunModelSizeB(result.bestModel, aiSelector, localModel);
|
|
1546
|
+
const speedEstimate = estimateTokenSpeedFromHardware(systemInfo, {
|
|
1547
|
+
modelSizeB,
|
|
1548
|
+
modelName: result.bestModel
|
|
1549
|
+
});
|
|
1550
|
+
const workingSetGB = estimateAiRunWorkingSetGB(modelSizeB, localModel);
|
|
1551
|
+
const localCount = result.localModelsCount || candidateModels.length;
|
|
1552
|
+
const dbCount = result.totalModelsEvaluated;
|
|
1553
|
+
const confidence = Number(result.confidence);
|
|
1554
|
+
const confidenceText = Number.isFinite(confidence)
|
|
1555
|
+
? `${Math.round(confidence * 100)}%`
|
|
1556
|
+
: 'N/A';
|
|
1557
|
+
const idealModel = result.recommendedFromDatabase;
|
|
1558
|
+
const usesFallback = idealModel && idealModel !== result.bestModel && result.isRecommendedInstalled === false;
|
|
1559
|
+
const measuredSpeed = formatAiRunMeasuredSpeed(benchmark);
|
|
1560
|
+
|
|
1561
|
+
console.log('\n' + chalk.bold('AI Run reference'));
|
|
1562
|
+
console.log(chalk.gray('----------------'));
|
|
1563
|
+
console.log(`${chalk.gray('Task:')} ${chalk.white(taskHint || 'general')}`);
|
|
1564
|
+
console.log(`${chalk.gray('Selected local model:')} ${chalk.green.bold(result.bestModel)}`);
|
|
1565
|
+
|
|
1566
|
+
if (idealModel) {
|
|
1567
|
+
const idealStatus = usesFallback ? chalk.yellow('not installed') : chalk.green('available');
|
|
1568
|
+
console.log(`${chalk.gray('Best database match:')} ${chalk.cyan(idealModel)} ${chalk.gray('(')}${idealStatus}${chalk.gray(')')}`);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
console.log(`${chalk.gray('Why this model:')} ${formatAiRunReason(result.reasoning || result.reason)}`);
|
|
1572
|
+
console.log(`${chalk.gray('Confidence:')} ${chalk.white(confidenceText)} ${chalk.gray(`via ${formatAiRunMethod(result.method)}`)}`);
|
|
1573
|
+
console.log(`${chalk.gray('Models evaluated:')} ${chalk.white(`${localCount} local`)}${dbCount ? chalk.gray(`, ${dbCount} database`) : ''}`);
|
|
1574
|
+
console.log(`${chalk.gray('Hardware:')} ${formatAiRunHardwareSummary(systemInfo)}`);
|
|
1575
|
+
console.log(`${chalk.gray('Estimated speed:')} ${chalk.yellow(`~${speedEstimate.tokensPerSecond} tokens/sec`)} ${chalk.gray(`${speedEstimate.backend}, generation only`)}`);
|
|
1576
|
+
|
|
1577
|
+
if (measuredSpeed) {
|
|
1578
|
+
const speedColor = benchmark?.success ? chalk.green : chalk.yellow;
|
|
1579
|
+
console.log(`${chalk.gray('Measured speed:')} ${speedColor(measuredSpeed)}`);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
console.log(`${chalk.gray('Memory reference:')} ${chalk.white(`~${formatAiRunNumber(modelSizeB)}B params, ~${formatAiRunNumber(workingSetGB)}GB working set`)}`);
|
|
1583
|
+
|
|
1584
|
+
if (usesFallback) {
|
|
1585
|
+
console.log(`${chalk.gray('Install ideal model:')} ${chalk.cyan(`ollama pull ${idealModel}`)}`);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function formatAiRunTurnSpeed(result = {}) {
|
|
1590
|
+
const evalSpeed = Number(result.evalTokensPerSecond);
|
|
1591
|
+
const preferredSpeed = evalSpeed > 0 ? evalSpeed : Number(result.tokensPerSecond);
|
|
1592
|
+
|
|
1593
|
+
if (!Number.isFinite(preferredSpeed) || preferredSpeed <= 0) {
|
|
1594
|
+
return '[speed unavailable]';
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
return `[${formatAiRunNumber(preferredSpeed)} tokens/sec]`;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
async function runAiRunChatTurn(client, modelName, messages) {
|
|
1601
|
+
let printed = false;
|
|
1602
|
+
const result = await client.streamChat(
|
|
1603
|
+
modelName,
|
|
1604
|
+
messages,
|
|
1605
|
+
{
|
|
1606
|
+
keepAlive: '5m',
|
|
1607
|
+
timeoutMs: 180000
|
|
1608
|
+
},
|
|
1609
|
+
(chunk) => {
|
|
1610
|
+
printed = true;
|
|
1611
|
+
process.stdout.write(chunk);
|
|
1612
|
+
}
|
|
1613
|
+
);
|
|
1614
|
+
|
|
1615
|
+
if (!printed && result.response) {
|
|
1616
|
+
process.stdout.write(result.response);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
const responseText = result.response || result.message?.content || '';
|
|
1620
|
+
const needsSpace = responseText.length > 0 && !/\s$/.test(responseText);
|
|
1621
|
+
process.stdout.write(`${needsSpace ? ' ' : ''}${chalk.gray(formatAiRunTurnSpeed(result))}\n\n`);
|
|
1622
|
+
|
|
1623
|
+
return result;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function askAiRunQuestion(rl, promptText) {
|
|
1627
|
+
return new Promise((resolve) => {
|
|
1628
|
+
let settled = false;
|
|
1629
|
+
const handleClose = () => {
|
|
1630
|
+
if (!settled) {
|
|
1631
|
+
settled = true;
|
|
1632
|
+
resolve(null);
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
rl.once('close', handleClose);
|
|
1637
|
+
rl.question(promptText, (answer) => {
|
|
1638
|
+
if (settled) return;
|
|
1639
|
+
settled = true;
|
|
1640
|
+
rl.off('close', handleClose);
|
|
1641
|
+
resolve(answer);
|
|
1642
|
+
});
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
async function runAiRunInteractiveChat(client, modelName) {
|
|
1647
|
+
const rl = readline.createInterface({
|
|
1648
|
+
input: process.stdin,
|
|
1649
|
+
output: process.stdout
|
|
1650
|
+
});
|
|
1651
|
+
const messages = [];
|
|
1652
|
+
let closed = false;
|
|
1653
|
+
|
|
1654
|
+
rl.on('SIGINT', () => {
|
|
1655
|
+
process.stdout.write('\n');
|
|
1656
|
+
rl.close();
|
|
1657
|
+
});
|
|
1658
|
+
rl.on('close', () => {
|
|
1659
|
+
closed = true;
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
try {
|
|
1663
|
+
while (!closed) {
|
|
1664
|
+
const input = await askAiRunQuestion(rl, chalk.cyan('>>> '));
|
|
1665
|
+
if (input === null) break;
|
|
1666
|
+
|
|
1667
|
+
const trimmed = String(input || '').trim();
|
|
1668
|
+
|
|
1669
|
+
if (!trimmed) {
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
if (['/bye', '/exit', '/quit', 'q'].includes(trimmed.toLowerCase())) {
|
|
1674
|
+
break;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
if (['/?', '/help'].includes(trimmed.toLowerCase())) {
|
|
1678
|
+
console.log('Commands: /bye, /exit, /quit');
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
messages.push({ role: 'user', content: input });
|
|
1683
|
+
|
|
1684
|
+
try {
|
|
1685
|
+
const response = await runAiRunChatTurn(client, modelName, messages);
|
|
1686
|
+
const assistantContent = response.response || response.message?.content || '';
|
|
1687
|
+
messages.push({ role: 'assistant', content: assistantContent });
|
|
1688
|
+
} catch (error) {
|
|
1689
|
+
console.error(chalk.red(`Chat request failed: ${error.message}`));
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
} finally {
|
|
1693
|
+
rl.close();
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1429
1697
|
function displayModelsStats(originalCount, filteredCount, options) {
|
|
1430
1698
|
console.log('\n' + chalk.bgGreen.white.bold(' DATABASE STATS '));
|
|
1431
1699
|
console.log(chalk.green('╭' + '─'.repeat(60)));
|
|
@@ -3204,7 +3472,7 @@ program
|
|
|
3204
3472
|
const spinner = ora('Checking Ollama integration...').start();
|
|
3205
3473
|
|
|
3206
3474
|
try {
|
|
3207
|
-
const checker = new (getLLMChecker())();
|
|
3475
|
+
const checker = new (getLLMChecker())({ verbose: false });
|
|
3208
3476
|
const analysis = await checker.analyze();
|
|
3209
3477
|
|
|
3210
3478
|
if (!analysis.ollamaInfo.available) {
|
|
@@ -3906,14 +4174,15 @@ program
|
|
|
3906
4174
|
.option('--json', 'Output in JSON format')
|
|
3907
4175
|
.action(async (options) => {
|
|
3908
4176
|
if (!options.json) showAsciiArt('list-models');
|
|
3909
|
-
const spinner = ora('📋 Loading models database...').start();
|
|
4177
|
+
const spinner = options.json ? null : ora('📋 Loading models database...').start();
|
|
3910
4178
|
|
|
3911
4179
|
try {
|
|
3912
4180
|
const checker = new (getLLMChecker())();
|
|
3913
|
-
const data = await checker.
|
|
4181
|
+
const data = await checker.loadOllamaModelData();
|
|
3914
4182
|
|
|
3915
4183
|
if (!data || !data.models) {
|
|
3916
|
-
spinner.fail('No models found in database');
|
|
4184
|
+
if (spinner) spinner.fail('No models found in database');
|
|
4185
|
+
else console.error('No models found in database');
|
|
3917
4186
|
return;
|
|
3918
4187
|
}
|
|
3919
4188
|
|
|
@@ -4004,9 +4273,9 @@ program
|
|
|
4004
4273
|
return (b.pulls || 0) - (a.pulls || 0);
|
|
4005
4274
|
});
|
|
4006
4275
|
|
|
4007
|
-
spinner.text = `Sorted by hardware compatibility (${getHardwareTierForDisplay(hardware)})`;
|
|
4276
|
+
if (spinner) spinner.text = `Sorted by hardware compatibility (${getHardwareTierForDisplay(hardware)})`;
|
|
4008
4277
|
} catch (error) {
|
|
4009
|
-
console.warn('Could not sort by hardware compatibility:', error.message);
|
|
4278
|
+
if (!options.json) console.warn('Could not sort by hardware compatibility:', error.message);
|
|
4010
4279
|
// Fallback a ordenar por popularidad
|
|
4011
4280
|
models.sort((a, b) => (b.pulls || 0) - (a.pulls || 0));
|
|
4012
4281
|
}
|
|
@@ -4019,7 +4288,7 @@ program
|
|
|
4019
4288
|
const limit = parseInt(options.limit) || 50;
|
|
4020
4289
|
const displayModels = models.slice(0, limit);
|
|
4021
4290
|
|
|
4022
|
-
spinner.succeed(`✅ Found ${models.length} models (showing ${displayModels.length})`);
|
|
4291
|
+
if (spinner) spinner.succeed(`✅ Found ${models.length} models (showing ${displayModels.length})`);
|
|
4023
4292
|
|
|
4024
4293
|
if (options.json) {
|
|
4025
4294
|
console.log(JSON.stringify(displayModels, null, 2));
|
|
@@ -4042,7 +4311,7 @@ program
|
|
|
4042
4311
|
}
|
|
4043
4312
|
|
|
4044
4313
|
} catch (error) {
|
|
4045
|
-
spinner.fail('Failed to load models');
|
|
4314
|
+
if (spinner) spinner.fail('Failed to load models');
|
|
4046
4315
|
console.error(chalk.red('Error:'), error.message);
|
|
4047
4316
|
if (process.env.DEBUG) {
|
|
4048
4317
|
console.error(error.stack);
|
|
@@ -4143,6 +4412,8 @@ program
|
|
|
4143
4412
|
'--calibrated [file]',
|
|
4144
4413
|
'Enable calibrated routing policy (optional file path; defaults to ~/.llm-checker/calibration-policy.{yaml,yml,json})'
|
|
4145
4414
|
)
|
|
4415
|
+
.option('--benchmark', 'Run a short local speed test before launching')
|
|
4416
|
+
.option('--reference-only', 'Show model choice and speed reference without launching Ollama')
|
|
4146
4417
|
.action(async (options) => {
|
|
4147
4418
|
showAsciiArt('ai-run');
|
|
4148
4419
|
// Check if Ollama is installed first
|
|
@@ -4156,6 +4427,14 @@ program
|
|
|
4156
4427
|
const aiSelector = new AIModelSelector();
|
|
4157
4428
|
const checker = new (getLLMChecker())();
|
|
4158
4429
|
const systemInfo = await checker.getSystemInfo();
|
|
4430
|
+
let ollamaClient = null;
|
|
4431
|
+
const getOllamaClient = () => {
|
|
4432
|
+
if (!ollamaClient) {
|
|
4433
|
+
const OllamaClient = require('../src/ollama/client');
|
|
4434
|
+
ollamaClient = new OllamaClient();
|
|
4435
|
+
}
|
|
4436
|
+
return ollamaClient;
|
|
4437
|
+
};
|
|
4159
4438
|
const routingPreference = resolveRoutingPolicyPreference({
|
|
4160
4439
|
policyOption: options.policy,
|
|
4161
4440
|
calibratedOption: options.calibrated
|
|
@@ -4164,18 +4443,18 @@ program
|
|
|
4164
4443
|
|
|
4165
4444
|
// Get available models or use provided ones
|
|
4166
4445
|
let candidateModels = options.models;
|
|
4446
|
+
let localModels = [];
|
|
4167
4447
|
|
|
4168
4448
|
if (!candidateModels) {
|
|
4169
|
-
spinner.text = '
|
|
4170
|
-
const
|
|
4171
|
-
const client = new OllamaClient();
|
|
4449
|
+
spinner.text = 'Getting available Ollama models...';
|
|
4450
|
+
const client = getOllamaClient();
|
|
4172
4451
|
|
|
4173
4452
|
try {
|
|
4174
|
-
|
|
4175
|
-
candidateModels =
|
|
4453
|
+
localModels = await client.getLocalModels();
|
|
4454
|
+
candidateModels = localModels.map(m => m.name || m.model);
|
|
4176
4455
|
|
|
4177
4456
|
if (candidateModels.length === 0) {
|
|
4178
|
-
spinner.fail('
|
|
4457
|
+
spinner.fail('No Ollama models found');
|
|
4179
4458
|
console.log('\nInstall some models first:');
|
|
4180
4459
|
console.log(' ollama pull llama2:7b');
|
|
4181
4460
|
console.log(' ollama pull mistral:7b');
|
|
@@ -4183,7 +4462,7 @@ program
|
|
|
4183
4462
|
return;
|
|
4184
4463
|
}
|
|
4185
4464
|
} catch (error) {
|
|
4186
|
-
spinner.fail('
|
|
4465
|
+
spinner.fail('Failed to get Ollama models');
|
|
4187
4466
|
console.error(chalk.red('Error:'), error.message);
|
|
4188
4467
|
return;
|
|
4189
4468
|
}
|
|
@@ -4224,28 +4503,62 @@ program
|
|
|
4224
4503
|
)}) are not installed locally. Falling back to AI selector.`
|
|
4225
4504
|
);
|
|
4226
4505
|
}
|
|
4227
|
-
result = await aiSelector.selectBestModel(candidateModels, systemSpecs, taskHint);
|
|
4506
|
+
result = await aiSelector.selectBestModel(candidateModels, systemSpecs, taskHint, { silent: true });
|
|
4228
4507
|
}
|
|
4229
4508
|
|
|
4230
4509
|
spinner.succeed(`Selected ${chalk.green.bold(result.bestModel)} (${result.method}, ${Math.round(result.confidence * 100)}% confidence)`);
|
|
4510
|
+
|
|
4511
|
+
let benchmark = null;
|
|
4512
|
+
if (options.benchmark) {
|
|
4513
|
+
const benchmarkSpinner = ora(`Measuring local throughput for ${result.bestModel}...`).start();
|
|
4514
|
+
try {
|
|
4515
|
+
benchmark = await getOllamaClient().testModelPerformance(
|
|
4516
|
+
result.bestModel,
|
|
4517
|
+
'Write one concise sentence about local LLM performance.'
|
|
4518
|
+
);
|
|
4519
|
+
|
|
4520
|
+
if (benchmark.success) {
|
|
4521
|
+
benchmarkSpinner.succeed(`Measured ${formatAiRunNumber(benchmark.tokensPerSecond)} tokens/sec`);
|
|
4522
|
+
} else {
|
|
4523
|
+
benchmarkSpinner.stop();
|
|
4524
|
+
console.log(chalk.yellow(`Benchmark unavailable: ${benchmark.error || 'unknown error'}`));
|
|
4525
|
+
}
|
|
4526
|
+
} catch (error) {
|
|
4527
|
+
benchmark = { success: false, error: error.message };
|
|
4528
|
+
benchmarkSpinner.stop();
|
|
4529
|
+
console.log(chalk.yellow(`Benchmark unavailable: ${error.message}`));
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4231
4533
|
displayCalibratedRoutingDecision('ai-run', calibratedPolicy, routeDecision, routingPreference.warnings);
|
|
4534
|
+
displayAiRunReference({
|
|
4535
|
+
result,
|
|
4536
|
+
systemInfo,
|
|
4537
|
+
taskHint,
|
|
4538
|
+
candidateModels,
|
|
4539
|
+
localModels,
|
|
4540
|
+
aiSelector,
|
|
4541
|
+
benchmark
|
|
4542
|
+
});
|
|
4543
|
+
|
|
4544
|
+
if (options.referenceOnly) {
|
|
4545
|
+
console.log(chalk.gray('\nReference-only mode: not launching Ollama.'));
|
|
4546
|
+
return;
|
|
4547
|
+
}
|
|
4232
4548
|
|
|
4233
|
-
// Execute the selected model
|
|
4234
|
-
console.log(chalk.magenta.bold(`\nLaunching ${result.bestModel}...`));
|
|
4235
|
-
console.log(chalk.gray(`Tip: Type ${chalk.cyan('/bye')} to exit the chat when finished\n`));
|
|
4236
|
-
|
|
4237
|
-
const args = ['run', result.bestModel];
|
|
4238
4549
|
if (options.prompt) {
|
|
4239
|
-
|
|
4550
|
+
console.log(chalk.cyan(`\n>>> ${options.prompt}`));
|
|
4551
|
+
await runAiRunChatTurn(
|
|
4552
|
+
getOllamaClient(),
|
|
4553
|
+
result.bestModel,
|
|
4554
|
+
[{ role: 'user', content: options.prompt }]
|
|
4555
|
+
);
|
|
4556
|
+
return;
|
|
4240
4557
|
}
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
ollamaProcess.on('error', (error) => {
|
|
4247
|
-
console.error(chalk.red('Failed to launch Ollama:'), error.message);
|
|
4248
|
-
});
|
|
4558
|
+
|
|
4559
|
+
console.log(chalk.magenta.bold(`\nStarting chat with ${result.bestModel}...`));
|
|
4560
|
+
console.log(chalk.gray(`Tip: Type ${chalk.cyan('/bye')} to exit the chat when finished\n`));
|
|
4561
|
+
await runAiRunInteractiveChat(getOllamaClient(), result.bestModel);
|
|
4249
4562
|
|
|
4250
4563
|
} catch (error) {
|
|
4251
4564
|
console.error(chalk.red('❌ AI-powered execution failed:'), error.message);
|
|
@@ -5128,9 +5441,23 @@ program
|
|
|
5128
5441
|
|
|
5129
5442
|
if (backend === 'rocm' && info.info) {
|
|
5130
5443
|
console.log(` ROCm: ${info.info.rocmVersion}`);
|
|
5131
|
-
|
|
5444
|
+
const integratedOnly = (info.info.gpus || []).length > 0 &&
|
|
5445
|
+
(info.info.gpus || []).every((gpu) => gpu.type === 'integrated');
|
|
5446
|
+
if (integratedOnly) {
|
|
5447
|
+
console.log(` Total dedicated aperture: ${info.info.totalVRAM || 0}GB`);
|
|
5448
|
+
console.log(` Total shared memory: ${info.info.totalSharedMemory || 0}GB`);
|
|
5449
|
+
} else {
|
|
5450
|
+
console.log(` Total VRAM: ${info.info.totalVRAM}GB`);
|
|
5451
|
+
}
|
|
5132
5452
|
for (const gpu of info.info.gpus) {
|
|
5133
|
-
|
|
5453
|
+
if (gpu.type === 'integrated') {
|
|
5454
|
+
const dedicated = gpu.memory?.dedicated || 0;
|
|
5455
|
+
const shared = gpu.memory?.shared || gpu.memory?.total || 0;
|
|
5456
|
+
const dedicatedLabel = dedicated > 0 ? `, ${dedicated}GB aperture` : '';
|
|
5457
|
+
console.log(` ${gpu.name}: ${shared}GB shared${dedicatedLabel} (Integrated)`);
|
|
5458
|
+
} else {
|
|
5459
|
+
console.log(` ${gpu.name}: ${gpu.memory.total}GB`);
|
|
5460
|
+
}
|
|
5134
5461
|
}
|
|
5135
5462
|
}
|
|
5136
5463
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-checker",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.13",
|
|
4
4
|
"description": "Intelligent CLI tool with AI-powered model selection that analyzes your hardware and recommends optimal LLM models for your system",
|
|
5
5
|
"bin": {
|
|
6
6
|
"llm-checker": "bin/cli.js",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"list-models": "node bin/enhanced_cli.js list-models",
|
|
37
37
|
"ai-check": "node bin/enhanced_cli.js ai-check",
|
|
38
38
|
"ai-run": "node bin/enhanced_cli.js ai-run",
|
|
39
|
+
"sync:seed": "node bin/enhanced_cli.js sync --force --quiet && node scripts/update-seed-db.js",
|
|
39
40
|
"benchmark": "cd ml-model && python python/benchmark_collector.py",
|
|
40
41
|
"train-ai": "cd ml-model && python python/train_model.py",
|
|
41
42
|
"postinstall": "echo 'LLM Checker installed. Run: llm-checker hw-detect'"
|
package/src/ai/model-selector.js
CHANGED
|
@@ -33,19 +33,20 @@ class AIModelSelector {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
async selectBestModel(candidateModels, systemSpecs = null, userPreference = 'general') {
|
|
36
|
+
async selectBestModel(candidateModels, systemSpecs = null, userPreference = 'general', options = {}) {
|
|
37
|
+
const log = options.silent ? () => {} : console.log;
|
|
38
|
+
const warn = options.silent ? () => {} : console.warn;
|
|
39
|
+
|
|
37
40
|
try {
|
|
38
41
|
// Para ai-run: usar TODOS los modelos de la base de datos para encontrar el mejor
|
|
39
42
|
// y luego verificar si está instalado localmente
|
|
40
|
-
|
|
43
|
+
log('🔍 Using comprehensive model database for selection...');
|
|
41
44
|
|
|
42
45
|
// Obtener todos los modelos de la base de datos de Ollama
|
|
43
|
-
const
|
|
44
|
-
const scraper = new OllamaNativeScraper();
|
|
45
|
-
const allModelData = await scraper.scrapeAllModels(false);
|
|
46
|
+
const allModelData = await this.loadModelDatabase();
|
|
46
47
|
const allAvailableModels = allModelData.models || [];
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
log(`Evaluating against ${allAvailableModels.length} models from database`);
|
|
49
50
|
|
|
50
51
|
// Usar el selector inteligente con TODOS los modelos disponibles
|
|
51
52
|
const result = this.intelligentSelector.selectBestModels(
|
|
@@ -68,7 +69,7 @@ class AIModelSelector {
|
|
|
68
69
|
let reason = result.best_model.reasoning;
|
|
69
70
|
|
|
70
71
|
if (!isLocallyInstalled) {
|
|
71
|
-
|
|
72
|
+
log(`Best model ${recommendedId} not installed locally`);
|
|
72
73
|
|
|
73
74
|
// Buscar el mejor modelo entre los instalados localmente
|
|
74
75
|
const localResult = this.intelligentSelector.selectBestModels(
|
|
@@ -83,7 +84,7 @@ class AIModelSelector {
|
|
|
83
84
|
confidence = localResult.best_model.confidence * 0.9; // Reducir confianza
|
|
84
85
|
reason = `${localResult.best_model.reasoning} (Locally installed alternative to recommended ${recommendedId})`;
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
log(`🔄 Using best local alternative: ${finalModel}`);
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
@@ -111,7 +112,7 @@ class AIModelSelector {
|
|
|
111
112
|
};
|
|
112
113
|
}
|
|
113
114
|
} catch (error) {
|
|
114
|
-
|
|
115
|
+
warn(`Comprehensive database selection failed: ${error.message}`);
|
|
115
116
|
|
|
116
117
|
// Fallback al método anterior con solo modelos locales
|
|
117
118
|
try {
|
|
@@ -139,7 +140,7 @@ class AIModelSelector {
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
} catch (localError) {
|
|
142
|
-
|
|
143
|
+
warn(`Local intelligent selection also failed: ${localError.message}`);
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -161,15 +162,45 @@ class AIModelSelector {
|
|
|
161
162
|
};
|
|
162
163
|
|
|
163
164
|
} catch (error) {
|
|
164
|
-
|
|
165
|
+
warn(`ONNX AI selection failed: ${error.message}`);
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
// Final fallback to simple heuristic
|
|
169
|
-
return this.fallbackSelection(candidateModels, systemSpecs);
|
|
170
|
+
return this.fallbackSelection(candidateModels, systemSpecs, options);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async loadModelDatabase() {
|
|
174
|
+
try {
|
|
175
|
+
const ModelDatabase = require('../data/model-database');
|
|
176
|
+
const database = new ModelDatabase();
|
|
177
|
+
await database.initialize();
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const models = database.getAllModelsWithVariants();
|
|
181
|
+
if (models.length > 0) {
|
|
182
|
+
return {
|
|
183
|
+
models,
|
|
184
|
+
total_count: models.length,
|
|
185
|
+
source: 'ollama_sqlite_database'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
} finally {
|
|
189
|
+
database.close();
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// Fall through to scraper cache.
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const { OllamaNativeScraper } = require('../ollama/native-scraper');
|
|
196
|
+
const scraper = new OllamaNativeScraper();
|
|
197
|
+
return scraper.scrapeAllModels(false);
|
|
170
198
|
}
|
|
171
199
|
|
|
172
|
-
fallbackSelection(candidateModels, systemSpecs = null) {
|
|
200
|
+
fallbackSelection(candidateModels, systemSpecs = null, options = {}) {
|
|
201
|
+
const log = options.silent ? () => {} : console.log;
|
|
202
|
+
const warn = options.silent ? () => {} : console.warn;
|
|
203
|
+
|
|
173
204
|
if (!systemSpecs) {
|
|
174
205
|
systemSpecs = {
|
|
175
206
|
total_ram_gb: 8,
|
|
@@ -179,7 +210,7 @@ class AIModelSelector {
|
|
|
179
210
|
};
|
|
180
211
|
}
|
|
181
212
|
|
|
182
|
-
|
|
213
|
+
log('🔄 Using fallback heuristic selection...');
|
|
183
214
|
|
|
184
215
|
// Use intelligent selector with basic heuristic mode
|
|
185
216
|
try {
|
|
@@ -202,7 +233,7 @@ class AIModelSelector {
|
|
|
202
233
|
};
|
|
203
234
|
}
|
|
204
235
|
} catch (error) {
|
|
205
|
-
|
|
236
|
+
warn(`Intelligent fallback failed: ${error.message}`);
|
|
206
237
|
}
|
|
207
238
|
|
|
208
239
|
// Ultimate fallback: simple memory-based selection
|
|
@@ -309,4 +340,4 @@ class AIModelSelector {
|
|
|
309
340
|
}
|
|
310
341
|
}
|
|
311
342
|
|
|
312
|
-
module.exports = AIModelSelector;
|
|
343
|
+
module.exports = AIModelSelector;
|