Selection strategies for evolution candidates, components, and evaluation.
Contains candidate selection (Pareto, greedy, epsilon-greedy), component selection (round-robin, all), and evaluation policy (full, subset) implementations.
Anticipated growth: tournament selection, adaptive selection strategies, custom evaluation scheduling.
Examples:
Create a candidate selector using the factory:
from gepa_adk.adapters.selection import create_candidate_selector
selector = create_candidate_selector("pareto")
Use a component selector and evaluation policy:
from gepa_adk.adapters.selection import (
RoundRobinComponentSelector,
SubsetEvaluationPolicy,
)
component_selector = RoundRobinComponentSelector()
eval_policy = SubsetEvaluationPolicy(subset_size=5)
See Also
Note
This package unifies candidate, component, and evaluation selection strategies.
CurrentBestCandidateSelector
Always select the candidate with the highest average score.
Note
A greedy selector always exploits the best-average candidate.
Examples:
selector = CurrentBestCandidateSelector()
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| class CurrentBestCandidateSelector:
"""Always select the candidate with the highest average score.
Note:
A greedy selector always exploits the best-average candidate.
Examples:
```python
selector = CurrentBestCandidateSelector()
candidate_idx = await selector.select_candidate(state)
```
"""
async def select_candidate(self, state: ParetoState) -> int:
"""Return the best-average candidate index.
Args:
state: Current evolution state with Pareto tracking.
Returns:
Selected candidate index.
Raises:
NoCandidateAvailableError: If no candidates are available.
Examples:
```python
candidate_idx = await selector.select_candidate(state)
```
"""
if state.best_average_idx is None:
raise NoCandidateAvailableError("No candidates available for selection")
return state.best_average_idx
|
select_candidate async
Return the best-average candidate index.
| PARAMETER | DESCRIPTION |
state | Current evolution state with Pareto tracking. TYPE: ParetoState |
| RETURNS | DESCRIPTION |
int | Selected candidate index. |
Examples:
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| async def select_candidate(self, state: ParetoState) -> int:
"""Return the best-average candidate index.
Args:
state: Current evolution state with Pareto tracking.
Returns:
Selected candidate index.
Raises:
NoCandidateAvailableError: If no candidates are available.
Examples:
```python
candidate_idx = await selector.select_candidate(state)
```
"""
if state.best_average_idx is None:
raise NoCandidateAvailableError("No candidates available for selection")
return state.best_average_idx
|
EpsilonGreedyCandidateSelector
Epsilon-greedy selection balancing exploration and exploitation.
Note
A mixed strategy sometimes explores and otherwise exploits the best.
| ATTRIBUTE | DESCRIPTION |
_epsilon | TYPE: float |
_rng | RNG for exploration decisions. TYPE: Random |
Examples:
selector = EpsilonGreedyCandidateSelector(epsilon=0.1, rng=random.Random(7))
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| class EpsilonGreedyCandidateSelector:
"""Epsilon-greedy selection balancing exploration and exploitation.
Note:
A mixed strategy sometimes explores and otherwise exploits the best.
Attributes:
_epsilon (float): Exploration probability.
_rng (random.Random): RNG for exploration decisions.
Examples:
```python
selector = EpsilonGreedyCandidateSelector(epsilon=0.1, rng=random.Random(7))
candidate_idx = await selector.select_candidate(state)
```
"""
def __init__(self, epsilon: float, rng: random.Random | None = None) -> None:
"""Initialize the selector.
Args:
epsilon: Probability of random exploration.
rng: Optional random number generator for reproducibility.
Raises:
ConfigurationError: If epsilon is outside [0.0, 1.0].
"""
if not 0.0 <= epsilon <= 1.0:
raise ConfigurationError(
"epsilon must be between 0.0 and 1.0",
field="epsilon",
value=epsilon,
constraint="0.0 <= epsilon <= 1.0",
)
self._epsilon = epsilon
self._rng = rng or random.Random()
async def select_candidate(self, state: ParetoState) -> int:
"""Select a candidate using epsilon-greedy strategy.
Args:
state: Current evolution state with Pareto tracking.
Returns:
Selected candidate index.
Raises:
NoCandidateAvailableError: If no candidates are available.
Examples:
```python
candidate_idx = await selector.select_candidate(state)
```
"""
if not state.candidates:
raise NoCandidateAvailableError("No candidates available for selection")
if self._rng.random() < self._epsilon:
return self._rng.randint(0, len(state.candidates) - 1)
if state.best_average_idx is None:
raise NoCandidateAvailableError("No candidates available for selection")
return state.best_average_idx
|
__init__
__init__(epsilon: float, rng: Random | None = None) -> None
Initialize the selector.
| PARAMETER | DESCRIPTION |
epsilon | Probability of random exploration. TYPE: float |
rng | Optional random number generator for reproducibility. TYPE: Random | None DEFAULT: None |
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| def __init__(self, epsilon: float, rng: random.Random | None = None) -> None:
"""Initialize the selector.
Args:
epsilon: Probability of random exploration.
rng: Optional random number generator for reproducibility.
Raises:
ConfigurationError: If epsilon is outside [0.0, 1.0].
"""
if not 0.0 <= epsilon <= 1.0:
raise ConfigurationError(
"epsilon must be between 0.0 and 1.0",
field="epsilon",
value=epsilon,
constraint="0.0 <= epsilon <= 1.0",
)
self._epsilon = epsilon
self._rng = rng or random.Random()
|
select_candidate async
Select a candidate using epsilon-greedy strategy.
| PARAMETER | DESCRIPTION |
state | Current evolution state with Pareto tracking. TYPE: ParetoState |
| RETURNS | DESCRIPTION |
int | Selected candidate index. |
Examples:
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| async def select_candidate(self, state: ParetoState) -> int:
"""Select a candidate using epsilon-greedy strategy.
Args:
state: Current evolution state with Pareto tracking.
Returns:
Selected candidate index.
Raises:
NoCandidateAvailableError: If no candidates are available.
Examples:
```python
candidate_idx = await selector.select_candidate(state)
```
"""
if not state.candidates:
raise NoCandidateAvailableError("No candidates available for selection")
if self._rng.random() < self._epsilon:
return self._rng.randint(0, len(state.candidates) - 1)
if state.best_average_idx is None:
raise NoCandidateAvailableError("No candidates available for selection")
return state.best_average_idx
|
ParetoCandidateSelector
Sample from the Pareto front proportional to leadership frequency.
Note
A Pareto selector emphasizes candidates that lead more examples.
| ATTRIBUTE | DESCRIPTION |
_rng | RNG for sampling candidates. TYPE: Random |
Examples:
selector = ParetoCandidateSelector(rng=random.Random(42))
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| class ParetoCandidateSelector:
"""Sample from the Pareto front proportional to leadership frequency.
Note:
A Pareto selector emphasizes candidates that lead more examples.
Attributes:
_rng (random.Random): RNG for sampling candidates.
Examples:
```python
selector = ParetoCandidateSelector(rng=random.Random(42))
candidate_idx = await selector.select_candidate(state)
```
"""
def __init__(self, rng: random.Random | None = None) -> None:
"""Initialize the selector.
Args:
rng: Optional random number generator for reproducibility.
"""
self._rng = rng or random.Random()
async def select_candidate(self, state: ParetoState) -> int:
"""Select a candidate index from the Pareto frontier.
Args:
state: Current evolution state with Pareto tracking.
Returns:
Selected candidate index.
Raises:
NoCandidateAvailableError: If no candidates or leaders exist.
Examples:
```python
candidate_idx = await selector.select_candidate(state)
```
"""
if not state.candidates:
raise NoCandidateAvailableError("No candidates available for selection")
weights = state.frontier.get_selection_weights()
if not weights:
raise NoCandidateAvailableError("Pareto frontier is empty")
sampling_list = [
candidate_idx
for candidate_idx, weight in weights.items()
for _ in range(weight)
]
if not sampling_list:
raise NoCandidateAvailableError("Pareto frontier has no leaders")
return self._rng.choice(sampling_list)
|
__init__
__init__(rng: Random | None = None) -> None
Initialize the selector.
| PARAMETER | DESCRIPTION |
rng | Optional random number generator for reproducibility. TYPE: Random | None DEFAULT: None |
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| def __init__(self, rng: random.Random | None = None) -> None:
"""Initialize the selector.
Args:
rng: Optional random number generator for reproducibility.
"""
self._rng = rng or random.Random()
|
select_candidate async
Select a candidate index from the Pareto frontier.
| PARAMETER | DESCRIPTION |
state | Current evolution state with Pareto tracking. TYPE: ParetoState |
| RETURNS | DESCRIPTION |
int | Selected candidate index. |
Examples:
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| async def select_candidate(self, state: ParetoState) -> int:
"""Select a candidate index from the Pareto frontier.
Args:
state: Current evolution state with Pareto tracking.
Returns:
Selected candidate index.
Raises:
NoCandidateAvailableError: If no candidates or leaders exist.
Examples:
```python
candidate_idx = await selector.select_candidate(state)
```
"""
if not state.candidates:
raise NoCandidateAvailableError("No candidates available for selection")
weights = state.frontier.get_selection_weights()
if not weights:
raise NoCandidateAvailableError("Pareto frontier is empty")
sampling_list = [
candidate_idx
for candidate_idx, weight in weights.items()
for _ in range(weight)
]
if not sampling_list:
raise NoCandidateAvailableError("Pareto frontier has no leaders")
return self._rng.choice(sampling_list)
|
AllComponentSelector
Selects all available components for simultaneous update.
This selector returns the full list of components every time, enabling simultaneous evolution of all parts of the candidate.
Examples:
selector = AllComponentSelector()
all_comps = await selector.select_components(["a", "b"], 1, 0)
# Returns ["a", "b"]
Note
Always returns all components, enabling comprehensive mutations across the entire candidate in a single iteration.
Source code in src/gepa_adk/adapters/selection/component_selector.py
| class AllComponentSelector:
"""Selects all available components for simultaneous update.
This selector returns the full list of components every time, enabling
simultaneous evolution of all parts of the candidate.
Examples:
```python
selector = AllComponentSelector()
all_comps = await selector.select_components(["a", "b"], 1, 0)
# Returns ["a", "b"]
```
Note:
Always returns all components, enabling comprehensive mutations
across the entire candidate in a single iteration.
"""
async def select_components(
self, components: list[str], iteration: int, candidate_idx: int
) -> list[str]:
"""Select all components to update.
Args:
components: List of available component keys.
iteration: Current global iteration number (unused).
candidate_idx: Index of the candidate being evolved (unused).
Returns:
List containing all component keys.
Raises:
ValueError: If components list is empty.
Examples:
```python
selected = await selector.select_components(["a", "b"], 1, 0)
```
Note:
Outputs the complete component list unchanged, enabling
simultaneous evolution of all candidate parts.
"""
if not components:
raise ValueError("No components provided for selection")
return list(components)
|
select_components async
select_components(
components: list[str],
iteration: int,
candidate_idx: int,
) -> list[str]
Select all components to update.
| PARAMETER | DESCRIPTION |
components | List of available component keys. TYPE: list[str] |
iteration | Current global iteration number (unused). TYPE: int |
candidate_idx | Index of the candidate being evolved (unused). TYPE: int |
| RETURNS | DESCRIPTION |
list[str] | List containing all component keys. |
| RAISES | DESCRIPTION |
ValueError | If components list is empty. |
Examples:
selected = await selector.select_components(["a", "b"], 1, 0)
Note
Outputs the complete component list unchanged, enabling simultaneous evolution of all candidate parts.
Source code in src/gepa_adk/adapters/selection/component_selector.py
| async def select_components(
self, components: list[str], iteration: int, candidate_idx: int
) -> list[str]:
"""Select all components to update.
Args:
components: List of available component keys.
iteration: Current global iteration number (unused).
candidate_idx: Index of the candidate being evolved (unused).
Returns:
List containing all component keys.
Raises:
ValueError: If components list is empty.
Examples:
```python
selected = await selector.select_components(["a", "b"], 1, 0)
```
Note:
Outputs the complete component list unchanged, enabling
simultaneous evolution of all candidate parts.
"""
if not components:
raise ValueError("No components provided for selection")
return list(components)
|
RoundRobinComponentSelector
Selects components in a round-robin fashion.
This selector cycles through the list of components one by one, maintaining state per candidate index to ensure consistent rotation.
| ATTRIBUTE | DESCRIPTION |
_next_index | Mapping of candidate_idx to next component index. TYPE: dict[int, int] |
Examples:
selector = RoundRobinComponentSelector()
# First call selects first component
c1 = await selector.select_components(["a", "b"], 1, 0) # ["a"]
# Second call selects second component
c2 = await selector.select_components(["a", "b"], 2, 0) # ["b"]
Note
Alternates through components sequentially, ensuring balanced evolution across all candidate parts.
Source code in src/gepa_adk/adapters/selection/component_selector.py
| class RoundRobinComponentSelector:
"""Selects components in a round-robin fashion.
This selector cycles through the list of components one by one, maintaining
state per candidate index to ensure consistent rotation.
Attributes:
_next_index (dict[int, int]): Mapping of candidate_idx to next component index.
Examples:
```python
selector = RoundRobinComponentSelector()
# First call selects first component
c1 = await selector.select_components(["a", "b"], 1, 0) # ["a"]
# Second call selects second component
c2 = await selector.select_components(["a", "b"], 2, 0) # ["b"]
```
Note:
Alternates through components sequentially, ensuring balanced evolution
across all candidate parts.
"""
def __init__(self) -> None:
"""Initialize the round-robin selector.
Note:
Creates empty index tracking dictionary for per-candidate rotation state.
"""
self._next_index: dict[int, int] = defaultdict(int)
async def select_components(
self, components: list[str], iteration: int, candidate_idx: int
) -> list[str]:
"""Select a single component to update using round-robin logic.
Args:
components: List of available component keys.
iteration: Current global iteration number (unused by this strategy).
candidate_idx: Index of the candidate being evolved.
Returns:
List containing the single selected component key.
Raises:
ValueError: If components list is empty.
Examples:
```python
selected = await selector.select_components(["a", "b"], 1, 0)
```
Note:
Outputs one component per call, advancing the rotation index for
the specified candidate.
"""
if not components:
raise ValueError("No components provided for selection")
# Get current index for this candidate
current_idx = self._next_index[candidate_idx]
# Select component
selected = components[current_idx % len(components)]
# Advance index for next time
self._next_index[candidate_idx] = (current_idx + 1) % len(components)
return [selected]
|
__init__
Initialize the round-robin selector.
Note
Creates empty index tracking dictionary for per-candidate rotation state.
Source code in src/gepa_adk/adapters/selection/component_selector.py
| def __init__(self) -> None:
"""Initialize the round-robin selector.
Note:
Creates empty index tracking dictionary for per-candidate rotation state.
"""
self._next_index: dict[int, int] = defaultdict(int)
|
select_components async
select_components(
components: list[str],
iteration: int,
candidate_idx: int,
) -> list[str]
Select a single component to update using round-robin logic.
| PARAMETER | DESCRIPTION |
components | List of available component keys. TYPE: list[str] |
iteration | Current global iteration number (unused by this strategy). TYPE: int |
candidate_idx | Index of the candidate being evolved. TYPE: int |
| RETURNS | DESCRIPTION |
list[str] | List containing the single selected component key. |
| RAISES | DESCRIPTION |
ValueError | If components list is empty. |
Examples:
selected = await selector.select_components(["a", "b"], 1, 0)
Note
Outputs one component per call, advancing the rotation index for the specified candidate.
Source code in src/gepa_adk/adapters/selection/component_selector.py
| async def select_components(
self, components: list[str], iteration: int, candidate_idx: int
) -> list[str]:
"""Select a single component to update using round-robin logic.
Args:
components: List of available component keys.
iteration: Current global iteration number (unused by this strategy).
candidate_idx: Index of the candidate being evolved.
Returns:
List containing the single selected component key.
Raises:
ValueError: If components list is empty.
Examples:
```python
selected = await selector.select_components(["a", "b"], 1, 0)
```
Note:
Outputs one component per call, advancing the rotation index for
the specified candidate.
"""
if not components:
raise ValueError("No components provided for selection")
# Get current index for this candidate
current_idx = self._next_index[candidate_idx]
# Select component
selected = components[current_idx % len(components)]
# Advance index for next time
self._next_index[candidate_idx] = (current_idx + 1) % len(components)
return [selected]
|
FullEvaluationPolicy
Evaluation policy that scores all validation examples every iteration.
This is the default evaluation policy, providing complete visibility into solution performance across all validation examples.
Note
Always returns all valset IDs, ensuring complete evaluation coverage each iteration.
Examples:
policy = FullEvaluationPolicy()
batch = policy.get_eval_batch([0, 1, 2, 3, 4], state)
# Returns: [0, 1, 2, 3, 4]
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| class FullEvaluationPolicy:
"""Evaluation policy that scores all validation examples every iteration.
This is the default evaluation policy, providing complete visibility
into solution performance across all validation examples.
Note:
Always returns all valset IDs, ensuring complete evaluation coverage
each iteration.
Examples:
```python
policy = FullEvaluationPolicy()
batch = policy.get_eval_batch([0, 1, 2, 3, 4], state)
# Returns: [0, 1, 2, 3, 4]
```
"""
def get_eval_batch(
self,
valset_ids: Sequence[int],
state: ParetoState,
target_candidate_idx: int | None = None,
) -> list[int]:
"""Return all validation example indices.
Args:
valset_ids (Sequence[int]): All available validation example indices.
state (ParetoState): Current evolution state (unused for full evaluation).
target_candidate_idx (int | None): Optional candidate being evaluated
(unused).
Returns:
list[int]: List of all valset_ids.
Note:
Outputs the complete valset for comprehensive evaluation coverage.
Examples:
```python
policy = FullEvaluationPolicy()
batch = policy.get_eval_batch([0, 1, 2], state)
assert batch == [0, 1, 2]
```
"""
return list(valset_ids)
def get_best_candidate(self, state: ParetoState) -> int:
"""Return index of candidate with highest average score.
Args:
state: Current evolution state with candidate scores.
Returns:
Index of best performing candidate.
Raises:
NoCandidateAvailableError: If state has no candidates.
Note:
Outputs the candidate index with the highest mean score across
all evaluated examples.
Examples:
```python
policy = FullEvaluationPolicy()
best_idx = policy.get_best_candidate(state)
```
"""
if not state.candidates:
raise NoCandidateAvailableError("No candidates available")
best_idx = None
best_score = float("-inf")
for candidate_idx in range(len(state.candidates)):
score = self.get_valset_score(candidate_idx, state)
if score > best_score:
best_score = score
best_idx = candidate_idx
if best_idx is None:
raise NoCandidateAvailableError("No scored candidates available")
return best_idx
def get_valset_score(self, candidate_idx: int, state: ParetoState) -> float:
"""Return mean score across all evaluated examples for a candidate.
Args:
candidate_idx (int): Index of candidate to score.
state (ParetoState): Current evolution state.
Returns:
float: Mean score across all examples, or float('-inf') if no scores.
Note:
Outputs the arithmetic mean of all scores for the candidate,
or negative infinity if no scores exist.
Examples:
```python
policy = FullEvaluationPolicy()
score = policy.get_valset_score(0, state)
```
"""
scores = state.candidate_scores.get(candidate_idx)
if not scores:
return float("-inf")
return fmean(scores.values())
|
get_eval_batch
get_eval_batch(
valset_ids: Sequence[int],
state: ParetoState,
target_candidate_idx: int | None = None,
) -> list[int]
Return all validation example indices.
| PARAMETER | DESCRIPTION |
valset_ids | All available validation example indices. TYPE: Sequence[int] |
state | Current evolution state (unused for full evaluation). TYPE: ParetoState |
target_candidate_idx | Optional candidate being evaluated (unused). TYPE: int | None DEFAULT: None |
| RETURNS | DESCRIPTION |
list[int] | list[int]: List of all valset_ids. |
Note
Outputs the complete valset for comprehensive evaluation coverage.
Examples:
policy = FullEvaluationPolicy()
batch = policy.get_eval_batch([0, 1, 2], state)
assert batch == [0, 1, 2]
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| def get_eval_batch(
self,
valset_ids: Sequence[int],
state: ParetoState,
target_candidate_idx: int | None = None,
) -> list[int]:
"""Return all validation example indices.
Args:
valset_ids (Sequence[int]): All available validation example indices.
state (ParetoState): Current evolution state (unused for full evaluation).
target_candidate_idx (int | None): Optional candidate being evaluated
(unused).
Returns:
list[int]: List of all valset_ids.
Note:
Outputs the complete valset for comprehensive evaluation coverage.
Examples:
```python
policy = FullEvaluationPolicy()
batch = policy.get_eval_batch([0, 1, 2], state)
assert batch == [0, 1, 2]
```
"""
return list(valset_ids)
|
get_best_candidate
Return index of candidate with highest average score.
| PARAMETER | DESCRIPTION |
state | Current evolution state with candidate scores. TYPE: ParetoState |
| RETURNS | DESCRIPTION |
int | Index of best performing candidate. |
Note
Outputs the candidate index with the highest mean score across all evaluated examples.
Examples:
policy = FullEvaluationPolicy()
best_idx = policy.get_best_candidate(state)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| def get_best_candidate(self, state: ParetoState) -> int:
"""Return index of candidate with highest average score.
Args:
state: Current evolution state with candidate scores.
Returns:
Index of best performing candidate.
Raises:
NoCandidateAvailableError: If state has no candidates.
Note:
Outputs the candidate index with the highest mean score across
all evaluated examples.
Examples:
```python
policy = FullEvaluationPolicy()
best_idx = policy.get_best_candidate(state)
```
"""
if not state.candidates:
raise NoCandidateAvailableError("No candidates available")
best_idx = None
best_score = float("-inf")
for candidate_idx in range(len(state.candidates)):
score = self.get_valset_score(candidate_idx, state)
if score > best_score:
best_score = score
best_idx = candidate_idx
if best_idx is None:
raise NoCandidateAvailableError("No scored candidates available")
return best_idx
|
get_valset_score
get_valset_score(
candidate_idx: int, state: ParetoState
) -> float
Return mean score across all evaluated examples for a candidate.
| PARAMETER | DESCRIPTION |
candidate_idx | Index of candidate to score. TYPE: int |
state | TYPE: ParetoState |
| RETURNS | DESCRIPTION |
float | Mean score across all examples, or float('-inf') if no scores. TYPE: float |
Note
Outputs the arithmetic mean of all scores for the candidate, or negative infinity if no scores exist.
Examples:
policy = FullEvaluationPolicy()
score = policy.get_valset_score(0, state)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| def get_valset_score(self, candidate_idx: int, state: ParetoState) -> float:
"""Return mean score across all evaluated examples for a candidate.
Args:
candidate_idx (int): Index of candidate to score.
state (ParetoState): Current evolution state.
Returns:
float: Mean score across all examples, or float('-inf') if no scores.
Note:
Outputs the arithmetic mean of all scores for the candidate,
or negative infinity if no scores exist.
Examples:
```python
policy = FullEvaluationPolicy()
score = policy.get_valset_score(0, state)
```
"""
scores = state.candidate_scores.get(candidate_idx)
if not scores:
return float("-inf")
return fmean(scores.values())
|
SubsetEvaluationPolicy
Evaluation policy that scores a configurable subset with round-robin coverage.
This policy reduces evaluation cost for large validation sets by evaluating only a subset of examples per iteration, using round-robin selection to ensure all examples are eventually covered.
Note
Advances offset each iteration to provide round-robin coverage across the full valset over multiple iterations.
| ATTRIBUTE | DESCRIPTION |
subset_size | If int, absolute count of examples to evaluate per iteration. If float in the range 0.0 to 1.0 exclusive-inclusive, fraction of total valset size. TYPE: int | float |
_offset | Internal state tracking current position for round-robin selection. TYPE: int |
Examples:
# Evaluate 20% of valset per iteration
policy = SubsetEvaluationPolicy(subset_size=0.2)
# Evaluate exactly 5 examples per iteration
policy = SubsetEvaluationPolicy(subset_size=5)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| class SubsetEvaluationPolicy:
"""Evaluation policy that scores a configurable subset with round-robin coverage.
This policy reduces evaluation cost for large validation sets by evaluating
only a subset of examples per iteration, using round-robin selection to
ensure all examples are eventually covered.
Note:
Advances offset each iteration to provide round-robin coverage across
the full valset over multiple iterations.
Attributes:
subset_size (int | float): If int, absolute count of examples to evaluate
per iteration. If float in the range 0.0 to 1.0 exclusive-inclusive,
fraction of total valset size.
_offset (int): Internal state tracking current position for round-robin
selection.
Examples:
```python
# Evaluate 20% of valset per iteration
policy = SubsetEvaluationPolicy(subset_size=0.2)
# Evaluate exactly 5 examples per iteration
policy = SubsetEvaluationPolicy(subset_size=5)
```
"""
def __init__(self, subset_size: int | float = 0.2) -> None:
"""Initialize subset evaluation policy.
Args:
subset_size: If int, evaluate this many examples per iteration.
If float, evaluate this fraction of total valset.
Default: 0.2 (20% of valset per iteration).
Note:
Creates policy with initial offset of 0 for round-robin selection.
"""
self.subset_size = subset_size
self._offset = 0
def get_eval_batch(
self,
valset_ids: Sequence[int],
state: ParetoState,
target_candidate_idx: int | None = None,
) -> list[int]:
"""Return subset of validation example indices with round-robin selection.
Args:
valset_ids: All available validation example indices.
state: Current evolution state (unused for subset selection).
target_candidate_idx: Optional candidate being evaluated (unused).
Returns:
List of example indices to evaluate this iteration.
Uses round-robin to ensure all examples are eventually covered.
Raises:
ValueError: If subset_size is outside the allowed range.
Note:
Outputs a subset of valset IDs starting at the current offset,
wrapping around if needed to provide round-robin coverage.
Examples:
```python
policy = SubsetEvaluationPolicy(subset_size=0.25)
batch = policy.get_eval_batch(list(range(8)), state)
assert len(batch) == 2
```
"""
if not valset_ids:
return []
total_size = len(valset_ids)
# Calculate subset count
if isinstance(self.subset_size, float):
if not (0.0 < self.subset_size <= 1.0):
raise ValueError(
f"subset_size float must be in (0.0, 1.0], got {self.subset_size}"
)
subset_count = max(1, int(self.subset_size * total_size))
else:
subset_count = self.subset_size
# Fallback to full evaluation if subset_size exceeds valset size
if subset_count >= total_size:
return list(valset_ids)
# Round-robin selection: slice starting at _offset, wrapping around
result: list[int] = []
for i in range(subset_count):
idx = (self._offset + i) % total_size
result.append(valset_ids[idx])
# Advance offset for next iteration
self._offset = (self._offset + subset_count) % total_size
return result
def get_best_candidate(self, state: ParetoState) -> int:
"""Return index of candidate with highest average score.
Args:
state (ParetoState): Current evolution state with candidate scores.
Returns:
int: Index of best performing candidate.
Raises:
NoCandidateAvailableError: If state has no candidates.
Note:
Outputs the candidate index with the highest mean score across
evaluated examples, consistent with FullEvaluationPolicy behavior.
Examples:
```python
policy = SubsetEvaluationPolicy()
best_idx = policy.get_best_candidate(state)
```
"""
if not state.candidates:
raise NoCandidateAvailableError("No candidates available")
best_idx = None
best_score = float("-inf")
for candidate_idx in range(len(state.candidates)):
score = self.get_valset_score(candidate_idx, state)
if score > best_score:
best_score = score
best_idx = candidate_idx
if best_idx is None:
raise NoCandidateAvailableError("No scored candidates available")
return best_idx
def get_valset_score(self, candidate_idx: int, state: ParetoState) -> float:
"""Return mean score across evaluated examples for a candidate.
Args:
candidate_idx (int): Index of candidate to score.
state (ParetoState): Current evolution state.
Returns:
float: Mean score across evaluated examples, or float('-inf') if no scores.
Note:
Outputs the arithmetic mean of scores for the candidate across
only the examples that were actually evaluated (subset).
Examples:
```python
policy = SubsetEvaluationPolicy()
score = policy.get_valset_score(0, state)
```
"""
scores = state.candidate_scores.get(candidate_idx)
if not scores:
return float("-inf")
return fmean(scores.values())
|
__init__
__init__(subset_size: int | float = 0.2) -> None
Initialize subset evaluation policy.
| PARAMETER | DESCRIPTION |
subset_size | If int, evaluate this many examples per iteration. If float, evaluate this fraction of total valset. Default: 0.2 (20% of valset per iteration). TYPE: int | float DEFAULT: 0.2 |
Note
Creates policy with initial offset of 0 for round-robin selection.
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| def __init__(self, subset_size: int | float = 0.2) -> None:
"""Initialize subset evaluation policy.
Args:
subset_size: If int, evaluate this many examples per iteration.
If float, evaluate this fraction of total valset.
Default: 0.2 (20% of valset per iteration).
Note:
Creates policy with initial offset of 0 for round-robin selection.
"""
self.subset_size = subset_size
self._offset = 0
|
get_eval_batch
get_eval_batch(
valset_ids: Sequence[int],
state: ParetoState,
target_candidate_idx: int | None = None,
) -> list[int]
Return subset of validation example indices with round-robin selection.
| PARAMETER | DESCRIPTION |
valset_ids | All available validation example indices. TYPE: Sequence[int] |
state | Current evolution state (unused for subset selection). TYPE: ParetoState |
target_candidate_idx | Optional candidate being evaluated (unused). TYPE: int | None DEFAULT: None |
| RETURNS | DESCRIPTION |
list[int] | List of example indices to evaluate this iteration. |
list[int] | Uses round-robin to ensure all examples are eventually covered. |
| RAISES | DESCRIPTION |
ValueError | If subset_size is outside the allowed range. |
Note
Outputs a subset of valset IDs starting at the current offset, wrapping around if needed to provide round-robin coverage.
Examples:
policy = SubsetEvaluationPolicy(subset_size=0.25)
batch = policy.get_eval_batch(list(range(8)), state)
assert len(batch) == 2
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| def get_eval_batch(
self,
valset_ids: Sequence[int],
state: ParetoState,
target_candidate_idx: int | None = None,
) -> list[int]:
"""Return subset of validation example indices with round-robin selection.
Args:
valset_ids: All available validation example indices.
state: Current evolution state (unused for subset selection).
target_candidate_idx: Optional candidate being evaluated (unused).
Returns:
List of example indices to evaluate this iteration.
Uses round-robin to ensure all examples are eventually covered.
Raises:
ValueError: If subset_size is outside the allowed range.
Note:
Outputs a subset of valset IDs starting at the current offset,
wrapping around if needed to provide round-robin coverage.
Examples:
```python
policy = SubsetEvaluationPolicy(subset_size=0.25)
batch = policy.get_eval_batch(list(range(8)), state)
assert len(batch) == 2
```
"""
if not valset_ids:
return []
total_size = len(valset_ids)
# Calculate subset count
if isinstance(self.subset_size, float):
if not (0.0 < self.subset_size <= 1.0):
raise ValueError(
f"subset_size float must be in (0.0, 1.0], got {self.subset_size}"
)
subset_count = max(1, int(self.subset_size * total_size))
else:
subset_count = self.subset_size
# Fallback to full evaluation if subset_size exceeds valset size
if subset_count >= total_size:
return list(valset_ids)
# Round-robin selection: slice starting at _offset, wrapping around
result: list[int] = []
for i in range(subset_count):
idx = (self._offset + i) % total_size
result.append(valset_ids[idx])
# Advance offset for next iteration
self._offset = (self._offset + subset_count) % total_size
return result
|
get_best_candidate
Return index of candidate with highest average score.
| PARAMETER | DESCRIPTION |
state | Current evolution state with candidate scores. TYPE: ParetoState |
| RETURNS | DESCRIPTION |
int | Index of best performing candidate. TYPE: int |
Note
Outputs the candidate index with the highest mean score across evaluated examples, consistent with FullEvaluationPolicy behavior.
Examples:
policy = SubsetEvaluationPolicy()
best_idx = policy.get_best_candidate(state)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| def get_best_candidate(self, state: ParetoState) -> int:
"""Return index of candidate with highest average score.
Args:
state (ParetoState): Current evolution state with candidate scores.
Returns:
int: Index of best performing candidate.
Raises:
NoCandidateAvailableError: If state has no candidates.
Note:
Outputs the candidate index with the highest mean score across
evaluated examples, consistent with FullEvaluationPolicy behavior.
Examples:
```python
policy = SubsetEvaluationPolicy()
best_idx = policy.get_best_candidate(state)
```
"""
if not state.candidates:
raise NoCandidateAvailableError("No candidates available")
best_idx = None
best_score = float("-inf")
for candidate_idx in range(len(state.candidates)):
score = self.get_valset_score(candidate_idx, state)
if score > best_score:
best_score = score
best_idx = candidate_idx
if best_idx is None:
raise NoCandidateAvailableError("No scored candidates available")
return best_idx
|
get_valset_score
get_valset_score(
candidate_idx: int, state: ParetoState
) -> float
Return mean score across evaluated examples for a candidate.
| PARAMETER | DESCRIPTION |
candidate_idx | Index of candidate to score. TYPE: int |
state | TYPE: ParetoState |
| RETURNS | DESCRIPTION |
float | Mean score across evaluated examples, or float('-inf') if no scores. TYPE: float |
Note
Outputs the arithmetic mean of scores for the candidate across only the examples that were actually evaluated (subset).
Examples:
policy = SubsetEvaluationPolicy()
score = policy.get_valset_score(0, state)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
| def get_valset_score(self, candidate_idx: int, state: ParetoState) -> float:
"""Return mean score across evaluated examples for a candidate.
Args:
candidate_idx (int): Index of candidate to score.
state (ParetoState): Current evolution state.
Returns:
float: Mean score across evaluated examples, or float('-inf') if no scores.
Note:
Outputs the arithmetic mean of scores for the candidate across
only the examples that were actually evaluated (subset).
Examples:
```python
policy = SubsetEvaluationPolicy()
score = policy.get_valset_score(0, state)
```
"""
scores = state.candidate_scores.get(candidate_idx)
if not scores:
return float("-inf")
return fmean(scores.values())
|
create_candidate_selector
create_candidate_selector(
selector_type: str,
*,
epsilon: float = 0.1,
rng: Random | None = None,
) -> CandidateSelectorProtocol
Create a candidate selector by name.
| PARAMETER | DESCRIPTION |
selector_type | Selector identifier (pareto, greedy, epsilon_greedy). TYPE: str |
epsilon | Exploration rate for epsilon-greedy selector. TYPE: float DEFAULT: 0.1 |
rng | Optional RNG for selectors using randomness. TYPE: Random | None DEFAULT: None |
Examples:
selector = create_candidate_selector("pareto")
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
| def create_candidate_selector(
selector_type: str,
*,
epsilon: float = 0.1,
rng: random.Random | None = None,
) -> CandidateSelectorProtocol:
"""Create a candidate selector by name.
Args:
selector_type: Selector identifier (pareto, greedy, epsilon_greedy).
epsilon: Exploration rate for epsilon-greedy selector.
rng: Optional RNG for selectors using randomness.
Returns:
CandidateSelectorProtocol implementation.
Raises:
ConfigurationError: If selector_type is unsupported.
Examples:
```python
selector = create_candidate_selector("pareto")
candidate_idx = await selector.select_candidate(state)
```
"""
normalized = selector_type.strip().lower()
if normalized in {"pareto"}:
return ParetoCandidateSelector(rng=rng)
if normalized in {"greedy", "current_best", "current-best"}:
return CurrentBestCandidateSelector()
if normalized in {"epsilon_greedy", "epsilon-greedy"}:
return EpsilonGreedyCandidateSelector(epsilon=epsilon, rng=rng)
raise ConfigurationError(
"selector_type must be one of pareto, greedy, epsilon_greedy",
field="selector_type",
value=selector_type,
constraint="pareto|greedy|epsilon_greedy",
)
|
create_component_selector
Create a component selector strategy from a string alias.
| PARAMETER | DESCRIPTION |
selector_type | Name of the selector strategy. Supported values: - 'round_robin', 'roundrobin': Round-robin cycling. - 'all', 'all_components': All components simultaneously. TYPE: str |
| RAISES | DESCRIPTION |
ValueError | If selector_type is unknown. |
Examples:
# Create round-robin selector
selector = create_component_selector("round_robin")
# Create all-components selector
selector = create_component_selector("all")
Note
Supports flexible string aliases with normalization for common variations (underscores, hyphens, case-insensitive).
Source code in src/gepa_adk/adapters/selection/component_selector.py
| def create_component_selector(selector_type: str) -> ComponentSelectorProtocol:
"""Create a component selector strategy from a string alias.
Args:
selector_type: Name of the selector strategy.
Supported values:
- 'round_robin', 'roundrobin': Round-robin cycling.
- 'all', 'all_components': All components simultaneously.
Returns:
Instance of requested component selector.
Raises:
ValueError: If selector_type is unknown.
Examples:
```python
# Create round-robin selector
selector = create_component_selector("round_robin")
# Create all-components selector
selector = create_component_selector("all")
```
Note:
Supports flexible string aliases with normalization for common
variations (underscores, hyphens, case-insensitive).
"""
normalized = selector_type.lower().replace("_", "").replace("-", "")
if normalized == "roundrobin":
return RoundRobinComponentSelector()
elif normalized in ("all", "allcomponents"):
return AllComponentSelector()
raise ValueError(f"Unknown component selector: {selector_type}")
|