crewai-ts
Version:
TypeScript port of crewAI for agent-based workflows
160 lines (159 loc) • 8.59 kB
JavaScript
import { Command } from '../Command.js';
/**
* Test crew command optimized for memory efficiency and type safety
* Implements proper null checking and string validation
*/
export class TestCrewCommand extends Command {
name = 'test-crew';
description = 'Test the crew and evaluate the results';
syntax = '[options]';
examples = [
'test-crew',
'test-crew --iterations 3',
'test-crew -n 5 --model gpt-4'
];
async execute(args) {
const parsedArgs = this.parseArgs(args);
const startTime = Date.now();
try {
// Dynamically import required modules only when needed (optimization)
const { default: chalk } = await import('chalk');
const { default: ora } = await import('ora');
// Show spinner for better UX
// Ensure model is always a valid string for type safety
const safeModel = parsedArgs.model || 'gpt-3.5-turbo';
const spinner = ora(`Testing crew for ${parsedArgs.iterations} iterations with model ${safeModel}...`).start();
// Import the crew evaluation service with proper type handling
// Use type assertion to ensure TypeScript recognizes the module
const evaluationServiceModule = await import('../../services/CrewEvaluationService.js');
const { CrewEvaluationService } = evaluationServiceModule;
spinner.text = 'Initializing evaluation service...';
const evaluationService = new CrewEvaluationService();
// Setup progress tracking with optimized memory usage
let currentIteration = 0;
const iterations = parsedArgs.iterations;
// Use a typed progress interval with minimal allocations
const progressInterval = setInterval(() => {
spinner.text = `Testing crew... ${currentIteration}/${iterations} iterations`;
}, 1000);
// Track execution progress with memory-efficient callback implementation
evaluationService.onIterationComplete((iteration) => {
// Update without creating new objects or closures
currentIteration = iteration;
});
// Execute the tests with memory-efficient metrics collection
// Create type-safe evaluation options to prevent undefined values
// Ensure model is never undefined with proper string safety
// Explicit typing to optimize memory usage
const evaluationOptions = {
iterations: parsedArgs.iterations,
model: parsedArgs.model || 'gpt-3.5-turbo', // Use null coalescing for type safety
verbose: !!parsedArgs.verbose // Convert to boolean for type safety
};
const results = await evaluationService.evaluate(evaluationOptions);
// Clean up progress tracking
clearInterval(progressInterval);
// Operation completed successfully
const executionTime = ((Date.now() - startTime) / 1000).toFixed(2);
spinner.succeed(`Testing completed in ${executionTime}s`);
// Display results with memory-efficient formatting that minimizes string operations
console.log('\nTest Results:\n');
// Overall metrics
console.log(chalk.bold('Overall Performance:'));
console.log(`- Success Rate: ${(results.successRate * 100).toFixed(1)}%`);
console.log(`- Average Task Completion Time: ${results.averageTaskTimeMs.toFixed(0)}ms`);
console.log(`- Total Tasks Executed: ${results.totalTasks}`);
// Display agent performance if available
if (results.agentPerformance && results.agentPerformance.length > 0) {
console.log('\n' + chalk.bold('Agent Performance:'));
// Efficient memory usage by avoiding large array creation and using direct iteration
// Define the agent type for proper type checking
results.agentPerformance.forEach((agent) => {
console.log(`- ${chalk.cyan(agent.name)}: ${(agent.successRate * 100).toFixed(1)}% success, ${agent.completedTasks} tasks`);
});
}
// Display more detailed results if verbose
if (parsedArgs.verbose && results.taskResults) {
console.log('\n' + chalk.bold('Detailed Task Results:'));
// Print in a memory-efficient way without creating large intermediate data structures
const taskCount = Math.min(results.taskResults.length, 5); // Limit to 5 tasks to avoid console spam
for (let i = 0; i < taskCount; i++) {
const task = results.taskResults[i];
console.log(`\nTask ${i + 1}: ${task.name}`);
console.log(`- Success: ${task.success ? chalk.green('Yes') : chalk.red('No')}`);
console.log(`- Time: ${task.executionTimeMs.toFixed(0)}ms`);
// Only show a truncated version of the output to save memory
if (task.output && typeof task.output === 'string') {
const maxLength = 100;
const output = task.output.length > maxLength ?
task.output.substring(0, maxLength) + '...' :
task.output;
console.log(`- Output: ${output}`);
}
}
if (results.taskResults.length > 5) {
console.log(`\n...and ${results.taskResults.length - 5} more tasks`);
}
}
}
catch (error) {
console.error('Error during crew testing:', error);
process.exit(1);
}
}
/**
* Parse command line arguments for the test-crew command
* Optimized for handling various argument formats
*/
parseArgs(args) {
// Default values with proper type annotations for memory efficiency
const result = {
iterations: 3,
model: 'gpt-3.5-turbo',
verbose: false
};
// Parse options with optimized pattern matching
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if ((arg === '-n' || arg === '--iterations') && i + 1 < args.length) {
// Memory-optimized string parsing with type safety
const index = ++i;
if (index < args.length) {
const iterArg = args[index];
// Avoid unnecessary parse operations by doing type checking first
if (typeof iterArg === 'string') {
// Convert only once and store result to avoid repeated conversions
const iterations = parseInt(iterArg.trim(), 10);
// Validate parsed value before assignment
if (!isNaN(iterations) && iterations > 0 && iterations < 1000) { // Add upper limit for safety
result.iterations = iterations;
}
}
}
}
else if ((arg === '-m' || arg === '--model') && i + 1 < args.length) {
// Enhanced string safety with comprehensive null/undefined checks for memory optimization
const index = ++i;
if (index < args.length) {
const modelArg = args[index];
// Triple validation for maximum type safety with memory optimization
// 1. Check if string type
// 2. Verify non-empty after trimming (avoid spaces-only strings)
// 3. Check minimum length for valid model names
if (typeof modelArg === 'string' && modelArg.trim().length > 0) {
const trimmedModel = modelArg.trim();
// Only assign if it passes all validations - avoid unnecessary assignments
if (trimmedModel.length >= 3) { // Most model names are at least 3 chars
result.model = trimmedModel;
}
}
// Default is already set in the result object initialization
}
}
else if (arg === '--verbose' || arg === '-v') {
result.verbose = true;
}
}
return result;
}
}