Spaces:
Sleeping
Sleeping
| """ | |
| Fitness-Guided Crossover Operator. | |
| Adapts LLEGO's fitness-guided crossover for text prompts. | |
| Based on: Decision Tree Induction Through LLMs via Semantically-Aware Evolution (ICLR 2025) | |
| """ | |
| from typing import List, Callable, TYPE_CHECKING | |
| import logging | |
| from .base_operator import BaseCrossoverOperator | |
| if TYPE_CHECKING: | |
| from .models import PromptCandidate | |
| logger = logging.getLogger(__name__) | |
| class FitnessGuidedCrossover(BaseCrossoverOperator): | |
| """ | |
| Fitness-guided crossover for text prompts. | |
| Combines high-performing parent prompts to generate offspring | |
| that target specific fitness levels using LLM semantic understanding. | |
| From LLEGO paper: | |
| "Fitness-guided crossover exploits high-performing regions of the search space | |
| by combining parent trees targeting a desired fitness level f* = f_max + α(f_max - f_min)" | |
| Reference: https://github.com/nicolashuynh/LLEGO | |
| """ | |
| def __init__(self, alpha: float = 0.1): | |
| """ | |
| Initialize crossover operator. | |
| Args: | |
| alpha: Fitness extrapolation parameter. | |
| Higher α = target higher fitness than parents. | |
| Default 0.1 from LLEGO paper (target 10% above best parent). | |
| """ | |
| self.alpha = alpha | |
| logger.debug(f"FitnessGuidedCrossover initialized with α={alpha}") | |
| def __call__( | |
| self, | |
| parents: List["PromptCandidate"], | |
| target_fitness: float, | |
| llm: Callable[[str], str] | |
| ) -> str: | |
| """ | |
| Combine parent prompts targeting specific fitness. | |
| Args: | |
| parents: List of PromptCandidate objects (2+ parents) | |
| target_fitness: Desired fitness for offspring | |
| llm: Language model callable | |
| Returns: | |
| str: Offspring prompt | |
| Raises: | |
| ValueError: If fewer than 2 parents provided | |
| """ | |
| if len(parents) < 2: | |
| raise ValueError("Crossover requires at least 2 parents") | |
| # Sort parents by fitness (best first) | |
| sorted_parents = sorted(parents, key=lambda p: p.fitness, reverse=True) | |
| logger.debug(f"Crossover: {len(parents)} parents, target fitness={target_fitness:.3f}") | |
| # Build crossover prompt and call LLM | |
| crossover_prompt = self._build_prompt(sorted_parents, target_fitness) | |
| new_prompt = llm(crossover_prompt) | |
| return new_prompt | |
| def _build_prompt( | |
| self, | |
| parents: List["PromptCandidate"], | |
| target_fitness: float | |
| ) -> str: | |
| """ | |
| Build LLM prompt for crossover operation. | |
| Args: | |
| parents: Sorted list of parent candidates (best first) | |
| target_fitness: Target fitness for offspring | |
| Returns: | |
| str: Prompt for LLM | |
| """ | |
| # Truncate parents to prevent safety filter issues | |
| MAX_PARENT_LENGTH = 350 | |
| # Build parent descriptions (limit to top 2) | |
| parent_descriptions = [] | |
| for i, parent in enumerate(parents[:2]): | |
| truncated = parent.prompt[:MAX_PARENT_LENGTH] | |
| if len(parent.prompt) > MAX_PARENT_LENGTH: | |
| truncated += "..." | |
| parent_descriptions.append( | |
| f"P{i+1} (f={parent.fitness:.2f}): {truncated}\n" | |
| ) | |
| prompt = f"""Combine these prompts into ONE improved version (target fitness: {target_fitness:.2f}). | |
| {' '.join(parent_descriptions)} | |
| Instructions: | |
| 1. Merge the best rules/principles from both parents | |
| 2. Organize logic clearly (e.g., "For X tasks: do Y", "If Z: then A") | |
| 3. Add structure to handle different cases systematically | |
| 4. Keep output format (Element: X, Description:, Reason:) | |
| 5. Max 600 chars | |
| Output ONLY the combined prompt:""" | |
| return prompt | |