295 lines
13 KiB
JavaScript
295 lines
13 KiB
JavaScript
/**
|
|
* SONA Training CLI Commands
|
|
*
|
|
* Train specialized agents with SONA continuous learning
|
|
*/
|
|
import { AgentFactory, CodebaseTrainer, AgentTemplates } from '../../services/sona-agent-training.js';
|
|
import { ValidationUtils } from '../../services/sona-types.js';
|
|
import { readFileSync, writeFileSync, readdirSync, statSync, mkdirSync } from 'fs';
|
|
import { join, extname, resolve } from 'path';
|
|
export function createSONATrainingCommands(program) {
|
|
const sonaTrain = program
|
|
.command('sona-train')
|
|
.description('Train specialized SONA agents');
|
|
// Create agent
|
|
sonaTrain
|
|
.command('create-agent')
|
|
.description('Create a new specialized agent')
|
|
.requiredOption('-n, --name <name>', 'Agent name')
|
|
.option('-p, --purpose <type>', 'Agent purpose: simple, complex, diverse', 'simple')
|
|
.option('-t, --template <template>', 'Use template: code, chat, data, rag, planner, expert')
|
|
.option('--domain <domain>', 'Domain for expert template')
|
|
.action(async (options) => {
|
|
try {
|
|
const factory = new AgentFactory();
|
|
let config;
|
|
if (options.template) {
|
|
switch (options.template) {
|
|
case 'code':
|
|
config = AgentTemplates.codeAssistant();
|
|
break;
|
|
case 'chat':
|
|
config = AgentTemplates.chatBot();
|
|
break;
|
|
case 'data':
|
|
config = AgentTemplates.dataAnalyst();
|
|
break;
|
|
case 'rag':
|
|
config = AgentTemplates.ragAgent();
|
|
break;
|
|
case 'planner':
|
|
config = AgentTemplates.taskPlanner();
|
|
break;
|
|
case 'expert':
|
|
if (!options.domain) {
|
|
throw new Error('--domain required for expert template');
|
|
}
|
|
config = AgentTemplates.domainExpert(options.domain);
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown template: ${options.template}`);
|
|
}
|
|
config.name = options.name;
|
|
}
|
|
else {
|
|
config = {
|
|
name: options.name,
|
|
purpose: options.purpose
|
|
};
|
|
}
|
|
const engine = factory.createAgent(options.name, config);
|
|
const stats = factory.getAgentStats(options.name);
|
|
console.log('\n✅ Agent created successfully!');
|
|
console.log(` Name: ${stats?.name}`);
|
|
console.log(` Purpose: ${stats?.purpose}`);
|
|
console.log(` Base LoRA Rank: ${stats?.config.baseLoraRank}`);
|
|
console.log(` Pattern Clusters: ${stats?.config.patternClusters}`);
|
|
console.log(` Quality Threshold: ${stats?.config.qualityThreshold}`);
|
|
console.log(` Route: ${stats?.config.route || 'default'}\n`);
|
|
// Save agent config (with path validation)
|
|
const baseDir = resolve(process.cwd(), '.sona-agents');
|
|
mkdirSync(baseDir, { recursive: true });
|
|
const safePath = ValidationUtils.sanitizePath(join('.sona-agents', `${options.name}.json`), process.cwd());
|
|
writeFileSync(safePath, JSON.stringify(stats, null, 2));
|
|
console.log(` Config saved: ${safePath}\n`);
|
|
}
|
|
catch (error) {
|
|
console.error(`\n❌ Error creating agent: ${error.message}\n`);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
// Train agent
|
|
sonaTrain
|
|
.command('train')
|
|
.description('Train an agent on examples')
|
|
.requiredOption('-n, --name <name>', 'Agent name')
|
|
.requiredOption('-d, --data <file>', 'Training data file (JSONL)')
|
|
.option('-b, --batch-size <number>', 'Batch size', '100')
|
|
.action(async (options) => {
|
|
try {
|
|
const factory = new AgentFactory();
|
|
// Load agent config (with path validation)
|
|
const safePath = ValidationUtils.sanitizePath(join('.sona-agents', `${options.name}.json`), process.cwd());
|
|
const agentConfig = JSON.parse(readFileSync(safePath, 'utf8'));
|
|
// Recreate agent
|
|
factory.createAgent(options.name, agentConfig.config);
|
|
// Load training data
|
|
const dataContent = readFileSync(options.data, 'utf8');
|
|
const lines = dataContent.split('\n').filter(l => l.trim());
|
|
const examples = lines.map(line => JSON.parse(line));
|
|
console.log(`\n🎓 Training agent: ${options.name}`);
|
|
console.log(` Examples: ${examples.length}`);
|
|
console.log(` Batch size: ${options.batchSize}\n`);
|
|
// Train in batches
|
|
const batchSize = parseInt(options.batchSize);
|
|
let totalTrained = 0;
|
|
for (let i = 0; i < examples.length; i += batchSize) {
|
|
const batch = examples.slice(i, i + batchSize);
|
|
const trained = await factory.trainAgent(options.name, batch);
|
|
totalTrained += trained;
|
|
console.log(` Batch ${Math.floor(i / batchSize) + 1}: ${trained} examples`);
|
|
}
|
|
const stats = factory.getAgentStats(options.name);
|
|
console.log(`\n✅ Training complete!`);
|
|
console.log(` Total examples: ${totalTrained}`);
|
|
console.log(` Avg quality: ${stats?.avgQuality.toFixed(3)}`);
|
|
console.log(` Patterns learned: ${stats?.patterns}\n`);
|
|
}
|
|
catch (error) {
|
|
console.error(`\n❌ Error training agent: ${error.message}\n`);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
// Index codebase
|
|
sonaTrain
|
|
.command('index-codebase')
|
|
.description('Index a codebase for pattern learning')
|
|
.requiredOption('-p, --path <path>', 'Path to codebase')
|
|
.option('-e, --extensions <exts>', 'File extensions (comma-separated)', 'ts,js,py,rs,go')
|
|
.option('--max-files <number>', 'Maximum files to index', '1000')
|
|
.action(async (options) => {
|
|
try {
|
|
const trainer = new CodebaseTrainer();
|
|
console.log(`\n📚 Indexing codebase: ${options.path}\n`);
|
|
// Find code files
|
|
const extensions = options.extensions.split(',').map((e) => e.trim());
|
|
const files = findCodeFiles(options.path, extensions, parseInt(options.maxFiles));
|
|
console.log(` Found ${files.length} files\n`);
|
|
// Index codebase
|
|
const chunks = await trainer.indexCodebase(files);
|
|
const stats = trainer.getStats();
|
|
console.log(`\n✅ Indexing complete!`);
|
|
console.log(` Files indexed: ${files.length}`);
|
|
console.log(` Code chunks: ${chunks}`);
|
|
console.log(` Patterns: ${stats.totalPatterns || 0}\n`);
|
|
// Save index (with path validation)
|
|
const safePath = ValidationUtils.sanitizePath('.sona-codebase-index.json', process.cwd());
|
|
writeFileSync(safePath, JSON.stringify({
|
|
path: options.path,
|
|
files: files.length,
|
|
chunks,
|
|
stats,
|
|
indexed: new Date().toISOString()
|
|
}, null, 2));
|
|
console.log(` Index saved: ${safePath}\n`);
|
|
}
|
|
catch (error) {
|
|
console.error(`\n❌ Error indexing codebase: ${error.message}\n`);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
// List agents
|
|
sonaTrain
|
|
.command('list')
|
|
.description('List all trained agents')
|
|
.action(() => {
|
|
try {
|
|
const factory = new AgentFactory();
|
|
// Load all agent configs (with path validation)
|
|
const baseDir = resolve(process.cwd(), '.sona-agents');
|
|
try {
|
|
const files = readdirSync(baseDir);
|
|
const agents = files
|
|
.filter(f => f.endsWith('.json'))
|
|
.map(f => {
|
|
const safePath = ValidationUtils.sanitizePath(join('.sona-agents', f), process.cwd());
|
|
return JSON.parse(readFileSync(safePath, 'utf8'));
|
|
});
|
|
if (agents.length === 0) {
|
|
console.log('\n📝 No agents found. Create one with: sona-train create-agent\n');
|
|
return;
|
|
}
|
|
console.log('\n📋 Trained Agents:\n');
|
|
console.log(' Name Purpose Training Avg Quality Patterns');
|
|
console.log(' ' + '─'.repeat(70));
|
|
for (const agent of agents) {
|
|
console.log(` ${agent.name.padEnd(20)} ` +
|
|
`${agent.purpose.padEnd(10)} ` +
|
|
`${agent.trainingCount.toString().padEnd(9)} ` +
|
|
`${agent.avgQuality.toFixed(3).padEnd(12)} ` +
|
|
`${agent.patterns}`);
|
|
}
|
|
console.log('');
|
|
}
|
|
catch (dirError) {
|
|
if (dirError.code === 'ENOENT') {
|
|
console.log('\n📝 No agents found. Create one with: sona-train create-agent\n');
|
|
}
|
|
else {
|
|
throw dirError;
|
|
}
|
|
}
|
|
}
|
|
catch (error) {
|
|
console.error(`\n❌ Error listing agents: ${error.message}\n`);
|
|
}
|
|
});
|
|
// Query agent
|
|
sonaTrain
|
|
.command('query')
|
|
.description('Query an agent with pattern matching')
|
|
.requiredOption('-n, --name <name>', 'Agent name')
|
|
.requiredOption('-q, --query <text>', 'Query text')
|
|
.option('-k <number>', 'Number of patterns to retrieve', '5')
|
|
.action(async (options) => {
|
|
try {
|
|
const factory = new AgentFactory();
|
|
// Load agent config (with path validation)
|
|
const safePath = ValidationUtils.sanitizePath(join('.sona-agents', `${options.name}.json`), process.cwd());
|
|
const agentConfig = JSON.parse(readFileSync(safePath, 'utf8'));
|
|
factory.createAgent(options.name, agentConfig.config);
|
|
// Mock embedding (in production, use actual embedding service)
|
|
const queryEmbedding = mockEmbedding(options.query);
|
|
// Find patterns
|
|
const patterns = await factory.findPatterns(options.name, queryEmbedding, parseInt(options.k));
|
|
console.log(`\n🔍 Query: "${options.query}"\n`);
|
|
console.log(` Found ${patterns.length} similar patterns:\n`);
|
|
for (let i = 0; i < patterns.length; i++) {
|
|
const p = patterns[i];
|
|
console.log(` ${i + 1}. Quality: ${p.avgQuality?.toFixed(3) || 'N/A'}`);
|
|
console.log(` Similarity: ${p.similarity?.toFixed(3) || 'N/A'}`);
|
|
console.log(` Context: ${p.context || 'none'}\n`);
|
|
}
|
|
}
|
|
catch (error) {
|
|
console.error(`\n❌ Error querying agent: ${error.message}\n`);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
return sonaTrain;
|
|
}
|
|
/**
|
|
* Find code files in directory
|
|
*/
|
|
function findCodeFiles(dir, extensions, maxFiles) {
|
|
const files = [];
|
|
function scan(currentDir) {
|
|
if (files.length >= maxFiles)
|
|
return;
|
|
const entries = readdirSync(currentDir);
|
|
for (const entry of entries) {
|
|
if (files.length >= maxFiles)
|
|
break;
|
|
const fullPath = join(currentDir, entry);
|
|
const stat = statSync(fullPath);
|
|
if (stat.isDirectory()) {
|
|
if (!entry.startsWith('.') && entry !== 'node_modules') {
|
|
scan(fullPath);
|
|
}
|
|
}
|
|
else {
|
|
const ext = extname(entry).slice(1);
|
|
if (extensions.includes(ext)) {
|
|
try {
|
|
const content = readFileSync(fullPath, 'utf8');
|
|
files.push({
|
|
path: fullPath,
|
|
language: ext,
|
|
content
|
|
});
|
|
}
|
|
catch (error) {
|
|
// Skip unreadable files
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
scan(dir);
|
|
return files;
|
|
}
|
|
/**
|
|
* Mock embedding (replace with actual embedding service)
|
|
*/
|
|
function mockEmbedding(text) {
|
|
const hash = text.split('').reduce((acc, char) => {
|
|
return ((acc << 5) - acc) + char.charCodeAt(0);
|
|
}, 0);
|
|
const embedding = new Array(3072);
|
|
for (let i = 0; i < 3072; i++) {
|
|
const seed = hash + i;
|
|
embedding[i] = (Math.sin(seed) * 10000) - Math.floor(Math.sin(seed) * 10000);
|
|
}
|
|
return embedding;
|
|
}
|
|
//# sourceMappingURL=sona-train.js.map
|