prompt-evolution.ts•38.2 kB
/**
* Evolution Engine - Orchestrates the genetic evolutionary prompt adaptation process
*
* This class coordinates the entire evolution process including:
* - Population management and candidate lifecycle tracking
* - Generation cycles with mutation and selection
* - Convergence detection and early stopping
* - Integration with ParetoFrontier, ReflectionEngine, and PromptMutator
* - Fault tolerance and recovery mechanisms
* - Evolution history tracking and metrics collection
*/
import type {
EvolutionConfig,
EvolutionResult,
PromptCandidate,
ExecutionTrajectory,
TaskContext,
StartEvolutionParams
} from '../types/gepa';
import type { ParetoFrontier, ConvergenceMetrics } from './pareto-frontier';
import type { ReflectionEngine } from './reflection-engine';
import type { PromptMutator } from '../services/prompt-mutator';
import type { LLMAdapter } from '../services/llm-adapter';
import type { TrajectoryStore } from './trajectory-store';
// Evolution Engine Dependencies Interface
export interface EvolutionEngineDependencies {
llmAdapter: LLMAdapter;
paretoFrontier: ParetoFrontier;
reflectionEngine: ReflectionEngine;
promptMutator: PromptMutator;
trajectoryStore: TrajectoryStore;
config: Partial<EvolutionConfig>;
}
// Evolution Metrics Interface
export interface EvolutionMetrics {
totalGenerations: number;
totalRollouts: number;
bestScore: number;
averageScore: number;
convergenceMetrics: ConvergenceMetrics;
diversityTrend: number[];
improvementRate: number;
stagnationCount: number;
}
// Evaluation Cache Entry
interface EvaluationCacheEntry {
score: number;
timestamp: number;
trajectory?: ExecutionTrajectory;
}
// Generation Statistics
interface GenerationStats {
averageFitness: number;
bestFitness: number;
diversityScore: number;
mutationSuccessRate: number;
evaluationTime: number;
}
export class EvolutionEngine {
public readonly config: Required<EvolutionConfig>;
private readonly llmAdapter: LLMAdapter;
private readonly paretoFrontier: ParetoFrontier;
private readonly reflectionEngine: ReflectionEngine;
private readonly promptMutator: PromptMutator;
private readonly trajectoryStore: TrajectoryStore;
// State management
private evaluationCache = new Map<string, EvaluationCacheEntry>();
private currentEvolutionId?: string;
private isShutdown = false;
private activeConcurrentEvaluations = 0;
private readonly maxConcurrentEvaluations = 5;
// Metrics tracking
private generationStats: GenerationStats[] = [];
private stagnationCounter = 0;
private lastBestScore = 0;
private readonly stagnationThreshold = 3;
private readonly targetScore = 0.95;
private readonly improvementThreshold = 0.01;
constructor(dependencies: EvolutionEngineDependencies) {
this.validateDependencies(dependencies);
this.llmAdapter = dependencies.llmAdapter;
this.paretoFrontier = dependencies.paretoFrontier;
this.reflectionEngine = dependencies.reflectionEngine;
this.promptMutator = dependencies.promptMutator;
this.trajectoryStore = dependencies.trajectoryStore;
// Set default configuration with proper typing
this.config = {
taskDescription: dependencies.config.taskDescription || '',
seedPrompt: dependencies.config.seedPrompt || '',
targetModules: dependencies.config.targetModules || [],
maxGenerations: dependencies.config.maxGenerations ?? 10,
populationSize: dependencies.config.populationSize ?? 20,
mutationRate: dependencies.config.mutationRate ?? 0.4
};
this.validateConfig();
}
/**
* Validate required dependencies
*/
private validateDependencies(dependencies: EvolutionEngineDependencies): void {
if (!dependencies.llmAdapter) {
throw new Error('LLM adapter is required');
}
if (!dependencies.paretoFrontier) {
throw new Error('Pareto frontier is required');
}
if (!dependencies.reflectionEngine) {
throw new Error('Reflection engine is required');
}
if (!dependencies.promptMutator) {
throw new Error('Prompt mutator is required');
}
if (!dependencies.trajectoryStore) {
throw new Error('Trajectory store is required');
}
if (!dependencies.config) {
throw new Error('Configuration is required');
}
}
/**
* Validate evolution configuration
*/
private validateConfig(): void {
if (!this.config.taskDescription || this.config.taskDescription.trim() === '') {
throw new Error('Task description is required');
}
if (this.config.maxGenerations <= 0) {
throw new Error('Max generations must be positive');
}
if (this.config.populationSize <= 0) {
throw new Error('Population size must be positive');
}
if (this.config.mutationRate < 0 || this.config.mutationRate > 1) {
throw new Error('Mutation rate must be between 0 and 1');
}
}
/**
* Start the complete evolution process
*/
async startEvolution(params: StartEvolutionParams): Promise<EvolutionResult> {
// Validate parameters
this.validateEvolutionParams(params);
// Generate unique evolution ID
this.currentEvolutionId = this.generateEvolutionId();
// Create task context
const taskContext = this.createTaskContext(params);
// Initialize population
const initialPopulation = await this.initializePopulation(params);
if (initialPopulation.length === 0) {
throw new Error('Failed to initialize population');
}
// Evolution history tracking
const evolutionHistory: PromptCandidate[][] = [];
let currentPopulation = initialPopulation;
let generation = 0;
let convergenceAchieved = false;
let totalRollouts = 0;
// Reset evolution state
this.generationStats = [];
this.stagnationCounter = 0;
this.lastBestScore = 0;
try {
// Evolution loop
while (generation < this.config.maxGenerations && !convergenceAchieved && !this.isShutdown) {
generation++;
// Run generation cycle
const newGeneration = await this.runGeneration(currentPopulation, taskContext, generation);
// Update evolution history
this.updateEvolutionHistory(evolutionHistory, newGeneration);
// Check for convergence
const convergenceMetrics = this.paretoFrontier.getConvergenceMetrics();
convergenceAchieved = this.detectConvergence(convergenceMetrics, generation);
// Check early stopping conditions
const shouldStop = await this.checkEarlyStoppingConditions(newGeneration);
if (shouldStop) {
break;
}
// Update population for next generation
currentPopulation = newGeneration;
totalRollouts += newGeneration.reduce((sum, candidate) => sum + candidate.rolloutCount, 0);
// Store generation metrics
await this.storeGenerationMetrics(generation, newGeneration, convergenceMetrics);
}
// Get best candidate from final population
const bestPrompt = this.getBestCandidate(currentPopulation);
// Compile evolution result
const result: EvolutionResult = {
evolutionId: this.currentEvolutionId,
taskDescription: params.taskDescription,
generations: generation,
bestPrompt,
convergenceAchieved,
totalRollouts,
evolutionHistory
};
// Store final evolution result
await this.storeEvolutionResult(result);
return result;
} catch (error: any) {
// Handle evolution failures gracefully
const fallbackResult = await this.createFallbackResult(params, currentPopulation, generation, evolutionHistory);
throw new Error(`Evolution failed: ${error.message}. Fallback result: ${fallbackResult.evolutionId}`);
}
}
/**
* Run a single generation cycle
*/
async runGeneration(
population: PromptCandidate[],
taskContext: TaskContext,
generation: number
): Promise<PromptCandidate[]> {
const startTime = Date.now();
try {
// Generate new candidates through mutation
const newCandidates = await this.generateCandidates(population, taskContext);
// Combine with existing population
const allCandidates = [...population, ...newCandidates];
// Evaluate all candidates
await this.evaluatePopulation(allCandidates, taskContext);
// Select survivors for next generation
const survivors = await this.selectSurvivors(allCandidates, this.config.populationSize);
// Update generation statistics
const stats: GenerationStats = {
averageFitness: survivors.reduce((sum, c) => sum + c.averageScore, 0) / survivors.length,
bestFitness: Math.max(...survivors.map(c => c.averageScore)),
diversityScore: this.calculateDiversityScore(survivors),
mutationSuccessRate: newCandidates.length / (population.length * this.config.mutationRate),
evaluationTime: Date.now() - startTime
};
this.generationStats.push(stats);
// Check for improvement and update stagnation counter
this.updateStagnationCounter(stats.bestFitness);
return survivors;
} catch (error: any) {
// Handle generation failures with fallback
// eslint-disable-next-line no-console
console.warn(`Generation ${generation} failed: ${error.message}. Using fallback selection.`);
return this.createFallbackGeneration(population);
}
}
/**
* Generate new candidate mutations from current population (OPTIMIZED: Parallel processing)
*/
async generateCandidates(population: PromptCandidate[], taskContext: TaskContext): Promise<PromptCandidate[]> {
if (population.length === 0) {
return [];
}
const maxCandidates = Math.min(this.config.populationSize, population.length * 3);
const candidatePool = new CandidatePool(maxCandidates);
try {
// OPTIMIZATION: Get trajectories once and reuse
const trajectories = await this.getRecentTrajectories(population);
const trajectoryMap = new Map<string, ExecutionTrajectory[]>();
trajectories.forEach(t => {
if (!trajectoryMap.has(t.promptId)) {
trajectoryMap.set(t.promptId, []);
}
const trajectories = trajectoryMap.get(t.promptId);
if (trajectories) {
trajectories.push(t);
}
});
// OPTIMIZATION: Parallel mutation generation with limits
const mutationPromises: Promise<PromptCandidate[]>[] = [];
// Reflective mutations (top 5 candidates)
const topCandidates = population.slice(0, Math.min(5, population.length));
mutationPromises.push(
this.generateReflectiveMutationsBatch(topCandidates, trajectoryMap, maxCandidates / 3)
);
// Crossover mutations (if enough candidates)
if (population.length >= 2) {
mutationPromises.push(
this.generateCrossoverMutationsBatch(population.slice(0, 10), maxCandidates / 3)
);
}
// Adaptive mutations (top 3 candidates)
const adaptiveCandidates = population.slice(0, Math.min(3, population.length));
mutationPromises.push(
this.generateAdaptiveMutationsBatch(adaptiveCandidates, taskContext, maxCandidates / 3)
);
// OPTIMIZATION: Wait for all mutation types to complete
const mutationResults = await Promise.allSettled(mutationPromises);
// Collect successful results
mutationResults.forEach(result => {
if (result.status === 'fulfilled') {
candidatePool.addCandidates(result.value);
} else {
// eslint-disable-next-line no-console
console.warn(`Mutation batch failed: ${result.reason}`);
}
});
return candidatePool.getCandidates();
} catch (error: any) {
// eslint-disable-next-line no-console
console.warn(`Failed to generate candidates: ${error.message}`);
return [];
}
}
/**
* Evaluate a single candidate's performance
*/
async evaluateCandidate(candidate: PromptCandidate, taskContext: TaskContext): Promise<number> {
// Check cache first
const cacheKey = this.createCacheKey(candidate, taskContext);
const cached = this.evaluationCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < 3600000) { // 1 hour cache
return cached.score;
}
// Wait for available evaluation slot
while (this.activeConcurrentEvaluations >= this.maxConcurrentEvaluations) {
await this.sleep(100);
}
this.activeConcurrentEvaluations++;
try {
// Evaluate with retry logic
const result = await this.evaluateWithRetry(candidate.content, taskContext, 3);
// Update candidate metrics
candidate.taskPerformance.set(taskContext.description, result.score);
candidate.averageScore = this.calculateAverageScore(candidate);
candidate.rolloutCount++;
candidate.lastEvaluated = new Date();
// Cache result
this.evaluationCache.set(cacheKey, {
score: result.score,
timestamp: Date.now(),
...(result.trajectory ? { trajectory: result.trajectory } : {})
});
// Store trajectory if available
if (result.trajectory) {
try {
await this.trajectoryStore.save(result.trajectory);
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to store trajectory: ${error}`);
}
}
return result.score;
} catch (error: any) {
// eslint-disable-next-line no-console
console.warn(`Failed to evaluate candidate ${candidate.id}: ${error.message}`);
return 0; // Return 0 on evaluation failure
} finally {
this.activeConcurrentEvaluations--;
}
}
/**
* Evaluate with exponential backoff retry
*/
private async evaluateWithRetry(
prompt: string,
taskContext: TaskContext,
maxRetries: number
): Promise<{ score: number; trajectory?: ExecutionTrajectory; metrics?: any }> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await this.llmAdapter.evaluatePrompt(prompt, taskContext);
} catch (error: any) {
lastError = error;
if (attempt < maxRetries - 1) {
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
await this.sleep(delay);
}
}
}
throw lastError || new Error('Evaluation failed after retries');
}
/**
* Evaluate entire population with concurrency control (OPTIMIZED: Adaptive batching)
*/
private async evaluatePopulation(candidates: PromptCandidate[], taskContext: TaskContext): Promise<void> {
// OPTIMIZATION: Adaptive batch sizing based on system resources
const optimalBatchSize = Math.min(
this.maxConcurrentEvaluations,
Math.max(2, Math.floor(candidates.length / 4)) // Dynamic sizing
);
// OPTIMIZATION: Priority-based evaluation (evaluate best candidates first)
const sortedCandidates = [...candidates].sort((a, b) => b.averageScore - a.averageScore);
for (let i = 0; i < sortedCandidates.length; i += optimalBatchSize) {
const batch = sortedCandidates.slice(i, i + optimalBatchSize);
// OPTIMIZATION: Use Promise.allSettled to continue on partial failures
const evaluationResults = await Promise.allSettled(
batch.map(candidate => this.evaluateCandidate(candidate, taskContext))
);
// Log failed evaluations without stopping the process
evaluationResults.forEach((result, index) => {
if (result.status === 'rejected') {
const candidate = batch[index];
const candidateId = candidate?.id || 'unknown';
// eslint-disable-next-line no-console
console.warn(`Evaluation failed for candidate ${candidateId}: ${result.reason}`);
}
});
}
}
/**
* Select survivors for next generation using Pareto frontier
*/
async selectSurvivors(candidates: PromptCandidate[], targetSize: number): Promise<PromptCandidate[]> {
// Add all candidates to Pareto frontier
for (const candidate of candidates) {
try {
await this.paretoFrontier.addCandidate(candidate);
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to add candidate to frontier: ${error}`);
}
}
// Get current frontier
const frontierPoints = this.paretoFrontier.getFrontier();
const frontierCandidates = frontierPoints.map(point => point.candidate);
// If we have enough candidates from frontier, use them
if (frontierCandidates.length >= targetSize) {
return frontierCandidates.slice(0, targetSize);
}
// Otherwise, supplement with best performing candidates
const remainingCandidates = candidates
.filter(c => !frontierCandidates.some(fc => fc.id === c.id))
.sort((a, b) => b.averageScore - a.averageScore);
const survivors = [
...frontierCandidates,
...remainingCandidates.slice(0, targetSize - frontierCandidates.length)
];
return survivors.slice(0, targetSize);
}
/**
* Detect convergence based on diversity and performance metrics
*/
detectConvergence(metrics: ConvergenceMetrics, generation: number): boolean {
// Require minimum generations before allowing convergence
if (generation < 3) {
return false;
}
// Check multiple convergence criteria
const diversityConverged = metrics.diversity < 0.05;
const spacingConverged = metrics.spacing < 0.05;
const spreadConverged = metrics.spread < 0.05;
const hypervolumeStable = metrics.hypervolume > 0.9;
// Consider converged if multiple criteria are met
const convergenceCriteriaMet = [
diversityConverged,
spacingConverged,
spreadConverged,
hypervolumeStable
].filter(Boolean).length >= 3;
return convergenceCriteriaMet;
}
/**
* Check early stopping conditions
*/
private async checkEarlyStoppingConditions(
generation: PromptCandidate[]
): Promise<boolean> {
const bestCandidate = this.getBestCandidate(generation);
// Stop if target score reached
if (bestCandidate.averageScore >= this.targetScore) {
return true;
}
// Stop if stagnation detected
if (this.stagnationCounter >= this.stagnationThreshold) {
return true;
}
// Stop if shutdown requested
if (this.isShutdown) {
return true;
}
return false;
}
/**
* Update evolution history with memory management
*/
updateEvolutionHistory(
history: PromptCandidate[][],
generation: PromptCandidate[]
): void {
history.push([...generation]);
// Trim history to prevent memory issues (keep last 50 generations)
if (history.length > 50) {
history.splice(0, history.length - 50);
}
}
/**
* Get best candidate from population
*/
getBestCandidate(candidates: PromptCandidate[]): PromptCandidate {
if (candidates.length === 0) {
throw new Error('No candidates available');
}
return candidates.reduce((best, current) =>
current.averageScore > best.averageScore ? current : best
);
}
/**
* Get comprehensive evolution metrics
*/
getEvolutionMetrics(history: PromptCandidate[][], totalRollouts: number): EvolutionMetrics {
if (history.length === 0) {
return {
totalGenerations: 0,
totalRollouts: 0,
bestScore: 0,
averageScore: 0,
convergenceMetrics: this.paretoFrontier.getConvergenceMetrics(),
diversityTrend: [],
improvementRate: 0,
stagnationCount: this.stagnationCounter
};
}
const allCandidates = history.flat();
const scores = allCandidates.map(c => c.averageScore);
const bestScore = Math.max(...scores);
const averageScore = scores.reduce((sum, score) => sum + score, 0) / scores.length;
const diversityTrend = this.generationStats.map(stats => stats.diversityScore);
const improvementRate = this.calculateImprovementRate();
return {
totalGenerations: history.length,
totalRollouts,
bestScore,
averageScore,
convergenceMetrics: this.paretoFrontier.getConvergenceMetrics(),
diversityTrend,
improvementRate,
stagnationCount: this.stagnationCounter
};
}
/**
* Shutdown evolution engine and cleanup resources
*/
async shutdown(): Promise<void> {
this.isShutdown = true;
// Wait for ongoing evaluations to complete
while (this.activeConcurrentEvaluations > 0) {
await this.sleep(100);
}
// Shutdown dependencies
await this.llmAdapter.shutdown();
this.promptMutator.shutdown();
// Clear caches
this.evaluationCache.clear();
this.generationStats = [];
}
// Private helper methods
/**
* Validate evolution parameters
*/
private validateEvolutionParams(params: StartEvolutionParams): void {
if (!params.taskDescription || params.taskDescription.trim() === '') {
throw new Error('Task description is required');
}
if (params.config) {
if (params.config.maxGenerations !== undefined && params.config.maxGenerations <= 0) {
throw new Error('Max generations must be positive');
}
if (params.config.populationSize !== undefined && params.config.populationSize <= 0) {
throw new Error('Population size must be positive');
}
if (params.config.mutationRate !== undefined &&
(params.config.mutationRate < 0 || params.config.mutationRate > 1)) {
throw new Error('Mutation rate must be between 0 and 1');
}
}
}
/**
* Generate unique evolution ID
*/
private generateEvolutionId(): string {
return `evolution-${Date.now()}-${Math.random().toString(36).substring(2)}`;
}
/**
* Create task context from parameters
*/
private createTaskContext(params: StartEvolutionParams): TaskContext {
return {
taskId: this.generateEvolutionId(),
description: params.taskDescription,
category: 'prompt-evolution',
difficulty: 'medium',
requiredCapabilities: params.targetModules || ['reasoning'],
expectedDuration: 60
};
}
/**
* Initialize population with seed prompt
*/
private async initializePopulation(params: StartEvolutionParams): Promise<PromptCandidate[]> {
const seedPrompt = params.seedPrompt || this.config.seedPrompt || 'You are a helpful assistant.';
const seedCandidate: PromptCandidate = {
id: this.generateCandidateId(),
content: seedPrompt,
generation: 0,
taskPerformance: new Map(),
averageScore: 0,
rolloutCount: 0,
createdAt: new Date(),
lastEvaluated: new Date(),
mutationType: 'initial'
};
return [seedCandidate];
}
/**
* Get recent trajectories for reflection analysis (OPTIMIZED: Batch loading)
*/
private async getRecentTrajectories(population: PromptCandidate[]): Promise<ExecutionTrajectory[]> {
try {
const promptIds = population.map(p => p.id).slice(0, 5); // Limit to prevent performance issues
// OPTIMIZATION: Batch parallel loading instead of sequential
const trajectoryPromises = promptIds.map(async promptId => {
try {
return await this.trajectoryStore.query({
promptId,
limit: 10
});
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to query trajectories for prompt ${promptId}: ${error}`);
return [];
}
});
const trajectoryBatches = await Promise.all(trajectoryPromises);
return trajectoryBatches.flat();
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to get recent trajectories: ${error}`);
return [];
}
}
/**
* Create cache key for evaluation results
*/
private createCacheKey(candidate: PromptCandidate, taskContext: TaskContext): string {
const contentHash = this.hashString(candidate.content);
const contextHash = this.hashString(JSON.stringify(taskContext));
return `${contentHash}-${contextHash}`;
}
/**
* Calculate average score across all tasks
*/
private calculateAverageScore(candidate: PromptCandidate): number {
if (candidate.taskPerformance.size === 0) {
return 0;
}
const scores = Array.from(candidate.taskPerformance.values());
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
}
/**
* Calculate diversity score for population
*/
private calculateDiversityScore(population: PromptCandidate[]): number {
if (population.length < 2) {
return 0;
}
// Use genetic diversity calculation from PromptMutator
return this.promptMutator.calculateGeneticDiversity(population);
}
/**
* Update stagnation counter based on improvement
*/
private updateStagnationCounter(currentBestScore: number): void {
const improvement = currentBestScore - this.lastBestScore;
if (improvement < this.improvementThreshold) {
this.stagnationCounter++;
} else {
this.stagnationCounter = 0;
}
this.lastBestScore = currentBestScore;
}
/**
* Calculate improvement rate over recent generations
*/
private calculateImprovementRate(): number {
if (this.generationStats.length < 2) {
return 0;
}
const recentStats = this.generationStats.slice(-5); // Last 5 generations
const firstStat = recentStats[0];
const lastStat = recentStats[recentStats.length - 1];
if (!firstStat || !lastStat) return 0;
const firstScore = firstStat.bestFitness;
const lastScore = lastStat.bestFitness;
return (lastScore - firstScore) / recentStats.length;
}
/**
* Create fallback generation in case of failures
*/
private createFallbackGeneration(population: PromptCandidate[]): PromptCandidate[] {
// Return best performers from current population
return population
.sort((a, b) => b.averageScore - a.averageScore)
.slice(0, Math.min(this.config.populationSize, population.length));
}
/**
* Create fallback result for failed evolution
*/
private async createFallbackResult(
params: StartEvolutionParams,
currentPopulation: PromptCandidate[],
generation: number,
history: PromptCandidate[][]
): Promise<EvolutionResult> {
const bestPrompt = currentPopulation.length > 0
? this.getBestCandidate(currentPopulation)
: await this.createEmergencyCandidate(params);
return {
evolutionId: this.currentEvolutionId || this.generateEvolutionId(),
taskDescription: params.taskDescription,
generations: generation,
bestPrompt,
convergenceAchieved: false,
totalRollouts: currentPopulation.reduce((sum, c) => sum + c.rolloutCount, 0),
evolutionHistory: history
};
}
/**
* Create emergency candidate when all else fails
*/
private async createEmergencyCandidate(params: StartEvolutionParams): Promise<PromptCandidate> {
return {
id: this.generateCandidateId(),
content: params.seedPrompt || 'You are a helpful assistant.',
generation: 0,
taskPerformance: new Map(),
averageScore: 0,
rolloutCount: 0,
createdAt: new Date(),
lastEvaluated: new Date(),
mutationType: 'initial'
};
}
/**
* Store generation metrics for analysis
*/
private async storeGenerationMetrics(
generation: number,
population: PromptCandidate[],
convergenceMetrics: ConvergenceMetrics
): Promise<void> {
try {
const metrics = {
evolutionId: this.currentEvolutionId,
generation,
populationSize: population.length,
averageScore: population.reduce((sum, c) => sum + c.averageScore, 0) / population.length,
bestScore: Math.max(...population.map(c => c.averageScore)),
convergenceMetrics,
timestamp: new Date()
};
// Store metrics (implementation would depend on storage system)
// await this.metricsStore.store(metrics);
console.log(`Generation ${generation} metrics:`, metrics);
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to store generation metrics: ${error}`);
}
}
/**
* Store final evolution result
*/
private async storeEvolutionResult(result: EvolutionResult): Promise<void> {
try {
// Store evolution result (implementation would depend on storage system)
// await this.evolutionStore.store(result);
console.log('Evolution result stored:', result.evolutionId);
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to store evolution result: ${error}`);
}
}
/**
* Generate unique candidate ID
*/
private generateCandidateId(): string {
return `candidate-${Date.now()}-${Math.random().toString(36).substring(2)}`;
}
/**
* Simple string hash function
*/
private hashString(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash.toString();
}
/**
* Sleep utility for concurrency control
*/
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Generate reflective mutations with batch processing
*/
private async generateReflectiveMutationsBatch(
candidates: PromptCandidate[],
trajectoryMap: Map<string, ExecutionTrajectory[]>,
maxCandidates: number
): Promise<PromptCandidate[]> {
const mutations: PromptCandidate[] = [];
for (const candidate of candidates.slice(0, Math.min(5, candidates.length))) {
try {
const trajectories = trajectoryMap.get(candidate.id) || [];
if (trajectories.length === 0) continue;
// Analyze failed trajectories for this candidate
const failedTrajectories = trajectories.filter(t => !t.finalResult.success);
if (failedTrajectories.length === 0) continue;
// Get reflection analysis
const failedTrajectory = failedTrajectories[0];
if (!failedTrajectory) continue;
const analysis = await this.reflectionEngine.analyzeTrajectory(failedTrajectory);
// Generate mutations based on suggestions
for (const suggestion of analysis.suggestions.slice(0, 2)) {
const mutatedPrompt = await this.applyImprovement(candidate.content, suggestion);
const mutatedCandidate = this.createMutatedCandidate(
candidate,
mutatedPrompt,
'reflective'
);
mutations.push(mutatedCandidate);
if (mutations.length >= maxCandidates) break;
}
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to generate reflective mutation for candidate ${candidate.id}:`, error);
}
if (mutations.length >= maxCandidates) break;
}
return mutations;
}
/**
* Generate crossover mutations with batch processing
*/
private async generateCrossoverMutationsBatch(
candidates: PromptCandidate[],
maxCandidates: number
): Promise<PromptCandidate[]> {
const mutations: PromptCandidate[] = [];
// Simple crossover: combine best parts of different candidates
for (let i = 0; i < candidates.length && mutations.length < maxCandidates; i++) {
for (let j = i + 1; j < candidates.length && mutations.length < maxCandidates; j++) {
try {
const parent1 = candidates[i];
const parent2 = candidates[j];
if (!parent1 || !parent2) {
continue;
}
// Simple crossover by combining prompts
const crossoverPrompt = this.performCrossover(parent1.content, parent2.content);
const crossoverCandidate = this.createMutatedCandidate(
parent1,
crossoverPrompt,
'crossover'
);
mutations.push(crossoverCandidate);
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to generate crossover mutation:`, error);
}
}
}
return mutations;
}
/**
* Generate adaptive mutations with batch processing
*/
private async generateAdaptiveMutationsBatch(
candidates: PromptCandidate[],
taskContext: TaskContext,
maxCandidates: number
): Promise<PromptCandidate[]> {
const mutations: PromptCandidate[] = [];
for (const candidate of candidates.slice(0, Math.min(3, candidates.length))) {
try {
// Generate adaptive variations
const adaptivePrompt = await this.generateAdaptiveVariation(candidate.content, taskContext);
const adaptiveCandidate = this.createMutatedCandidate(
candidate,
adaptivePrompt,
'adaptive'
);
mutations.push(adaptiveCandidate);
if (mutations.length >= maxCandidates) break;
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to generate adaptive mutation for candidate ${candidate.id}:`, error);
}
}
return mutations;
}
/**
* Apply improvement suggestion to prompt
*/
private async applyImprovement(prompt: string, improvement: any): Promise<string> {
try {
return await this.llmAdapter.generateMutation(prompt, improvement);
} catch (error) {
// eslint-disable-next-line no-console
console.warn('Failed to apply improvement via LLM, using fallback:', error);
return this.applyImprovementFallback(prompt, improvement);
}
}
/**
* Fallback improvement application
*/
private applyImprovementFallback(prompt: string, improvement: any): string {
let modifiedPrompt = prompt;
switch (improvement.type) {
case 'add_instruction':
modifiedPrompt += `\n\nAdditional instruction: ${improvement.proposedChange}`;
break;
case 'clarify_step':
modifiedPrompt += `\n\nClarification: ${improvement.proposedChange}`;
break;
case 'add_example':
modifiedPrompt += `\n\nExample: ${improvement.proposedChange}`;
break;
case 'add_constraint':
modifiedPrompt += `\n\nConstraint: ${improvement.proposedChange}`;
break;
default:
modifiedPrompt += `\n\nImprovement: ${improvement.proposedChange}`;
}
return modifiedPrompt;
}
/**
* Perform crossover between two prompts
*/
private performCrossover(prompt1: string, prompt2: string): string {
// Simple crossover: take first half of prompt1 and second half of prompt2
const lines1 = prompt1.split('\n');
const lines2 = prompt2.split('\n');
const midpoint1 = Math.floor(lines1.length / 2);
const midpoint2 = Math.floor(lines2.length / 2);
const firstHalf = lines1.slice(0, midpoint1);
const secondHalf = lines2.slice(midpoint2);
return [...firstHalf, ...secondHalf].join('\n');
}
/**
* Generate adaptive variation based on task context
*/
private async generateAdaptiveVariation(prompt: string, taskContext: TaskContext): Promise<string> {
// Add task-specific adaptations
let adaptedPrompt = prompt;
if (taskContext.requiredCapabilities.includes('reasoning')) {
adaptedPrompt += '\n\nUse step-by-step reasoning to solve this problem.';
}
if (taskContext.difficulty === 'hard') {
adaptedPrompt += '\n\nThis is a complex problem. Take your time and think carefully.';
}
if (taskContext.expectedDuration > 120) {
adaptedPrompt += '\n\nThis may require multiple steps. Plan your approach first.';
}
return adaptedPrompt;
}
/**
* Create mutated candidate from parent
*/
private createMutatedCandidate(
parent: PromptCandidate,
newContent: string,
mutationType: string
): PromptCandidate {
return {
id: this.generateCandidateId(),
content: newContent,
generation: parent.generation + 1,
taskPerformance: new Map(),
averageScore: 0,
rolloutCount: 0,
createdAt: new Date(),
lastEvaluated: new Date(),
mutationType: mutationType as 'initial' | 'reflection' | 'crossover' | 'random'
};
}
}
/**
* Candidate Pool for managing generation candidates
*/
class CandidatePool {
private candidates: PromptCandidate[] = [];
private readonly maxSize: number;
constructor(maxSize: number) {
this.maxSize = maxSize;
}
addCandidates(newCandidates: PromptCandidate[]): void {
this.candidates.push(...newCandidates);
// Deduplicate by content
const seen = new Set<string>();
this.candidates = this.candidates.filter(candidate => {
const key = this.getContentHash(candidate.content);
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
// Limit size
if (this.candidates.length > this.maxSize) {
this.candidates = this.candidates.slice(0, this.maxSize);
}
}
getCandidates(): PromptCandidate[] {
return [...this.candidates];
}
private getContentHash(content: string): string {
let hash = 0;
for (let i = 0; i < content.length; i++) {
const char = content.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash.toString(36);
}
}