Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion configs/heavy_hitters_workload.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ prompt:
- Implement all data structures yourself; no external packages.
- Keep memory bounded — prefer sketches, compact heaps, or sparse maps.
- Ensure deterministic behaviour for identical inputs.
- Modify only the code inside the EVOLVE-BLOCK markers so the harness can
safely integrate your design.

Inspiration:
- Count-Min Sketch variations with bias reduction or conservative updates.
Expand All @@ -39,12 +41,14 @@ prompt:
- "Maximise heavy-hitter recall (>0.9 is excellent)."
- "Keep mean relative error below 15%."
- "Minimise zero-frequency overestimation (avoid ghost heavy hitters)."
- "Keep per-observation memory under 32 bits when possible."
- "Keep per-observation memory under 32 bits when possible and improve on the baseline's ~25 bits."

conversation_starters:
- "Combine a Count-Min sketch with a bounded dictionary of suspects and decay their counts."
- "Use multiple reservoirs to capture bursty heavy hitters while retaining estimates via sketches."
- "Implement a layered SpaceSaving structure with compressed fingerprints."
- "Evolve a reversible hash sketch that tracks residual counts and refreshes stale entries with decay."
- "Blend top-k maintenance with stratified sampling so rare-but-growing keys can displace stale heavy hitters."

database:
population_size: 120
Expand Down
26 changes: 23 additions & 3 deletions heavy_hitters_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
import concurrent.futures
import importlib.util
import math
import sys
import traceback
from pathlib import Path
from types import ModuleType
from typing import Callable

from openevolve.evaluation_result import EvaluationResult

from randomize_evolve.evaluators.heavy_hitters import (
_REPO_ROOT = Path(__file__).resolve().parent
_SRC_PATH = _REPO_ROOT / "src"
if str(_SRC_PATH) not in sys.path:
sys.path.insert(0, str(_SRC_PATH))

from randomize_evolve.evaluators.heavy_hitters import ( # noqa: E402 (import after path tweak)
EvaluationResult as HeavyEvaluationResult,
)
from randomize_evolve.evaluators.heavy_hitters import Evaluator, EvaluatorConfig
from randomize_evolve.evaluators.heavy_hitters import Evaluator, EvaluatorConfig # noqa: E402

EVALUATION_TIMEOUT_S = 75

Expand Down Expand Up @@ -175,4 +181,18 @@ def _error_result(message: str, artifacts: dict) -> EvaluationResult:


def _score_to_reward(score: float) -> float:
return 0.0 if not math.isfinite(score) else 1.0 / (1.0 + max(score, 0.0))
"""Map the evaluator's raw score into a smooth reward in ``[0, 1]``.

The heavy hitter evaluator accumulates large penalty values to emphasise
accuracy, latency, and memory usage. The previous transformation
``1 / (1 + score)`` compressed the dynamic range of rewards so aggressively
that evolutionary search treated most candidates as equally poor. By using
an exponential falloff we keep the ordering identical while ensuring that
meaningful improvements translate into noticeably higher rewards.
"""

if not math.isfinite(score):
return 0.0

scaled = max(score, 0.0) / 4000.0
return math.exp(-scaled)
4 changes: 2 additions & 2 deletions run_heavy_hitters.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
_INITIAL_PROGRAM_PATH = pathlib.Path(__file__).parent / "initial_program_heavy_hitters.py"
INITIAL_PROGRAM_SOURCE = ProgramSource(_INITIAL_PROGRAM_PATH.read_text(encoding="utf-8"))

_EVALUATOR_PATH = Path(__file__).parent / "heavy_hitters_evaluator.py"
_EVALUATOR_PATH = pathlib.Path(__file__).parent / "heavy_hitters_evaluator.py"
_CONFIG_LOADER = ConfigLoader()


Expand All @@ -27,7 +27,7 @@ def _build_runner() -> OpenEvolveRunner:


def _build_workflow(provider) -> "EvolutionWorkflow":
from randomize_evolve.workflow.workflow import EvolutionWorkflow
from src.randomize_evolve.workflow.workflow import EvolutionWorkflow

runner = _build_runner()
reporter = EvolutionReporter()
Expand Down
36 changes: 31 additions & 5 deletions src/randomize_evolve/workflow/configuration.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import os
from dataclasses import dataclass
from dataclasses import dataclass, fields
from pathlib import Path
from typing import Any, Callable, Optional, Protocol

import yaml
from openevolve.config import Config, LLMModelConfig
from openevolve.config import (
Config,
DatabaseConfig,
EvaluatorConfig,
LLMConfig,
LLMModelConfig,
PromptConfig,
)


class APIKeyProvider:
Expand Down Expand Up @@ -140,10 +147,29 @@ def _load_with_openevolve(self, path: Path) -> Optional[Config]:
return None

def _construct_from_dict(self, data: dict[str, Any]) -> Optional[Config]:
filtered: dict[str, Any] = {}
allowed_top = {field.name for field in fields(Config)}
for key, value in data.items():
if key in allowed_top:
filtered[key] = value

for section, schema in (
("llm", LLMConfig),
("prompt", PromptConfig),
("database", DatabaseConfig),
("evaluator", EvaluatorConfig),
):
section_data = filtered.get(section)
if isinstance(section_data, dict):
allowed_fields = {field.name for field in fields(schema)}
filtered[section] = {
name: section_data[name]
for name in allowed_fields
if name in section_data
Comment on lines 149 to +168
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Filter YAML using pydantic model_fields, not dataclasses.fields

The new _construct_from_dict builds allowed_top and section field sets via dataclasses.fields(Config) and the same for LLMConfig, PromptConfig, etc. These types are pydantic BaseModels (the surrounding code already relies on Config.model_validate), so dataclasses.fields() raises TypeError: must be called with a dataclass type or instance. As a result the fallback loader now crashes whenever load_config() fails—which is exactly when this path is exercised—so heavy hitter workflows without an API key cannot load at all. Use pydantic’s model_fields (or another safe method) to enumerate allowed keys instead of dataclasses.fields.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex please fix this

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

try:
if hasattr(Config, "model_validate"):
return Config.model_validate(data) # type: ignore[attr-defined,return-value]
return Config(**data) # type: ignore[arg-type]
return Config.from_dict(filtered)
except Exception:
return None

Expand Down
2 changes: 1 addition & 1 deletion src/randomize_evolve/workflow/workflow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any

from randomize_evolve.workflow.program import ProgramSource, TemporaryProgramFile
from .program import ProgramSource, TemporaryProgramFile


class EvolutionWorkflow:
Expand Down