Skip to content

Commit 47ac08e

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents cf8c5e3 + 65cbbe8 commit 47ac08e

9 files changed

Lines changed: 661 additions & 41 deletions

File tree

configs/default_config.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,15 @@ prompt:
7878
# Feature extraction and program labeling thresholds
7979
# These control how the LLM perceives and categorizes programs
8080
suggest_simplification_after_chars: 500 # Suggest simplifying if program exceeds this many characters
81-
include_changes_under_chars: 100 # Include change descriptions in features if under this length
81+
include_changes_under_chars: 100 # Include change descriptions in features if under this length
8282
concise_implementation_max_lines: 10 # Label as "concise" if program has this many lines or fewer
8383
comprehensive_implementation_min_lines: 50 # Label as "comprehensive" if program has this many lines or more
8484

85+
# Diff summary formatting for "Previous Attempts" section
86+
# Controls how SEARCH/REPLACE blocks are displayed in prompts
87+
diff_summary_max_line_len: 100 # Truncate lines longer than this (with "...")
88+
diff_summary_max_lines: 30 # Max lines per SEARCH/REPLACE block
89+
8590
# Note: meta-prompting features are not yet implemented
8691

8792
# Database configuration

openevolve/config.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ class PromptConfig:
281281
50 # Label as "comprehensive" if program has this many lines or more
282282
)
283283

284+
# Diff summary formatting for "Previous Attempts" section
285+
diff_summary_max_line_len: int = 100 # Truncate lines longer than this
286+
diff_summary_max_lines: int = 30 # Max lines per SEARCH/REPLACE block
287+
284288
# Backward compatibility - deprecated
285289
code_length_threshold: Optional[int] = (
286290
None # Deprecated: use suggest_simplification_after_chars
@@ -340,7 +344,9 @@ class DatabaseConfig:
340344
artifact_size_threshold: int = 32 * 1024 # 32KB threshold
341345
cleanup_old_artifacts: bool = True
342346
artifact_retention_days: int = 30
343-
max_snapshot_artifacts: Optional[int] = 100 # Max artifacts in worker snapshots (None=unlimited)
347+
max_snapshot_artifacts: Optional[int] = (
348+
100 # Max artifacts in worker snapshots (None=unlimited)
349+
)
344350

345351
novelty_llm: Optional["LLMInterface"] = None
346352
embedding_model: Optional[str] = None
@@ -427,9 +433,18 @@ class Config:
427433
@classmethod
428434
def from_yaml(cls, path: Union[str, Path]) -> "Config":
429435
"""Load configuration from a YAML file"""
430-
with open(path, "r") as f:
436+
config_path = Path(path).resolve()
437+
with open(config_path, "r") as f:
431438
config_dict = yaml.safe_load(f)
432-
return cls.from_dict(config_dict)
439+
config = cls.from_dict(config_dict)
440+
441+
# Resolve template_dir relative to config file location
442+
if config.prompt.template_dir:
443+
template_path = Path(config.prompt.template_dir)
444+
if not template_path.is_absolute():
445+
config.prompt.template_dir = str((config_path.parent / template_path).resolve())
446+
447+
return config
433448

434449
@classmethod
435450
def from_dict(cls, config_dict: Dict[str, Any]) -> "Config":

openevolve/iteration.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import asyncio
2-
import os
3-
import uuid
42
import logging
3+
import os
54
import time
5+
import uuid
66
from dataclasses import dataclass
77

8-
from openevolve.database import Program, ProgramDatabase
98
from openevolve.config import Config
9+
from openevolve.database import Program, ProgramDatabase
1010
from openevolve.evaluator import Evaluator
1111
from openevolve.llm.ensemble import LLMEnsemble
1212
from openevolve.prompt.sampler import PromptSampler
@@ -63,8 +63,7 @@ async def run_iteration_with_shared_db(
6363
# Build prompt
6464
if config.prompt.programs_as_changes_description:
6565
parent_changes_desc = (
66-
parent.changes_description
67-
or config.prompt.initial_changes_description
66+
parent.changes_description or config.prompt.initial_changes_description
6867
)
6968
child_changes_desc = parent_changes_desc
7069
else:
@@ -115,20 +114,34 @@ async def run_iteration_with_shared_db(
115114
return None
116115

117116
child_code, _ = apply_diff_blocks(parent.code, code_blocks)
118-
child_changes_desc, desc_applied = apply_diff_blocks(parent_changes_desc, desc_blocks)
117+
child_changes_desc, desc_applied = apply_diff_blocks(
118+
parent_changes_desc, desc_blocks
119+
)
119120

120121
# Must update the previous changes description
121-
if desc_applied == 0 or not child_changes_desc.strip() or child_changes_desc.strip() == parent_changes_desc.strip():
122+
if (
123+
desc_applied == 0
124+
or not child_changes_desc.strip()
125+
or child_changes_desc.strip() == parent_changes_desc.strip()
126+
):
122127
logger.warning(
123128
f"Iteration {iteration+1}: changes_description was not updated or empty, program is discarded"
124129
)
125130
return None
126131

127-
changes_summary = format_diff_summary(code_blocks)
132+
changes_summary = format_diff_summary(
133+
code_blocks,
134+
max_line_len=config.prompt.diff_summary_max_line_len,
135+
max_lines=config.prompt.diff_summary_max_lines,
136+
)
128137
else:
129138
# All diffs applied only to code
130139
child_code = apply_diff(parent.code, llm_response, config.diff_pattern)
131-
changes_summary = format_diff_summary(diff_blocks)
140+
changes_summary = format_diff_summary(
141+
diff_blocks,
142+
max_line_len=config.prompt.diff_summary_max_line_len,
143+
max_lines=config.prompt.diff_summary_max_lines,
144+
)
132145
else:
133146
# Parse full rewrite
134147
new_code = parse_full_rewrite(llm_response, config.language)

openevolve/process_parallel.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class SerializableResult:
3333
artifacts: Optional[Dict[str, Any]] = None
3434
iteration: int = 0
3535
error: Optional[str] = None
36+
target_island: Optional[int] = None # Island where child should be placed
3637

3738

3839
def _worker_init(config_dict: dict, evaluation_file: str, parent_env: dict = None) -> None:
@@ -170,8 +171,7 @@ def _run_iteration_worker(
170171
# Build prompt
171172
if _worker_config.prompt.programs_as_changes_description:
172173
parent_changes_desc = (
173-
parent.changes_description
174-
or _worker_config.prompt.initial_changes_description
174+
parent.changes_description or _worker_config.prompt.initial_changes_description
175175
)
176176
child_changes_desc = parent_changes_desc
177177
else:
@@ -223,7 +223,9 @@ def _run_iteration_worker(
223223

224224
diff_blocks = extract_diffs(llm_response, _worker_config.diff_pattern)
225225
if not diff_blocks:
226-
return SerializableResult(error="No valid diffs found in response", iteration=iteration)
226+
return SerializableResult(
227+
error="No valid diffs found in response", iteration=iteration
228+
)
227229

228230
if _worker_config.prompt.programs_as_changes_description:
229231
try:
@@ -236,20 +238,34 @@ def _run_iteration_worker(
236238
return SerializableResult(error=str(e), iteration=iteration)
237239

238240
child_code, _ = apply_diff_blocks(parent.code, code_blocks)
239-
child_changes_desc, desc_applied = apply_diff_blocks(parent_changes_desc, desc_blocks)
241+
child_changes_desc, desc_applied = apply_diff_blocks(
242+
parent_changes_desc, desc_blocks
243+
)
240244

241245
# Must update the previous changes description
242-
if desc_applied == 0 or not child_changes_desc.strip() or child_changes_desc.strip() == parent_changes_desc.strip():
246+
if (
247+
desc_applied == 0
248+
or not child_changes_desc.strip()
249+
or child_changes_desc.strip() == parent_changes_desc.strip()
250+
):
243251
return SerializableResult(
244252
error="changes_description was not updated or empty, program is discarded",
245253
iteration=iteration,
246254
)
247255

248-
changes_summary = format_diff_summary(code_blocks)
256+
changes_summary = format_diff_summary(
257+
code_blocks,
258+
max_line_len=_worker_config.prompt.diff_summary_max_line_len,
259+
max_lines=_worker_config.prompt.diff_summary_max_lines,
260+
)
249261
else:
250262
# All diffs applied only to code
251263
child_code = apply_diff(parent.code, llm_response, _worker_config.diff_pattern)
252-
changes_summary = format_diff_summary(diff_blocks)
264+
changes_summary = format_diff_summary(
265+
diff_blocks,
266+
max_line_len=_worker_config.prompt.diff_summary_max_line_len,
267+
max_lines=_worker_config.prompt.diff_summary_max_lines,
268+
)
253269
else:
254270
from openevolve.utils.code_utils import parse_full_rewrite
255271

@@ -297,6 +313,9 @@ def _run_iteration_worker(
297313

298314
iteration_time = time.time() - iteration_start
299315

316+
# Get target island from snapshot (where child should be placed)
317+
target_island = db_snapshot.get("sampling_island")
318+
300319
return SerializableResult(
301320
child_program_dict=child_program.to_dict(),
302321
parent_id=parent.id,
@@ -305,6 +324,7 @@ def _run_iteration_worker(
305324
llm_response=llm_response,
306325
artifacts=artifacts,
307326
iteration=iteration,
327+
target_island=target_island,
308328
)
309329

310330
except Exception as e:
@@ -539,9 +559,14 @@ async def run_evolution(
539559
# Reconstruct program from dict
540560
child_program = Program(**result.child_program_dict)
541561

542-
# Add to database (will auto-inherit parent's island)
543-
# No need to specify target_island - database will handle parent island inheritance
544-
self.database.add(child_program, iteration=completed_iteration)
562+
# Add to database with explicit target_island to ensure proper island placement
563+
# This fixes issue #391: children should go to the target island, not inherit
564+
# from the parent (which may be from a different island due to fallback sampling)
565+
self.database.add(
566+
child_program,
567+
iteration=completed_iteration,
568+
target_island=result.target_island,
569+
)
545570

546571
# Store artifacts
547572
if result.artifacts:
@@ -588,10 +613,8 @@ async def run_evolution(
588613

589614
# Island management
590615
# get current program island id
591-
island_id = child_program.metadata.get(
592-
"island", self.database.current_island
593-
)
594-
#use this to increment island generation
616+
island_id = child_program.metadata.get("island", self.database.current_island)
617+
# use this to increment island generation
595618
self.database.increment_island_generation(island_idx=island_id)
596619

597620
# Check migration
@@ -709,7 +732,7 @@ async def run_evolution(
709732
f"(best score: {best_score:.4f})"
710733
)
711734
break
712-
735+
713736
else:
714737
# Event-based early stopping
715738
if current_score == self.config.convergence_threshold:

openevolve/prompt/templates.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
import os
66
import json
7+
import logging
78
from pathlib import Path
89
from typing import Dict, List, Optional, Union, Any
910

11+
logger = logging.getLogger(__name__)
12+
1013
# Base system message template for evolution
1114
BASE_SYSTEM_TEMPLATE = """You are an expert software developer tasked with iteratively improving a codebase.
1215
Your job is to analyze the current program and suggest improvements based on feedback from previous attempts.
@@ -185,8 +188,13 @@ def __init__(self, custom_template_dir: Optional[str] = None):
185188
self._load_from_directory(self.default_dir)
186189

187190
# 2. Override with custom templates (if provided)
188-
if self.custom_dir and self.custom_dir.exists():
189-
self._load_from_directory(self.custom_dir)
191+
if self.custom_dir:
192+
if self.custom_dir.exists():
193+
self._load_from_directory(self.custom_dir)
194+
else:
195+
logger.warning(
196+
f"Custom template directory does not exist, using default prompt."
197+
)
190198

191199
def _load_from_directory(self, directory: Path) -> None:
192200
"""Load all templates and fragments from a directory"""

openevolve/utils/code_utils.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,32 @@ def parse_full_rewrite(llm_response: str, language: str = "python") -> Optional[
120120
return llm_response
121121

122122

123-
def format_diff_summary(diff_blocks: List[Tuple[str, str]]) -> str:
123+
def _format_block_lines(lines: List[str], max_line_len: int = 100, max_lines: int = 30) -> str:
124+
"""Format a block of lines for diff summary: show all lines (truncated per line, optional cap)."""
125+
truncated = []
126+
for line in lines[:max_lines]:
127+
s = line.rstrip()
128+
if len(s) > max_line_len:
129+
s = s[: max_line_len - 3] + "..."
130+
truncated.append(" " + s)
131+
if len(lines) > max_lines:
132+
truncated.append(f" ... ({len(lines) - max_lines} more lines)")
133+
return "\n".join(truncated) if truncated else " (empty)"
134+
135+
136+
def format_diff_summary(
137+
diff_blocks: List[Tuple[str, str]],
138+
max_line_len: int = 100,
139+
max_lines: int = 30,
140+
) -> str:
124141
"""
125-
Create a human-readable summary of the diff
142+
Create a human-readable summary of the diff.
143+
For multi-line blocks, shows the full search and replace content (all lines).
126144
127145
Args:
128146
diff_blocks: List of (search_text, replace_text) tuples
147+
max_line_len: Maximum characters per line before truncation (default: 100)
148+
max_lines: Maximum lines per SEARCH/REPLACE block (default: 30)
129149
130150
Returns:
131151
Summary string
@@ -136,17 +156,12 @@ def format_diff_summary(diff_blocks: List[Tuple[str, str]]) -> str:
136156
search_lines = search_text.strip().split("\n")
137157
replace_lines = replace_text.strip().split("\n")
138158

139-
# Create a short summary
140159
if len(search_lines) == 1 and len(replace_lines) == 1:
141160
summary.append(f"Change {i+1}: '{search_lines[0]}' to '{replace_lines[0]}'")
142161
else:
143-
search_summary = (
144-
f"{len(search_lines)} lines" if len(search_lines) > 1 else search_lines[0]
145-
)
146-
replace_summary = (
147-
f"{len(replace_lines)} lines" if len(replace_lines) > 1 else replace_lines[0]
148-
)
149-
summary.append(f"Change {i+1}: Replace {search_summary} with {replace_summary}")
162+
search_block = _format_block_lines(search_lines, max_line_len, max_lines)
163+
replace_block = _format_block_lines(replace_lines, max_line_len, max_lines)
164+
summary.append(f"Change {i+1}: Replace:\n{search_block}\nwith:\n{replace_block}")
150165

151166
return "\n".join(summary)
152167

0 commit comments

Comments
 (0)