claude-evolve 1.9.6 → 1.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
Binary file
|
|
Binary file
|
package/lib/evolve_run.py
CHANGED
|
@@ -63,12 +63,9 @@ class WorkerPool:
|
|
|
63
63
|
cmd.extend(['--timeout', str(self.timeout)])
|
|
64
64
|
|
|
65
65
|
try:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
stderr=subprocess.STDOUT,
|
|
70
|
-
text=True
|
|
71
|
-
)
|
|
66
|
+
# Don't capture output - let it stream directly to terminal
|
|
67
|
+
# This provides real-time visibility into which models are being used
|
|
68
|
+
proc = subprocess.Popen(cmd)
|
|
72
69
|
self.workers[proc.pid] = proc
|
|
73
70
|
print(f"[RUN] Spawned worker {proc.pid}", file=sys.stderr)
|
|
74
71
|
return proc.pid
|
|
@@ -86,14 +83,6 @@ class WorkerPool:
|
|
|
86
83
|
if ret is not None:
|
|
87
84
|
finished_pids.append(pid)
|
|
88
85
|
exit_codes.append(ret)
|
|
89
|
-
|
|
90
|
-
# Log output
|
|
91
|
-
if proc.stdout:
|
|
92
|
-
output = proc.stdout.read()
|
|
93
|
-
if output:
|
|
94
|
-
for line in output.strip().split('\n'):
|
|
95
|
-
print(f"[WORKER-{pid}] {line}", file=sys.stderr)
|
|
96
|
-
|
|
97
86
|
print(f"[RUN] Worker {pid} exited with code {ret}", file=sys.stderr)
|
|
98
87
|
|
|
99
88
|
for pid in finished_pids:
|
package/lib/evolve_worker.py
CHANGED
|
@@ -31,7 +31,7 @@ SCRIPT_DIR = Path(__file__).parent
|
|
|
31
31
|
sys.path.insert(0, str(SCRIPT_DIR.parent))
|
|
32
32
|
|
|
33
33
|
from lib.evolution_csv import EvolutionCSV
|
|
34
|
-
from lib.ai_cli import
|
|
34
|
+
from lib.ai_cli import call_ai_with_backoff, get_git_protection_warning, AIError
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@dataclass
|
|
@@ -46,8 +46,11 @@ class Config:
|
|
|
46
46
|
python_cmd: str = "python3"
|
|
47
47
|
memory_limit_mb: int = 0
|
|
48
48
|
timeout_seconds: int = 600
|
|
49
|
-
max_ai_retries: int = 3
|
|
50
49
|
max_candidates: int = 5
|
|
50
|
+
# Retry configuration with exponential backoff
|
|
51
|
+
max_rounds: int = 10
|
|
52
|
+
initial_wait: int = 60
|
|
53
|
+
max_wait: int = 600
|
|
51
54
|
|
|
52
55
|
|
|
53
56
|
@dataclass
|
|
@@ -139,52 +142,42 @@ This is especially important for models with smaller context windows (like GLM).
|
|
|
139
142
|
|
|
140
143
|
CRITICAL: If you do not know how to implement what was asked for, or if the requested change is unclear or not feasible, you MUST refuse to make any changes. DO NOT modify the code if you are uncertain about the implementation. Simply respond that you cannot implement the requested change and explain why. It is better to refuse than to make incorrect or random changes."""
|
|
141
144
|
|
|
142
|
-
def
|
|
145
|
+
def _call_ai_with_backoff(self, prompt: str, target_file: Path) -> Tuple[bool, str]:
|
|
143
146
|
"""
|
|
144
|
-
Call AI with
|
|
147
|
+
Call AI with round-based retry and exponential backoff.
|
|
148
|
+
|
|
149
|
+
AIDEV-NOTE: Uses call_ai_with_backoff which tries all models in the pool,
|
|
150
|
+
then waits with exponential backoff if all fail, and repeats.
|
|
145
151
|
|
|
146
152
|
Returns:
|
|
147
153
|
Tuple of (success, model_name)
|
|
148
154
|
"""
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
# Get file hash before AI call
|
|
156
|
+
hash_before = self._file_hash(target_file) if target_file.exists() else None
|
|
151
157
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
try:
|
|
159
|
+
output, model = call_ai_with_backoff(
|
|
160
|
+
prompt,
|
|
161
|
+
command="run",
|
|
162
|
+
working_dir=self.config.evolution_dir,
|
|
163
|
+
max_rounds=self.config.max_rounds,
|
|
164
|
+
initial_wait=self.config.initial_wait,
|
|
165
|
+
max_wait=self.config.max_wait
|
|
166
|
+
)
|
|
156
167
|
|
|
157
|
-
#
|
|
158
|
-
|
|
168
|
+
# Check if file was modified
|
|
169
|
+
hash_after = self._file_hash(target_file) if target_file.exists() else None
|
|
159
170
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
print(f"[WORKER-{os.getpid()}] AI did not modify file", file=sys.stderr)
|
|
171
|
-
|
|
172
|
-
except RateLimitError as e:
|
|
173
|
-
print(f"[WORKER-{os.getpid()}] Rate limit: {e}", file=sys.stderr)
|
|
174
|
-
raise # Propagate to caller
|
|
175
|
-
except APIExhaustedError as e:
|
|
176
|
-
print(f"[WORKER-{os.getpid()}] API exhausted: {e}", file=sys.stderr)
|
|
177
|
-
raise # Propagate to caller
|
|
178
|
-
except TimeoutError as e:
|
|
179
|
-
print(f"[WORKER-{os.getpid()}] Timeout: {e}", file=sys.stderr)
|
|
180
|
-
except AIError as e:
|
|
181
|
-
print(f"[WORKER-{os.getpid()}] AI error: {e}", file=sys.stderr)
|
|
182
|
-
|
|
183
|
-
if attempt < self.config.max_ai_retries:
|
|
184
|
-
print(f"[WORKER-{os.getpid()}] Will retry with different model...", file=sys.stderr)
|
|
185
|
-
time.sleep(2)
|
|
186
|
-
|
|
187
|
-
return False, ""
|
|
171
|
+
if hash_before != hash_after and hash_after is not None:
|
|
172
|
+
print(f"[WORKER-{os.getpid()}] AI successfully modified file (model: {model})", file=sys.stderr)
|
|
173
|
+
return True, model
|
|
174
|
+
else:
|
|
175
|
+
print(f"[WORKER-{os.getpid()}] AI completed but did not modify file", file=sys.stderr)
|
|
176
|
+
return False, model
|
|
177
|
+
|
|
178
|
+
except AIError as e:
|
|
179
|
+
print(f"[WORKER-{os.getpid()}] All AI retries exhausted: {e}", file=sys.stderr)
|
|
180
|
+
return False, ""
|
|
188
181
|
|
|
189
182
|
def _file_hash(self, path: Path) -> Optional[str]:
|
|
190
183
|
"""Get file hash."""
|
|
@@ -329,33 +322,19 @@ CRITICAL: If you do not know how to implement what was asked for, or if the requ
|
|
|
329
322
|
print(f"[WORKER-{os.getpid()}] Copying {source_file} to {target_file}", file=sys.stderr)
|
|
330
323
|
shutil.copy(source_file, target_file)
|
|
331
324
|
|
|
332
|
-
# Call AI to modify
|
|
325
|
+
# Call AI to modify (uses round-based retry with backoff)
|
|
333
326
|
prompt = self._build_prompt(candidate, target_file.name)
|
|
327
|
+
success, model = self._call_ai_with_backoff(prompt, target_file)
|
|
334
328
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if not success:
|
|
339
|
-
print(f"[WORKER-{os.getpid()}] AI failed after all retries", file=sys.stderr)
|
|
340
|
-
target_file.unlink(missing_ok=True)
|
|
341
|
-
return 77 # AI generation failed
|
|
342
|
-
|
|
343
|
-
# Record model used
|
|
344
|
-
if model:
|
|
345
|
-
with EvolutionCSV(self.config.csv_path) as csv:
|
|
346
|
-
csv.update_candidate_field(candidate.id, 'run-LLM', model)
|
|
347
|
-
|
|
348
|
-
except RateLimitError:
|
|
329
|
+
if not success:
|
|
330
|
+
print(f"[WORKER-{os.getpid()}] AI failed after all retries", file=sys.stderr)
|
|
349
331
|
target_file.unlink(missing_ok=True)
|
|
350
|
-
|
|
351
|
-
csv.update_candidate_status(candidate.id, 'pending')
|
|
352
|
-
return 2 # Rate limit
|
|
332
|
+
return 77 # AI generation failed
|
|
353
333
|
|
|
354
|
-
|
|
355
|
-
|
|
334
|
+
# Record model used
|
|
335
|
+
if model:
|
|
356
336
|
with EvolutionCSV(self.config.csv_path) as csv:
|
|
357
|
-
csv.
|
|
358
|
-
return 3 # API exhausted
|
|
337
|
+
csv.update_candidate_field(candidate.id, 'run-LLM', model)
|
|
359
338
|
|
|
360
339
|
# Check syntax
|
|
361
340
|
if not self._check_syntax(target_file):
|
|
@@ -475,6 +454,8 @@ def load_config_from_yaml(config_path: Optional[str] = None) -> Config:
|
|
|
475
454
|
p = base_dir / p
|
|
476
455
|
return str(p.resolve())
|
|
477
456
|
|
|
457
|
+
ideation = data.get('ideation', {})
|
|
458
|
+
|
|
478
459
|
return Config(
|
|
479
460
|
csv_path=resolve(data.get('csv_file', 'evolution.csv')),
|
|
480
461
|
evolution_dir=str(base_dir.resolve()),
|
|
@@ -485,8 +466,10 @@ def load_config_from_yaml(config_path: Optional[str] = None) -> Config:
|
|
|
485
466
|
python_cmd=data.get('python_cmd', 'python3'),
|
|
486
467
|
memory_limit_mb=data.get('memory_limit_mb', 0),
|
|
487
468
|
timeout_seconds=data.get('timeout_seconds', 600),
|
|
488
|
-
|
|
489
|
-
|
|
469
|
+
max_candidates=data.get('worker_max_candidates', 5),
|
|
470
|
+
max_rounds=ideation.get('max_rounds', 10),
|
|
471
|
+
initial_wait=ideation.get('initial_wait', 60),
|
|
472
|
+
max_wait=ideation.get('max_wait', 600)
|
|
490
473
|
)
|
|
491
474
|
|
|
492
475
|
|