@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.
Files changed (171) hide show
  1. package/README.md +447 -160
  2. package/bin/cli.js +1066 -0
  3. package/bin/gpu-delegate.js +67 -0
  4. package/bin/ruvllm-adapters.js +5 -0
  5. package/bin/ruvllm-gpu-loop.js +5 -0
  6. package/bin/serve.js +265 -0
  7. package/dist/cjs/benchmarks/embedding-benchmark.d.ts +102 -0
  8. package/dist/cjs/benchmarks/embedding-benchmark.d.ts.map +1 -0
  9. package/dist/cjs/benchmarks/embedding-benchmark.js +436 -0
  10. package/dist/cjs/benchmarks/index.d.ts +34 -0
  11. package/dist/cjs/benchmarks/index.d.ts.map +1 -0
  12. package/dist/cjs/benchmarks/index.js +132 -0
  13. package/dist/cjs/benchmarks/model-comparison.d.ts +71 -0
  14. package/dist/cjs/benchmarks/model-comparison.d.ts.map +1 -0
  15. package/dist/cjs/benchmarks/model-comparison.js +476 -0
  16. package/dist/cjs/benchmarks/routing-benchmark.d.ts +70 -0
  17. package/dist/cjs/benchmarks/routing-benchmark.d.ts.map +1 -0
  18. package/dist/cjs/benchmarks/routing-benchmark.js +289 -0
  19. package/dist/cjs/contrastive.d.ts +229 -0
  20. package/dist/cjs/contrastive.d.ts.map +1 -0
  21. package/dist/cjs/contrastive.js +589 -0
  22. package/dist/cjs/engine.d.ts +160 -0
  23. package/dist/cjs/engine.d.ts.map +1 -0
  24. package/dist/cjs/engine.js +552 -0
  25. package/dist/cjs/export.d.ts +182 -0
  26. package/dist/cjs/export.d.ts.map +1 -0
  27. package/dist/cjs/export.js +422 -0
  28. package/dist/cjs/federated.d.ts +233 -0
  29. package/dist/cjs/federated.d.ts.map +1 -0
  30. package/dist/cjs/federated.js +525 -0
  31. package/dist/cjs/index.d.ts +68 -0
  32. package/dist/cjs/index.d.ts.map +1 -0
  33. package/dist/cjs/index.js +104 -0
  34. package/dist/cjs/intelligence.d.ts +136 -0
  35. package/dist/cjs/intelligence.d.ts.map +1 -0
  36. package/dist/cjs/intelligence.js +226 -0
  37. package/dist/cjs/lora.d.ts +251 -0
  38. package/dist/cjs/lora.d.ts.map +1 -0
  39. package/dist/cjs/lora.js +509 -0
  40. package/dist/cjs/models.d.ts +129 -0
  41. package/dist/cjs/models.d.ts.map +1 -0
  42. package/dist/cjs/models.js +323 -0
  43. package/dist/cjs/native.d.ts +140 -0
  44. package/dist/cjs/native.d.ts.map +1 -0
  45. package/dist/cjs/native.js +98 -0
  46. package/dist/cjs/session.d.ts +80 -0
  47. package/dist/cjs/session.d.ts.map +1 -0
  48. package/dist/cjs/session.js +203 -0
  49. package/dist/cjs/simd.d.ts +90 -0
  50. package/dist/cjs/simd.d.ts.map +1 -0
  51. package/dist/cjs/simd.js +209 -0
  52. package/dist/cjs/sona.d.ts +223 -0
  53. package/dist/cjs/sona.d.ts.map +1 -0
  54. package/dist/cjs/sona.js +562 -0
  55. package/dist/cjs/streaming.d.ts +55 -0
  56. package/dist/cjs/streaming.d.ts.map +1 -0
  57. package/dist/cjs/streaming.js +131 -0
  58. package/dist/cjs/training.d.ts +312 -0
  59. package/dist/cjs/training.d.ts.map +1 -0
  60. package/dist/cjs/training.js +610 -0
  61. package/dist/cjs/types.d.ts +696 -0
  62. package/dist/cjs/types.d.ts.map +1 -0
  63. package/dist/cjs/types.js +6 -0
  64. package/dist/esm/benchmarks/embedding-benchmark.d.ts +102 -0
  65. package/dist/esm/benchmarks/embedding-benchmark.d.ts.map +1 -0
  66. package/dist/esm/benchmarks/embedding-benchmark.js +427 -0
  67. package/dist/esm/benchmarks/index.d.ts +34 -0
  68. package/dist/esm/benchmarks/index.d.ts.map +1 -0
  69. package/dist/esm/benchmarks/index.js +110 -0
  70. package/dist/esm/benchmarks/model-comparison.d.ts +71 -0
  71. package/dist/esm/benchmarks/model-comparison.d.ts.map +1 -0
  72. package/dist/esm/benchmarks/model-comparison.js +434 -0
  73. package/dist/esm/benchmarks/routing-benchmark.d.ts +70 -0
  74. package/dist/esm/benchmarks/routing-benchmark.d.ts.map +1 -0
  75. package/dist/esm/benchmarks/routing-benchmark.js +283 -0
  76. package/dist/esm/contrastive.d.ts +229 -0
  77. package/dist/esm/contrastive.d.ts.map +1 -0
  78. package/dist/esm/contrastive.js +578 -0
  79. package/dist/esm/engine.d.ts +160 -0
  80. package/dist/esm/engine.d.ts.map +1 -0
  81. package/dist/esm/engine.js +548 -0
  82. package/dist/esm/export.d.ts +182 -0
  83. package/dist/esm/export.d.ts.map +1 -0
  84. package/dist/esm/export.js +414 -0
  85. package/dist/esm/federated.d.ts +233 -0
  86. package/dist/esm/federated.d.ts.map +1 -0
  87. package/dist/esm/federated.js +520 -0
  88. package/dist/esm/index.d.ts +68 -0
  89. package/dist/esm/index.d.ts.map +1 -0
  90. package/dist/esm/index.js +84 -0
  91. package/dist/esm/intelligence.d.ts +136 -0
  92. package/dist/esm/intelligence.d.ts.map +1 -0
  93. package/dist/esm/intelligence.js +188 -0
  94. package/dist/esm/lora.d.ts +251 -0
  95. package/dist/esm/lora.d.ts.map +1 -0
  96. package/dist/esm/lora.js +504 -0
  97. package/dist/esm/models.d.ts +129 -0
  98. package/dist/esm/models.d.ts.map +1 -0
  99. package/dist/esm/models.js +282 -0
  100. package/dist/esm/native.d.ts +140 -0
  101. package/dist/esm/native.d.ts.map +1 -0
  102. package/dist/esm/native.js +100 -0
  103. package/dist/esm/session.d.ts +80 -0
  104. package/dist/esm/session.d.ts.map +1 -0
  105. package/dist/esm/session.js +199 -0
  106. package/dist/esm/simd.d.ts +90 -0
  107. package/dist/esm/simd.d.ts.map +1 -0
  108. package/dist/esm/simd.js +205 -0
  109. package/dist/esm/sona.d.ts +223 -0
  110. package/dist/esm/sona.d.ts.map +1 -0
  111. package/dist/esm/sona.js +556 -0
  112. package/dist/esm/streaming.d.ts +55 -0
  113. package/dist/esm/streaming.d.ts.map +1 -0
  114. package/dist/esm/streaming.js +126 -0
  115. package/dist/esm/training.d.ts +312 -0
  116. package/dist/esm/training.d.ts.map +1 -0
  117. package/dist/esm/training.js +603 -0
  118. package/dist/esm/types.d.ts +696 -0
  119. package/dist/esm/types.d.ts.map +1 -0
  120. package/dist/esm/types.js +5 -0
  121. package/gpu/_load.cjs +38 -0
  122. package/gpu/_load.mjs +33 -0
  123. package/gpu/adapters.cjs +3 -0
  124. package/gpu/adapters.mjs +5 -0
  125. package/gpu/engine.cjs +3 -0
  126. package/gpu/engine.mjs +5 -0
  127. package/gpu/fixture.cjs +3 -0
  128. package/gpu/fixture.mjs +5 -0
  129. package/gpu/gpu-host.cjs +3 -0
  130. package/gpu/gpu-host.mjs +5 -0
  131. package/gpu/gpu-loop.cjs +3 -0
  132. package/gpu/gpu-loop.mjs +5 -0
  133. package/gpu/gpu-spool-adapter.cjs +3 -0
  134. package/gpu/gpu-spool-adapter.mjs +5 -0
  135. package/gpu/vast-host-adapter.cjs +3 -0
  136. package/gpu/vast-host-adapter.mjs +5 -0
  137. package/package.json +159 -49
  138. package/scripts/ensemble-model-compare.js +287 -0
  139. package/scripts/fix-esm-extensions.mjs +220 -0
  140. package/scripts/huggingface/README.md +537 -0
  141. package/scripts/huggingface/publish.sh +112 -0
  142. package/scripts/hybrid-model-compare.js +373 -0
  143. package/scripts/improved-model-compare.js +288 -0
  144. package/scripts/optimized-model-compare.js +364 -0
  145. package/scripts/real-model-compare.js +280 -0
  146. package/scripts/smoke-esm.mjs +82 -0
  147. package/scripts/training/agentic-flow-capabilities.json +329 -0
  148. package/scripts/training/claude-code-synth.js +505 -0
  149. package/scripts/training/claude-flow-capabilities.json +1322 -0
  150. package/scripts/training/claude-hard-negatives.js +222 -0
  151. package/scripts/training/contrastive-finetune.js +517 -0
  152. package/scripts/training/ecosystem-triplets.jsonl +1467 -0
  153. package/scripts/training/generate-ecosystem-triplets.js +1324 -0
  154. package/scripts/training/routing-dataset.js +634 -0
  155. package/scripts/training/ruvector-capabilities.json +798 -0
  156. package/scripts/training/validate-ecosystem.js +381 -0
  157. package/scripts/training/validation-results.json +23 -0
  158. package/GPU-LOOP.md +0 -229
  159. package/LICENSE +0 -201
  160. package/NOTICE +0 -9
  161. package/examples/gpu-loop.example.json +0 -26
  162. package/src/adapters-cli.mjs +0 -23
  163. package/src/adapters.mjs +0 -91
  164. package/src/cli.mjs +0 -30
  165. package/src/engine.mjs +0 -238
  166. package/src/fixture.mjs +0 -38
  167. package/src/gpu-host.mjs +0 -47
  168. package/src/gpu-loop-cli.mjs +0 -67
  169. package/src/gpu-loop.mjs +0 -184
  170. package/src/gpu-spool-adapter.mjs +0 -80
  171. package/src/vast-host-adapter.mjs +0 -274
@@ -0,0 +1,517 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Contrastive Fine-tuning for RuvLTRA Claude Code Router
4
+ *
5
+ * Uses triplet loss to fine-tune embeddings:
6
+ * - Anchor: task description
7
+ * - Positive: correct agent description
8
+ * - Negative: wrong agent description (hard negative)
9
+ *
10
+ * Goal: minimize distance(anchor, positive) and maximize distance(anchor, negative)
11
+ */
12
+
13
+ const { execSync } = require('child_process');
14
+ const { existsSync, writeFileSync, readFileSync, mkdirSync } = require('fs');
15
+ const { join } = require('path');
16
+ const { homedir } = require('os');
17
+
18
+ const MODELS_DIR = join(homedir(), '.ruvllm', 'models');
19
+ const OUTPUT_DIR = join(homedir(), '.ruvllm', 'training');
20
+ const RUVLTRA_MODEL = join(MODELS_DIR, 'ruvltra-claude-code-0.5b-q4_k_m.gguf');
21
+
22
+ // Import training data
23
+ const { AGENT_TRAINING_DATA, generateTrainingDataset, generateContrastivePairs, getDatasetStats } = require('./routing-dataset');
24
+
25
+ // Build agent descriptions from training data
26
+ const AGENT_DESCRIPTIONS = {};
27
+ for (const [agent, data] of Object.entries(AGENT_TRAINING_DATA)) {
28
+ AGENT_DESCRIPTIONS[agent] = data.description;
29
+ }
30
+
31
+ // Get training data
32
+ const TRAINING_EXAMPLES = generateTrainingDataset();
33
+ const CONTRASTIVE_PAIRS_RAW = generateContrastivePairs();
34
+
35
+ // Training configuration
36
+ const CONFIG = {
37
+ epochs: 10,
38
+ batchSize: 16,
39
+ learningRate: 0.0001,
40
+ margin: 0.5, // Triplet loss margin
41
+ temperature: 0.07, // InfoNCE temperature
42
+ hardNegativeRatio: 0.7, // Ratio of hard negatives
43
+ outputPath: join(OUTPUT_DIR, 'ruvltra-finetuned'),
44
+ };
45
+
46
+ /**
47
+ * Get embedding from model
48
+ */
49
+ function getEmbedding(modelPath, text) {
50
+ try {
51
+ const sanitized = text.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 500);
52
+ const result = execSync(
53
+ `llama-embedding -m "${modelPath}" -p "${sanitized}" --embd-output-format json 2>/dev/null`,
54
+ { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }
55
+ );
56
+ const json = JSON.parse(result);
57
+ return json.data[json.data.length - 1].embedding;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Compute cosine similarity
65
+ */
66
+ function cosineSimilarity(a, b) {
67
+ if (!a || !b || a.length !== b.length) return 0;
68
+ let dot = 0, normA = 0, normB = 0;
69
+ for (let i = 0; i < a.length; i++) {
70
+ dot += a[i] * b[i];
71
+ normA += a[i] * a[i];
72
+ normB += b[i] * b[i];
73
+ }
74
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB) || 1);
75
+ }
76
+
77
+ /**
78
+ * Compute triplet loss
79
+ * L = max(0, margin + d(anchor, positive) - d(anchor, negative))
80
+ */
81
+ function tripletLoss(anchorEmb, positiveEmb, negativeEmb, margin = CONFIG.margin) {
82
+ const posDist = 1 - cosineSimilarity(anchorEmb, positiveEmb);
83
+ const negDist = 1 - cosineSimilarity(anchorEmb, negativeEmb);
84
+ return Math.max(0, margin + posDist - negDist);
85
+ }
86
+
87
+ /**
88
+ * Compute InfoNCE loss (contrastive)
89
+ */
90
+ function infoNCELoss(anchorEmb, positiveEmb, negativeEmbs, temperature = CONFIG.temperature) {
91
+ const posSim = cosineSimilarity(anchorEmb, positiveEmb) / temperature;
92
+ const negSims = negativeEmbs.map(neg => cosineSimilarity(anchorEmb, neg) / temperature);
93
+
94
+ // Softmax denominator
95
+ const maxSim = Math.max(posSim, ...negSims);
96
+ const expPos = Math.exp(posSim - maxSim);
97
+ const expNegs = negSims.map(sim => Math.exp(sim - maxSim));
98
+ const denominator = expPos + expNegs.reduce((a, b) => a + b, 0);
99
+
100
+ // Cross-entropy loss
101
+ return -Math.log(expPos / denominator);
102
+ }
103
+
104
+ /**
105
+ * Prepare training batches with triplets
106
+ */
107
+ function prepareTrainingData(modelPath) {
108
+ console.log('Preparing training data...');
109
+
110
+ // Pre-compute agent description embeddings
111
+ const agentEmbeddings = {};
112
+ for (const [agent, desc] of Object.entries(AGENT_DESCRIPTIONS)) {
113
+ process.stdout.write(` Embedding ${agent}... `);
114
+ agentEmbeddings[agent] = getEmbedding(modelPath, desc);
115
+ console.log('done');
116
+ }
117
+
118
+ // Create triplets from training examples
119
+ const triplets = [];
120
+ const agents = Object.keys(AGENT_DESCRIPTIONS);
121
+
122
+ console.log(`\nGenerating triplets from ${TRAINING_EXAMPLES.length} examples...`);
123
+
124
+ // Group examples by agent
125
+ const examplesByAgent = {};
126
+ for (const ex of TRAINING_EXAMPLES) {
127
+ if (!examplesByAgent[ex.agent]) examplesByAgent[ex.agent] = [];
128
+ examplesByAgent[ex.agent].push(ex);
129
+ }
130
+
131
+ // Create triplets: anchor task, positive agent, negative agent
132
+ for (const example of TRAINING_EXAMPLES.slice(0, 200)) { // Limit for speed
133
+ const anchorEmb = getEmbedding(modelPath, example.task);
134
+ if (!anchorEmb) continue;
135
+
136
+ const positiveAgent = example.agent;
137
+ const positiveEmb = agentEmbeddings[positiveAgent];
138
+
139
+ // Get hard negatives (confusing agents)
140
+ const hardNegatives = example.confusing_with
141
+ ? [example.confusing_with]
142
+ : agents.filter(a => a !== positiveAgent).slice(0, 2);
143
+
144
+ for (const negAgent of hardNegatives) {
145
+ const negativeEmb = agentEmbeddings[negAgent];
146
+ if (negativeEmb) {
147
+ triplets.push({
148
+ anchor: example.task,
149
+ anchorEmb,
150
+ positive: positiveAgent,
151
+ positiveEmb,
152
+ negative: negAgent,
153
+ negativeEmb,
154
+ isHard: !!example.confusing_with,
155
+ });
156
+ }
157
+ }
158
+
159
+ // Add random negative for diversity
160
+ const randomNeg = agents.filter(a => a !== positiveAgent)[Math.floor(Math.random() * (agents.length - 1))];
161
+ if (agentEmbeddings[randomNeg]) {
162
+ triplets.push({
163
+ anchor: example.task,
164
+ anchorEmb,
165
+ positive: positiveAgent,
166
+ positiveEmb,
167
+ negative: randomNeg,
168
+ negativeEmb: agentEmbeddings[randomNeg],
169
+ isHard: false,
170
+ });
171
+ }
172
+ }
173
+
174
+ console.log(`Created ${triplets.length} triplets`);
175
+ return { triplets, agentEmbeddings };
176
+ }
177
+
178
+ /**
179
+ * Compute gradient for embedding update (simplified)
180
+ * In practice, this would be done via proper backprop
181
+ */
182
+ function computeGradient(anchorEmb, positiveEmb, negativeEmb, lr = CONFIG.learningRate) {
183
+ const dim = anchorEmb.length;
184
+ const gradient = new Array(dim).fill(0);
185
+
186
+ // Pull anchor towards positive
187
+ for (let i = 0; i < dim; i++) {
188
+ gradient[i] += lr * (positiveEmb[i] - anchorEmb[i]);
189
+ }
190
+
191
+ // Push anchor away from negative
192
+ for (let i = 0; i < dim; i++) {
193
+ gradient[i] -= lr * 0.5 * (negativeEmb[i] - anchorEmb[i]);
194
+ }
195
+
196
+ return gradient;
197
+ }
198
+
199
+ /**
200
+ * Export training data for external fine-tuning tools
201
+ */
202
+ function exportTrainingData(triplets, outputPath) {
203
+ console.log(`\nExporting training data to ${outputPath}...`);
204
+
205
+ // JSONL format for fine-tuning
206
+ const jsonlData = triplets.map(t => ({
207
+ anchor: t.anchor,
208
+ positive: t.positive,
209
+ negative: t.negative,
210
+ isHard: t.isHard,
211
+ }));
212
+
213
+ // CSV format for analysis
214
+ const csvData = [
215
+ 'anchor,positive,negative,is_hard',
216
+ ...triplets.map(t => `"${t.anchor.replace(/"/g, '""')}",${t.positive},${t.negative},${t.isHard}`)
217
+ ].join('\n');
218
+
219
+ // Embedding matrix for direct training
220
+ const embeddingData = {
221
+ anchors: triplets.map(t => t.anchorEmb),
222
+ positives: triplets.map(t => t.positiveEmb),
223
+ negatives: triplets.map(t => t.negativeEmb),
224
+ labels: triplets.map(t => t.positive),
225
+ };
226
+
227
+ mkdirSync(outputPath, { recursive: true });
228
+ writeFileSync(join(outputPath, 'triplets.jsonl'), jsonlData.map(JSON.stringify).join('\n'));
229
+ writeFileSync(join(outputPath, 'triplets.csv'), csvData);
230
+ writeFileSync(join(outputPath, 'embeddings.json'), JSON.stringify(embeddingData, null, 2));
231
+
232
+ console.log(` Exported ${triplets.length} triplets`);
233
+ return outputPath;
234
+ }
235
+
236
+ /**
237
+ * Simulate training loop (compute losses)
238
+ */
239
+ function simulateTraining(triplets, epochs = CONFIG.epochs) {
240
+ console.log(`\nSimulating ${epochs} epochs of training...`);
241
+
242
+ const batchSize = CONFIG.batchSize;
243
+ const history = [];
244
+
245
+ for (let epoch = 0; epoch < epochs; epoch++) {
246
+ let epochLoss = 0;
247
+ let batchCount = 0;
248
+
249
+ // Shuffle triplets
250
+ const shuffled = [...triplets].sort(() => Math.random() - 0.5);
251
+
252
+ for (let i = 0; i < shuffled.length; i += batchSize) {
253
+ const batch = shuffled.slice(i, i + batchSize);
254
+ let batchLoss = 0;
255
+
256
+ for (const triplet of batch) {
257
+ const loss = tripletLoss(
258
+ triplet.anchorEmb,
259
+ triplet.positiveEmb,
260
+ triplet.negativeEmb
261
+ );
262
+ batchLoss += loss;
263
+ }
264
+
265
+ epochLoss += batchLoss / batch.length;
266
+ batchCount++;
267
+ }
268
+
269
+ const avgLoss = epochLoss / batchCount;
270
+ history.push({ epoch: epoch + 1, loss: avgLoss });
271
+
272
+ process.stdout.write(` Epoch ${epoch + 1}/${epochs}: loss = ${avgLoss.toFixed(4)}\r`);
273
+ }
274
+
275
+ console.log('\n');
276
+ return history;
277
+ }
278
+
279
+ /**
280
+ * Evaluate model on test set
281
+ */
282
+ function evaluateModel(modelPath, agentEmbeddings) {
283
+ const ROUTING_TESTS = [
284
+ { task: 'Implement a binary search function in TypeScript', expected: 'coder' },
285
+ { task: 'Write unit tests for the authentication module', expected: 'tester' },
286
+ { task: 'Review the pull request for security vulnerabilities', expected: 'reviewer' },
287
+ { task: 'Research best practices for React state management', expected: 'researcher' },
288
+ { task: 'Design the database schema for user profiles', expected: 'architect' },
289
+ { task: 'Fix the null pointer exception in the login handler', expected: 'debugger' },
290
+ { task: 'Audit the API endpoints for XSS vulnerabilities', expected: 'security-architect' },
291
+ { task: 'Write JSDoc comments for the utility functions', expected: 'documenter' },
292
+ { task: 'Refactor the payment module to use async/await', expected: 'refactorer' },
293
+ { task: 'Optimize the database queries for the dashboard', expected: 'optimizer' },
294
+ { task: 'Set up the CI/CD pipeline for the microservices', expected: 'devops' },
295
+ { task: 'Generate OpenAPI documentation for the REST API', expected: 'api-docs' },
296
+ { task: 'Create a sprint plan for the next two weeks', expected: 'planner' },
297
+ { task: 'Build a React component for user registration', expected: 'coder' },
298
+ { task: 'Debug memory leak in the WebSocket handler', expected: 'debugger' },
299
+ { task: 'Investigate slow API response times', expected: 'researcher' },
300
+ { task: 'Check code for potential race conditions', expected: 'reviewer' },
301
+ { task: 'Add integration tests for the payment gateway', expected: 'tester' },
302
+ { task: 'Plan the architecture for real-time notifications', expected: 'architect' },
303
+ { task: 'Cache the frequently accessed user data', expected: 'optimizer' },
304
+ ];
305
+
306
+ let correct = 0;
307
+ const results = [];
308
+
309
+ for (const test of ROUTING_TESTS) {
310
+ const taskEmb = getEmbedding(modelPath, test.task);
311
+
312
+ let bestAgent = 'coder';
313
+ let bestSim = -1;
314
+
315
+ for (const [agent, emb] of Object.entries(agentEmbeddings)) {
316
+ const sim = cosineSimilarity(taskEmb, emb);
317
+ if (sim > bestSim) {
318
+ bestSim = sim;
319
+ bestAgent = agent;
320
+ }
321
+ }
322
+
323
+ const isCorrect = bestAgent === test.expected;
324
+ if (isCorrect) correct++;
325
+ results.push({ task: test.task, expected: test.expected, got: bestAgent, correct: isCorrect });
326
+ }
327
+
328
+ return { accuracy: correct / ROUTING_TESTS.length, correct, total: ROUTING_TESTS.length, results };
329
+ }
330
+
331
+ /**
332
+ * Generate LoRA adapter configuration
333
+ */
334
+ function generateLoRAConfig(outputPath) {
335
+ const loraConfig = {
336
+ model_type: 'qwen2',
337
+ base_model: 'Qwen/Qwen2.5-0.5B',
338
+ output_dir: outputPath,
339
+
340
+ // LoRA parameters
341
+ lora_r: 8,
342
+ lora_alpha: 16,
343
+ lora_dropout: 0.05,
344
+ target_modules: ['q_proj', 'v_proj', 'k_proj', 'o_proj'],
345
+
346
+ // Training parameters
347
+ learning_rate: CONFIG.learningRate,
348
+ num_train_epochs: CONFIG.epochs,
349
+ per_device_train_batch_size: CONFIG.batchSize,
350
+ gradient_accumulation_steps: 4,
351
+ warmup_ratio: 0.1,
352
+
353
+ // Contrastive loss parameters
354
+ loss_type: 'triplet',
355
+ margin: CONFIG.margin,
356
+ temperature: CONFIG.temperature,
357
+
358
+ // Data
359
+ train_data: join(outputPath, 'triplets.jsonl'),
360
+ eval_data: join(outputPath, 'eval.jsonl'),
361
+ };
362
+
363
+ writeFileSync(join(outputPath, 'lora_config.json'), JSON.stringify(loraConfig, null, 2));
364
+ return loraConfig;
365
+ }
366
+
367
+ /**
368
+ * Generate training script for external tools
369
+ */
370
+ function generateTrainingScript(outputPath) {
371
+ const script = `#!/bin/bash
372
+ # RuvLTRA Fine-tuning Script
373
+ # Prerequisites: pip install transformers peft accelerate
374
+
375
+ set -e
376
+
377
+ MODEL_PATH="${outputPath}"
378
+ BASE_MODEL="Qwen/Qwen2.5-0.5B"
379
+
380
+ echo "=== RuvLTRA Contrastive Fine-tuning ==="
381
+ echo "Base model: $BASE_MODEL"
382
+ echo "Output: $MODEL_PATH"
383
+
384
+ # Check for training data
385
+ if [ ! -f "$MODEL_PATH/triplets.jsonl" ]; then
386
+ echo "Error: Training data not found at $MODEL_PATH/triplets.jsonl"
387
+ exit 1
388
+ fi
389
+
390
+ # Install dependencies if needed
391
+ python3 -c "import transformers, peft" 2>/dev/null || {
392
+ echo "Installing dependencies..."
393
+ pip install transformers peft accelerate sentencepiece
394
+ }
395
+
396
+ # Fine-tune with LoRA
397
+ python3 << 'PYTHON'
398
+ import json
399
+ import torch
400
+ from pathlib import Path
401
+ from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
402
+ from peft import LoraConfig, get_peft_model, TaskType
403
+
404
+ # Load config
405
+ config_path = Path("${outputPath}/lora_config.json")
406
+ with open(config_path) as f:
407
+ config = json.load(f)
408
+
409
+ print(f"Loading base model: {config['base_model']}")
410
+
411
+ # Load model and tokenizer
412
+ tokenizer = AutoTokenizer.from_pretrained(config['base_model'])
413
+ model = AutoModelForCausalLM.from_pretrained(
414
+ config['base_model'],
415
+ torch_dtype=torch.float16,
416
+ device_map='auto'
417
+ )
418
+
419
+ # Configure LoRA
420
+ lora_config = LoraConfig(
421
+ r=config['lora_r'],
422
+ lora_alpha=config['lora_alpha'],
423
+ lora_dropout=config['lora_dropout'],
424
+ target_modules=config['target_modules'],
425
+ task_type=TaskType.CAUSAL_LM,
426
+ )
427
+
428
+ model = get_peft_model(model, lora_config)
429
+ model.print_trainable_parameters()
430
+
431
+ print("Model ready for fine-tuning!")
432
+ print(f"Training data: {config['train_data']}")
433
+ print("Note: Full training requires GPU. This script validates the setup.")
434
+ PYTHON
435
+
436
+ echo ""
437
+ echo "=== Setup Complete ==="
438
+ echo "To train on GPU, run the full training pipeline."
439
+ echo "Training data exported to: $MODEL_PATH/triplets.jsonl"
440
+ `;
441
+
442
+ writeFileSync(join(outputPath, 'train.sh'), script);
443
+ execSync(`chmod +x "${join(outputPath, 'train.sh')}"`);
444
+ return join(outputPath, 'train.sh');
445
+ }
446
+
447
+ /**
448
+ * Main training pipeline
449
+ */
450
+ async function main() {
451
+ console.log('╔═══════════════════════════════════════════════════════════════════════════════════╗');
452
+ console.log('║ RuvLTRA Contrastive Fine-tuning Pipeline ║');
453
+ console.log('╚═══════════════════════════════════════════════════════════════════════════════════╝\n');
454
+
455
+ if (!existsSync(RUVLTRA_MODEL)) {
456
+ console.error('RuvLTRA model not found. Run download-models.sh first.');
457
+ process.exit(1);
458
+ }
459
+
460
+ const stats = getDatasetStats();
461
+ console.log(`Model: ${RUVLTRA_MODEL}`);
462
+ console.log(`Training examples: ${stats.totalExamples}`);
463
+ console.log(`Contrastive pairs: ${stats.contrastivePairs}`);
464
+ console.log(`Output: ${CONFIG.outputPath}\n`);
465
+
466
+ // Prepare training data
467
+ const { triplets, agentEmbeddings } = prepareTrainingData(RUVLTRA_MODEL);
468
+
469
+ // Export for external training
470
+ exportTrainingData(triplets, CONFIG.outputPath);
471
+
472
+ // Generate LoRA config
473
+ const loraConfig = generateLoRAConfig(CONFIG.outputPath);
474
+ console.log('Generated LoRA config:', join(CONFIG.outputPath, 'lora_config.json'));
475
+
476
+ // Generate training script
477
+ const scriptPath = generateTrainingScript(CONFIG.outputPath);
478
+ console.log('Generated training script:', scriptPath);
479
+
480
+ // Simulate training to show expected loss curve
481
+ const history = simulateTraining(triplets);
482
+
483
+ // Evaluate current model
484
+ console.log('─────────────────────────────────────────────────────────────────');
485
+ console.log(' CURRENT MODEL EVALUATION');
486
+ console.log('─────────────────────────────────────────────────────────────────\n');
487
+
488
+ const evalResult = evaluateModel(RUVLTRA_MODEL, agentEmbeddings);
489
+ console.log(`Embedding-only accuracy: ${(evalResult.accuracy * 100).toFixed(1)}%\n`);
490
+
491
+ // Summary
492
+ console.log('═══════════════════════════════════════════════════════════════════════════════════');
493
+ console.log(' TRAINING SUMMARY');
494
+ console.log('═══════════════════════════════════════════════════════════════════════════════════\n');
495
+
496
+ console.log('Training data exported:');
497
+ console.log(` - ${join(CONFIG.outputPath, 'triplets.jsonl')} (${triplets.length} triplets)`);
498
+ console.log(` - ${join(CONFIG.outputPath, 'triplets.csv')} (spreadsheet format)`);
499
+ console.log(` - ${join(CONFIG.outputPath, 'embeddings.json')} (precomputed embeddings)`);
500
+ console.log(` - ${join(CONFIG.outputPath, 'lora_config.json')} (LoRA configuration)`);
501
+ console.log(` - ${join(CONFIG.outputPath, 'train.sh')} (training script)\n`);
502
+
503
+ console.log('Expected training loss (simulated):');
504
+ console.log(` Initial: ${history[0].loss.toFixed(4)}`);
505
+ console.log(` Final: ${history[history.length - 1].loss.toFixed(4)}`);
506
+ console.log(` Improvement: ${((1 - history[history.length - 1].loss / history[0].loss) * 100).toFixed(1)}%\n`);
507
+
508
+ console.log('To fine-tune on GPU:');
509
+ console.log(` cd ${CONFIG.outputPath}`);
510
+ console.log(' ./train.sh\n');
511
+
512
+ console.log('After training, convert to GGUF:');
513
+ console.log(' python convert_lora.py --base Qwen/Qwen2.5-0.5B --lora ./lora-adapter');
514
+ console.log(' llama-quantize model-merged.gguf ruvltra-finetuned-q4_k_m.gguf q4_k_m\n');
515
+ }
516
+
517
+ main().catch(console.error);