@ruvector/ruvllm 4.0.1 → 4.1.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/README.md +447 -160
- package/bin/cli.js +1066 -0
- package/bin/gpu-delegate.js +67 -0
- package/bin/ruvllm-adapters.js +5 -0
- package/bin/ruvllm-gpu-loop.js +5 -0
- package/bin/serve.js +265 -0
- package/dist/cjs/benchmarks/embedding-benchmark.d.ts +102 -0
- package/dist/cjs/benchmarks/embedding-benchmark.d.ts.map +1 -0
- package/dist/cjs/benchmarks/embedding-benchmark.js +436 -0
- package/dist/cjs/benchmarks/index.d.ts +34 -0
- package/dist/cjs/benchmarks/index.d.ts.map +1 -0
- package/dist/cjs/benchmarks/index.js +132 -0
- package/dist/cjs/benchmarks/model-comparison.d.ts +71 -0
- package/dist/cjs/benchmarks/model-comparison.d.ts.map +1 -0
- package/dist/cjs/benchmarks/model-comparison.js +476 -0
- package/dist/cjs/benchmarks/routing-benchmark.d.ts +70 -0
- package/dist/cjs/benchmarks/routing-benchmark.d.ts.map +1 -0
- package/dist/cjs/benchmarks/routing-benchmark.js +289 -0
- package/dist/cjs/contrastive.d.ts +229 -0
- package/dist/cjs/contrastive.d.ts.map +1 -0
- package/dist/cjs/contrastive.js +589 -0
- package/dist/cjs/engine.d.ts +160 -0
- package/dist/cjs/engine.d.ts.map +1 -0
- package/dist/cjs/engine.js +552 -0
- package/dist/cjs/export.d.ts +182 -0
- package/dist/cjs/export.d.ts.map +1 -0
- package/dist/cjs/export.js +422 -0
- package/dist/cjs/federated.d.ts +233 -0
- package/dist/cjs/federated.d.ts.map +1 -0
- package/dist/cjs/federated.js +525 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +104 -0
- package/dist/cjs/intelligence.d.ts +136 -0
- package/dist/cjs/intelligence.d.ts.map +1 -0
- package/dist/cjs/intelligence.js +226 -0
- package/dist/cjs/lora.d.ts +251 -0
- package/dist/cjs/lora.d.ts.map +1 -0
- package/dist/cjs/lora.js +509 -0
- package/dist/cjs/models.d.ts +129 -0
- package/dist/cjs/models.d.ts.map +1 -0
- package/dist/cjs/models.js +323 -0
- package/dist/cjs/native.d.ts +140 -0
- package/dist/cjs/native.d.ts.map +1 -0
- package/dist/cjs/native.js +98 -0
- package/dist/cjs/session.d.ts +80 -0
- package/dist/cjs/session.d.ts.map +1 -0
- package/dist/cjs/session.js +203 -0
- package/dist/cjs/simd.d.ts +90 -0
- package/dist/cjs/simd.d.ts.map +1 -0
- package/dist/cjs/simd.js +209 -0
- package/dist/cjs/sona.d.ts +223 -0
- package/dist/cjs/sona.d.ts.map +1 -0
- package/dist/cjs/sona.js +562 -0
- package/dist/cjs/streaming.d.ts +55 -0
- package/dist/cjs/streaming.d.ts.map +1 -0
- package/dist/cjs/streaming.js +131 -0
- package/dist/cjs/training.d.ts +312 -0
- package/dist/cjs/training.d.ts.map +1 -0
- package/dist/cjs/training.js +610 -0
- package/dist/cjs/types.d.ts +696 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +6 -0
- package/dist/esm/benchmarks/embedding-benchmark.d.ts +102 -0
- package/dist/esm/benchmarks/embedding-benchmark.d.ts.map +1 -0
- package/dist/esm/benchmarks/embedding-benchmark.js +427 -0
- package/dist/esm/benchmarks/index.d.ts +34 -0
- package/dist/esm/benchmarks/index.d.ts.map +1 -0
- package/dist/esm/benchmarks/index.js +110 -0
- package/dist/esm/benchmarks/model-comparison.d.ts +71 -0
- package/dist/esm/benchmarks/model-comparison.d.ts.map +1 -0
- package/dist/esm/benchmarks/model-comparison.js +434 -0
- package/dist/esm/benchmarks/routing-benchmark.d.ts +70 -0
- package/dist/esm/benchmarks/routing-benchmark.d.ts.map +1 -0
- package/dist/esm/benchmarks/routing-benchmark.js +283 -0
- package/dist/esm/contrastive.d.ts +229 -0
- package/dist/esm/contrastive.d.ts.map +1 -0
- package/dist/esm/contrastive.js +578 -0
- package/dist/esm/engine.d.ts +160 -0
- package/dist/esm/engine.d.ts.map +1 -0
- package/dist/esm/engine.js +548 -0
- package/dist/esm/export.d.ts +182 -0
- package/dist/esm/export.d.ts.map +1 -0
- package/dist/esm/export.js +414 -0
- package/dist/esm/federated.d.ts +233 -0
- package/dist/esm/federated.d.ts.map +1 -0
- package/dist/esm/federated.js +520 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +84 -0
- package/dist/esm/intelligence.d.ts +136 -0
- package/dist/esm/intelligence.d.ts.map +1 -0
- package/dist/esm/intelligence.js +188 -0
- package/dist/esm/lora.d.ts +251 -0
- package/dist/esm/lora.d.ts.map +1 -0
- package/dist/esm/lora.js +504 -0
- package/dist/esm/models.d.ts +129 -0
- package/dist/esm/models.d.ts.map +1 -0
- package/dist/esm/models.js +282 -0
- package/dist/esm/native.d.ts +140 -0
- package/dist/esm/native.d.ts.map +1 -0
- package/dist/esm/native.js +100 -0
- package/dist/esm/session.d.ts +80 -0
- package/dist/esm/session.d.ts.map +1 -0
- package/dist/esm/session.js +199 -0
- package/dist/esm/simd.d.ts +90 -0
- package/dist/esm/simd.d.ts.map +1 -0
- package/dist/esm/simd.js +205 -0
- package/dist/esm/sona.d.ts +223 -0
- package/dist/esm/sona.d.ts.map +1 -0
- package/dist/esm/sona.js +556 -0
- package/dist/esm/streaming.d.ts +55 -0
- package/dist/esm/streaming.d.ts.map +1 -0
- package/dist/esm/streaming.js +126 -0
- package/dist/esm/training.d.ts +312 -0
- package/dist/esm/training.d.ts.map +1 -0
- package/dist/esm/training.js +603 -0
- package/dist/esm/types.d.ts +696 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +5 -0
- package/gpu/_load.cjs +38 -0
- package/gpu/_load.mjs +33 -0
- package/gpu/adapters.cjs +3 -0
- package/gpu/adapters.mjs +5 -0
- package/gpu/engine.cjs +3 -0
- package/gpu/engine.mjs +5 -0
- package/gpu/fixture.cjs +3 -0
- package/gpu/fixture.mjs +5 -0
- package/gpu/gpu-host.cjs +3 -0
- package/gpu/gpu-host.mjs +5 -0
- package/gpu/gpu-loop.cjs +3 -0
- package/gpu/gpu-loop.mjs +5 -0
- package/gpu/gpu-spool-adapter.cjs +3 -0
- package/gpu/gpu-spool-adapter.mjs +5 -0
- package/gpu/vast-host-adapter.cjs +3 -0
- package/gpu/vast-host-adapter.mjs +5 -0
- package/package.json +159 -49
- package/scripts/ensemble-model-compare.js +287 -0
- package/scripts/fix-esm-extensions.mjs +220 -0
- package/scripts/huggingface/README.md +537 -0
- package/scripts/huggingface/publish.sh +112 -0
- package/scripts/hybrid-model-compare.js +373 -0
- package/scripts/improved-model-compare.js +288 -0
- package/scripts/optimized-model-compare.js +364 -0
- package/scripts/real-model-compare.js +280 -0
- package/scripts/smoke-esm.mjs +82 -0
- package/scripts/training/agentic-flow-capabilities.json +329 -0
- package/scripts/training/claude-code-synth.js +505 -0
- package/scripts/training/claude-flow-capabilities.json +1322 -0
- package/scripts/training/claude-hard-negatives.js +222 -0
- package/scripts/training/contrastive-finetune.js +517 -0
- package/scripts/training/ecosystem-triplets.jsonl +1467 -0
- package/scripts/training/generate-ecosystem-triplets.js +1324 -0
- package/scripts/training/routing-dataset.js +634 -0
- package/scripts/training/ruvector-capabilities.json +798 -0
- package/scripts/training/validate-ecosystem.js +381 -0
- package/scripts/training/validation-results.json +23 -0
- package/GPU-LOOP.md +0 -229
- package/LICENSE +0 -201
- package/NOTICE +0 -9
- package/examples/gpu-loop.example.json +0 -26
- package/src/adapters-cli.mjs +0 -23
- package/src/adapters.mjs +0 -91
- package/src/cli.mjs +0 -30
- package/src/engine.mjs +0 -238
- package/src/fixture.mjs +0 -38
- package/src/gpu-host.mjs +0 -47
- package/src/gpu-loop-cli.mjs +0 -67
- package/src/gpu-loop.mjs +0 -184
- package/src/gpu-spool-adapter.mjs +0 -80
- package/src/vast-host-adapter.mjs +0 -274
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Run a CLI shipped by @ruvector/ruvllm-gpu-loop (the GPU job loop, adapter
|
|
4
|
+
* archives and bounded weight experiments that were @ruvector/ruvllm 4.0.x).
|
|
5
|
+
* Used by `ruvllm gpu ...` and the `ruvllm-gpu-loop` / `ruvllm-adapters` bins.
|
|
6
|
+
*/
|
|
7
|
+
const { spawnSync } = require('node:child_process');
|
|
8
|
+
const { readFileSync } = require('node:fs');
|
|
9
|
+
const { dirname, resolve } = require('node:path');
|
|
10
|
+
|
|
11
|
+
const GPU_LOOP_PACKAGE = '@ruvector/ruvllm-gpu-loop';
|
|
12
|
+
|
|
13
|
+
function resolveGpuBin(binName) {
|
|
14
|
+
let manifestPath;
|
|
15
|
+
try {
|
|
16
|
+
manifestPath = require.resolve(`${GPU_LOOP_PACKAGE}/package.json`);
|
|
17
|
+
} catch {
|
|
18
|
+
const error = new Error(
|
|
19
|
+
`"${binName}" is provided by the optional package ${GPU_LOOP_PACKAGE}, which is not installed ` +
|
|
20
|
+
`(optional dependencies are skipped by --omit=optional, and it requires Node 22+).\n` +
|
|
21
|
+
`Install it with: npm install ${GPU_LOOP_PACKAGE}`);
|
|
22
|
+
error.code = 'RUVLLM_GPU_LOOP_MISSING';
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
26
|
+
const rel = manifest.bin && manifest.bin[binName];
|
|
27
|
+
if (typeof rel !== 'string') {
|
|
28
|
+
const error = new Error(`${GPU_LOOP_PACKAGE}@${manifest.version} has no "${binName}" command`);
|
|
29
|
+
error.code = 'RUVLLM_GPU_LOOP_BIN_MISSING';
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
return resolve(dirname(manifestPath), rel);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Run `binName` from the GPU-loop package with `args`; exits with its status. */
|
|
36
|
+
function delegate(binName, args) {
|
|
37
|
+
let binPath;
|
|
38
|
+
try {
|
|
39
|
+
binPath = resolveGpuBin(binName);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`Error: ${error.message}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const result = spawnSync(process.execPath, [binPath, ...args], { stdio: 'inherit' });
|
|
45
|
+
if (result.error) {
|
|
46
|
+
console.error(`Error: could not run ${binName}: ${result.error.message}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (result.signal) process.kill(process.pid, result.signal);
|
|
50
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `ruvllm gpu <args>`:
|
|
55
|
+
* gpu loop <args> -> ruvllm-gpu-loop (status, tick, ...)
|
|
56
|
+
* gpu adapters <args> -> ruvllm-adapters (export, training-plan, select)
|
|
57
|
+
* gpu <args> -> ruvllm-gpu (run, fixture, --help; the 4.0.x `ruvllm` CLI)
|
|
58
|
+
*/
|
|
59
|
+
function runGpuCommand(args) {
|
|
60
|
+
const [first, ...rest] = args;
|
|
61
|
+
if (first === 'loop') return delegate('ruvllm-gpu-loop', rest);
|
|
62
|
+
if (first === 'adapters') return delegate('ruvllm-adapters', rest);
|
|
63
|
+
if (first === undefined) return delegate('ruvllm-gpu', ['--help']);
|
|
64
|
+
return delegate('ruvllm-gpu', args);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { GPU_LOOP_PACKAGE, resolveGpuBin, delegate, runGpuCommand };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// Compatibility bin: `ruvllm-adapters` shipped with @ruvector/ruvllm 4.0.x and now lives in
|
|
4
|
+
// @ruvector/ruvllm-gpu-loop (installed as an optional dependency).
|
|
5
|
+
require('./gpu-delegate.js').delegate('ruvllm-adapters', process.argv.slice(2));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// Compatibility bin: `ruvllm-gpu-loop` shipped with @ruvector/ruvllm 4.0.x and now lives in
|
|
4
|
+
// @ruvector/ruvllm-gpu-loop (installed as an optional dependency).
|
|
5
|
+
require('./gpu-delegate.js').delegate('ruvllm-gpu-loop', process.argv.slice(2));
|
package/bin/serve.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* `ruvllm serve` - local HTTP server over a loaded GGUF model.
|
|
4
|
+
*
|
|
5
|
+
* Endpoints (the contract used by @claude-flow/providers' RuVector provider):
|
|
6
|
+
* GET /health -> { status, model, sona, hnsw, native, version }
|
|
7
|
+
* GET /v1/models -> OpenAI-style model list
|
|
8
|
+
* POST /query -> { query, session_id? } -> routed answer
|
|
9
|
+
* POST /v1/chat/completions -> OpenAI-compatible chat (stream or not)
|
|
10
|
+
*
|
|
11
|
+
* Strict by design: the server only starts once a model is loaded. A healthy
|
|
12
|
+
* /health tells clients that /query will be answered by a real model, so a
|
|
13
|
+
* server that cannot generate must not be running (clients then fall back,
|
|
14
|
+
* e.g. to Ollama). Generation is synchronous in the native binding, so
|
|
15
|
+
* requests are served one at a time; `stream: true` returns the finished
|
|
16
|
+
* completion as a single SSE chunk followed by [DONE] (no token streaming).
|
|
17
|
+
*/
|
|
18
|
+
const http = require('node:http');
|
|
19
|
+
const { randomUUID } = require('node:crypto');
|
|
20
|
+
|
|
21
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
22
|
+
const DEFAULT_PORT = 3000;
|
|
23
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
function httpError(status, code, message) {
|
|
26
|
+
const error = new Error(message);
|
|
27
|
+
error.status = status;
|
|
28
|
+
error.code = code;
|
|
29
|
+
return error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readJson(req, limit) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
let size = 0;
|
|
35
|
+
const chunks = [];
|
|
36
|
+
req.on('data', (chunk) => {
|
|
37
|
+
size += chunk.length;
|
|
38
|
+
if (size > limit) {
|
|
39
|
+
reject(httpError(413, 'request_too_large', `request body exceeds ${limit} bytes`));
|
|
40
|
+
req.destroy();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
chunks.push(chunk);
|
|
44
|
+
});
|
|
45
|
+
req.on('end', () => {
|
|
46
|
+
try {
|
|
47
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
48
|
+
const body = text.length ? JSON.parse(text) : {};
|
|
49
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
50
|
+
throw new Error('body must be a JSON object');
|
|
51
|
+
}
|
|
52
|
+
resolve(body);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
reject(httpError(400, 'invalid_json', `invalid JSON body: ${error.message}`));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
req.on('error', reject);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function send(res, status, payload) {
|
|
62
|
+
const body = JSON.stringify(payload);
|
|
63
|
+
res.writeHead(status, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body) });
|
|
64
|
+
res.end(body);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function finiteNumber(value, name, min, max) {
|
|
68
|
+
if (value === undefined || value === null) return undefined;
|
|
69
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {
|
|
70
|
+
throw httpError(400, 'invalid_request', `${name} must be a number in [${min}, ${max}]`);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Map OpenAI-style request fields onto the engine's GenerationConfig. */
|
|
76
|
+
function generationConfig(body, defaults) {
|
|
77
|
+
const config = { ...defaults };
|
|
78
|
+
const maxTokens = finiteNumber(body.max_tokens ?? body.max_completion_tokens, 'max_tokens', 1, 1 << 20);
|
|
79
|
+
const temperature = finiteNumber(body.temperature, 'temperature', 0, 10);
|
|
80
|
+
const topP = finiteNumber(body.top_p, 'top_p', 0, 1);
|
|
81
|
+
const topK = finiteNumber(body.top_k, 'top_k', 0, 1 << 20);
|
|
82
|
+
const seed = finiteNumber(body.seed, 'seed', 0, Number.MAX_SAFE_INTEGER);
|
|
83
|
+
if (maxTokens !== undefined) config.maxTokens = Math.floor(maxTokens);
|
|
84
|
+
if (temperature !== undefined) config.temperature = temperature;
|
|
85
|
+
if (topP !== undefined) config.topP = topP;
|
|
86
|
+
if (topK !== undefined) config.topK = Math.floor(topK);
|
|
87
|
+
if (seed !== undefined) config.seed = Math.floor(seed);
|
|
88
|
+
if (body.stop !== undefined) {
|
|
89
|
+
const stop = typeof body.stop === 'string' ? [body.stop] : body.stop;
|
|
90
|
+
if (!Array.isArray(stop) || stop.some((s) => typeof s !== 'string')) {
|
|
91
|
+
throw httpError(400, 'invalid_request', 'stop must be a string or an array of strings');
|
|
92
|
+
}
|
|
93
|
+
config.stopSequences = stop;
|
|
94
|
+
}
|
|
95
|
+
return config;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function chatMessages(body) {
|
|
99
|
+
const { messages } = body;
|
|
100
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
101
|
+
throw httpError(400, 'invalid_request', 'messages must be a non-empty array');
|
|
102
|
+
}
|
|
103
|
+
return messages.map((m, i) => {
|
|
104
|
+
if (!m || !['system', 'user', 'assistant'].includes(m.role)) {
|
|
105
|
+
throw httpError(400, 'invalid_request', `messages[${i}].role must be system, user or assistant`);
|
|
106
|
+
}
|
|
107
|
+
let content = m.content;
|
|
108
|
+
if (Array.isArray(content)) {
|
|
109
|
+
content = content.map((part) => (part && part.type === 'text' && typeof part.text === 'string' ? part.text : '')).join('');
|
|
110
|
+
}
|
|
111
|
+
if (typeof content !== 'string') {
|
|
112
|
+
throw httpError(400, 'invalid_request', `messages[${i}].content must be a string`);
|
|
113
|
+
}
|
|
114
|
+
return { role: m.role, content };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function usage(promptTokens, completionTokens) {
|
|
119
|
+
const p = promptTokens || 0;
|
|
120
|
+
const c = completionTokens || 0;
|
|
121
|
+
return { prompt_tokens: p, completion_tokens: c, total_tokens: p + c };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build the HTTP server around an engine that already has a model loaded.
|
|
126
|
+
* `engine` needs: query(text, config), chat(messages, config), route(text),
|
|
127
|
+
* isModelLoaded(), isNativeLoaded(). Exported for tests.
|
|
128
|
+
*/
|
|
129
|
+
function createServer(engine, options = {}) {
|
|
130
|
+
const modelId = options.modelId || 'ruvllm';
|
|
131
|
+
const version = options.version || 'unknown';
|
|
132
|
+
const defaults = options.defaults || {};
|
|
133
|
+
const limit = options.maxBodyBytes || MAX_BODY_BYTES;
|
|
134
|
+
const learning = options.learningEnabled !== false;
|
|
135
|
+
|
|
136
|
+
async function handle(req, res) {
|
|
137
|
+
const url = new URL(req.url, 'http://localhost');
|
|
138
|
+
const route = `${req.method} ${url.pathname}`;
|
|
139
|
+
|
|
140
|
+
if (route === 'GET /health') {
|
|
141
|
+
return send(res, 200, {
|
|
142
|
+
status: 'ok',
|
|
143
|
+
model: modelId,
|
|
144
|
+
modelLoaded: engine.isModelLoaded(),
|
|
145
|
+
native: engine.isNativeLoaded(),
|
|
146
|
+
sona: learning,
|
|
147
|
+
hnsw: engine.isNativeLoaded(),
|
|
148
|
+
version,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (route === 'GET /v1/models') {
|
|
153
|
+
return send(res, 200, { object: 'list', data: [{ id: modelId, object: 'model', owned_by: 'ruvllm' }] });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (route === 'POST /query') {
|
|
157
|
+
const body = await readJson(req, limit);
|
|
158
|
+
if (typeof body.query !== 'string' || body.query.length === 0) {
|
|
159
|
+
throw httpError(400, 'invalid_request', 'query must be a non-empty string');
|
|
160
|
+
}
|
|
161
|
+
const config = generationConfig(body, defaults);
|
|
162
|
+
const response = engine.query(body.query, config);
|
|
163
|
+
return send(res, 200, {
|
|
164
|
+
id: response.requestId || `ruvllm-${randomUUID()}`,
|
|
165
|
+
model: modelId,
|
|
166
|
+
content: response.text,
|
|
167
|
+
done: response.finishReason !== 'length',
|
|
168
|
+
finish_reason: response.finishReason || 'stop',
|
|
169
|
+
session_id: typeof body.session_id === 'string' ? body.session_id : undefined,
|
|
170
|
+
usage: usage(response.promptTokens, response.completionTokens),
|
|
171
|
+
router_metrics: { route: response.model, confidence: response.confidence, context_size: response.contextSize },
|
|
172
|
+
latency_ms: response.latencyMs,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (route === 'POST /v1/chat/completions') {
|
|
177
|
+
const body = await readJson(req, limit);
|
|
178
|
+
const messages = chatMessages(body);
|
|
179
|
+
const config = generationConfig(body, defaults);
|
|
180
|
+
const result = engine.chat(messages, config);
|
|
181
|
+
const id = `chatcmpl-${randomUUID()}`;
|
|
182
|
+
const created = Math.floor(Date.now() / 1000);
|
|
183
|
+
const finish = result.finishReason === 'length' ? 'length' : 'stop';
|
|
184
|
+
if (body.stream === true) {
|
|
185
|
+
res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' });
|
|
186
|
+
const chunk = (delta, finishReason, extra) => `data: ${JSON.stringify({
|
|
187
|
+
id, object: 'chat.completion.chunk', created, model: modelId,
|
|
188
|
+
choices: [{ index: 0, delta, finish_reason: finishReason }], ...extra,
|
|
189
|
+
})}\n\n`;
|
|
190
|
+
res.write(chunk({ role: 'assistant', content: result.text }, null));
|
|
191
|
+
res.write(chunk({}, finish, { usage: usage(result.promptTokens, result.completionTokens) }));
|
|
192
|
+
res.end('data: [DONE]\n\n');
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
return send(res, 200, {
|
|
196
|
+
id, object: 'chat.completion', created, model: modelId,
|
|
197
|
+
choices: [{ index: 0, message: { role: 'assistant', content: result.text }, finish_reason: finish }],
|
|
198
|
+
usage: usage(result.promptTokens, result.completionTokens),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
throw httpError(404, 'not_found', `no route for ${route}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return http.createServer((req, res) => {
|
|
206
|
+
handle(req, res).catch((error) => {
|
|
207
|
+
if (res.headersSent) {
|
|
208
|
+
res.destroy(error);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const status = error.status
|
|
212
|
+
|| (error.code === 'RUVLLM_NO_LANGUAGE_MODEL' ? 503 : 500);
|
|
213
|
+
send(res, status, { error: { code: error.code || 'server_error', message: error.message } });
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* CLI entry: `ruvllm serve <model.gguf> [--host H] [--port P] [--max-context N]`.
|
|
220
|
+
* `RuvLLM` is passed in so this file does not load dist/ by itself.
|
|
221
|
+
*/
|
|
222
|
+
function runServe({ RuvLLM, version }, positional, flags) {
|
|
223
|
+
const modelPath = (typeof flags.model === 'string' && flags.model) || positional[0];
|
|
224
|
+
if (!modelPath) {
|
|
225
|
+
console.error('Error: ruvllm serve needs a model: ruvllm serve <path/to/model.gguf> (or --model <path>)');
|
|
226
|
+
console.error('Download one with: ruvllm models download claude-code');
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const host = typeof flags.host === 'string' ? flags.host : DEFAULT_HOST;
|
|
230
|
+
const port = flags.port === undefined ? DEFAULT_PORT : Number(flags.port);
|
|
231
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
232
|
+
console.error('Error: --port must be an integer in [0, 65535]');
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const llm = new RuvLLM({ learningEnabled: !flags['no-learning'] });
|
|
237
|
+
const loadOptions = {};
|
|
238
|
+
if (flags['max-context']) loadOptions.maxContext = parseInt(flags['max-context'], 10);
|
|
239
|
+
let info;
|
|
240
|
+
try {
|
|
241
|
+
info = llm.loadModel(modelPath, loadOptions);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(`Error: ${error.message}`);
|
|
244
|
+
if (error.code) console.error(`(${error.code}; the server was not started)`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const defaults = {};
|
|
249
|
+
if (flags['max-tokens']) defaults.maxTokens = parseInt(flags['max-tokens'], 10);
|
|
250
|
+
if (flags.temperature) defaults.temperature = parseFloat(flags.temperature);
|
|
251
|
+
const server = createServer(llm, {
|
|
252
|
+
modelId: info.name, version, defaults, learningEnabled: !flags['no-learning'],
|
|
253
|
+
});
|
|
254
|
+
server.listen(port, host, () => {
|
|
255
|
+
const address = server.address();
|
|
256
|
+
console.error(`Loaded ${info.name} (${info.architecture}, ${info.quantization ?? 'quantization unknown'})`);
|
|
257
|
+
console.error(`ruvllm serve listening on http://${address.address}:${address.port} (GET /health, POST /query, POST /v1/chat/completions)`);
|
|
258
|
+
});
|
|
259
|
+
const stop = () => server.close(() => process.exit(0));
|
|
260
|
+
process.once('SIGINT', stop);
|
|
261
|
+
process.once('SIGTERM', stop);
|
|
262
|
+
return server;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = { createServer, runServe, DEFAULT_HOST, DEFAULT_PORT };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding Quality Benchmark for RuvLTRA Models
|
|
3
|
+
*
|
|
4
|
+
* Tests embedding quality for Claude Code use cases:
|
|
5
|
+
* - Code similarity detection
|
|
6
|
+
* - Task clustering
|
|
7
|
+
* - Semantic search accuracy
|
|
8
|
+
*/
|
|
9
|
+
export interface EmbeddingPair {
|
|
10
|
+
id: string;
|
|
11
|
+
text1: string;
|
|
12
|
+
text2: string;
|
|
13
|
+
similarity: 'high' | 'medium' | 'low' | 'none';
|
|
14
|
+
category: string;
|
|
15
|
+
}
|
|
16
|
+
export interface EmbeddingResult {
|
|
17
|
+
pairId: string;
|
|
18
|
+
expectedSimilarity: string;
|
|
19
|
+
computedScore: number;
|
|
20
|
+
correct: boolean;
|
|
21
|
+
latencyMs: number;
|
|
22
|
+
}
|
|
23
|
+
export interface ClusterTestCase {
|
|
24
|
+
id: string;
|
|
25
|
+
items: string[];
|
|
26
|
+
expectedCluster: string;
|
|
27
|
+
}
|
|
28
|
+
export interface EmbeddingBenchmarkResults {
|
|
29
|
+
similarityAccuracy: number;
|
|
30
|
+
similarityByCategory: Record<string, number>;
|
|
31
|
+
avgSimilarityLatencyMs: number;
|
|
32
|
+
clusterPurity: number;
|
|
33
|
+
silhouetteScore: number;
|
|
34
|
+
searchMRR: number;
|
|
35
|
+
searchNDCG: number;
|
|
36
|
+
similarityResults: EmbeddingResult[];
|
|
37
|
+
totalPairs: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Ground truth similarity pairs for testing
|
|
41
|
+
* Tests whether embeddings correctly capture semantic similarity
|
|
42
|
+
*/
|
|
43
|
+
export declare const SIMILARITY_TEST_PAIRS: EmbeddingPair[];
|
|
44
|
+
/**
|
|
45
|
+
* Search relevance test cases
|
|
46
|
+
* Query + documents with relevance scores
|
|
47
|
+
*/
|
|
48
|
+
export interface SearchTestCase {
|
|
49
|
+
id: string;
|
|
50
|
+
query: string;
|
|
51
|
+
documents: {
|
|
52
|
+
text: string;
|
|
53
|
+
relevance: number;
|
|
54
|
+
}[];
|
|
55
|
+
}
|
|
56
|
+
export declare const SEARCH_TEST_CASES: SearchTestCase[];
|
|
57
|
+
/**
|
|
58
|
+
* Cluster test cases - items that should cluster together
|
|
59
|
+
*/
|
|
60
|
+
export declare const CLUSTER_TEST_CASES: ClusterTestCase[];
|
|
61
|
+
/**
|
|
62
|
+
* Check if computed similarity matches expected category
|
|
63
|
+
*/
|
|
64
|
+
export declare function isCorrectSimilarity(expected: 'high' | 'medium' | 'low' | 'none', computed: number): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Calculate Mean Reciprocal Rank for search results
|
|
67
|
+
*/
|
|
68
|
+
export declare function calculateMRR(rankings: {
|
|
69
|
+
relevant: boolean;
|
|
70
|
+
}[][]): number;
|
|
71
|
+
/**
|
|
72
|
+
* Calculate NDCG for search results
|
|
73
|
+
*/
|
|
74
|
+
export declare function calculateNDCG(results: {
|
|
75
|
+
relevance: number;
|
|
76
|
+
}[], idealOrder: {
|
|
77
|
+
relevance: number;
|
|
78
|
+
}[]): number;
|
|
79
|
+
/**
|
|
80
|
+
* Calculate silhouette score for clustering
|
|
81
|
+
*/
|
|
82
|
+
export declare function calculateSilhouette(embeddings: number[][], labels: number[]): number;
|
|
83
|
+
/**
|
|
84
|
+
* Run the embedding benchmark
|
|
85
|
+
*/
|
|
86
|
+
export declare function runEmbeddingBenchmark(embedder: (text: string) => number[], similarityFn: (a: number[], b: number[]) => number): EmbeddingBenchmarkResults;
|
|
87
|
+
/**
|
|
88
|
+
* Format embedding benchmark results for display
|
|
89
|
+
*/
|
|
90
|
+
export declare function formatEmbeddingResults(results: EmbeddingBenchmarkResults): string;
|
|
91
|
+
declare const _default: {
|
|
92
|
+
SIMILARITY_TEST_PAIRS: EmbeddingPair[];
|
|
93
|
+
SEARCH_TEST_CASES: SearchTestCase[];
|
|
94
|
+
CLUSTER_TEST_CASES: ClusterTestCase[];
|
|
95
|
+
runEmbeddingBenchmark: typeof runEmbeddingBenchmark;
|
|
96
|
+
formatEmbeddingResults: typeof formatEmbeddingResults;
|
|
97
|
+
isCorrectSimilarity: typeof isCorrectSimilarity;
|
|
98
|
+
calculateMRR: typeof calculateMRR;
|
|
99
|
+
calculateNDCG: typeof calculateNDCG;
|
|
100
|
+
};
|
|
101
|
+
export default _default;
|
|
102
|
+
//# sourceMappingURL=embedding-benchmark.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-benchmark.d.ts","sourceRoot":"","sources":["../../../src/benchmarks/embedding-benchmark.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC/C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IAExC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,sBAAsB,EAAE,MAAM,CAAC;IAG/B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IAGxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IAGnB,iBAAiB,EAAE,eAAe,EAAE,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,EA8ChD,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClD;AAED,eAAO,MAAM,iBAAiB,EAAE,cAAc,EAwD7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,eAAe,EAwD/C,CAAC;AAYF;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,EAC5C,QAAQ,EAAE,MAAM,GACf,OAAO,CAGT;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,EAAE,EAAE,GAClC,MAAM,CASR;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,EAChC,UAAU,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,GAClC,MAAM,CAUR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAAE,EAAE,EACtB,MAAM,EAAE,MAAM,EAAE,GACf,MAAM,CA8CR;AAUD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,EACpC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,MAAM,GACjD,yBAAyB,CA0G3B;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,CAyDjF;;;;;;;;;;;AAED,wBASE"}
|