weco 0.2.19__py3-none-any.whl → 0.2.20__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/api.py +24 -40
- weco/cli.py +193 -235
- weco/panels.py +13 -10
- {weco-0.2.19.dist-info → weco-0.2.20.dist-info}/METADATA +4 -1
- weco-0.2.20.dist-info/RECORD +12 -0
- {weco-0.2.19.dist-info → weco-0.2.20.dist-info}/WHEEL +1 -1
- weco-0.2.19.dist-info/RECORD +0 -12
- {weco-0.2.19.dist-info → weco-0.2.20.dist-info}/entry_points.txt +0 -0
- {weco-0.2.19.dist-info → weco-0.2.20.dist-info}/licenses/LICENSE +0 -0
- {weco-0.2.19.dist-info → weco-0.2.20.dist-info}/top_level.txt +0 -0
weco/api.py
CHANGED
|
@@ -17,7 +17,7 @@ def handle_api_error(e: requests.exceptions.HTTPError, console: rich.console.Con
|
|
|
17
17
|
# sys.exit(1)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def
|
|
20
|
+
def start_optimization_run(
|
|
21
21
|
console: rich.console.Console,
|
|
22
22
|
source_code: str,
|
|
23
23
|
evaluation_command: str,
|
|
@@ -29,14 +29,14 @@ def start_optimization_session(
|
|
|
29
29
|
search_policy_config: Dict[str, Any],
|
|
30
30
|
additional_instructions: str = None,
|
|
31
31
|
api_keys: Dict[str, Any] = {},
|
|
32
|
-
auth_headers: dict = {},
|
|
32
|
+
auth_headers: dict = {},
|
|
33
33
|
timeout: int = 800,
|
|
34
34
|
) -> Dict[str, Any]:
|
|
35
|
-
"""Start the optimization
|
|
35
|
+
"""Start the optimization run."""
|
|
36
36
|
with console.status("[bold green]Starting Optimization..."):
|
|
37
37
|
try:
|
|
38
38
|
response = requests.post(
|
|
39
|
-
f"{__base_url__}/
|
|
39
|
+
f"{__base_url__}/runs",
|
|
40
40
|
json={
|
|
41
41
|
"source_code": source_code,
|
|
42
42
|
"additional_instructions": additional_instructions,
|
|
@@ -49,37 +49,37 @@ def start_optimization_session(
|
|
|
49
49
|
},
|
|
50
50
|
"metadata": {"client_name": "cli", "client_version": __pkg_version__, **api_keys},
|
|
51
51
|
},
|
|
52
|
-
headers=auth_headers,
|
|
52
|
+
headers=auth_headers,
|
|
53
53
|
timeout=timeout,
|
|
54
54
|
)
|
|
55
55
|
response.raise_for_status()
|
|
56
56
|
return response.json()
|
|
57
57
|
except requests.exceptions.HTTPError as e:
|
|
58
58
|
handle_api_error(e, console)
|
|
59
|
-
sys.exit(1)
|
|
59
|
+
sys.exit(1)
|
|
60
60
|
except requests.exceptions.RequestException as e:
|
|
61
|
-
console.print(f"[bold red]Network Error starting
|
|
61
|
+
console.print(f"[bold red]Network Error starting run: {e}[/]")
|
|
62
62
|
sys.exit(1)
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def evaluate_feedback_then_suggest_next_solution(
|
|
66
|
-
|
|
66
|
+
run_id: str,
|
|
67
67
|
execution_output: str,
|
|
68
68
|
additional_instructions: str = None,
|
|
69
69
|
api_keys: Dict[str, Any] = {},
|
|
70
|
-
auth_headers: dict = {},
|
|
70
|
+
auth_headers: dict = {},
|
|
71
71
|
timeout: int = 800,
|
|
72
72
|
) -> Dict[str, Any]:
|
|
73
73
|
"""Evaluate the feedback and suggest the next solution."""
|
|
74
74
|
try:
|
|
75
75
|
response = requests.post(
|
|
76
|
-
f"{__base_url__}/
|
|
76
|
+
f"{__base_url__}/runs/{run_id}/suggest",
|
|
77
77
|
json={
|
|
78
78
|
"execution_output": execution_output,
|
|
79
79
|
"additional_instructions": additional_instructions,
|
|
80
80
|
"metadata": {**api_keys},
|
|
81
81
|
},
|
|
82
|
-
headers=auth_headers,
|
|
82
|
+
headers=auth_headers,
|
|
83
83
|
timeout=timeout,
|
|
84
84
|
)
|
|
85
85
|
response.raise_for_status()
|
|
@@ -93,16 +93,13 @@ def evaluate_feedback_then_suggest_next_solution(
|
|
|
93
93
|
raise # Re-raise the exception
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
def
|
|
97
|
-
|
|
96
|
+
def get_optimization_run_status(
|
|
97
|
+
run_id: str, include_history: bool = False, auth_headers: dict = {}, timeout: int = 800
|
|
98
98
|
) -> Dict[str, Any]:
|
|
99
|
-
"""Get the current status of the optimization
|
|
99
|
+
"""Get the current status of the optimization run."""
|
|
100
100
|
try:
|
|
101
101
|
response = requests.get(
|
|
102
|
-
f"{__base_url__}/
|
|
103
|
-
params={"include_history": include_history},
|
|
104
|
-
headers=auth_headers,
|
|
105
|
-
timeout=timeout,
|
|
102
|
+
f"{__base_url__}/runs/{run_id}", params={"include_history": include_history}, headers=auth_headers, timeout=timeout
|
|
106
103
|
)
|
|
107
104
|
response.raise_for_status()
|
|
108
105
|
return response.json()
|
|
@@ -114,42 +111,30 @@ def get_optimization_session_status(
|
|
|
114
111
|
raise # Re-raise
|
|
115
112
|
|
|
116
113
|
|
|
117
|
-
def send_heartbeat(
|
|
118
|
-
session_id: str,
|
|
119
|
-
auth_headers: dict = {},
|
|
120
|
-
timeout: int = 10, # Shorter timeout for non-critical heartbeat
|
|
121
|
-
) -> bool:
|
|
114
|
+
def send_heartbeat(run_id: str, auth_headers: dict = {}, timeout: int = 10) -> bool:
|
|
122
115
|
"""Send a heartbeat signal to the backend."""
|
|
123
116
|
try:
|
|
124
|
-
response = requests.put(f"{__base_url__}/
|
|
125
|
-
response.raise_for_status()
|
|
117
|
+
response = requests.put(f"{__base_url__}/runs/{run_id}/heartbeat", headers=auth_headers, timeout=timeout)
|
|
118
|
+
response.raise_for_status()
|
|
126
119
|
return True
|
|
127
120
|
except requests.exceptions.HTTPError as e:
|
|
128
|
-
# Log non-critical errors like 409 Conflict (session not running)
|
|
129
121
|
if e.response.status_code == 409:
|
|
130
|
-
print(f"Heartbeat ignored:
|
|
122
|
+
print(f"Heartbeat ignored: Run {run_id} is not running.", file=sys.stderr)
|
|
131
123
|
else:
|
|
132
|
-
print(f"Heartbeat failed for
|
|
133
|
-
# Don't exit, just report failure
|
|
124
|
+
print(f"Heartbeat failed for run {run_id}: HTTP {e.response.status_code}", file=sys.stderr)
|
|
134
125
|
return False
|
|
135
126
|
except requests.exceptions.RequestException as e:
|
|
136
|
-
|
|
137
|
-
print(f"Heartbeat network error for session {session_id}: {e}", file=sys.stderr)
|
|
127
|
+
print(f"Heartbeat network error for run {run_id}: {e}", file=sys.stderr)
|
|
138
128
|
return False
|
|
139
129
|
|
|
140
130
|
|
|
141
131
|
def report_termination(
|
|
142
|
-
|
|
143
|
-
status_update: str,
|
|
144
|
-
reason: str,
|
|
145
|
-
details: Optional[str] = None,
|
|
146
|
-
auth_headers: dict = {},
|
|
147
|
-
timeout: int = 30, # Reasonably longer timeout for important termination message
|
|
132
|
+
run_id: str, status_update: str, reason: str, details: Optional[str] = None, auth_headers: dict = {}, timeout: int = 30
|
|
148
133
|
) -> bool:
|
|
149
134
|
"""Report the termination reason to the backend."""
|
|
150
135
|
try:
|
|
151
136
|
response = requests.post(
|
|
152
|
-
f"{__base_url__}/
|
|
137
|
+
f"{__base_url__}/runs/{run_id}/terminate",
|
|
153
138
|
json={"status_update": status_update, "termination_reason": reason, "termination_details": details},
|
|
154
139
|
headers=auth_headers,
|
|
155
140
|
timeout=timeout,
|
|
@@ -157,6 +142,5 @@ def report_termination(
|
|
|
157
142
|
response.raise_for_status()
|
|
158
143
|
return True
|
|
159
144
|
except requests.exceptions.RequestException as e:
|
|
160
|
-
|
|
161
|
-
print(f"Warning: Failed to report termination to backend for session {session_id}: {e}", file=sys.stderr)
|
|
145
|
+
print(f"Warning: Failed to report termination to backend for run {run_id}: {e}", file=sys.stderr)
|
|
162
146
|
return False
|
weco/cli.py
CHANGED
|
@@ -14,9 +14,9 @@ from rich.panel import Panel
|
|
|
14
14
|
from rich.traceback import install
|
|
15
15
|
from rich.prompt import Prompt
|
|
16
16
|
from .api import (
|
|
17
|
-
|
|
17
|
+
start_optimization_run,
|
|
18
18
|
evaluate_feedback_then_suggest_next_solution,
|
|
19
|
-
|
|
19
|
+
get_optimization_run_status,
|
|
20
20
|
handle_api_error,
|
|
21
21
|
send_heartbeat,
|
|
22
22
|
report_termination,
|
|
@@ -51,15 +51,15 @@ console = Console()
|
|
|
51
51
|
# --- Global variable for heartbeat thread ---
|
|
52
52
|
heartbeat_thread = None
|
|
53
53
|
stop_heartbeat_event = threading.Event()
|
|
54
|
-
|
|
54
|
+
current_run_id_for_heartbeat = None
|
|
55
55
|
current_auth_headers_for_heartbeat = {}
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
# --- Heartbeat Sender Class ---
|
|
59
59
|
class HeartbeatSender(threading.Thread):
|
|
60
|
-
def __init__(self,
|
|
60
|
+
def __init__(self, run_id: str, auth_headers: dict, stop_event: threading.Event, interval: int = 30):
|
|
61
61
|
super().__init__(daemon=True) # Daemon thread exits when main thread exits
|
|
62
|
-
self.
|
|
62
|
+
self.run_id = run_id
|
|
63
63
|
self.auth_headers = auth_headers
|
|
64
64
|
self.interval = interval
|
|
65
65
|
self.stop_event = stop_event
|
|
@@ -67,7 +67,7 @@ class HeartbeatSender(threading.Thread):
|
|
|
67
67
|
def run(self):
|
|
68
68
|
try:
|
|
69
69
|
while not self.stop_event.is_set():
|
|
70
|
-
if not send_heartbeat(self.
|
|
70
|
+
if not send_heartbeat(self.run_id, self.auth_headers):
|
|
71
71
|
# send_heartbeat itself prints errors to stderr if it returns False
|
|
72
72
|
# No explicit HeartbeatSender log needed here unless more detail is desired for a False return
|
|
73
73
|
pass # Continue trying as per original logic
|
|
@@ -79,9 +79,7 @@ class HeartbeatSender(threading.Thread):
|
|
|
79
79
|
|
|
80
80
|
except Exception as e:
|
|
81
81
|
# Catch any unexpected error in the loop to prevent silent thread death
|
|
82
|
-
print(
|
|
83
|
-
f"[ERROR HeartbeatSender] Unhandled exception in run loop for session {self.session_id}: {e}", file=sys.stderr
|
|
84
|
-
)
|
|
82
|
+
print(f"[ERROR HeartbeatSender] Unhandled exception in run loop for run {self.run_id}: {e}", file=sys.stderr)
|
|
85
83
|
traceback.print_exc(file=sys.stderr)
|
|
86
84
|
# The loop will break due to the exception, and thread will terminate via finally.
|
|
87
85
|
|
|
@@ -97,13 +95,14 @@ def signal_handler(signum, frame):
|
|
|
97
95
|
heartbeat_thread.join(timeout=2) # Give it a moment to stop
|
|
98
96
|
|
|
99
97
|
# Report termination (best effort)
|
|
100
|
-
if
|
|
98
|
+
if current_run_id_for_heartbeat:
|
|
101
99
|
report_termination(
|
|
102
|
-
|
|
100
|
+
run_id=current_run_id_for_heartbeat,
|
|
103
101
|
status_update="terminated",
|
|
104
102
|
reason=f"user_terminated_{signal_name.lower()}",
|
|
105
103
|
details=f"Process terminated by signal {signal_name} ({signum}).",
|
|
106
104
|
auth_headers=current_auth_headers_for_heartbeat,
|
|
105
|
+
timeout=3,
|
|
107
106
|
)
|
|
108
107
|
|
|
109
108
|
# Exit gracefully
|
|
@@ -158,7 +157,7 @@ def perform_login(console: Console):
|
|
|
158
157
|
|
|
159
158
|
try:
|
|
160
159
|
token_response = requests.post(
|
|
161
|
-
f"{__base_url__}/auth/device/token",
|
|
160
|
+
f"{__base_url__}/auth/device/token",
|
|
162
161
|
json={"grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": device_code},
|
|
163
162
|
)
|
|
164
163
|
|
|
@@ -172,12 +171,10 @@ def perform_login(console: Console):
|
|
|
172
171
|
# Unexpected 202 response format
|
|
173
172
|
console.print(f"\n[bold red]Error:[/] Received unexpected 202 response: {token_data}")
|
|
174
173
|
return False
|
|
175
|
-
|
|
176
174
|
# Check for standard OAuth2 errors (often 400 Bad Request)
|
|
177
175
|
elif token_response.status_code == 400:
|
|
178
176
|
token_data = token_response.json()
|
|
179
177
|
error_code = token_data.get("error", "unknown_error")
|
|
180
|
-
# NOTE: Removed "authorization_pending" check from here
|
|
181
178
|
if error_code == "slow_down":
|
|
182
179
|
interval += 5 # Increase polling interval if instructed
|
|
183
180
|
live_status.update(f"Waiting... (slowing down polling to {interval}s)")
|
|
@@ -195,7 +192,6 @@ def perform_login(console: Console):
|
|
|
195
192
|
|
|
196
193
|
# Check for other non-200/non-202/non-400 HTTP errors
|
|
197
194
|
token_response.raise_for_status()
|
|
198
|
-
|
|
199
195
|
# If successful (200 OK and no 'error' field)
|
|
200
196
|
token_data = token_response.json()
|
|
201
197
|
if "access_token" in token_data:
|
|
@@ -206,19 +202,17 @@ def perform_login(console: Console):
|
|
|
206
202
|
else:
|
|
207
203
|
# Unexpected successful response format
|
|
208
204
|
console.print("\n[bold red]Error:[/] Received unexpected response from server during polling.")
|
|
209
|
-
print(token_data)
|
|
205
|
+
print(token_data)
|
|
210
206
|
return False
|
|
211
|
-
|
|
212
207
|
except requests.exceptions.RequestException as e:
|
|
213
208
|
# Handle network errors during polling gracefully
|
|
214
209
|
live_status.update("Waiting... (network error, retrying)")
|
|
215
210
|
console.print(f"\n[bold yellow]Warning:[/] Network error during polling: {e}. Retrying...")
|
|
216
|
-
# Optional: implement backoff strategy
|
|
217
211
|
time.sleep(interval * 2) # Simple backoff
|
|
218
|
-
|
|
219
|
-
except requests.exceptions.HTTPError as e: # Catch HTTPError specifically for handle_api_error
|
|
212
|
+
except requests.exceptions.HTTPError as e:
|
|
220
213
|
handle_api_error(e, console)
|
|
221
|
-
except requests.exceptions.RequestException as e:
|
|
214
|
+
except requests.exceptions.RequestException as e:
|
|
215
|
+
# Catch other request errors
|
|
222
216
|
console.print(f"\n[bold red]Network Error:[/] {e}")
|
|
223
217
|
return False
|
|
224
218
|
except Exception as e:
|
|
@@ -233,17 +227,15 @@ def main() -> None:
|
|
|
233
227
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
234
228
|
|
|
235
229
|
# --- Perform Update Check ---
|
|
236
|
-
# Import __pkg_version__ here to avoid circular import issues if it's also used in modules imported by cli.py
|
|
237
230
|
from . import __pkg_version__
|
|
238
231
|
|
|
239
|
-
check_for_cli_updates(__pkg_version__)
|
|
232
|
+
check_for_cli_updates(__pkg_version__)
|
|
240
233
|
|
|
241
234
|
# --- Argument Parsing ---
|
|
242
235
|
parser = argparse.ArgumentParser(
|
|
243
236
|
description="[bold cyan]Weco CLI[/]", formatter_class=argparse.RawDescriptionHelpFormatter
|
|
244
237
|
)
|
|
245
|
-
|
|
246
|
-
subparsers = parser.add_subparsers(dest="command", help="Available commands", required=True) # Make command required
|
|
238
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands", required=True)
|
|
247
239
|
|
|
248
240
|
# --- Run Command ---
|
|
249
241
|
run_parser = subparsers.add_parser(
|
|
@@ -298,33 +290,27 @@ def main() -> None:
|
|
|
298
290
|
help="Description of additional instruction or path to a file containing additional instructions. Defaults to None.",
|
|
299
291
|
)
|
|
300
292
|
|
|
301
|
-
# --- Logout Command ---
|
|
302
293
|
_ = subparsers.add_parser("logout", help="Log out from Weco and clear saved API key.")
|
|
303
|
-
|
|
304
294
|
args = parser.parse_args()
|
|
305
295
|
|
|
306
|
-
# --- Handle Logout Command ---
|
|
307
296
|
if args.command == "logout":
|
|
308
297
|
clear_api_key()
|
|
309
298
|
sys.exit(0)
|
|
310
|
-
|
|
311
|
-
# --- Handle Run Command ---
|
|
312
299
|
elif args.command == "run":
|
|
313
|
-
global heartbeat_thread,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
# --- Check Authentication ---
|
|
300
|
+
global heartbeat_thread, current_run_id_for_heartbeat, current_auth_headers_for_heartbeat # Allow modification of globals
|
|
301
|
+
run_id = None # Initialize run_id (we receive this from the API after starting the run)
|
|
302
|
+
optimization_completed_normally = False
|
|
303
|
+
user_stop_requested_flag = False
|
|
318
304
|
weco_api_key = load_weco_api_key()
|
|
319
305
|
llm_api_keys = read_api_keys_from_env() # Read keys from client environment
|
|
320
306
|
|
|
307
|
+
# --- Login/Authentication Handling ---
|
|
321
308
|
if not weco_api_key:
|
|
322
309
|
login_choice = Prompt.ask(
|
|
323
310
|
"Log in to Weco to save run history or use anonymously? ([bold]L[/]ogin / [bold]S[/]kip)",
|
|
324
311
|
choices=["l", "s"],
|
|
325
312
|
default="s",
|
|
326
313
|
).lower()
|
|
327
|
-
|
|
328
314
|
if login_choice == "l":
|
|
329
315
|
console.print("[cyan]Starting login process...[/]")
|
|
330
316
|
if not perform_login(console):
|
|
@@ -334,7 +320,6 @@ def main() -> None:
|
|
|
334
320
|
if not weco_api_key:
|
|
335
321
|
console.print("[bold red]Error: Login completed but failed to retrieve API key.[/]")
|
|
336
322
|
sys.exit(1)
|
|
337
|
-
|
|
338
323
|
elif login_choice == "s":
|
|
339
324
|
console.print("[yellow]Proceeding anonymously. LLM API keys must be provided via environment variables.[/]")
|
|
340
325
|
if not llm_api_keys:
|
|
@@ -343,7 +328,6 @@ def main() -> None:
|
|
|
343
328
|
)
|
|
344
329
|
sys.exit(1)
|
|
345
330
|
|
|
346
|
-
# --- Prepare API Call Arguments ---
|
|
347
331
|
auth_headers = {}
|
|
348
332
|
if weco_api_key:
|
|
349
333
|
auth_headers["Authorization"] = f"Bearer {weco_api_key}"
|
|
@@ -351,7 +335,7 @@ def main() -> None:
|
|
|
351
335
|
|
|
352
336
|
# --- Main Run Logic ---
|
|
353
337
|
try:
|
|
354
|
-
# ---
|
|
338
|
+
# --- Read Command Line Arguments ---
|
|
355
339
|
evaluation_command = args.eval_command
|
|
356
340
|
metric_name = args.metric
|
|
357
341
|
maximize = args.goal in ["maximize", "max"]
|
|
@@ -375,13 +359,9 @@ def main() -> None:
|
|
|
375
359
|
"debug_prob": 0.5,
|
|
376
360
|
"max_debug_depth": max(1, math.ceil(0.1 * steps)),
|
|
377
361
|
}
|
|
378
|
-
# API request timeout
|
|
379
362
|
timeout = 800
|
|
380
|
-
# Read additional instructions
|
|
381
363
|
additional_instructions = read_additional_instructions(additional_instructions=args.additional_instructions)
|
|
382
|
-
# Read source code path
|
|
383
364
|
source_fp = pathlib.Path(args.source)
|
|
384
|
-
# Read source code content
|
|
385
365
|
source_code = read_from_path(fp=source_fp, is_json=False)
|
|
386
366
|
|
|
387
367
|
# --- Panel Initialization ---
|
|
@@ -395,8 +375,8 @@ def main() -> None:
|
|
|
395
375
|
layout = create_optimization_layout()
|
|
396
376
|
end_optimization_layout = create_end_optimization_layout()
|
|
397
377
|
|
|
398
|
-
# --- Start Optimization
|
|
399
|
-
|
|
378
|
+
# --- Start Optimization Run ---
|
|
379
|
+
run_response = start_optimization_run(
|
|
400
380
|
console=console,
|
|
401
381
|
source_code=source_code,
|
|
402
382
|
evaluation_command=evaluation_command,
|
|
@@ -411,42 +391,39 @@ def main() -> None:
|
|
|
411
391
|
auth_headers=auth_headers,
|
|
412
392
|
timeout=timeout,
|
|
413
393
|
)
|
|
414
|
-
|
|
415
|
-
|
|
394
|
+
run_id = run_response["run_id"]
|
|
395
|
+
current_run_id_for_heartbeat = run_id
|
|
416
396
|
|
|
417
397
|
# --- Start Heartbeat Thread ---
|
|
418
|
-
stop_heartbeat_event.clear()
|
|
419
|
-
heartbeat_thread = HeartbeatSender(
|
|
398
|
+
stop_heartbeat_event.clear()
|
|
399
|
+
heartbeat_thread = HeartbeatSender(run_id, auth_headers, stop_heartbeat_event)
|
|
420
400
|
heartbeat_thread.start()
|
|
421
401
|
|
|
422
402
|
# --- Live Update Loop ---
|
|
423
403
|
refresh_rate = 4
|
|
424
404
|
with Live(layout, refresh_per_second=refresh_rate, screen=True) as live:
|
|
425
|
-
# Define the runs directory (.runs/<
|
|
426
|
-
runs_dir = pathlib.Path(args.log_dir) /
|
|
405
|
+
# Define the runs directory (.runs/<run-id>) to store logs and results
|
|
406
|
+
runs_dir = pathlib.Path(args.log_dir) / run_id
|
|
427
407
|
runs_dir.mkdir(parents=True, exist_ok=True)
|
|
428
|
-
|
|
429
408
|
# Write the initial code string to the logs
|
|
430
|
-
write_to_path(fp=runs_dir / f"step_0{source_fp.suffix}", content=
|
|
431
|
-
|
|
409
|
+
write_to_path(fp=runs_dir / f"step_0{source_fp.suffix}", content=run_response["code"])
|
|
432
410
|
# Write the initial code string to the source file path
|
|
433
|
-
write_to_path(fp=source_fp, content=
|
|
411
|
+
write_to_path(fp=source_fp, content=run_response["code"])
|
|
434
412
|
|
|
435
413
|
# Update the panels with the initial solution
|
|
436
|
-
summary_panel.
|
|
414
|
+
summary_panel.set_run_id(run_id=run_id) # Add run id now that we have it
|
|
437
415
|
# Set the step of the progress bar
|
|
438
416
|
summary_panel.set_step(step=0)
|
|
439
417
|
# Update the token counts
|
|
440
|
-
summary_panel.update_token_counts(usage=
|
|
441
|
-
|
|
442
|
-
plan_panel.update(plan=session_response["plan"])
|
|
418
|
+
summary_panel.update_token_counts(usage=run_response["usage"])
|
|
419
|
+
plan_panel.update(plan=run_response["plan"])
|
|
443
420
|
# Build the metric tree
|
|
444
421
|
tree_panel.build_metric_tree(
|
|
445
422
|
nodes=[
|
|
446
423
|
{
|
|
447
|
-
"solution_id":
|
|
424
|
+
"solution_id": run_response["solution_id"],
|
|
448
425
|
"parent_id": None,
|
|
449
|
-
"code":
|
|
426
|
+
"code": run_response["code"],
|
|
450
427
|
"step": 0,
|
|
451
428
|
"metric_value": None,
|
|
452
429
|
"is_buggy": False,
|
|
@@ -454,15 +431,11 @@ def main() -> None:
|
|
|
454
431
|
]
|
|
455
432
|
)
|
|
456
433
|
# Set the current solution as unevaluated since we haven't run the evaluation function and fed it back to the model yet
|
|
457
|
-
tree_panel.set_unevaluated_node(node_id=
|
|
434
|
+
tree_panel.set_unevaluated_node(node_id=run_response["solution_id"])
|
|
458
435
|
# Update the solution panels with the initial solution and get the panel displays
|
|
459
436
|
solution_panels.update(
|
|
460
437
|
current_node=Node(
|
|
461
|
-
id=
|
|
462
|
-
parent_id=None,
|
|
463
|
-
code=session_response["code"],
|
|
464
|
-
metric=None,
|
|
465
|
-
is_buggy=False,
|
|
438
|
+
id=run_response["solution_id"], parent_id=None, code=run_response["code"], metric=None, is_buggy=False
|
|
466
439
|
),
|
|
467
440
|
best_node=None,
|
|
468
441
|
)
|
|
@@ -482,12 +455,8 @@ def main() -> None:
|
|
|
482
455
|
transition_delay=0.1,
|
|
483
456
|
)
|
|
484
457
|
|
|
485
|
-
# # Send initial heartbeat immediately after starting
|
|
486
|
-
# send_heartbeat(session_id, auth_headers)
|
|
487
|
-
|
|
488
458
|
# Run evaluation on the initial solution
|
|
489
459
|
term_out = run_evaluation(eval_command=args.eval_command)
|
|
490
|
-
|
|
491
460
|
# Update the evaluation output panel
|
|
492
461
|
eval_output_panel.update(output=term_out)
|
|
493
462
|
smooth_update(
|
|
@@ -503,40 +472,50 @@ def main() -> None:
|
|
|
503
472
|
current_additional_instructions = read_additional_instructions(
|
|
504
473
|
additional_instructions=args.additional_instructions
|
|
505
474
|
)
|
|
475
|
+
if run_id:
|
|
476
|
+
try:
|
|
477
|
+
current_status_response = get_optimization_run_status(
|
|
478
|
+
run_id=run_id, include_history=False, timeout=30, auth_headers=auth_headers
|
|
479
|
+
)
|
|
480
|
+
current_run_status_val = current_status_response.get("status")
|
|
481
|
+
if current_run_status_val == "stopping":
|
|
482
|
+
console.print("\n[bold yellow]Stop request received. Terminating run gracefully...[/]")
|
|
483
|
+
user_stop_requested_flag = True
|
|
484
|
+
break
|
|
485
|
+
except requests.exceptions.RequestException as e:
|
|
486
|
+
console.print(
|
|
487
|
+
f"\n[bold red]Warning: Could not check run status: {e}. Continuing optimization...[/]"
|
|
488
|
+
)
|
|
489
|
+
except Exception as e:
|
|
490
|
+
console.print(
|
|
491
|
+
f"\n[bold red]Warning: Error checking run status: {e}. Continuing optimization...[/]"
|
|
492
|
+
)
|
|
506
493
|
|
|
507
494
|
# Send feedback and get next suggestion
|
|
508
495
|
eval_and_next_solution_response = evaluate_feedback_then_suggest_next_solution(
|
|
509
|
-
|
|
496
|
+
run_id=run_id,
|
|
510
497
|
execution_output=term_out,
|
|
511
|
-
additional_instructions=current_additional_instructions,
|
|
512
|
-
api_keys=llm_api_keys,
|
|
513
|
-
auth_headers=auth_headers,
|
|
498
|
+
additional_instructions=current_additional_instructions,
|
|
499
|
+
api_keys=llm_api_keys,
|
|
500
|
+
auth_headers=auth_headers,
|
|
514
501
|
timeout=timeout,
|
|
515
502
|
)
|
|
516
|
-
|
|
517
|
-
# Save next solution (.runs/<session-id>/step_<step>.<extension>)
|
|
503
|
+
# Save next solution (.runs/<run-id>/step_<step>.<extension>)
|
|
518
504
|
write_to_path(
|
|
519
505
|
fp=runs_dir / f"step_{step}{source_fp.suffix}", content=eval_and_next_solution_response["code"]
|
|
520
506
|
)
|
|
521
|
-
|
|
522
507
|
# Write the next solution to the source file
|
|
523
508
|
write_to_path(fp=source_fp, content=eval_and_next_solution_response["code"])
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
# the best solution, its score, and the history to plot the tree
|
|
527
|
-
status_response = get_optimization_session_status(
|
|
528
|
-
session_id=session_id, include_history=True, timeout=timeout, auth_headers=auth_headers
|
|
509
|
+
status_response = get_optimization_run_status(
|
|
510
|
+
run_id=run_id, include_history=True, timeout=timeout, auth_headers=auth_headers
|
|
529
511
|
)
|
|
530
|
-
|
|
531
|
-
# Update the step of the progress bar
|
|
512
|
+
# Update the step of the progress bar, token counts, plan and metric tree
|
|
532
513
|
summary_panel.set_step(step=step)
|
|
533
|
-
# Update the token counts
|
|
534
514
|
summary_panel.update_token_counts(usage=eval_and_next_solution_response["usage"])
|
|
535
|
-
# Update the plan
|
|
536
515
|
plan_panel.update(plan=eval_and_next_solution_response["plan"])
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
516
|
+
|
|
517
|
+
nodes_list_from_status = status_response.get("nodes")
|
|
518
|
+
tree_panel.build_metric_tree(nodes=nodes_list_from_status if nodes_list_from_status is not None else [])
|
|
540
519
|
tree_panel.set_unevaluated_node(node_id=eval_and_next_solution_response["solution_id"])
|
|
541
520
|
|
|
542
521
|
# Update the solution panels with the next solution and best solution (and score)
|
|
@@ -552,27 +531,25 @@ def main() -> None:
|
|
|
552
531
|
else:
|
|
553
532
|
best_solution_node = None
|
|
554
533
|
|
|
555
|
-
# Create a node for the current solution
|
|
556
534
|
current_solution_node = None
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
535
|
+
if status_response.get("nodes"):
|
|
536
|
+
for node_data in status_response["nodes"]:
|
|
537
|
+
if node_data["solution_id"] == eval_and_next_solution_response["solution_id"]:
|
|
538
|
+
current_solution_node = Node(
|
|
539
|
+
id=node_data["solution_id"],
|
|
540
|
+
parent_id=node_data["parent_id"],
|
|
541
|
+
code=node_data["code"],
|
|
542
|
+
metric=node_data["metric_value"],
|
|
543
|
+
is_buggy=node_data["is_buggy"],
|
|
544
|
+
)
|
|
566
545
|
if current_solution_node is None:
|
|
567
|
-
raise ValueError("Current solution node not found in
|
|
546
|
+
raise ValueError("Current solution node not found in nodes list from status response")
|
|
547
|
+
|
|
568
548
|
# Update the solution panels with the current and best solution
|
|
569
549
|
solution_panels.update(current_node=current_solution_node, best_node=best_solution_node)
|
|
570
550
|
current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=step)
|
|
571
|
-
|
|
572
551
|
# Clear evaluation output since we are running a evaluation on a new solution
|
|
573
552
|
eval_output_panel.clear()
|
|
574
|
-
|
|
575
|
-
# Update displays with smooth transitions
|
|
576
553
|
smooth_update(
|
|
577
554
|
live=live,
|
|
578
555
|
layout=layout,
|
|
@@ -586,171 +563,152 @@ def main() -> None:
|
|
|
586
563
|
],
|
|
587
564
|
transition_delay=0.08, # Slightly longer delay for more noticeable transitions
|
|
588
565
|
)
|
|
589
|
-
|
|
590
|
-
# Run evaluation on the current solution
|
|
591
566
|
term_out = run_evaluation(eval_command=args.eval_command)
|
|
592
567
|
eval_output_panel.update(output=term_out)
|
|
593
|
-
|
|
594
|
-
# Update evaluation output with a smooth transition
|
|
595
568
|
smooth_update(
|
|
596
569
|
live=live,
|
|
597
570
|
layout=layout,
|
|
598
571
|
sections_to_update=[("eval_output", eval_output_panel.get_display())],
|
|
599
|
-
transition_delay=0.1,
|
|
572
|
+
transition_delay=0.1,
|
|
600
573
|
)
|
|
601
574
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
# Final evaluation report
|
|
608
|
-
eval_and_next_solution_response = evaluate_feedback_then_suggest_next_solution(
|
|
609
|
-
session_id=session_id,
|
|
610
|
-
execution_output=term_out,
|
|
611
|
-
additional_instructions=current_additional_instructions,
|
|
612
|
-
api_keys=llm_api_keys,
|
|
613
|
-
timeout=timeout,
|
|
614
|
-
auth_headers=auth_headers,
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
# Update the progress bar
|
|
618
|
-
summary_panel.set_step(step=steps)
|
|
619
|
-
# Update the token counts
|
|
620
|
-
summary_panel.update_token_counts(usage=eval_and_next_solution_response["usage"])
|
|
621
|
-
# No need to update the plan panel since we have finished the optimization
|
|
622
|
-
# Get the optimization session status for
|
|
623
|
-
# the best solution, its score, and the history to plot the tree
|
|
624
|
-
status_response = get_optimization_session_status(
|
|
625
|
-
session_id=session_id, include_history=True, timeout=timeout, auth_headers=auth_headers
|
|
626
|
-
)
|
|
627
|
-
# Build the metric tree
|
|
628
|
-
tree_panel.build_metric_tree(nodes=status_response["history"])
|
|
629
|
-
# No need to set any solution to unevaluated since we have finished the optimization
|
|
630
|
-
# and all solutions have been evaluated
|
|
631
|
-
# No neeed to update the current solution panel since we have finished the optimization
|
|
632
|
-
# We only need to update the best solution panel
|
|
633
|
-
# Figure out if we have a best solution so far
|
|
634
|
-
if status_response["best_result"] is not None:
|
|
635
|
-
best_solution_node = Node(
|
|
636
|
-
id=status_response["best_result"]["solution_id"],
|
|
637
|
-
parent_id=status_response["best_result"]["parent_id"],
|
|
638
|
-
code=status_response["best_result"]["code"],
|
|
639
|
-
metric=status_response["best_result"]["metric_value"],
|
|
640
|
-
is_buggy=status_response["best_result"]["is_buggy"],
|
|
575
|
+
if not user_stop_requested_flag:
|
|
576
|
+
# Re-read instructions from the original source (file path or string) BEFORE each suggest call
|
|
577
|
+
current_additional_instructions = read_additional_instructions(
|
|
578
|
+
additional_instructions=args.additional_instructions
|
|
641
579
|
)
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if best_solution_node is not None and best_solution_node.metric is not None
|
|
651
|
-
else "[red] No valid solution found.[/]"
|
|
652
|
-
)
|
|
653
|
-
end_optimization_layout["summary"].update(summary_panel.get_display(final_message=final_message))
|
|
654
|
-
end_optimization_layout["tree"].update(tree_panel.get_display(is_done=True))
|
|
655
|
-
end_optimization_layout["best_solution"].update(best_solution_panel)
|
|
656
|
-
|
|
657
|
-
# Save optimization results
|
|
658
|
-
# If the best solution does not exist or is has not been measured at the end of the optimization
|
|
659
|
-
# save the original solution as the best solution
|
|
660
|
-
if best_solution_node is not None:
|
|
661
|
-
best_solution_code = best_solution_node.code
|
|
662
|
-
best_solution_score = best_solution_node.metric
|
|
663
|
-
else:
|
|
664
|
-
best_solution_code = None
|
|
665
|
-
best_solution_score = None
|
|
666
|
-
|
|
667
|
-
if best_solution_code is None or best_solution_score is None:
|
|
668
|
-
best_solution_content = f"# Weco could not find a better solution\n\n{read_from_path(fp=runs_dir / f'step_0{source_fp.suffix}', is_json=False)}"
|
|
669
|
-
else:
|
|
670
|
-
# Format score for the comment
|
|
671
|
-
best_score_str = (
|
|
672
|
-
format_number(best_solution_score)
|
|
673
|
-
if best_solution_score is not None and isinstance(best_solution_score, (int, float))
|
|
674
|
-
else "N/A"
|
|
580
|
+
# Evaluate the final solution thats been generated
|
|
581
|
+
eval_and_next_solution_response = evaluate_feedback_then_suggest_next_solution(
|
|
582
|
+
run_id=run_id,
|
|
583
|
+
execution_output=term_out,
|
|
584
|
+
additional_instructions=current_additional_instructions,
|
|
585
|
+
api_keys=llm_api_keys,
|
|
586
|
+
timeout=timeout,
|
|
587
|
+
auth_headers=auth_headers,
|
|
675
588
|
)
|
|
676
|
-
|
|
677
|
-
|
|
589
|
+
summary_panel.set_step(step=steps)
|
|
590
|
+
summary_panel.update_token_counts(usage=eval_and_next_solution_response["usage"])
|
|
591
|
+
status_response = get_optimization_run_status(
|
|
592
|
+
run_id=run_id, include_history=True, timeout=timeout, auth_headers=auth_headers
|
|
678
593
|
)
|
|
594
|
+
# No need to update the plan panel since we have finished the optimization
|
|
595
|
+
# Get the optimization run status for
|
|
596
|
+
# the best solution, its score, and the history to plot the tree
|
|
597
|
+
nodes_list_from_status_final = status_response.get("nodes")
|
|
598
|
+
tree_panel.build_metric_tree(
|
|
599
|
+
nodes=nodes_list_from_status_final if nodes_list_from_status_final is not None else []
|
|
600
|
+
)
|
|
601
|
+
# No need to set any solution to unevaluated since we have finished the optimization
|
|
602
|
+
# and all solutions have been evaluated
|
|
603
|
+
# No neeed to update the current solution panel since we have finished the optimization
|
|
604
|
+
# We only need to update the best solution panel
|
|
605
|
+
# Figure out if we have a best solution so far
|
|
606
|
+
if status_response["best_result"] is not None:
|
|
607
|
+
best_solution_node = Node(
|
|
608
|
+
id=status_response["best_result"]["solution_id"],
|
|
609
|
+
parent_id=status_response["best_result"]["parent_id"],
|
|
610
|
+
code=status_response["best_result"]["code"],
|
|
611
|
+
metric=status_response["best_result"]["metric_value"],
|
|
612
|
+
is_buggy=status_response["best_result"]["is_buggy"],
|
|
613
|
+
)
|
|
614
|
+
else:
|
|
615
|
+
best_solution_node = None
|
|
616
|
+
solution_panels.update(current_node=None, best_node=best_solution_node)
|
|
617
|
+
_, best_solution_panel = solution_panels.get_display(current_step=steps)
|
|
618
|
+
# Update the end optimization layout
|
|
619
|
+
final_message = (
|
|
620
|
+
f"{summary_panel.metric_name.capitalize()} {'maximized' if summary_panel.maximize else 'minimized'}! Best solution {summary_panel.metric_name.lower()} = [green]{status_response['best_result']['metric_value']}[/] 🏆"
|
|
621
|
+
if best_solution_node is not None and best_solution_node.metric is not None
|
|
622
|
+
else "[red] No valid solution found.[/]"
|
|
623
|
+
)
|
|
624
|
+
end_optimization_layout["summary"].update(summary_panel.get_display(final_message=final_message))
|
|
625
|
+
end_optimization_layout["tree"].update(tree_panel.get_display(is_done=True))
|
|
626
|
+
end_optimization_layout["best_solution"].update(best_solution_panel)
|
|
627
|
+
|
|
628
|
+
# Save optimization results
|
|
629
|
+
# If the best solution does not exist or is has not been measured at the end of the optimization
|
|
630
|
+
# save the original solution as the best solution
|
|
631
|
+
if best_solution_node is not None:
|
|
632
|
+
best_solution_code = best_solution_node.code
|
|
633
|
+
best_solution_score = best_solution_node.metric
|
|
634
|
+
else:
|
|
635
|
+
best_solution_code = None
|
|
636
|
+
best_solution_score = None
|
|
679
637
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
638
|
+
if best_solution_code is None or best_solution_score is None:
|
|
639
|
+
best_solution_content = f"# Weco could not find a better solution\n\n{read_from_path(fp=runs_dir / f'step_0{source_fp.suffix}', is_json=False)}"
|
|
640
|
+
else:
|
|
641
|
+
# Format score for the comment
|
|
642
|
+
best_score_str = (
|
|
643
|
+
format_number(best_solution_score)
|
|
644
|
+
if best_solution_score is not None and isinstance(best_solution_score, (int, float))
|
|
645
|
+
else "N/A"
|
|
646
|
+
)
|
|
647
|
+
best_solution_content = (
|
|
648
|
+
f"# Best solution from Weco with a score of {best_score_str}\n\n{best_solution_code}"
|
|
649
|
+
)
|
|
650
|
+
# Save best solution to .runs/<run-id>/best.<extension>
|
|
651
|
+
write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_content)
|
|
652
|
+
# write the best solution to the source file
|
|
653
|
+
write_to_path(fp=source_fp, content=best_solution_content)
|
|
654
|
+
# Mark as completed normally for the finally block
|
|
655
|
+
optimization_completed_normally = True
|
|
656
|
+
console.print(end_optimization_layout)
|
|
690
657
|
|
|
691
658
|
except Exception as e:
|
|
692
659
|
# Catch errors during the main optimization loop or setup
|
|
693
660
|
try:
|
|
694
|
-
error_message = e.response.json()["detail"]
|
|
661
|
+
error_message = e.response.json()["detail"]
|
|
695
662
|
except Exception:
|
|
696
|
-
error_message = str(e)
|
|
663
|
+
error_message = str(e)
|
|
697
664
|
console.print(Panel(f"[bold red]Error: {error_message}", title="[bold red]Optimization Error", border_style="red"))
|
|
698
|
-
# Print traceback for debugging if needed (can be noisy)
|
|
699
|
-
# console.print_exception(show_locals=False)
|
|
700
|
-
|
|
701
665
|
# Ensure optimization_completed_normally is False
|
|
702
666
|
optimization_completed_normally = False
|
|
703
|
-
|
|
704
|
-
# Prepare details for termination report
|
|
705
667
|
error_details = traceback.format_exc()
|
|
706
|
-
|
|
707
|
-
# Exit code will be handled by finally block or sys.exit below
|
|
708
|
-
exit_code = 1 # Indicate error
|
|
709
|
-
# No sys.exit here, let finally block run
|
|
710
|
-
|
|
668
|
+
exit_code = 1
|
|
711
669
|
finally:
|
|
712
670
|
# This block runs whether the try block completed normally or raised an exception
|
|
713
|
-
|
|
714
671
|
# Stop heartbeat thread
|
|
715
672
|
stop_heartbeat_event.set()
|
|
716
673
|
if heartbeat_thread and heartbeat_thread.is_alive():
|
|
717
|
-
heartbeat_thread.join(timeout=2)
|
|
674
|
+
heartbeat_thread.join(timeout=2)
|
|
718
675
|
|
|
719
|
-
# Report final status if a
|
|
720
|
-
if
|
|
721
|
-
|
|
722
|
-
|
|
676
|
+
# Report final status if a run was started
|
|
677
|
+
if run_id:
|
|
678
|
+
final_status_update = "unknown"
|
|
679
|
+
final_reason_code = "unknown_termination"
|
|
723
680
|
final_details = None
|
|
724
|
-
|
|
725
681
|
if optimization_completed_normally:
|
|
726
|
-
|
|
727
|
-
|
|
682
|
+
final_status_update = "completed"
|
|
683
|
+
final_reason_code = "completed_successfully"
|
|
684
|
+
elif user_stop_requested_flag:
|
|
685
|
+
final_status_update = "terminated"
|
|
686
|
+
final_reason_code = "user_requested_stop"
|
|
687
|
+
final_details = "Run stopped by user request via dashboard."
|
|
728
688
|
else:
|
|
729
|
-
|
|
689
|
+
final_status_update = "error"
|
|
690
|
+
final_reason_code = "error_cli_internal"
|
|
730
691
|
if "error_details" in locals():
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
final_details =
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
692
|
+
final_details = locals()["error_details"]
|
|
693
|
+
elif "e" in locals() and isinstance(locals()["e"], Exception):
|
|
694
|
+
final_details = traceback.format_exc()
|
|
695
|
+
else:
|
|
696
|
+
final_details = "CLI terminated unexpectedly without a specific exception captured."
|
|
697
|
+
# Keep default 'unknown' if we somehow end up here without error/completion/signal
|
|
737
698
|
# Avoid reporting if terminated by signal handler (already reported)
|
|
738
699
|
# Check a flag or rely on status not being 'unknown'
|
|
739
|
-
if
|
|
700
|
+
if final_status_update != "unknown":
|
|
740
701
|
report_termination(
|
|
741
|
-
|
|
742
|
-
status_update=
|
|
743
|
-
reason=
|
|
702
|
+
run_id=run_id,
|
|
703
|
+
status_update=final_status_update,
|
|
704
|
+
reason=final_reason_code,
|
|
744
705
|
details=final_details,
|
|
745
|
-
auth_headers=
|
|
706
|
+
auth_headers=current_auth_headers_for_heartbeat,
|
|
746
707
|
)
|
|
747
|
-
|
|
748
|
-
# Ensure proper exit code if an error occurred
|
|
749
|
-
if not optimization_completed_normally and "exit_code" in locals() and exit_code != 0:
|
|
750
|
-
sys.exit(exit_code)
|
|
751
|
-
elif not optimization_completed_normally:
|
|
752
|
-
# Generic error exit if no specific code was set but try block failed
|
|
753
|
-
sys.exit(1)
|
|
754
|
-
else:
|
|
755
|
-
# Normal exit
|
|
708
|
+
if optimization_completed_normally:
|
|
756
709
|
sys.exit(0)
|
|
710
|
+
elif user_stop_requested_flag:
|
|
711
|
+
console.print("[yellow]Run terminated by user request.[/]")
|
|
712
|
+
sys.exit(0)
|
|
713
|
+
else:
|
|
714
|
+
sys.exit(locals().get("exit_code", 1))
|
weco/panels.py
CHANGED
|
@@ -11,9 +11,9 @@ from .__init__ import __dashboard_url__
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class SummaryPanel:
|
|
14
|
-
"""Holds a summary of the optimization
|
|
14
|
+
"""Holds a summary of the optimization run."""
|
|
15
15
|
|
|
16
|
-
def __init__(self, maximize: bool, metric_name: str, total_steps: int, model: str, runs_dir: str,
|
|
16
|
+
def __init__(self, maximize: bool, metric_name: str, total_steps: int, model: str, runs_dir: str, run_id: str = None):
|
|
17
17
|
self.maximize = maximize
|
|
18
18
|
self.metric_name = metric_name
|
|
19
19
|
self.goal = ("Maximizing" if self.maximize else "Minimizing") + f" {self.metric_name}..."
|
|
@@ -22,7 +22,7 @@ class SummaryPanel:
|
|
|
22
22
|
self.total_steps = total_steps
|
|
23
23
|
self.model = model
|
|
24
24
|
self.runs_dir = runs_dir
|
|
25
|
-
self.
|
|
25
|
+
self.run_id = run_id if run_id is not None else "N/A"
|
|
26
26
|
self.dashboard_url = "N/A"
|
|
27
27
|
self.progress = Progress(
|
|
28
28
|
TextColumn("[progress.description]{task.description}"),
|
|
@@ -34,14 +34,14 @@ class SummaryPanel:
|
|
|
34
34
|
)
|
|
35
35
|
self.task_id = self.progress.add_task("", total=total_steps)
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
"""Set the
|
|
39
|
-
self.
|
|
40
|
-
self.set_dashboard_url(
|
|
37
|
+
def set_run_id(self, run_id: str):
|
|
38
|
+
"""Set the run ID."""
|
|
39
|
+
self.run_id = run_id
|
|
40
|
+
self.set_dashboard_url(run_id=run_id)
|
|
41
41
|
|
|
42
|
-
def set_dashboard_url(self,
|
|
42
|
+
def set_dashboard_url(self, run_id: str):
|
|
43
43
|
"""Set the dashboard URL."""
|
|
44
|
-
self.dashboard_url = f"{__dashboard_url__}/runs/{
|
|
44
|
+
self.dashboard_url = f"{__dashboard_url__}/runs/{run_id}"
|
|
45
45
|
|
|
46
46
|
def set_step(self, step: int):
|
|
47
47
|
"""Set the current step."""
|
|
@@ -70,7 +70,7 @@ class SummaryPanel:
|
|
|
70
70
|
summary_table.add_row(f"[bold cyan]Model:[/] {self.model}")
|
|
71
71
|
summary_table.add_row("")
|
|
72
72
|
# Log directory
|
|
73
|
-
summary_table.add_row(f"[bold cyan]Logs:[/] [blue underline]{self.runs_dir}/{self.
|
|
73
|
+
summary_table.add_row(f"[bold cyan]Logs:[/] [blue underline]{self.runs_dir}/{self.run_id}[/]")
|
|
74
74
|
summary_table.add_row("")
|
|
75
75
|
# Dashboard link
|
|
76
76
|
summary_table.add_row(f"[bold cyan]Dashboard:[/] [blue underline]{self.dashboard_url}[/]")
|
|
@@ -175,6 +175,9 @@ class MetricTreePanel:
|
|
|
175
175
|
|
|
176
176
|
def build_metric_tree(self, nodes: List[dict]):
|
|
177
177
|
"""Build the tree from the list of nodes."""
|
|
178
|
+
# Defensive: treat None as empty list
|
|
179
|
+
if nodes is None:
|
|
180
|
+
nodes = []
|
|
178
181
|
# First clear then tree
|
|
179
182
|
self.metric_tree.clear()
|
|
180
183
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weco
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.20
|
|
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: MIT
|
|
@@ -29,6 +29,9 @@ Dynamic: license-file
|
|
|
29
29
|
[](https://docs.weco.ai/)
|
|
30
30
|
[](https://badge.fury.io/py/weco)
|
|
31
31
|
[](https://arxiv.org/abs/2502.13138)
|
|
32
|
+
[](https://colab.research.google.com/github/WecoAI/weco-cli/blob/main/examples/hello-kernel-world/colab_notebook_walkthrough.ipynb)
|
|
33
|
+
|
|
34
|
+
`pip install weco`
|
|
32
35
|
|
|
33
36
|
</div>
|
|
34
37
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
weco/__init__.py,sha256=npWmRgLxfVK69GdyxIujnI87xqmPCBrZWxxAxL_QQOc,478
|
|
2
|
+
weco/api.py,sha256=xHCyQPto1Lv9QysiOFwVf5NnWDh6LBCNfPLyq-L7nys,5873
|
|
3
|
+
weco/auth.py,sha256=IPfiLthcNRkPyM8pWHTyDLvikw83sigacpY1PmeA03Y,2343
|
|
4
|
+
weco/cli.py,sha256=e4h5bxeg2n95AlYXanfxLbcURWchjWTES2Kwx5AjKn0,36115
|
|
5
|
+
weco/panels.py,sha256=lsTHTh-XdYMH3ZV_WBteEcIt2hTWGGtqfUjGlYRHl70,13598
|
|
6
|
+
weco/utils.py,sha256=LVTBo3dduJmhlbotcYoUW2nLx6IRtKs4eDFR52Qltcg,5244
|
|
7
|
+
weco-0.2.20.dist-info/licenses/LICENSE,sha256=p_GQqJBvuZgkLNboYKyH-5dhpTDlKs2wq2TVM55WrWE,1065
|
|
8
|
+
weco-0.2.20.dist-info/METADATA,sha256=rK-Y9Q0zwaKUBS0bNZfUNvL82RXUiijVcIbW3i_IKKk,10955
|
|
9
|
+
weco-0.2.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
weco-0.2.20.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
|
|
11
|
+
weco-0.2.20.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
12
|
+
weco-0.2.20.dist-info/RECORD,,
|
weco-0.2.19.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
weco/__init__.py,sha256=npWmRgLxfVK69GdyxIujnI87xqmPCBrZWxxAxL_QQOc,478
|
|
2
|
-
weco/api.py,sha256=lJJ0j0-bABiQXDlRb43fCo7ky0N_HwfZgFdMktRKQ90,6635
|
|
3
|
-
weco/auth.py,sha256=IPfiLthcNRkPyM8pWHTyDLvikw83sigacpY1PmeA03Y,2343
|
|
4
|
-
weco/cli.py,sha256=eI468fxpMTfGPL-aX6EMYxh0NuaRxpaLVF_Jj2DiFhU,36383
|
|
5
|
-
weco/panels.py,sha256=pM_YGnmcXM_1CBcxo_EAzOV3g_4NFdLS4MqDqx7THbA,13563
|
|
6
|
-
weco/utils.py,sha256=LVTBo3dduJmhlbotcYoUW2nLx6IRtKs4eDFR52Qltcg,5244
|
|
7
|
-
weco-0.2.19.dist-info/licenses/LICENSE,sha256=p_GQqJBvuZgkLNboYKyH-5dhpTDlKs2wq2TVM55WrWE,1065
|
|
8
|
-
weco-0.2.19.dist-info/METADATA,sha256=3VBVsCqr7p332A10KsLr168GvOIKcCOWWfGDv8ViF7I,10729
|
|
9
|
-
weco-0.2.19.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
10
|
-
weco-0.2.19.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
|
|
11
|
-
weco-0.2.19.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
12
|
-
weco-0.2.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|