Skip to content

Commit 5173ce7

Browse files
committed
feat(ruvector-cli): integrate tiny-dancer router (train/route/info)
npx ruvector tiny-dancer train <draco.json> --out model.safetensors trains a FastGRNN cost-optimal router from a DRACO {embedding,scores} dataset; `route` loads + routes; `info` reports availability. @ruvector/tiny-dancer added as an optionalDependency (native binary, lazy-required with install hint). Bump 0.2.31. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 1cb17ae commit 5173ce7

2 files changed

Lines changed: 95 additions & 3 deletions

File tree

npm/packages/ruvector/bin/cli.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,6 +1875,97 @@ program
18751875
console.log('');
18761876
});
18771877

1878+
// =============================================================================
1879+
// Tiny Dancer - cost-optimal FastGRNN model router (train + route)
1880+
// =============================================================================
1881+
1882+
const tinyDancer = program
1883+
.command('tiny-dancer')
1884+
.alias('td')
1885+
.description('Cost-optimal FastGRNN model router — train from a DRACO dataset and route with it (requires @ruvector/tiny-dancer)');
1886+
1887+
function loadTinyDancer() {
1888+
try {
1889+
return require('@ruvector/tiny-dancer');
1890+
} catch (e) {
1891+
console.error(chalk.red('\n This command requires @ruvector/tiny-dancer'));
1892+
console.error(chalk.yellow(' Install it: npm install @ruvector/tiny-dancer'));
1893+
console.error(chalk.dim(' (native router; ships for linux/macos/windows incl. musl + arm64)\n'));
1894+
process.exit(1);
1895+
}
1896+
}
1897+
1898+
tinyDancer
1899+
.command('train <draco>')
1900+
.description('Train a FastGRNN router from a DRACO dataset (rows of {embedding, scores}) into a .safetensors model')
1901+
.requiredOption('--out <path>', 'Output .safetensors model path')
1902+
.option('--input-dim <n>', 'Embedding/feature dimension (default: inferred from the first row)')
1903+
.option('--prices <json>', 'Price table as JSON or @file, e.g. \'{"haiku":1,"opus":15}\'')
1904+
.option('--epochs <n>', 'Training epochs', '40')
1905+
.option('--lr <n>', 'Learning rate', '0.05')
1906+
.option('--hidden <n>', 'Hidden dimension', '12')
1907+
.option('--tolerance <n>', 'Cheap-model "good enough" tolerance', '0.05')
1908+
.action(async (draco, options) => {
1909+
const td = loadTinyDancer();
1910+
const parsed = JSON.parse(fs.readFileSync(draco, 'utf8'));
1911+
const rows = Array.isArray(parsed) ? parsed : parsed.rows;
1912+
const prices = options.prices
1913+
? JSON.parse(options.prices.startsWith('@') ? fs.readFileSync(options.prices.slice(1), 'utf8') : options.prices)
1914+
: (parsed.prices || {});
1915+
if (!Array.isArray(rows) || rows.length === 0) {
1916+
console.error(chalk.red(' DRACO file must contain rows of { embedding, scores }')); process.exit(1);
1917+
}
1918+
if (!prices || Object.keys(prices).length === 0) {
1919+
console.error(chalk.red(' Provide a price table via --prices or a "prices" field in the file')); process.exit(1);
1920+
}
1921+
const inputDim = options.inputDim ? parseInt(options.inputDim, 10) : (rows[0].embedding || []).length;
1922+
console.log(chalk.cyan(`\n Training FastGRNN router: ${rows.length} rows, dim ${inputDim}`));
1923+
const res = await td.trainRouter(rows, prices, {
1924+
outputPath: options.out,
1925+
inputDim,
1926+
hiddenDim: parseInt(options.hidden, 10),
1927+
epochs: parseInt(options.epochs, 10),
1928+
learningRate: parseFloat(options.lr),
1929+
tolerance: parseFloat(options.tolerance),
1930+
});
1931+
console.log(chalk.green(` ✓ trained: acc=${res.trainAccuracy.toFixed(3)} val=${res.valAccuracy.toFixed(3)} loss=${res.trainLoss.toFixed(4)}`));
1932+
console.log(chalk.white(` ✓ saved: ${res.modelPath} (${res.modelBytes} bytes, ${res.epochsRun} epochs)`));
1933+
console.log(chalk.gray(` Load it: new Router({ modelPath: '${res.modelPath}' })\n`));
1934+
});
1935+
1936+
tinyDancer
1937+
.command('route <model>')
1938+
.description('Route a query through a trained model. --query: JSON embedding array; --candidates: JSON file of [{id, embedding}]')
1939+
.requiredOption('--query <json>', 'Query embedding as a JSON array or @file')
1940+
.requiredOption('--candidates <file>', 'Candidates JSON file: [{ id, embedding }]')
1941+
.option('--threshold <n>', 'Confidence threshold', '0.85')
1942+
.action(async (model, options) => {
1943+
const td = loadTinyDancer();
1944+
const queryEmbedding = JSON.parse(options.query.startsWith('@') ? fs.readFileSync(options.query.slice(1), 'utf8') : options.query);
1945+
const candidates = JSON.parse(fs.readFileSync(options.candidates, 'utf8'));
1946+
const router = new td.Router({ modelPath: model, confidenceThreshold: parseFloat(options.threshold) });
1947+
const resp = await router.route({ queryEmbedding, candidates });
1948+
console.log(chalk.cyan('\n Routing decisions (best first):'));
1949+
for (const d of resp.decisions) {
1950+
console.log(` ${chalk.white(d.candidateId)} conf=${d.confidence.toFixed(3)} light=${d.useLightweight} unc=${d.uncertainty.toFixed(3)}`);
1951+
}
1952+
console.log(chalk.gray(` inference ${resp.inferenceTimeUs}µs over ${resp.candidatesProcessed} candidates\n`));
1953+
});
1954+
1955+
tinyDancer
1956+
.command('info')
1957+
.description('Show tiny-dancer availability and version')
1958+
.action(() => {
1959+
try {
1960+
const td = require('@ruvector/tiny-dancer');
1961+
console.log(chalk.green(`\n @ruvector/tiny-dancer ${td.version()}${td.hello()}`));
1962+
console.log(chalk.gray(' train: npx ruvector tiny-dancer train <draco.json> --out model.safetensors'));
1963+
console.log(chalk.gray(' route: npx ruvector tiny-dancer route <model.safetensors> --query <emb> --candidates <file>\n'));
1964+
} catch {
1965+
console.log(chalk.yellow('\n @ruvector/tiny-dancer not installed. npm install @ruvector/tiny-dancer\n'));
1966+
}
1967+
});
1968+
18781969
// =============================================================================
18791970
// Server Commands - HTTP/gRPC server
18801971
// =============================================================================

npm/packages/ruvector/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ruvector",
3-
"version": "0.2.30",
4-
"description": "Self-learning vector database for Node.js hybrid search, Graph RAG, FlashAttention-3, HNSW, 50+ attention mechanisms",
3+
"version": "0.2.31",
4+
"description": "Self-learning vector database for Node.js \u2014 hybrid search, Graph RAG, FlashAttention-3, HNSW, 50+ attention mechanisms",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"bin": {
@@ -82,7 +82,8 @@
8282
"ora": "^5.4.1"
8383
},
8484
"optionalDependencies": {
85-
"@ruvector/rvf": "^0.1.0"
85+
"@ruvector/rvf": "^0.1.0",
86+
"@ruvector/tiny-dancer": "^0.1.21"
8687
},
8788
"devDependencies": {
8889
"@types/node": "^20.10.5",

0 commit comments

Comments
 (0)