Skip to content
Draft
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
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,40 @@ OpenEvolve implements a sophisticated **evolutionary coding pipeline** that goes
- **Adaptive Feature Dimensions**: Custom quality-diversity metrics
- **Migration Patterns**: Ring topology with controlled gene flow
- **Multi-Strategy Sampling**: Elite, diverse, and exploratory selection
- **Evaluator-Defined Identity**: Set `program_identity_artifact` to reject
structurally duplicate measured children across the complete run history
- **Measured-Phenotype Identity**: Set `phenotype_identity_artifact` to reject
structurally different children whose complete measured behavior is already
present
- **Observed-Result Allocation**: Optional `database.controller_scheduler`
routes new calls using a domain-selected `score_metric`, validity, diversity,
rejection rate, exploration, and token efficiency. Set `diversity_artifact`
to count evaluator-defined niches, and reserve at least one island so live
parallel scheduling retains a real allocation choice. Optionally set
`adaptive_parallelism` below the worker count to use fast balanced warmup
followed by a fresher-context quality phase. Set it to `1` when complete
archive freshness matters more than late-stage throughput. The allocator
reads retained seed and migrated-program scores as well as child results.
Set `leader_score_band` to keep post-warmup calls within an absolute score
distance of the best retained population. Set `parent_score_band` to keep
post-warmup parents within a score distance of the selected population's
retained leader
- **Compact Measured-History Context**: Set
`prompt.archive_context_artifact` to show the proposal model a deterministic,
bounded inventory of one evaluator-defined artifact from measured programs.
The append-only values survive MAP-Elites displacement and checkpoint resume
- **Filtered Proposal Neighborhoods**: Set
`prompt.proposal_neighborhood_artifact` when an evaluator can enumerate
valid parent-local options. The controller removes identities measured
anywhere in the complete persisted history, including behaviorally rejected
or displaced programs, and renders the bounded remainder as
`proposal-options.json` without embedding domain rules in OpenEvolve
- **Prompt Artifact Selection**: Set ordered
`prompt.artifact_include_names` to keep complete evaluator receipts in the
archive while rendering only task-relevant artifacts to the proposal model
- **Auditable Rejections**: Usage receipts distinguish measured candidates
from archive admission and record the selected parent plus a provider-response
digest without duplicating full prompt content

</details>

Expand Down Expand Up @@ -859,14 +893,20 @@ Just set the `api_base` in your config to point to your endpoint.

**Multiple success metrics:**

1. **Primary Metric**: Your evaluator's `combined_score` or metric average
1. **Primary Metric**: `selection_score` when supplied, then `combined_score`
2. **Convergence**: Best score improvement over time
3. **Diversity**: MAP-Elites grid coverage
4. **Efficiency**: Iterations to reach target performance
5. **Robustness**: Performance across different test cases

**Use the visualizer** to track all metrics in real-time and identify when evolution has converged.

For exact scientific searches, an evaluator can additionally return
`selection_eligible`. A value at or below zero retains the attempt in lineage
records but excludes it from parent selection, MAP-Elites cells, and the elite
archive. This separates non-negotiable admission checks from the
multi-objective quality score while preserving older evaluator behavior.

</details>

### **Contributors**
Expand Down
15 changes: 13 additions & 2 deletions openevolve/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import uuid
import inspect
from typing import Union, Callable, Optional, List, Dict, Any, Tuple
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path

from openevolve.controller import OpenEvolve
Expand All @@ -25,6 +25,10 @@ class EvolutionResult:
best_code: str
metrics: Dict[str, Any]
output_dir: Optional[str]
completion_reason: str = "unknown"
last_completed_iteration: Optional[int] = None
completed_iteration_count: int = 0
llm_usage: Dict[str, Any] = field(default_factory=dict)

def __repr__(self):
return f"EvolutionResult(best_score={self.best_score:.4f})"
Expand Down Expand Up @@ -171,19 +175,26 @@ async def _run_evolution_async(
best_code = best_program.code
metrics = best_program.metrics or {}

if "combined_score" in metrics:
if "selection_score" in metrics:
best_score = metrics["selection_score"]
elif "combined_score" in metrics:
best_score = metrics["combined_score"]
elif metrics:
numeric_metrics = [v for v in metrics.values() if isinstance(v, (int, float))]
if numeric_metrics:
best_score = sum(numeric_metrics) / len(numeric_metrics)

llm_usage = getattr(controller, "llm_usage", {})
return EvolutionResult(
best_program=best_program,
best_score=best_score,
best_code=best_code,
metrics=metrics,
output_dir=actual_output_dir if not cleanup else None,
completion_reason=controller.completion_reason,
last_completed_iteration=controller.last_completed_iteration,
completed_iteration_count=controller.completed_iteration_count,
llm_usage=dict(llm_usage) if isinstance(llm_usage, dict) else {},
)

finally:
Expand Down
36 changes: 36 additions & 0 deletions openevolve/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

import argparse
import asyncio
import json
import logging
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional

from openevolve import OpenEvolve
Expand All @@ -25,6 +27,16 @@ def parse_args() -> argparse.Namespace:
"evaluation_file", help="Path to the evaluation file containing an 'evaluate' function"
)

parser.add_argument(
"--seed-program",
action="append",
default=[],
help=(
"Additional evaluated starting parent. Repeat the option to seed "
"multiple islands while retaining initial_program as the incumbent."
),
)

parser.add_argument("--config", "-c", help="Path to configuration file (YAML)", default=None)

parser.add_argument("--output", "-o", help="Output directory for results", default=None)
Expand Down Expand Up @@ -77,6 +89,10 @@ async def main_async() -> int:
if not os.path.exists(args.evaluation_file):
print(f"Error: Evaluation file '{args.evaluation_file}' not found")
return 1
missing_seeds = [path for path in args.seed_program if not os.path.exists(path)]
if missing_seeds:
print(f"Error: Seed program file not found: '{missing_seeds[0]}'")
return 1

# Load base config from file or defaults
config = load_config(args.config)
Expand Down Expand Up @@ -110,6 +126,7 @@ async def main_async() -> int:
evaluation_file=args.evaluation_file,
config=config,
output_dir=args.output,
seed_program_paths=args.seed_program,
)

# Load from checkpoint if specified
Expand Down Expand Up @@ -161,6 +178,25 @@ async def main_async() -> int:
print(f"\nLatest checkpoint saved at: {latest_checkpoint}")
print(f"To resume, use: --checkpoint {latest_checkpoint}")

summary = {
"schema_version": 1,
"record_type": "openevolve_run_summary",
"completion_reason": openevolve.completion_reason,
"last_completed_iteration": openevolve.last_completed_iteration,
"completed_iteration_count": openevolve.completed_iteration_count,
"latest_checkpoint": latest_checkpoint,
"best_program_id": best_program.id,
"best_program_metrics": best_program.metrics,
"llm_usage": openevolve.llm_usage,
}
summary_path = Path(openevolve.output_dir) / "run-summary.json"
temporary = summary_path.with_name(f".{summary_path.name}.tmp")
temporary.write_text(
json.dumps(summary, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
temporary.replace(summary_path)

return 0

except Exception as e:
Expand Down
Loading