weco 0.2.22__py3-none-any.whl → 0.2.24__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 +84 -87
- weco/auth.py +7 -5
- weco/chatbot.py +34 -23
- weco/cli.py +10 -1
- weco/constants.py +7 -0
- weco/optimizer.py +26 -21
- weco/panels.py +105 -59
- weco/utils.py +47 -12
- {weco-0.2.22.dist-info → weco-0.2.24.dist-info}/METADATA +36 -25
- weco-0.2.24.dist-info/RECORD +15 -0
- weco-0.2.22.dist-info/RECORD +0 -14
- {weco-0.2.22.dist-info → weco-0.2.24.dist-info}/WHEEL +0 -0
- {weco-0.2.22.dist-info → weco-0.2.24.dist-info}/entry_points.txt +0 -0
- {weco-0.2.22.dist-info → weco-0.2.24.dist-info}/licenses/LICENSE +0 -0
- {weco-0.2.22.dist-info → weco-0.2.24.dist-info}/top_level.txt +0 -0
weco/api.py
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from typing import Dict, Any, Optional, Union, Tuple, List
|
|
3
|
-
|
|
4
3
|
import requests
|
|
5
|
-
from requests.adapters import HTTPAdapter
|
|
6
|
-
from urllib3.util.retry import Retry
|
|
7
4
|
from rich.console import Console
|
|
8
5
|
|
|
9
6
|
from weco import __pkg_version__, __base_url__
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# --- Session Configuration ---
|
|
13
|
-
def _get_weco_session() -> requests.Session:
|
|
14
|
-
session = requests.Session()
|
|
15
|
-
retry_strategy = Retry(
|
|
16
|
-
total=3,
|
|
17
|
-
status_forcelist=[429, 500, 502, 503, 504], # Retry on these server errors and rate limiting
|
|
18
|
-
allowed_methods=["HEAD", "GET", "PUT", "POST", "DELETE", "OPTIONS"], # Case-insensitive
|
|
19
|
-
backoff_factor=1, # e.g., sleep for 0s, 2s, 4s between retries (factor * (2 ** ({number of total retries} - 1)))
|
|
20
|
-
)
|
|
21
|
-
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
22
|
-
session.mount("http://", adapter)
|
|
23
|
-
session.mount("https://", adapter)
|
|
24
|
-
return session
|
|
7
|
+
from .constants import DEFAULT_API_TIMEOUT
|
|
25
8
|
|
|
26
9
|
|
|
27
10
|
def handle_api_error(e: requests.exceptions.HTTPError, console: Console) -> None:
|
|
@@ -48,13 +31,12 @@ def start_optimization_run(
|
|
|
48
31
|
additional_instructions: str = None,
|
|
49
32
|
api_keys: Dict[str, Any] = {},
|
|
50
33
|
auth_headers: dict = {},
|
|
51
|
-
timeout: Union[int, Tuple[int, int]] =
|
|
34
|
+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
|
|
52
35
|
) -> Dict[str, Any]:
|
|
53
36
|
"""Start the optimization run."""
|
|
54
37
|
with console.status("[bold green]Starting Optimization..."):
|
|
55
38
|
try:
|
|
56
|
-
|
|
57
|
-
response = session.post(
|
|
39
|
+
response = requests.post(
|
|
58
40
|
f"{__base_url__}/runs",
|
|
59
41
|
json={
|
|
60
42
|
"source_code": source_code,
|
|
@@ -72,27 +54,33 @@ def start_optimization_run(
|
|
|
72
54
|
timeout=timeout,
|
|
73
55
|
)
|
|
74
56
|
response.raise_for_status()
|
|
75
|
-
|
|
76
|
-
|
|
57
|
+
result = response.json()
|
|
58
|
+
# Handle None values for code and plan fields
|
|
59
|
+
if result.get("plan") is None:
|
|
60
|
+
result["plan"] = ""
|
|
61
|
+
if result.get("code") is None:
|
|
62
|
+
result["code"] = ""
|
|
63
|
+
return result
|
|
64
|
+
except Exception as e:
|
|
77
65
|
handle_api_error(e, console)
|
|
78
66
|
sys.exit(1)
|
|
79
|
-
except
|
|
80
|
-
console.print(f"[bold red]
|
|
67
|
+
except Exception as e:
|
|
68
|
+
console.print(f"[bold red]Error starting run: {e}[/]")
|
|
81
69
|
sys.exit(1)
|
|
82
70
|
|
|
83
71
|
|
|
84
72
|
def evaluate_feedback_then_suggest_next_solution(
|
|
73
|
+
console: Console,
|
|
85
74
|
run_id: str,
|
|
86
75
|
execution_output: str,
|
|
87
76
|
additional_instructions: str = None,
|
|
88
77
|
api_keys: Dict[str, Any] = {},
|
|
89
78
|
auth_headers: dict = {},
|
|
90
|
-
timeout: Union[int, Tuple[int, int]] =
|
|
79
|
+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
|
|
91
80
|
) -> Dict[str, Any]:
|
|
92
81
|
"""Evaluate the feedback and suggest the next solution."""
|
|
93
82
|
try:
|
|
94
|
-
|
|
95
|
-
response = session.post(
|
|
83
|
+
response = requests.post(
|
|
96
84
|
f"{__base_url__}/runs/{run_id}/suggest",
|
|
97
85
|
json={
|
|
98
86
|
"execution_output": execution_output,
|
|
@@ -103,50 +91,73 @@ def evaluate_feedback_then_suggest_next_solution(
|
|
|
103
91
|
timeout=timeout,
|
|
104
92
|
)
|
|
105
93
|
response.raise_for_status()
|
|
106
|
-
|
|
94
|
+
result = response.json()
|
|
95
|
+
# Handle None values for code and plan fields
|
|
96
|
+
if result.get("plan") is None:
|
|
97
|
+
result["plan"] = ""
|
|
98
|
+
if result.get("code") is None:
|
|
99
|
+
result["code"] = ""
|
|
100
|
+
|
|
101
|
+
return result
|
|
107
102
|
except requests.exceptions.HTTPError as e:
|
|
108
103
|
# Allow caller to handle suggest errors, maybe retry or terminate
|
|
109
|
-
handle_api_error(e,
|
|
104
|
+
handle_api_error(e, console) # Use default console if none passed
|
|
110
105
|
raise # Re-raise the exception
|
|
111
|
-
except
|
|
112
|
-
print(f"
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f"Error: {e}") # Use print as console might not be available
|
|
113
108
|
raise # Re-raise the exception
|
|
114
109
|
|
|
115
110
|
|
|
116
111
|
def get_optimization_run_status(
|
|
117
|
-
|
|
112
|
+
console: Console,
|
|
113
|
+
run_id: str,
|
|
114
|
+
include_history: bool = False,
|
|
115
|
+
auth_headers: dict = {},
|
|
116
|
+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
|
|
118
117
|
) -> Dict[str, Any]:
|
|
119
118
|
"""Get the current status of the optimization run."""
|
|
120
119
|
try:
|
|
121
|
-
|
|
122
|
-
response = session.get(
|
|
120
|
+
response = requests.get(
|
|
123
121
|
f"{__base_url__}/runs/{run_id}", params={"include_history": include_history}, headers=auth_headers, timeout=timeout
|
|
124
122
|
)
|
|
125
123
|
response.raise_for_status()
|
|
126
|
-
|
|
124
|
+
result = response.json()
|
|
125
|
+
# Handle None values for code and plan fields in best_result and nodes
|
|
126
|
+
if result.get("best_result"):
|
|
127
|
+
if result["best_result"].get("code") is None:
|
|
128
|
+
result["best_result"]["code"] = ""
|
|
129
|
+
if result["best_result"].get("plan") is None:
|
|
130
|
+
result["best_result"]["plan"] = ""
|
|
131
|
+
# Handle None values for code and plan fields in nodes array
|
|
132
|
+
if result.get("nodes"):
|
|
133
|
+
for i, node in enumerate(result["nodes"]):
|
|
134
|
+
if node.get("plan") is None:
|
|
135
|
+
result["nodes"][i]["plan"] = ""
|
|
136
|
+
if node.get("code") is None:
|
|
137
|
+
result["nodes"][i]["code"] = ""
|
|
138
|
+
return result
|
|
127
139
|
except requests.exceptions.HTTPError as e:
|
|
128
|
-
handle_api_error(e,
|
|
140
|
+
handle_api_error(e, console) # Use default console
|
|
129
141
|
raise # Re-raise
|
|
130
|
-
except
|
|
131
|
-
print(f"
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"Error getting run status: {e}")
|
|
132
144
|
raise # Re-raise
|
|
133
145
|
|
|
134
146
|
|
|
135
|
-
def send_heartbeat(run_id: str, auth_headers: dict = {}, timeout: Union[int, Tuple[int, int]] = 10) -> bool:
|
|
147
|
+
def send_heartbeat(run_id: str, auth_headers: dict = {}, timeout: Union[int, Tuple[int, int]] = (10, 10)) -> bool:
|
|
136
148
|
"""Send a heartbeat signal to the backend."""
|
|
137
149
|
try:
|
|
138
|
-
|
|
139
|
-
response = session.put(f"{__base_url__}/runs/{run_id}/heartbeat", headers=auth_headers, timeout=timeout)
|
|
150
|
+
response = requests.put(f"{__base_url__}/runs/{run_id}/heartbeat", headers=auth_headers, timeout=timeout)
|
|
140
151
|
response.raise_for_status()
|
|
141
152
|
return True
|
|
142
153
|
except requests.exceptions.HTTPError as e:
|
|
143
154
|
if e.response.status_code == 409:
|
|
144
|
-
print(
|
|
155
|
+
print("Polling ignore: Run {run_id} is not running.", file=sys.stderr)
|
|
145
156
|
else:
|
|
146
|
-
print(f"
|
|
157
|
+
print(f"Polling failed for run {run_id}: HTTP {e.response.status_code}", file=sys.stderr)
|
|
147
158
|
return False
|
|
148
|
-
except
|
|
149
|
-
print(f"
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print(f"Error sending heartbeat for run {run_id}: {e}", file=sys.stderr)
|
|
150
161
|
return False
|
|
151
162
|
|
|
152
163
|
|
|
@@ -156,12 +167,11 @@ def report_termination(
|
|
|
156
167
|
reason: str,
|
|
157
168
|
details: Optional[str] = None,
|
|
158
169
|
auth_headers: dict = {},
|
|
159
|
-
timeout: Union[int, Tuple[int, int]] = 30,
|
|
170
|
+
timeout: Union[int, Tuple[int, int]] = (10, 30),
|
|
160
171
|
) -> bool:
|
|
161
172
|
"""Report the termination reason to the backend."""
|
|
162
173
|
try:
|
|
163
|
-
|
|
164
|
-
response = session.post(
|
|
174
|
+
response = requests.post(
|
|
165
175
|
f"{__base_url__}/runs/{run_id}/terminate",
|
|
166
176
|
json={"status_update": status_update, "termination_reason": reason, "termination_details": details},
|
|
167
177
|
headers=auth_headers,
|
|
@@ -169,7 +179,7 @@ def report_termination(
|
|
|
169
179
|
)
|
|
170
180
|
response.raise_for_status()
|
|
171
181
|
return True
|
|
172
|
-
except
|
|
182
|
+
except Exception as e:
|
|
173
183
|
print(f"Warning: Failed to report termination to backend for run {run_id}: {e}", file=sys.stderr)
|
|
174
184
|
return False
|
|
175
185
|
|
|
@@ -195,24 +205,24 @@ def _determine_model_and_api_key() -> tuple[str, dict[str, str]]:
|
|
|
195
205
|
api_key_dict = {"GEMINI_API_KEY": llm_api_keys["GEMINI_API_KEY"]}
|
|
196
206
|
else:
|
|
197
207
|
# This should never happen if determine_default_model works correctly
|
|
198
|
-
raise ValueError(f"Unknown model
|
|
208
|
+
raise ValueError(f"Unknown default model choice: {model}")
|
|
199
209
|
|
|
200
210
|
return model, api_key_dict
|
|
201
211
|
|
|
202
212
|
|
|
203
213
|
def get_optimization_suggestions_from_codebase(
|
|
214
|
+
console: Console,
|
|
204
215
|
gitingest_summary: str,
|
|
205
216
|
gitingest_tree: str,
|
|
206
217
|
gitingest_content_str: str,
|
|
207
|
-
console: Console,
|
|
208
218
|
auth_headers: dict = {},
|
|
209
|
-
timeout: Union[int, Tuple[int, int]] =
|
|
219
|
+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
|
|
210
220
|
) -> Optional[List[Dict[str, Any]]]:
|
|
211
221
|
"""Analyze codebase and get optimization suggestions using the model-agnostic backend API."""
|
|
222
|
+
model, api_key_dict = _determine_model_and_api_key()
|
|
212
223
|
try:
|
|
213
224
|
model, api_key_dict = _determine_model_and_api_key()
|
|
214
|
-
|
|
215
|
-
response = session.post(
|
|
225
|
+
response = requests.post(
|
|
216
226
|
f"{__base_url__}/onboard/analyze-codebase",
|
|
217
227
|
json={
|
|
218
228
|
"gitingest_summary": gitingest_summary,
|
|
@@ -228,30 +238,27 @@ def get_optimization_suggestions_from_codebase(
|
|
|
228
238
|
result = response.json()
|
|
229
239
|
return [option for option in result.get("options", [])]
|
|
230
240
|
|
|
231
|
-
except
|
|
241
|
+
except Exception as e:
|
|
232
242
|
handle_api_error(e, console)
|
|
233
243
|
return None
|
|
234
|
-
except requests.exceptions.RequestException as e:
|
|
235
|
-
console.print(f"[bold red]Network Error getting optimization suggestions: {e}[/]")
|
|
236
|
-
return None
|
|
237
244
|
except Exception as e:
|
|
238
|
-
console.print(f"[bold red]Error
|
|
245
|
+
console.print(f"[bold red]Error: {e}[/]")
|
|
239
246
|
return None
|
|
240
247
|
|
|
241
248
|
|
|
242
249
|
def generate_evaluation_script_and_metrics(
|
|
250
|
+
console: Console,
|
|
243
251
|
target_file: str,
|
|
244
252
|
description: str,
|
|
245
253
|
gitingest_content_str: str,
|
|
246
|
-
console: Console,
|
|
247
254
|
auth_headers: dict = {},
|
|
248
|
-
timeout: Union[int, Tuple[int, int]] =
|
|
255
|
+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
|
|
249
256
|
) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
|
|
250
257
|
"""Generate evaluation script and determine metrics using the model-agnostic backend API."""
|
|
258
|
+
model, api_key_dict = _determine_model_and_api_key()
|
|
251
259
|
try:
|
|
252
260
|
model, api_key_dict = _determine_model_and_api_key()
|
|
253
|
-
|
|
254
|
-
response = session.post(
|
|
261
|
+
response = requests.post(
|
|
255
262
|
f"{__base_url__}/onboard/generate-script",
|
|
256
263
|
json={
|
|
257
264
|
"target_file": target_file,
|
|
@@ -266,33 +273,29 @@ def generate_evaluation_script_and_metrics(
|
|
|
266
273
|
response.raise_for_status()
|
|
267
274
|
result = response.json()
|
|
268
275
|
return result.get("script_content"), result.get("metric_name"), result.get("goal"), result.get("reasoning")
|
|
269
|
-
|
|
270
276
|
except requests.exceptions.HTTPError as e:
|
|
271
277
|
handle_api_error(e, console)
|
|
272
278
|
return None, None, None, None
|
|
273
|
-
except requests.exceptions.RequestException as e:
|
|
274
|
-
console.print(f"[bold red]Network Error generating evaluation script: {e}[/]")
|
|
275
|
-
return None, None, None, None
|
|
276
279
|
except Exception as e:
|
|
277
|
-
console.print(f"[bold red]Error
|
|
280
|
+
console.print(f"[bold red]Error: {e}[/]")
|
|
278
281
|
return None, None, None, None
|
|
279
282
|
|
|
280
283
|
|
|
281
284
|
def analyze_evaluation_environment(
|
|
285
|
+
console: Console,
|
|
282
286
|
target_file: str,
|
|
283
287
|
description: str,
|
|
284
288
|
gitingest_summary: str,
|
|
285
289
|
gitingest_tree: str,
|
|
286
290
|
gitingest_content_str: str,
|
|
287
|
-
console: Console,
|
|
288
291
|
auth_headers: dict = {},
|
|
289
|
-
timeout: Union[int, Tuple[int, int]] =
|
|
292
|
+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
|
|
290
293
|
) -> Optional[Dict[str, Any]]:
|
|
291
294
|
"""Analyze existing evaluation scripts and environment using the model-agnostic backend API."""
|
|
295
|
+
model, api_key_dict = _determine_model_and_api_key()
|
|
292
296
|
try:
|
|
293
297
|
model, api_key_dict = _determine_model_and_api_key()
|
|
294
|
-
|
|
295
|
-
response = session.post(
|
|
298
|
+
response = requests.post(
|
|
296
299
|
f"{__base_url__}/onboard/analyze-environment",
|
|
297
300
|
json={
|
|
298
301
|
"target_file": target_file,
|
|
@@ -309,30 +312,27 @@ def analyze_evaluation_environment(
|
|
|
309
312
|
response.raise_for_status()
|
|
310
313
|
return response.json()
|
|
311
314
|
|
|
312
|
-
except
|
|
315
|
+
except Exception as e:
|
|
313
316
|
handle_api_error(e, console)
|
|
314
317
|
return None
|
|
315
|
-
except requests.exceptions.RequestException as e:
|
|
316
|
-
console.print(f"[bold red]Network Error analyzing evaluation environment: {e}[/]")
|
|
317
|
-
return None
|
|
318
318
|
except Exception as e:
|
|
319
|
-
console.print(f"[bold red]Error
|
|
319
|
+
console.print(f"[bold red]Error: {e}[/]")
|
|
320
320
|
return None
|
|
321
321
|
|
|
322
322
|
|
|
323
323
|
def analyze_script_execution_requirements(
|
|
324
|
+
console: Console,
|
|
324
325
|
script_content: str,
|
|
325
326
|
script_path: str,
|
|
326
327
|
target_file: str,
|
|
327
|
-
console: Console,
|
|
328
328
|
auth_headers: dict = {},
|
|
329
|
-
timeout: Union[int, Tuple[int, int]] =
|
|
329
|
+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
|
|
330
330
|
) -> Optional[str]:
|
|
331
331
|
"""Analyze script to determine proper execution command using the model-agnostic backend API."""
|
|
332
|
+
model, api_key_dict = _determine_model_and_api_key()
|
|
332
333
|
try:
|
|
333
334
|
model, api_key_dict = _determine_model_and_api_key()
|
|
334
|
-
|
|
335
|
-
response = session.post(
|
|
335
|
+
response = requests.post(
|
|
336
336
|
f"{__base_url__}/onboard/analyze-script",
|
|
337
337
|
json={
|
|
338
338
|
"script_content": script_content,
|
|
@@ -348,12 +348,9 @@ def analyze_script_execution_requirements(
|
|
|
348
348
|
result = response.json()
|
|
349
349
|
return result.get("command", f"python {script_path}")
|
|
350
350
|
|
|
351
|
-
except
|
|
351
|
+
except Exception as e:
|
|
352
352
|
handle_api_error(e, console)
|
|
353
353
|
return f"python {script_path}"
|
|
354
|
-
except requests.exceptions.RequestException as e:
|
|
355
|
-
console.print(f"[bold red]Network Error analyzing script execution: {e}[/]")
|
|
356
|
-
return f"python {script_path}"
|
|
357
354
|
except Exception as e:
|
|
358
|
-
console.print(f"[bold red]Error
|
|
355
|
+
console.print(f"[bold red]Error: {e}[/]")
|
|
359
356
|
return f"python {script_path}"
|
weco/auth.py
CHANGED
|
@@ -35,7 +35,7 @@ def save_api_key(api_key: str):
|
|
|
35
35
|
# Set file permissions to read/write for owner only (600)
|
|
36
36
|
os.chmod(CREDENTIALS_FILE, stat.S_IRUSR | stat.S_IWUSR)
|
|
37
37
|
except OSError as e:
|
|
38
|
-
print(f"Error:
|
|
38
|
+
print(f"Error: Unable to save credentials file or set permissions on {CREDENTIALS_FILE}: {e}")
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def load_weco_api_key() -> str | None:
|
|
@@ -53,7 +53,7 @@ def load_weco_api_key() -> str | None:
|
|
|
53
53
|
credentials = json.load(f)
|
|
54
54
|
return credentials.get("api_key")
|
|
55
55
|
except (IOError, json.JSONDecodeError, OSError) as e:
|
|
56
|
-
print(f"Warning:
|
|
56
|
+
print(f"Warning: Unable to read credentials file at {CREDENTIALS_FILE}: {e}")
|
|
57
57
|
return None
|
|
58
58
|
|
|
59
59
|
|
|
@@ -64,7 +64,7 @@ def clear_api_key():
|
|
|
64
64
|
os.remove(CREDENTIALS_FILE)
|
|
65
65
|
print("Logged out successfully.")
|
|
66
66
|
except OSError as e:
|
|
67
|
-
print(f"Error:
|
|
67
|
+
print(f"Error: Unable to remove credentials file at {CREDENTIALS_FILE}: {e}")
|
|
68
68
|
else:
|
|
69
69
|
print("Already logged out.")
|
|
70
70
|
|
|
@@ -129,7 +129,9 @@ def perform_login(console: Console):
|
|
|
129
129
|
continue # Continue polling
|
|
130
130
|
else:
|
|
131
131
|
# Unexpected 202 response format
|
|
132
|
-
console.print(
|
|
132
|
+
console.print(
|
|
133
|
+
f"\n[bold red]Error:[/] Received unexpected response from authentication server: {token_data}"
|
|
134
|
+
)
|
|
133
135
|
return False
|
|
134
136
|
# Check for standard OAuth2 errors (often 400 Bad Request)
|
|
135
137
|
elif token_response.status_code == 400:
|
|
@@ -146,7 +148,7 @@ def perform_login(console: Console):
|
|
|
146
148
|
console.print("\n[bold red]Error:[/] Authorization denied by user.")
|
|
147
149
|
return False
|
|
148
150
|
else: # invalid_grant, etc.
|
|
149
|
-
error_desc = token_data.get("error_description", "Unknown error
|
|
151
|
+
error_desc = token_data.get("error_description", "Unknown authentication error occurred.")
|
|
150
152
|
console.print(f"\n[bold red]Error:[/] {error_desc} ({error_code})")
|
|
151
153
|
return False
|
|
152
154
|
|
weco/chatbot.py
CHANGED
|
@@ -50,7 +50,7 @@ class UserInteractionHelper:
|
|
|
50
50
|
|
|
51
51
|
if attempts >= max_retries:
|
|
52
52
|
self.console.print(f"[red]Maximum retry attempts ({max_retries}) reached. Exiting.[/]")
|
|
53
|
-
raise Exception("Maximum retry attempts exceeded")
|
|
53
|
+
raise Exception("Maximum retry attempts exceeded. Please try again.")
|
|
54
54
|
|
|
55
55
|
# Show available options without the full prompt
|
|
56
56
|
if choices:
|
|
@@ -66,7 +66,7 @@ class UserInteractionHelper:
|
|
|
66
66
|
continue
|
|
67
67
|
|
|
68
68
|
# This should never be reached due to the exception above, but just in case
|
|
69
|
-
raise Exception("Unexpected error
|
|
69
|
+
raise Exception("Unexpected error while selecting a choice")
|
|
70
70
|
|
|
71
71
|
def get_choice_numeric(self, prompt: str, max_number: int, default: int = None, max_retries: int = 5) -> int:
|
|
72
72
|
"""Get numeric choice with validation and error handling."""
|
|
@@ -87,7 +87,7 @@ class UserInteractionHelper:
|
|
|
87
87
|
|
|
88
88
|
if attempts >= max_retries:
|
|
89
89
|
self.console.print(f"[red]Maximum retry attempts ({max_retries}) reached. Exiting.[/]")
|
|
90
|
-
raise Exception("Maximum retry attempts exceeded")
|
|
90
|
+
raise Exception("Maximum retry attempts exceeded. Please try again.")
|
|
91
91
|
|
|
92
92
|
# Show valid range
|
|
93
93
|
self.console.print(f"Please enter a number between [bold]1[/] and [bold]{max_number}[/]")
|
|
@@ -115,7 +115,7 @@ class UserInteractionHelper:
|
|
|
115
115
|
|
|
116
116
|
if attempts >= max_retries:
|
|
117
117
|
self.console.print(f"[red]Maximum retry attempts ({max_retries}) reached. Exiting.[/]")
|
|
118
|
-
raise Exception("Maximum retry attempts exceeded")
|
|
118
|
+
raise Exception("Maximum retry attempts exceeded. Please try again.")
|
|
119
119
|
|
|
120
120
|
self.console.print("Valid options: [bold]y[/] / [bold]n[/]")
|
|
121
121
|
if default:
|
|
@@ -123,7 +123,7 @@ class UserInteractionHelper:
|
|
|
123
123
|
|
|
124
124
|
continue
|
|
125
125
|
|
|
126
|
-
raise Exception("Unexpected error
|
|
126
|
+
raise Exception("Unexpected error while selecting an option")
|
|
127
127
|
|
|
128
128
|
def display_optimization_options_table(self, options: List[Dict[str, str]]) -> None:
|
|
129
129
|
"""Display optimization options in a formatted table."""
|
|
@@ -215,7 +215,10 @@ class Chatbot:
|
|
|
215
215
|
|
|
216
216
|
with self.console.status("[bold green]Generating optimization suggestions...[/]"):
|
|
217
217
|
result = get_optimization_suggestions_from_codebase(
|
|
218
|
-
self.
|
|
218
|
+
console=self.console,
|
|
219
|
+
gitingest_summary=self.gitingest_summary,
|
|
220
|
+
gitingest_tree=self.gitingest_tree,
|
|
221
|
+
gitingest_content_str=self.gitingest_content_str,
|
|
219
222
|
)
|
|
220
223
|
|
|
221
224
|
if result and isinstance(result, list):
|
|
@@ -224,7 +227,7 @@ class Chatbot:
|
|
|
224
227
|
options = None
|
|
225
228
|
|
|
226
229
|
if not options or not isinstance(options, list):
|
|
227
|
-
self.console.print("[red]
|
|
230
|
+
self.console.print("[red]Unable to retrieve valid optimization options from the backend.[/]")
|
|
228
231
|
return None
|
|
229
232
|
|
|
230
233
|
if not options:
|
|
@@ -324,17 +327,19 @@ class Chatbot:
|
|
|
324
327
|
elif action == "g" or action == "r":
|
|
325
328
|
with self.console.status("[bold green]Generating evaluation script and determining metrics...[/]"):
|
|
326
329
|
result = generate_evaluation_script_and_metrics(
|
|
327
|
-
|
|
328
|
-
selected_option["
|
|
329
|
-
|
|
330
|
-
self.
|
|
330
|
+
console=self.console,
|
|
331
|
+
target_file=selected_option["target_file"],
|
|
332
|
+
description=selected_option["description"],
|
|
333
|
+
gitingest_content_str=self.gitingest_content_str,
|
|
331
334
|
)
|
|
332
335
|
if result and result[0]:
|
|
333
336
|
eval_script_content, metric_name, goal, reasoning = result
|
|
334
337
|
if reasoning:
|
|
335
338
|
self.console.print(f"[dim]Reasoning: {reasoning}[/]")
|
|
336
339
|
else:
|
|
337
|
-
self.console.print(
|
|
340
|
+
self.console.print(
|
|
341
|
+
"[red]Unable to generate an evaluation script. Please try providing a custom script path instead.[/]"
|
|
342
|
+
)
|
|
338
343
|
eval_script_content = None
|
|
339
344
|
metric_name = None
|
|
340
345
|
goal = None
|
|
@@ -371,7 +376,10 @@ class Chatbot:
|
|
|
371
376
|
# Analyze the script to determine the proper execution command
|
|
372
377
|
with self.console.status("[bold green]Analyzing script execution requirements...[/]"):
|
|
373
378
|
eval_command = analyze_script_execution_requirements(
|
|
374
|
-
|
|
379
|
+
console=self.console,
|
|
380
|
+
script_content=eval_script_content,
|
|
381
|
+
script_path=eval_script_path_str,
|
|
382
|
+
target_file=selected_option["target_file"],
|
|
375
383
|
)
|
|
376
384
|
|
|
377
385
|
return {
|
|
@@ -386,16 +394,16 @@ class Chatbot:
|
|
|
386
394
|
"""Get or create evaluation script configuration using intelligent conversation-guided approach."""
|
|
387
395
|
with self.console.status("[bold green]Analyzing evaluation environment...[/]"):
|
|
388
396
|
analysis = analyze_evaluation_environment(
|
|
389
|
-
|
|
390
|
-
selected_option["
|
|
391
|
-
|
|
392
|
-
self.
|
|
393
|
-
self.
|
|
394
|
-
self.
|
|
397
|
+
console=self.console,
|
|
398
|
+
target_file=selected_option["target_file"],
|
|
399
|
+
description=selected_option["description"],
|
|
400
|
+
gitingest_summary=self.gitingest_summary,
|
|
401
|
+
gitingest_tree=self.gitingest_tree,
|
|
402
|
+
gitingest_content_str=self.gitingest_content_str,
|
|
395
403
|
)
|
|
396
404
|
|
|
397
405
|
if not analysis:
|
|
398
|
-
self.console.print("[yellow]
|
|
406
|
+
self.console.print("[yellow]Unable to analyze evaluation environment. Falling back to script generation.[/]")
|
|
399
407
|
return self.handle_script_generation_workflow(selected_option)
|
|
400
408
|
|
|
401
409
|
self.evaluation_analysis = analysis
|
|
@@ -529,7 +537,10 @@ class Chatbot:
|
|
|
529
537
|
if not eval_command or eval_command == f"python {script_path}":
|
|
530
538
|
with self.console.status("[bold green]Analyzing script execution requirements...[/]"):
|
|
531
539
|
eval_command = analyze_script_execution_requirements(
|
|
532
|
-
|
|
540
|
+
console=self.console,
|
|
541
|
+
script_content=script_content,
|
|
542
|
+
script_path=script_path,
|
|
543
|
+
target_file=selected_option["target_file"],
|
|
533
544
|
)
|
|
534
545
|
|
|
535
546
|
self.current_step = "confirmation"
|
|
@@ -711,7 +722,7 @@ class Chatbot:
|
|
|
711
722
|
"""Setup evaluation environment for the selected optimization."""
|
|
712
723
|
eval_config = self.get_evaluation_configuration(selected_option)
|
|
713
724
|
if not eval_config:
|
|
714
|
-
self.console.print("[red]Evaluation script setup failed.[/]")
|
|
725
|
+
self.console.print("[red]Evaluation script setup failed. Please check your script configuration and try again.[/]")
|
|
715
726
|
return None
|
|
716
727
|
|
|
717
728
|
eval_config = self.confirm_and_finalize_evaluation_config(eval_config)
|
|
@@ -791,7 +802,7 @@ def run_onboarding_chatbot(
|
|
|
791
802
|
chatbot = Chatbot(project_path, console, run_parser, model)
|
|
792
803
|
chatbot.start()
|
|
793
804
|
except Exception as e:
|
|
794
|
-
console.print(f"[bold red]An unexpected error occurred
|
|
805
|
+
console.print(f"[bold red]An unexpected error occurred: {e}[/]")
|
|
795
806
|
import traceback
|
|
796
807
|
|
|
797
808
|
traceback.print_exc()
|
weco/cli.py
CHANGED
|
@@ -61,6 +61,12 @@ def configure_run_parser(run_parser: argparse.ArgumentParser) -> None:
|
|
|
61
61
|
type=str,
|
|
62
62
|
help="Description of additional instruction or path to a file containing additional instructions. Defaults to None.",
|
|
63
63
|
)
|
|
64
|
+
run_parser.add_argument(
|
|
65
|
+
"--eval-timeout",
|
|
66
|
+
type=int,
|
|
67
|
+
default=None,
|
|
68
|
+
help="Timeout in seconds for each evaluation. No timeout by default. Example: --eval-timeout 3600",
|
|
69
|
+
)
|
|
64
70
|
|
|
65
71
|
|
|
66
72
|
def execute_run_command(args: argparse.Namespace) -> None:
|
|
@@ -77,6 +83,7 @@ def execute_run_command(args: argparse.Namespace) -> None:
|
|
|
77
83
|
log_dir=args.log_dir,
|
|
78
84
|
additional_instructions=args.additional_instructions,
|
|
79
85
|
console=console,
|
|
86
|
+
eval_timeout=args.eval_timeout,
|
|
80
87
|
)
|
|
81
88
|
exit_code = 0 if success else 1
|
|
82
89
|
sys.exit(exit_code)
|
|
@@ -177,7 +184,9 @@ def main() -> None:
|
|
|
177
184
|
|
|
178
185
|
project_path = pathlib.Path(filtered_args[0]) if filtered_args else pathlib.Path.cwd()
|
|
179
186
|
if not project_path.is_dir():
|
|
180
|
-
console.print(
|
|
187
|
+
console.print(
|
|
188
|
+
f"[bold red]Error:[/] The path '{project_path}' is not a valid directory. Please provide a valid directory path."
|
|
189
|
+
)
|
|
181
190
|
sys.exit(1)
|
|
182
191
|
|
|
183
192
|
# Pass the run_parser and model to the chatbot
|