weco 0.3.3__py3-none-any.whl → 0.3.5__py3-none-any.whl
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.
- weco/cli.py +12 -1
- weco/optimizer.py +143 -49
- weco/panels.py +5 -0
- weco/utils.py +35 -0
- {weco-0.3.3.dist-info → weco-0.3.5.dist-info}/METADATA +3 -1
- weco-0.3.5.dist-info/RECORD +16 -0
- weco-0.3.3.dist-info/RECORD +0 -16
- {weco-0.3.3.dist-info → weco-0.3.5.dist-info}/WHEEL +0 -0
- {weco-0.3.3.dist-info → weco-0.3.5.dist-info}/entry_points.txt +0 -0
- {weco-0.3.3.dist-info → weco-0.3.5.dist-info}/licenses/LICENSE +0 -0
- {weco-0.3.3.dist-info → weco-0.3.5.dist-info}/top_level.txt +0 -0
weco/cli.py
CHANGED
|
@@ -72,6 +72,11 @@ def configure_run_parser(run_parser: argparse.ArgumentParser) -> None:
|
|
|
72
72
|
action="store_true",
|
|
73
73
|
help="Save execution output to .runs/<run-id>/outputs/step_<n>.out.txt with JSONL index",
|
|
74
74
|
)
|
|
75
|
+
run_parser.add_argument(
|
|
76
|
+
"--apply-change",
|
|
77
|
+
action="store_true",
|
|
78
|
+
help="Automatically apply the best solution to the source file without prompting",
|
|
79
|
+
)
|
|
75
80
|
|
|
76
81
|
|
|
77
82
|
def configure_credits_parser(credits_parser: argparse.ArgumentParser) -> None:
|
|
@@ -118,6 +123,11 @@ def configure_resume_parser(resume_parser: argparse.ArgumentParser) -> None:
|
|
|
118
123
|
resume_parser.add_argument(
|
|
119
124
|
"run_id", type=str, help="The UUID of the run to resume (e.g., '0002e071-1b67-411f-a514-36947f0c4b31')"
|
|
120
125
|
)
|
|
126
|
+
resume_parser.add_argument(
|
|
127
|
+
"--apply-change",
|
|
128
|
+
action="store_true",
|
|
129
|
+
help="Automatically apply the best solution to the source file without prompting",
|
|
130
|
+
)
|
|
121
131
|
|
|
122
132
|
|
|
123
133
|
def execute_run_command(args: argparse.Namespace) -> None:
|
|
@@ -136,6 +146,7 @@ def execute_run_command(args: argparse.Namespace) -> None:
|
|
|
136
146
|
console=console,
|
|
137
147
|
eval_timeout=args.eval_timeout,
|
|
138
148
|
save_logs=args.save_logs,
|
|
149
|
+
apply_change=args.apply_change,
|
|
139
150
|
)
|
|
140
151
|
exit_code = 0 if success else 1
|
|
141
152
|
sys.exit(exit_code)
|
|
@@ -145,7 +156,7 @@ def execute_resume_command(args: argparse.Namespace) -> None:
|
|
|
145
156
|
"""Execute the 'weco resume' command with all its logic."""
|
|
146
157
|
from .optimizer import resume_optimization
|
|
147
158
|
|
|
148
|
-
success = resume_optimization(run_id=args.run_id, console=console)
|
|
159
|
+
success = resume_optimization(run_id=args.run_id, console=console, apply_change=args.apply_change)
|
|
149
160
|
sys.exit(0 if success else 1)
|
|
150
161
|
|
|
151
162
|
|
weco/optimizer.py
CHANGED
|
@@ -30,7 +30,7 @@ from .panels import (
|
|
|
30
30
|
create_optimization_layout,
|
|
31
31
|
create_end_optimization_layout,
|
|
32
32
|
)
|
|
33
|
-
from .utils import read_additional_instructions, read_from_path, write_to_path,
|
|
33
|
+
from .utils import read_additional_instructions, read_from_path, write_to_path, run_evaluation_with_file_swap, smooth_update
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def save_execution_output(runs_dir: pathlib.Path, step: int, output: str) -> None:
|
|
@@ -134,6 +134,7 @@ def execute_optimization(
|
|
|
134
134
|
console: Optional[Console] = None,
|
|
135
135
|
eval_timeout: Optional[int] = None,
|
|
136
136
|
save_logs: bool = False,
|
|
137
|
+
apply_change: bool = False,
|
|
137
138
|
) -> bool:
|
|
138
139
|
"""
|
|
139
140
|
Execute the core optimization logic.
|
|
@@ -143,17 +144,25 @@ def execute_optimization(
|
|
|
143
144
|
"""
|
|
144
145
|
if console is None:
|
|
145
146
|
console = Console()
|
|
146
|
-
|
|
147
147
|
# Global variables for this optimization run
|
|
148
148
|
heartbeat_thread = None
|
|
149
149
|
stop_heartbeat_event = threading.Event()
|
|
150
150
|
current_run_id_for_heartbeat = None
|
|
151
151
|
current_auth_headers_for_heartbeat = {}
|
|
152
|
+
live_ref = None # Reference to the Live object for the optimization run
|
|
153
|
+
|
|
154
|
+
best_solution_code = None
|
|
155
|
+
original_source_code = None
|
|
152
156
|
|
|
153
157
|
# --- Signal Handler for this optimization run ---
|
|
154
158
|
def signal_handler(signum, frame):
|
|
159
|
+
nonlocal live_ref
|
|
160
|
+
|
|
161
|
+
if live_ref is not None:
|
|
162
|
+
live_ref.stop() # Stop the live update loop so that messages are printed to the console
|
|
163
|
+
|
|
155
164
|
signal_name = signal.Signals(signum).name
|
|
156
|
-
console.print(f"\n[bold yellow]Termination signal ({signal_name}) received. Shutting down...[/]")
|
|
165
|
+
console.print(f"\n[bold yellow]Termination signal ({signal_name}) received. Shutting down...[/]\n")
|
|
157
166
|
|
|
158
167
|
# Stop heartbeat thread
|
|
159
168
|
stop_heartbeat_event.set()
|
|
@@ -169,7 +178,7 @@ def execute_optimization(
|
|
|
169
178
|
details=f"Process terminated by signal {signal_name} ({signum}).",
|
|
170
179
|
auth_headers=current_auth_headers_for_heartbeat,
|
|
171
180
|
)
|
|
172
|
-
console.print(f"
|
|
181
|
+
console.print(f"[cyan]To resume this run, use:[/] [bold cyan]weco resume {current_run_id_for_heartbeat}[/]\n")
|
|
173
182
|
|
|
174
183
|
# Exit gracefully
|
|
175
184
|
sys.exit(0)
|
|
@@ -209,6 +218,7 @@ def execute_optimization(
|
|
|
209
218
|
processed_additional_instructions = read_additional_instructions(additional_instructions=additional_instructions)
|
|
210
219
|
source_fp = pathlib.Path(source)
|
|
211
220
|
source_code = read_from_path(fp=source_fp, is_json=False)
|
|
221
|
+
original_source_code = source_code
|
|
212
222
|
|
|
213
223
|
# --- Panel Initialization ---
|
|
214
224
|
summary_panel = SummaryPanel(maximize=maximize, metric_name=metric, total_steps=steps, model=model, runs_dir=log_dir)
|
|
@@ -252,6 +262,7 @@ def execute_optimization(
|
|
|
252
262
|
# --- Live Update Loop ---
|
|
253
263
|
refresh_rate = 4
|
|
254
264
|
with Live(layout, refresh_per_second=refresh_rate) as live:
|
|
265
|
+
live_ref = live
|
|
255
266
|
# Define the runs directory (.runs/<run-id>) to store logs and results
|
|
256
267
|
runs_dir = pathlib.Path(log_dir) / run_id
|
|
257
268
|
runs_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -272,10 +283,6 @@ def execute_optimization(
|
|
|
272
283
|
}
|
|
273
284
|
with open(jsonl_file, "w", encoding="utf-8") as f:
|
|
274
285
|
f.write(json.dumps(metadata) + "\n")
|
|
275
|
-
# Write the initial code string to the logs
|
|
276
|
-
write_to_path(fp=runs_dir / f"step_0{source_fp.suffix}", content=run_response["code"])
|
|
277
|
-
# Write the initial code string to the source file path
|
|
278
|
-
write_to_path(fp=source_fp, content=run_response["code"])
|
|
279
286
|
|
|
280
287
|
# Update the panels with the initial solution
|
|
281
288
|
# Add run id and run name now that we have it
|
|
@@ -307,6 +314,7 @@ def execute_optimization(
|
|
|
307
314
|
best_node=None,
|
|
308
315
|
)
|
|
309
316
|
current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=0)
|
|
317
|
+
|
|
310
318
|
# Update the live layout with the initial solution panels
|
|
311
319
|
smooth_update(
|
|
312
320
|
live=live,
|
|
@@ -321,8 +329,17 @@ def execute_optimization(
|
|
|
321
329
|
transition_delay=0.1,
|
|
322
330
|
)
|
|
323
331
|
|
|
324
|
-
#
|
|
325
|
-
|
|
332
|
+
# Write the initial code string to the logs
|
|
333
|
+
write_to_path(fp=runs_dir / f"step_0{source_fp.suffix}", content=run_response["code"])
|
|
334
|
+
# Run evaluation on the initial solution (file swap ensures original is restored)
|
|
335
|
+
term_out = run_evaluation_with_file_swap(
|
|
336
|
+
file_path=source_fp,
|
|
337
|
+
new_content=run_response["code"],
|
|
338
|
+
original_content=source_code,
|
|
339
|
+
eval_command=eval_command,
|
|
340
|
+
timeout=eval_timeout,
|
|
341
|
+
)
|
|
342
|
+
|
|
326
343
|
# Save logs if requested
|
|
327
344
|
if save_logs:
|
|
328
345
|
save_execution_output(runs_dir, step=0, output=term_out)
|
|
@@ -358,8 +375,7 @@ def execute_optimization(
|
|
|
358
375
|
)
|
|
359
376
|
# Save next solution (.runs/<run-id>/step_<step>.<extension>)
|
|
360
377
|
write_to_path(fp=runs_dir / f"step_{step}{source_fp.suffix}", content=eval_and_next_solution_response["code"])
|
|
361
|
-
|
|
362
|
-
write_to_path(fp=source_fp, content=eval_and_next_solution_response["code"])
|
|
378
|
+
|
|
363
379
|
status_response = get_optimization_run_status(
|
|
364
380
|
console=console, run_id=run_id, include_history=True, auth_headers=auth_headers
|
|
365
381
|
)
|
|
@@ -378,6 +394,16 @@ def execute_optimization(
|
|
|
378
394
|
status_response=status_response, solution_id=eval_and_next_solution_response["solution_id"]
|
|
379
395
|
)
|
|
380
396
|
|
|
397
|
+
# Set best solution and save optimization results
|
|
398
|
+
try:
|
|
399
|
+
best_solution_code = best_solution_node.code
|
|
400
|
+
except AttributeError:
|
|
401
|
+
# Can happen if the code was buggy
|
|
402
|
+
best_solution_code = read_from_path(fp=runs_dir / f"step_0{source_fp.suffix}", is_json=False)
|
|
403
|
+
|
|
404
|
+
# Save best solution to .runs/<run-id>/best.<extension>
|
|
405
|
+
write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_code)
|
|
406
|
+
|
|
381
407
|
# Update the solution panels with the current and best solution
|
|
382
408
|
solution_panels.update(current_node=current_solution_node, best_node=best_solution_node)
|
|
383
409
|
current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=step)
|
|
@@ -395,7 +421,16 @@ def execute_optimization(
|
|
|
395
421
|
],
|
|
396
422
|
transition_delay=0.08, # Slightly longer delay for more noticeable transitions
|
|
397
423
|
)
|
|
398
|
-
|
|
424
|
+
|
|
425
|
+
# Run evaluation and restore original code after
|
|
426
|
+
term_out = run_evaluation_with_file_swap(
|
|
427
|
+
file_path=source_fp,
|
|
428
|
+
new_content=eval_and_next_solution_response["code"],
|
|
429
|
+
original_content=source_code,
|
|
430
|
+
eval_command=eval_command,
|
|
431
|
+
timeout=eval_timeout,
|
|
432
|
+
)
|
|
433
|
+
|
|
399
434
|
# Save logs if requested
|
|
400
435
|
if save_logs:
|
|
401
436
|
save_execution_output(runs_dir, step=step, output=term_out)
|
|
@@ -425,10 +460,13 @@ def execute_optimization(
|
|
|
425
460
|
)
|
|
426
461
|
# No need to set any solution to unevaluated since we have finished the optimization
|
|
427
462
|
# and all solutions have been evaluated
|
|
428
|
-
# No
|
|
463
|
+
# No need to update the current solution panel since we have finished the optimization
|
|
429
464
|
# We only need to update the best solution panel
|
|
430
465
|
# Figure out if we have a best solution so far
|
|
431
466
|
best_solution_node = get_best_node_from_status(status_response=status_response)
|
|
467
|
+
best_solution_code = best_solution_node.code
|
|
468
|
+
# Save best solution to .runs/<run-id>/best.<extension>
|
|
469
|
+
write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_code)
|
|
432
470
|
solution_panels.update(current_node=None, best_node=best_solution_node)
|
|
433
471
|
_, best_solution_panel = solution_panels.get_display(current_step=steps)
|
|
434
472
|
# Update the end optimization layout
|
|
@@ -441,18 +479,6 @@ def execute_optimization(
|
|
|
441
479
|
end_optimization_layout["tree"].update(tree_panel.get_display(is_done=True))
|
|
442
480
|
end_optimization_layout["best_solution"].update(best_solution_panel)
|
|
443
481
|
|
|
444
|
-
# Save optimization results
|
|
445
|
-
# If the best solution does not exist or is has not been measured at the end of the optimization
|
|
446
|
-
# save the original solution as the best solution
|
|
447
|
-
if best_solution_node is not None:
|
|
448
|
-
best_solution_content = best_solution_node.code
|
|
449
|
-
else:
|
|
450
|
-
best_solution_content = read_from_path(fp=runs_dir / f"step_0{source_fp.suffix}", is_json=False)
|
|
451
|
-
|
|
452
|
-
# Save best solution to .runs/<run-id>/best.<extension>
|
|
453
|
-
write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_content)
|
|
454
|
-
# write the best solution to the source file
|
|
455
|
-
write_to_path(fp=source_fp, content=best_solution_content)
|
|
456
482
|
# Mark as completed normally for the finally block
|
|
457
483
|
optimization_completed_normally = True
|
|
458
484
|
live.update(end_optimization_layout)
|
|
@@ -464,7 +490,7 @@ def execute_optimization(
|
|
|
464
490
|
except Exception:
|
|
465
491
|
error_message = str(e)
|
|
466
492
|
console.print(Panel(f"[bold red]Error: {error_message}", title="[bold red]Optimization Error", border_style="red"))
|
|
467
|
-
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {run_id}[/]")
|
|
493
|
+
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {run_id}[/]\n")
|
|
468
494
|
# Ensure optimization_completed_normally is False
|
|
469
495
|
optimization_completed_normally = False
|
|
470
496
|
finally:
|
|
@@ -491,6 +517,17 @@ def execute_optimization(
|
|
|
491
517
|
else "CLI terminated unexpectedly without a specific exception captured."
|
|
492
518
|
)
|
|
493
519
|
|
|
520
|
+
if best_solution_code and best_solution_code != original_source_code:
|
|
521
|
+
# Determine whether to apply: automatically if --apply-change is set, otherwise ask user
|
|
522
|
+
should_apply = apply_change or Confirm.ask(
|
|
523
|
+
"Would you like to apply the best solution to the source file?", default=True
|
|
524
|
+
)
|
|
525
|
+
if should_apply:
|
|
526
|
+
write_to_path(fp=source_fp, content=best_solution_code)
|
|
527
|
+
console.print("\n[green]Best solution applied to the source file.[/]\n")
|
|
528
|
+
else:
|
|
529
|
+
console.print("\n[green]A better solution was not found. No changes to apply.[/]\n")
|
|
530
|
+
|
|
494
531
|
report_termination(
|
|
495
532
|
run_id=run_id,
|
|
496
533
|
status_update=status,
|
|
@@ -502,12 +539,12 @@ def execute_optimization(
|
|
|
502
539
|
# Handle exit
|
|
503
540
|
if user_stop_requested_flag:
|
|
504
541
|
console.print("[yellow]Run terminated by user request.[/]")
|
|
505
|
-
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {run_id}[/]")
|
|
542
|
+
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {run_id}[/]\n")
|
|
506
543
|
|
|
507
544
|
return optimization_completed_normally or user_stop_requested_flag
|
|
508
545
|
|
|
509
546
|
|
|
510
|
-
def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
547
|
+
def resume_optimization(run_id: str, console: Optional[Console] = None, apply_change: bool = False) -> bool:
|
|
511
548
|
"""Resume an interrupted run from the most recent node and continue optimization."""
|
|
512
549
|
if console is None:
|
|
513
550
|
console = Console()
|
|
@@ -517,11 +554,19 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
517
554
|
stop_heartbeat_event = threading.Event()
|
|
518
555
|
current_run_id_for_heartbeat = None
|
|
519
556
|
current_auth_headers_for_heartbeat = {}
|
|
557
|
+
live_ref = None # Reference to the Live object for the optimization run
|
|
558
|
+
|
|
559
|
+
best_solution_code = None
|
|
560
|
+
original_source_code = None
|
|
520
561
|
|
|
521
562
|
# Signal handler for this optimization run
|
|
522
563
|
def signal_handler(signum, frame):
|
|
564
|
+
nonlocal live_ref
|
|
565
|
+
if live_ref is not None:
|
|
566
|
+
live_ref.stop() # Stop the live update loop so that messages are printed to the console
|
|
567
|
+
|
|
523
568
|
signal_name = signal.Signals(signum).name
|
|
524
|
-
console.print(f"\n[bold yellow]Termination signal ({signal_name}) received. Shutting down...[/]")
|
|
569
|
+
console.print(f"\n[bold yellow]Termination signal ({signal_name}) received. Shutting down...[/]\n")
|
|
525
570
|
stop_heartbeat_event.set()
|
|
526
571
|
if heartbeat_thread and heartbeat_thread.is_alive():
|
|
527
572
|
heartbeat_thread.join(timeout=2)
|
|
@@ -533,7 +578,7 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
533
578
|
details=f"Process terminated by signal {signal_name} ({signum}).",
|
|
534
579
|
auth_headers=current_auth_headers_for_heartbeat,
|
|
535
580
|
)
|
|
536
|
-
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {current_run_id_for_heartbeat}[/]")
|
|
581
|
+
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {current_run_id_for_heartbeat}[/]\n")
|
|
537
582
|
sys.exit(0)
|
|
538
583
|
|
|
539
584
|
# Set up signal handlers for this run
|
|
@@ -619,11 +664,13 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
619
664
|
save_logs = bool(resume_resp.get("save_logs", False))
|
|
620
665
|
eval_timeout = resume_resp.get("eval_timeout")
|
|
621
666
|
|
|
622
|
-
#
|
|
667
|
+
# Read the original source code from the file before we start modifying it
|
|
623
668
|
source_fp = pathlib.Path(source_path)
|
|
624
669
|
source_fp.parent.mkdir(parents=True, exist_ok=True)
|
|
670
|
+
# Store the original content to restore after each evaluation
|
|
671
|
+
original_source_code = read_from_path(fp=source_fp, is_json=False) if source_fp.exists() else ""
|
|
672
|
+
# The code to restore is the code from the last step of the previous run
|
|
625
673
|
code_to_restore = resume_resp.get("code") or resume_resp.get("source_code") or ""
|
|
626
|
-
write_to_path(fp=source_fp, content=code_to_restore)
|
|
627
674
|
|
|
628
675
|
# Prepare UI panels
|
|
629
676
|
summary_panel = SummaryPanel(
|
|
@@ -649,12 +696,25 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
649
696
|
best_solution_node = get_best_node_from_status(status_response=status)
|
|
650
697
|
current_solution_node = get_node_from_status(status_response=status, solution_id=resume_resp.get("solution_id"))
|
|
651
698
|
|
|
699
|
+
# If there's no best solution yet (baseline evaluation didn't complete),
|
|
700
|
+
# mark the current node as unevaluated so the tree renders correctly
|
|
701
|
+
if best_solution_node is None:
|
|
702
|
+
tree_panel.set_unevaluated_node(node_id=resume_resp.get("solution_id"))
|
|
703
|
+
|
|
652
704
|
# Ensure runs dir exists
|
|
653
705
|
runs_dir = pathlib.Path(log_dir) / resume_resp["run_id"]
|
|
654
706
|
runs_dir.mkdir(parents=True, exist_ok=True)
|
|
655
707
|
# Persist last step's code into logs as step_<current_step>
|
|
656
708
|
write_to_path(fp=runs_dir / f"step_{current_step}{source_fp.suffix}", content=code_to_restore)
|
|
657
709
|
|
|
710
|
+
# Initialize best solution code
|
|
711
|
+
try:
|
|
712
|
+
best_solution_code = best_solution_node.code
|
|
713
|
+
except AttributeError:
|
|
714
|
+
# Edge case: best solution node is not available.
|
|
715
|
+
# This can happen if the user has cancelled the run before even running the baseline solution
|
|
716
|
+
pass # Leave best solution code as None
|
|
717
|
+
|
|
658
718
|
# Start Heartbeat Thread
|
|
659
719
|
stop_heartbeat_event.clear()
|
|
660
720
|
heartbeat_thread = HeartbeatSender(resume_resp["run_id"], auth_headers, stop_heartbeat_event)
|
|
@@ -667,6 +727,7 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
667
727
|
# --- Live UI ---
|
|
668
728
|
refresh_rate = 4
|
|
669
729
|
with Live(layout, refresh_per_second=refresh_rate) as live:
|
|
730
|
+
live_ref = live
|
|
670
731
|
# Initial panels
|
|
671
732
|
current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=current_step)
|
|
672
733
|
# Use backend-provided execution output only (no fallback)
|
|
@@ -689,7 +750,13 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
689
750
|
|
|
690
751
|
# If missing output, evaluate once before first suggest
|
|
691
752
|
if term_out is None or len(term_out.strip()) == 0:
|
|
692
|
-
term_out =
|
|
753
|
+
term_out = run_evaluation_with_file_swap(
|
|
754
|
+
file_path=source_fp,
|
|
755
|
+
new_content=code_to_restore,
|
|
756
|
+
original_content=original_source_code,
|
|
757
|
+
eval_command=eval_command,
|
|
758
|
+
timeout=eval_timeout,
|
|
759
|
+
)
|
|
693
760
|
eval_output_panel.update(output=term_out)
|
|
694
761
|
# Update the evaluation output panel
|
|
695
762
|
smooth_update(
|
|
@@ -727,9 +794,8 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
727
794
|
auth_headers=auth_headers,
|
|
728
795
|
)
|
|
729
796
|
|
|
730
|
-
# Save next solution
|
|
797
|
+
# Save next solution to logs
|
|
731
798
|
write_to_path(fp=runs_dir / f"step_{step}{source_fp.suffix}", content=eval_and_next_solution_response["code"])
|
|
732
|
-
write_to_path(fp=source_fp, content=eval_and_next_solution_response["code"])
|
|
733
799
|
|
|
734
800
|
# Refresh status with history and update panels
|
|
735
801
|
status_response = get_optimization_run_status(
|
|
@@ -744,6 +810,17 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
744
810
|
current_solution_node = get_node_from_status(
|
|
745
811
|
status_response=status_response, solution_id=eval_and_next_solution_response["solution_id"]
|
|
746
812
|
)
|
|
813
|
+
|
|
814
|
+
# Set best solution and save optimization results
|
|
815
|
+
try:
|
|
816
|
+
best_solution_code = best_solution_node.code
|
|
817
|
+
except AttributeError:
|
|
818
|
+
# Can happen if the code was buggy
|
|
819
|
+
best_solution_code = read_from_path(fp=runs_dir / f"step_0{source_fp.suffix}", is_json=False)
|
|
820
|
+
|
|
821
|
+
# Save best solution to .runs/<run-id>/best.<extension>
|
|
822
|
+
write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_code)
|
|
823
|
+
|
|
747
824
|
solution_panels.update(current_node=current_solution_node, best_node=best_solution_node)
|
|
748
825
|
current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=step)
|
|
749
826
|
eval_output_panel.clear()
|
|
@@ -760,8 +837,14 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
760
837
|
transition_delay=0.08,
|
|
761
838
|
)
|
|
762
839
|
|
|
763
|
-
# Evaluate this new solution
|
|
764
|
-
term_out =
|
|
840
|
+
# Evaluate this new solution and restore original code after
|
|
841
|
+
term_out = run_evaluation_with_file_swap(
|
|
842
|
+
file_path=source_fp,
|
|
843
|
+
new_content=eval_and_next_solution_response["code"],
|
|
844
|
+
original_content=original_source_code,
|
|
845
|
+
eval_command=eval_command,
|
|
846
|
+
timeout=eval_timeout,
|
|
847
|
+
)
|
|
765
848
|
if save_logs:
|
|
766
849
|
save_execution_output(runs_dir, step=step, output=term_out)
|
|
767
850
|
eval_output_panel.update(output=term_out)
|
|
@@ -789,6 +872,10 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
789
872
|
tree_panel.build_metric_tree(nodes=nodes_final)
|
|
790
873
|
# Best solution panel and final message
|
|
791
874
|
best_solution_node = get_best_node_from_status(status_response=status_response)
|
|
875
|
+
best_solution_code = best_solution_node.code
|
|
876
|
+
# Save best solution to .runs/<run-id>/best.<extension>
|
|
877
|
+
write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_code)
|
|
878
|
+
|
|
792
879
|
solution_panels.update(current_node=None, best_node=best_solution_node)
|
|
793
880
|
_, best_solution_panel = solution_panels.get_display(current_step=total_steps)
|
|
794
881
|
final_message = (
|
|
@@ -800,14 +887,6 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
800
887
|
end_optimization_layout["tree"].update(tree_panel.get_display(is_done=True))
|
|
801
888
|
end_optimization_layout["best_solution"].update(best_solution_panel)
|
|
802
889
|
|
|
803
|
-
# Save best
|
|
804
|
-
if best_solution_node is not None:
|
|
805
|
-
best_solution_content = best_solution_node.code
|
|
806
|
-
else:
|
|
807
|
-
best_solution_content = read_from_path(fp=runs_dir / f"step_0{source_fp.suffix}", is_json=False)
|
|
808
|
-
|
|
809
|
-
write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_content)
|
|
810
|
-
write_to_path(fp=source_fp, content=best_solution_content)
|
|
811
890
|
optimization_completed_normally = True
|
|
812
891
|
live.update(end_optimization_layout)
|
|
813
892
|
|
|
@@ -817,7 +896,7 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
817
896
|
except Exception:
|
|
818
897
|
error_message = str(e)
|
|
819
898
|
console.print(Panel(f"[bold red]Error: {error_message}", title="[bold red]Optimization Error", border_style="red"))
|
|
820
|
-
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {run_id}[/]")
|
|
899
|
+
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {run_id}[/]\n")
|
|
821
900
|
optimization_completed_normally = False
|
|
822
901
|
finally:
|
|
823
902
|
signal.signal(signal.SIGINT, original_sigint_handler)
|
|
@@ -826,7 +905,11 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
826
905
|
if heartbeat_thread and heartbeat_thread.is_alive():
|
|
827
906
|
heartbeat_thread.join(timeout=2)
|
|
828
907
|
|
|
829
|
-
|
|
908
|
+
try:
|
|
909
|
+
run_id = resume_resp.get("run_id")
|
|
910
|
+
except Exception:
|
|
911
|
+
run_id = None
|
|
912
|
+
|
|
830
913
|
# Report final status if run exists
|
|
831
914
|
if run_id:
|
|
832
915
|
if optimization_completed_normally:
|
|
@@ -840,6 +923,17 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
840
923
|
if "e" in locals() and isinstance(locals()["e"], Exception)
|
|
841
924
|
else "CLI terminated unexpectedly without a specific exception captured."
|
|
842
925
|
)
|
|
926
|
+
|
|
927
|
+
if best_solution_code and best_solution_code != original_source_code:
|
|
928
|
+
should_apply = apply_change or Confirm.ask(
|
|
929
|
+
"Would you like to apply the best solution to the source file?", default=True
|
|
930
|
+
)
|
|
931
|
+
if should_apply:
|
|
932
|
+
write_to_path(fp=source_fp, content=best_solution_code)
|
|
933
|
+
console.print("\n[green]Best solution applied to the source file.[/]\n")
|
|
934
|
+
else:
|
|
935
|
+
console.print("\n[green]A better solution was not found. No changes to apply.[/]\n")
|
|
936
|
+
|
|
843
937
|
report_termination(
|
|
844
938
|
run_id=run_id,
|
|
845
939
|
status_update=status,
|
|
@@ -849,5 +943,5 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
|
|
|
849
943
|
)
|
|
850
944
|
if user_stop_requested_flag:
|
|
851
945
|
console.print("[yellow]Run terminated by user request.[/]")
|
|
852
|
-
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {run_id}[/]")
|
|
946
|
+
console.print(f"\n[cyan]To resume this run, use:[/] [bold cyan]weco resume {run_id}[/]\n")
|
|
853
947
|
return optimization_completed_normally or user_stop_requested_flag
|
weco/panels.py
CHANGED
|
@@ -5,6 +5,8 @@ from rich.layout import Layout
|
|
|
5
5
|
from rich.panel import Panel
|
|
6
6
|
from rich.syntax import Syntax
|
|
7
7
|
from rich import box
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
8
10
|
from typing import Dict, List, Optional, Union, Tuple
|
|
9
11
|
from pathlib import Path
|
|
10
12
|
from .__init__ import __dashboard_url__
|
|
@@ -22,6 +24,7 @@ class SummaryPanel:
|
|
|
22
24
|
runs_dir: str,
|
|
23
25
|
run_id: str = None,
|
|
24
26
|
run_name: str = None,
|
|
27
|
+
console: Optional[Console] = None,
|
|
25
28
|
):
|
|
26
29
|
self.maximize = maximize
|
|
27
30
|
self.metric_name = metric_name
|
|
@@ -32,6 +35,8 @@ class SummaryPanel:
|
|
|
32
35
|
self.run_name = run_name if run_name is not None else "N/A"
|
|
33
36
|
self.dashboard_url = "N/A"
|
|
34
37
|
self.thinking_content = ""
|
|
38
|
+
self.user_input = ""
|
|
39
|
+
self.console = Console()
|
|
35
40
|
self.progress = Progress(
|
|
36
41
|
TextColumn("[progress.description]{task.description}"),
|
|
37
42
|
BarColumn(bar_width=20),
|
weco/utils.py
CHANGED
|
@@ -106,6 +106,41 @@ def truncate_output(output: str) -> str:
|
|
|
106
106
|
return output
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def run_evaluation_with_file_swap(
|
|
110
|
+
file_path: pathlib.Path, new_content: str, original_content: str, eval_command: str, timeout: int | None = None
|
|
111
|
+
) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Temporarily write new content to a file, run evaluation, then restore original.
|
|
114
|
+
|
|
115
|
+
This function ensures the file is always restored to its original state,
|
|
116
|
+
even if an exception occurs during evaluation.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
file_path: Path to the file to temporarily modify
|
|
120
|
+
new_content: The new content to write for evaluation
|
|
121
|
+
original_content: The original content to restore after evaluation
|
|
122
|
+
eval_command: The shell command to run for evaluation
|
|
123
|
+
timeout: Optional timeout for the evaluation command
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The output from running the evaluation command
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
Any exception raised by run_evaluation will be re-raised after
|
|
130
|
+
the file is restored to its original state.
|
|
131
|
+
"""
|
|
132
|
+
# Write the new content
|
|
133
|
+
write_to_path(fp=file_path, content=new_content)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
# Run the evaluation
|
|
137
|
+
output = run_evaluation(eval_command=eval_command, timeout=timeout)
|
|
138
|
+
return output
|
|
139
|
+
finally:
|
|
140
|
+
# Always restore the original file, even if evaluation fails
|
|
141
|
+
write_to_path(fp=file_path, content=original_content)
|
|
142
|
+
|
|
143
|
+
|
|
109
144
|
def run_evaluation(eval_command: str, timeout: int | None = None) -> str:
|
|
110
145
|
"""Run the evaluation command on the code and return the output."""
|
|
111
146
|
process = subprocess.Popen(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weco
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: Documentation for `weco`, a CLI for using Weco AI's code optimizer.
|
|
5
5
|
Author-email: Weco AI Team <contact@weco.ai>
|
|
6
6
|
License:
|
|
@@ -322,6 +322,7 @@ For more advanced examples, including [Triton](/examples/triton/README.md), [CUD
|
|
|
322
322
|
| `-l, --log-dir` | Path to the directory to log intermediate steps and final optimization result. | `.runs/` | `-l ./logs/` |
|
|
323
323
|
| `--eval-timeout` | Timeout in seconds for each step in evaluation. | No timeout (unlimited) | `--eval-timeout 3600` |
|
|
324
324
|
| `--save-logs` | Save execution output from each optimization step to disk. Creates timestamped directories with raw output files and a JSONL index for tracking execution history. | `False` | `--save-logs` |
|
|
325
|
+
| `--apply-change` | Automatically apply the best solution to the source file without prompting. | `False` | `--apply-change` |
|
|
325
326
|
|
|
326
327
|
---
|
|
327
328
|
|
|
@@ -375,6 +376,7 @@ Arguments for `weco resume`:
|
|
|
375
376
|
| Argument | Description | Example |
|
|
376
377
|
|----------|-------------|---------|
|
|
377
378
|
| `run-id` | The UUID of the run to resume (shown at the start of each run) | `0002e071-1b67-411f-a514-36947f0c4b31` |
|
|
379
|
+
| `--apply-change` | Automatically apply the best solution to the source file without prompting | `--apply-change` |
|
|
378
380
|
|
|
379
381
|
Notes:
|
|
380
382
|
- Works only for interrupted runs (status: `error`, `terminated`, etc.).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
weco/__init__.py,sha256=ClO0uT6GKOA0iSptvP0xbtdycf0VpoPTq37jHtvlhtw,303
|
|
2
|
+
weco/api.py,sha256=zKcI4riwruK6CjV_vcL8RlsJGRXO40iP0WxeETtzPIY,18430
|
|
3
|
+
weco/auth.py,sha256=O31Hoj-Loi8DWJJG2LfeWgUMuNqAUeGDpd2ZGjA9Ah0,9997
|
|
4
|
+
weco/chatbot.py,sha256=EIK2WaOul9gn_yHLThjsZV7RnE8t3XQPwgRkO5tybSU,38415
|
|
5
|
+
weco/cli.py,sha256=7Q6ZwkD8N2x_PRovaFkF9OvtKkKX3hs_CobvS8xKXkQ,11535
|
|
6
|
+
weco/constants.py,sha256=V6yFugTznKm5EC2_jr4I_whd7sqI80HiPggRn0az580,406
|
|
7
|
+
weco/credits.py,sha256=C08x-TRcLg3ccfKqMGNRY7zBn7t3r7LZ119bxgfztaI,7629
|
|
8
|
+
weco/optimizer.py,sha256=wG8_ZMF-SszWK-9rPXyaWvl60Q7EMTJbksSpKxf2pUk,45217
|
|
9
|
+
weco/panels.py,sha256=POHt0MdRKDykwUJYXcry92O41lpB9gxna55wFI9abWU,16272
|
|
10
|
+
weco/utils.py,sha256=SMFh7ngo6gsjb2g20wpPe0O0T8xR7U_7X4-kqxJHkGc,8308
|
|
11
|
+
weco-0.3.5.dist-info/licenses/LICENSE,sha256=9LUfoGHjLPtak2zps2kL2tm65HAZIICx_FbLaRuS4KU,11337
|
|
12
|
+
weco-0.3.5.dist-info/METADATA,sha256=OvCAH5Rg2rTFlUkqBkHYEAYx09ZTErUdL4npETqQdnk,29244
|
|
13
|
+
weco-0.3.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
weco-0.3.5.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
|
|
15
|
+
weco-0.3.5.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
16
|
+
weco-0.3.5.dist-info/RECORD,,
|
weco-0.3.3.dist-info/RECORD
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
weco/__init__.py,sha256=ClO0uT6GKOA0iSptvP0xbtdycf0VpoPTq37jHtvlhtw,303
|
|
2
|
-
weco/api.py,sha256=zKcI4riwruK6CjV_vcL8RlsJGRXO40iP0WxeETtzPIY,18430
|
|
3
|
-
weco/auth.py,sha256=O31Hoj-Loi8DWJJG2LfeWgUMuNqAUeGDpd2ZGjA9Ah0,9997
|
|
4
|
-
weco/chatbot.py,sha256=EIK2WaOul9gn_yHLThjsZV7RnE8t3XQPwgRkO5tybSU,38415
|
|
5
|
-
weco/cli.py,sha256=5TusCKQ8o4CUdqxOSkvWyjMzt86O9sj21SJoeU0Ni5w,11098
|
|
6
|
-
weco/constants.py,sha256=V6yFugTznKm5EC2_jr4I_whd7sqI80HiPggRn0az580,406
|
|
7
|
-
weco/credits.py,sha256=C08x-TRcLg3ccfKqMGNRY7zBn7t3r7LZ119bxgfztaI,7629
|
|
8
|
-
weco/optimizer.py,sha256=nOKFmwPdFLcQ7RF4ielsD7iRfPMxvGr07pS9ocbW9C8,41282
|
|
9
|
-
weco/panels.py,sha256=fnGPtmvxpx21AuBCtCFu1f_BpSxybNr2lhjIIKIutrY,16133
|
|
10
|
-
weco/utils.py,sha256=erDDrA_g3KSlel6YEAGALlV_k8ftT-VQnPT1BrmzK8k,7021
|
|
11
|
-
weco-0.3.3.dist-info/licenses/LICENSE,sha256=9LUfoGHjLPtak2zps2kL2tm65HAZIICx_FbLaRuS4KU,11337
|
|
12
|
-
weco-0.3.3.dist-info/METADATA,sha256=aAE9oMp_SKJiQSG189k7fsb5ovvidj2t1DTuqNfvc68,28700
|
|
13
|
-
weco-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
weco-0.3.3.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
|
|
15
|
-
weco-0.3.3.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
16
|
-
weco-0.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|