@ruvector/ruvllm 0.2.3 → 2.4.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 +379 -739
- package/bin/cli.js +619 -2
- 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 +3 -0
- package/dist/cjs/engine.d.ts.map +1 -1
- package/dist/cjs/engine.js +29 -9
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +9 -1
- 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/rlm/controller.d.ts +245 -0
- package/dist/cjs/rlm/controller.d.ts.map +1 -0
- package/dist/cjs/rlm/controller.js +554 -0
- package/dist/cjs/rlm/index.d.ts +62 -0
- package/dist/cjs/rlm/index.d.ts.map +1 -0
- package/dist/cjs/rlm/index.js +97 -0
- package/dist/cjs/rlm/training.d.ts +333 -0
- package/dist/cjs/rlm/training.d.ts.map +1 -0
- package/dist/cjs/rlm/training.js +680 -0
- package/dist/cjs/rlm/types.d.ts +189 -0
- package/dist/cjs/rlm/types.d.ts.map +1 -0
- package/dist/cjs/rlm/types.js +10 -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 +3 -0
- package/dist/esm/engine.d.ts.map +1 -1
- package/dist/esm/engine.js +29 -9
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +9 -1
- 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/rlm/controller.d.ts +245 -0
- package/dist/esm/rlm/controller.d.ts.map +1 -0
- package/dist/esm/rlm/controller.js +550 -0
- package/dist/esm/rlm/index.d.ts +62 -0
- package/dist/esm/rlm/index.d.ts.map +1 -0
- package/dist/esm/rlm/index.js +71 -0
- package/dist/esm/rlm/training.d.ts +333 -0
- package/dist/esm/rlm/training.d.ts.map +1 -0
- package/dist/esm/rlm/training.js +672 -0
- package/dist/esm/rlm/types.d.ts +189 -0
- package/dist/esm/rlm/types.d.ts.map +1 -0
- package/dist/esm/rlm/types.js +9 -0
- package/package.json +13 -11
- package/scripts/ensemble-model-compare.js +287 -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/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/rlm-dataset.js +671 -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
|
@@ -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);
|