Skip to content

Commit 646c981

Browse files
committed
Merge branch 'main' into fix-island-placement
2 parents 17b0c07 + e13e7f6 commit 646c981

File tree

6 files changed

+179
-34
lines changed

6 files changed

+179
-34
lines changed

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: 7 additions & 1 deletion
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

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: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,7 @@ def _run_iteration_worker(
171171
# Build prompt
172172
if _worker_config.prompt.programs_as_changes_description:
173173
parent_changes_desc = (
174-
parent.changes_description
175-
or _worker_config.prompt.initial_changes_description
174+
parent.changes_description or _worker_config.prompt.initial_changes_description
176175
)
177176
child_changes_desc = parent_changes_desc
178177
else:
@@ -224,7 +223,9 @@ def _run_iteration_worker(
224223

225224
diff_blocks = extract_diffs(llm_response, _worker_config.diff_pattern)
226225
if not diff_blocks:
227-
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+
)
228229

229230
if _worker_config.prompt.programs_as_changes_description:
230231
try:
@@ -237,20 +238,34 @@ def _run_iteration_worker(
237238
return SerializableResult(error=str(e), iteration=iteration)
238239

239240
child_code, _ = apply_diff_blocks(parent.code, code_blocks)
240-
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+
)
241244

242245
# Must update the previous changes description
243-
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+
):
244251
return SerializableResult(
245252
error="changes_description was not updated or empty, program is discarded",
246253
iteration=iteration,
247254
)
248255

249-
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+
)
250261
else:
251262
# All diffs applied only to code
252263
child_code = apply_diff(parent.code, llm_response, _worker_config.diff_pattern)
253-
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+
)
254269
else:
255270
from openevolve.utils.code_utils import parse_full_rewrite
256271

@@ -598,10 +613,8 @@ async def run_evolution(
598613

599614
# Island management
600615
# get current program island id
601-
island_id = child_program.metadata.get(
602-
"island", self.database.current_island
603-
)
604-
#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
605618
self.database.increment_island_generation(island_idx=island_id)
606619

607620
# Check migration
@@ -719,7 +732,7 @@ async def run_evolution(
719732
f"(best score: {best_score:.4f})"
720733
)
721734
break
722-
735+
723736
else:
724737
# Event-based early stopping
725738
if current_score == self.config.convergence_threshold:

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

tests/test_code_utils.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
"""
44

55
import unittest
6-
from openevolve.utils.code_utils import apply_diff, extract_diffs
6+
7+
from openevolve.utils.code_utils import (
8+
_format_block_lines,
9+
apply_diff,
10+
extract_diffs,
11+
format_diff_summary,
12+
)
713

814

915
class TestCodeUtils(unittest.TestCase):
@@ -89,5 +95,92 @@ def hello():
8995
)
9096

9197

98+
class TestFormatDiffSummary(unittest.TestCase):
99+
"""Tests for format_diff_summary showing actual diff content"""
100+
101+
def test_single_line_changes(self):
102+
"""Single-line changes should show inline format"""
103+
diff_blocks = [("x = 1", "x = 2")]
104+
result = format_diff_summary(diff_blocks)
105+
self.assertEqual(result, "Change 1: 'x = 1' to 'x = 2'")
106+
107+
def test_multi_line_changes_show_actual_content(self):
108+
"""Multi-line changes should show actual SEARCH/REPLACE content"""
109+
diff_blocks = [
110+
(
111+
"def old():\n return False",
112+
"def new():\n return True",
113+
)
114+
]
115+
result = format_diff_summary(diff_blocks)
116+
# Should contain actual code, not "2 lines"
117+
self.assertIn("def old():", result)
118+
self.assertIn("return False", result)
119+
self.assertIn("def new():", result)
120+
self.assertIn("return True", result)
121+
self.assertIn("Replace:", result)
122+
self.assertIn("with:", result)
123+
# Should NOT contain generic line count
124+
self.assertNotIn("2 lines", result)
125+
126+
def test_multiple_diff_blocks(self):
127+
"""Multiple diff blocks should be numbered"""
128+
diff_blocks = [
129+
("a = 1", "a = 2"),
130+
("def foo():\n pass", "def bar():\n return 1"),
131+
]
132+
result = format_diff_summary(diff_blocks)
133+
self.assertIn("Change 1:", result)
134+
self.assertIn("Change 2:", result)
135+
self.assertIn("'a = 1' to 'a = 2'", result)
136+
self.assertIn("def foo():", result)
137+
self.assertIn("def bar():", result)
138+
139+
def test_configurable_max_line_len(self):
140+
"""max_line_len parameter should control line truncation"""
141+
long_line = "x" * 50
142+
# Must be multi-line to trigger block format (single-line uses inline format)
143+
diff_blocks = [(long_line + "\nline2", "short\nline2")]
144+
# With default (100), no truncation
145+
result_default = format_diff_summary(diff_blocks)
146+
self.assertNotIn("...", result_default)
147+
# With max_line_len=30, should truncate the long line
148+
result_short = format_diff_summary(diff_blocks, max_line_len=30)
149+
self.assertIn("...", result_short)
150+
151+
def test_configurable_max_lines(self):
152+
"""max_lines parameter should control block truncation"""
153+
many_lines = "\n".join([f"line{i}" for i in range(20)])
154+
diff_blocks = [(many_lines, "replacement")]
155+
# With max_lines=10, should truncate
156+
result = format_diff_summary(diff_blocks, max_lines=10)
157+
self.assertIn("... (10 more lines)", result)
158+
159+
def test_block_lines_basic_formatting(self):
160+
"""Lines should be indented with 2 spaces"""
161+
lines = ["line1", "line2"]
162+
result = _format_block_lines(lines)
163+
self.assertEqual(result, " line1\n line2")
164+
165+
def test_block_lines_long_line_truncation(self):
166+
"""Lines over 100 chars should be truncated by default"""
167+
long_line = "x" * 150
168+
result = _format_block_lines([long_line])
169+
self.assertIn("...", result)
170+
self.assertLess(len(result.split("\n")[0]), 110)
171+
172+
def test_block_lines_many_lines_truncation(self):
173+
"""More than 30 lines should show truncation message by default"""
174+
lines = [f"line{i}" for i in range(50)]
175+
result = _format_block_lines(lines)
176+
self.assertIn("... (20 more lines)", result)
177+
self.assertEqual(len(result.split("\n")), 31)
178+
179+
def test_block_lines_empty_input(self):
180+
"""Empty input should return '(empty)'"""
181+
result = _format_block_lines([])
182+
self.assertEqual(result, " (empty)")
183+
184+
92185
if __name__ == "__main__":
93186
unittest.main()

0 commit comments

Comments
 (0)