Skip to content

Commit fe105d6

Browse files
dexhunterCopilotDhruvSrikanth
authored
feat: Add timeout params (#36)
* feat: Add step-timeout param * feat: Add overall timeout * fix: use eval-timeout instead of step-timeout * Update weco/api.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: Add a constant.py * fix: default to None (no limit) * Provide clearer response when evaluation times out Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: lint --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Dhruv Srikanth <51223342+DhruvSrikanth@users.noreply.github.com>
1 parent 7749a51 commit fe105d6

File tree

6 files changed

+51
-29
lines changed

6 files changed

+51
-29
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ For more advanced examples, including [Triton](/examples/triton/README.md), [CUD
134134
| `-M, --model` | Model identifier for the LLM to use (e.g., `o4-mini`, `claude-sonnet-4-0`). | `o4-mini` when `OPENAI_API_KEY` is set; `claude-sonnet-4-0` when `ANTHROPIC_API_KEY` is set; `gemini-2.5-pro` when `GEMINI_API_KEY` is set. | `-M o4-mini` |
135135
| `-i, --additional-instructions`| Natural language description of specific instructions **or** path to a file containing detailed instructions to guide the LLM. | `None` | `-i instructions.md` or `-i "Optimize the model for faster inference"`|
136136
| `-l, --log-dir` | Path to the directory to log intermediate steps and final optimization result. | `.runs/` | `-l ./logs/` |
137+
| `--eval-timeout` | Timeout in seconds for each step in evaluation. | No timeout (unlimited) | `--eval-timeout 3600` |
137138

138139
---
139140

weco/api.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from rich.console import Console
88

99
from weco import __pkg_version__, __base_url__
10+
from .constants import DEFAULT_API_TIMEOUT
1011

1112

1213
# --- Session Configuration ---
@@ -48,7 +49,7 @@ def start_optimization_run(
4849
additional_instructions: str = None,
4950
api_keys: Dict[str, Any] = {},
5051
auth_headers: dict = {},
51-
timeout: Union[int, Tuple[int, int]] = 800,
52+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
5253
) -> Dict[str, Any]:
5354
"""Start the optimization run."""
5455
with console.status("[bold green]Starting Optimization..."):
@@ -87,7 +88,7 @@ def evaluate_feedback_then_suggest_next_solution(
8788
additional_instructions: str = None,
8889
api_keys: Dict[str, Any] = {},
8990
auth_headers: dict = {},
90-
timeout: Union[int, Tuple[int, int]] = 800,
91+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
9192
) -> Dict[str, Any]:
9293
"""Evaluate the feedback and suggest the next solution."""
9394
try:
@@ -114,7 +115,10 @@ def evaluate_feedback_then_suggest_next_solution(
114115

115116

116117
def get_optimization_run_status(
117-
run_id: str, include_history: bool = False, auth_headers: dict = {}, timeout: Union[int, Tuple[int, int]] = 800
118+
run_id: str,
119+
include_history: bool = False,
120+
auth_headers: dict = {},
121+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
118122
) -> Dict[str, Any]:
119123
"""Get the current status of the optimization run."""
120124
try:
@@ -132,7 +136,7 @@ def get_optimization_run_status(
132136
raise # Re-raise
133137

134138

135-
def send_heartbeat(run_id: str, auth_headers: dict = {}, timeout: Union[int, Tuple[int, int]] = 10) -> bool:
139+
def send_heartbeat(run_id: str, auth_headers: dict = {}, timeout: Union[int, Tuple[int, int]] = (10, 10)) -> bool:
136140
"""Send a heartbeat signal to the backend."""
137141
try:
138142
session = _get_weco_session()
@@ -156,7 +160,7 @@ def report_termination(
156160
reason: str,
157161
details: Optional[str] = None,
158162
auth_headers: dict = {},
159-
timeout: Union[int, Tuple[int, int]] = 30,
163+
timeout: Union[int, Tuple[int, int]] = (10, 30),
160164
) -> bool:
161165
"""Report the termination reason to the backend."""
162166
try:
@@ -206,7 +210,7 @@ def get_optimization_suggestions_from_codebase(
206210
gitingest_content_str: str,
207211
console: Console,
208212
auth_headers: dict = {},
209-
timeout: Union[int, Tuple[int, int]] = 800,
213+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
210214
) -> Optional[List[Dict[str, Any]]]:
211215
"""Analyze codebase and get optimization suggestions using the model-agnostic backend API."""
212216
try:
@@ -245,7 +249,7 @@ def generate_evaluation_script_and_metrics(
245249
gitingest_content_str: str,
246250
console: Console,
247251
auth_headers: dict = {},
248-
timeout: Union[int, Tuple[int, int]] = 800,
252+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
249253
) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
250254
"""Generate evaluation script and determine metrics using the model-agnostic backend API."""
251255
try:
@@ -286,7 +290,7 @@ def analyze_evaluation_environment(
286290
gitingest_content_str: str,
287291
console: Console,
288292
auth_headers: dict = {},
289-
timeout: Union[int, Tuple[int, int]] = 800,
293+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
290294
) -> Optional[Dict[str, Any]]:
291295
"""Analyze existing evaluation scripts and environment using the model-agnostic backend API."""
292296
try:
@@ -326,7 +330,7 @@ def analyze_script_execution_requirements(
326330
target_file: str,
327331
console: Console,
328332
auth_headers: dict = {},
329-
timeout: Union[int, Tuple[int, int]] = 800,
333+
timeout: Union[int, Tuple[int, int]] = DEFAULT_API_TIMEOUT,
330334
) -> Optional[str]:
331335
"""Analyze script to determine proper execution command using the model-agnostic backend API."""
332336
try:

weco/cli.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ def configure_run_parser(run_parser: argparse.ArgumentParser) -> None:
6161
type=str,
6262
help="Description of additional instruction or path to a file containing additional instructions. Defaults to None.",
6363
)
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+
)
6470

6571

6672
def execute_run_command(args: argparse.Namespace) -> None:
@@ -77,6 +83,7 @@ def execute_run_command(args: argparse.Namespace) -> None:
7783
log_dir=args.log_dir,
7884
additional_instructions=args.additional_instructions,
7985
console=console,
86+
eval_timeout=args.eval_timeout,
8087
)
8188
exit_code = 0 if success else 1
8289
sys.exit(exit_code)

weco/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# weco/constants.py
2+
"""
3+
Constants for the Weco CLI package.
4+
"""
5+
6+
# API timeout configuration (connect_timeout, read_timeout) in seconds
7+
DEFAULT_API_TIMEOUT = (10, 800)

weco/optimizer.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
smooth_update,
3838
format_number,
3939
)
40+
from .constants import DEFAULT_API_TIMEOUT
4041

4142

4243
# --- Heartbeat Sender Class ---
@@ -78,6 +79,7 @@ def execute_optimization(
7879
log_dir: str = ".runs",
7980
additional_instructions: Optional[str] = None,
8081
console: Optional[Console] = None,
82+
eval_timeout: Optional[int] = None,
8183
) -> bool:
8284
"""
8385
Execute the core optimization logic.
@@ -153,7 +155,7 @@ def signal_handler(signum, frame):
153155
"debug_prob": 0.5,
154156
"max_debug_depth": max(1, math.ceil(0.1 * steps)),
155157
}
156-
timeout = 800
158+
api_timeout = DEFAULT_API_TIMEOUT
157159
processed_additional_instructions = read_additional_instructions(additional_instructions=additional_instructions)
158160
source_fp = pathlib.Path(source)
159161
source_code = read_from_path(fp=source_fp, is_json=False)
@@ -181,7 +183,7 @@ def signal_handler(signum, frame):
181183
additional_instructions=processed_additional_instructions,
182184
api_keys=llm_api_keys,
183185
auth_headers=auth_headers,
184-
timeout=timeout,
186+
timeout=api_timeout,
185187
)
186188
run_id = run_response["run_id"]
187189
current_run_id_for_heartbeat = run_id
@@ -248,7 +250,7 @@ def signal_handler(signum, frame):
248250
)
249251

250252
# Run evaluation on the initial solution
251-
term_out = run_evaluation(eval_command=eval_command)
253+
term_out = run_evaluation(eval_command=eval_command, timeout=eval_timeout)
252254
# Update the evaluation output panel
253255
eval_output_panel.update(output=term_out)
254256
smooth_update(
@@ -265,7 +267,7 @@ def signal_handler(signum, frame):
265267
if run_id:
266268
try:
267269
current_status_response = get_optimization_run_status(
268-
run_id=run_id, include_history=False, timeout=30, auth_headers=auth_headers
270+
run_id=run_id, include_history=False, timeout=(10, 30), auth_headers=auth_headers
269271
)
270272
current_run_status_val = current_status_response.get("status")
271273
if current_run_status_val == "stopping":
@@ -284,14 +286,14 @@ def signal_handler(signum, frame):
284286
additional_instructions=current_additional_instructions,
285287
api_keys=llm_api_keys,
286288
auth_headers=auth_headers,
287-
timeout=timeout,
289+
timeout=api_timeout,
288290
)
289291
# Save next solution (.runs/<run-id>/step_<step>.<extension>)
290292
write_to_path(fp=runs_dir / f"step_{step}{source_fp.suffix}", content=eval_and_next_solution_response["code"])
291293
# Write the next solution to the source file
292294
write_to_path(fp=source_fp, content=eval_and_next_solution_response["code"])
293295
status_response = get_optimization_run_status(
294-
run_id=run_id, include_history=True, timeout=timeout, auth_headers=auth_headers
296+
run_id=run_id, include_history=True, timeout=api_timeout, auth_headers=auth_headers
295297
)
296298
# Update the step of the progress bar, token counts, plan and metric tree
297299
summary_panel.set_step(step=step)
@@ -347,7 +349,7 @@ def signal_handler(signum, frame):
347349
],
348350
transition_delay=0.08, # Slightly longer delay for more noticeable transitions
349351
)
350-
term_out = run_evaluation(eval_command=eval_command)
352+
term_out = run_evaluation(eval_command=eval_command, timeout=eval_timeout)
351353
eval_output_panel.update(output=term_out)
352354
smooth_update(
353355
live=live,
@@ -365,13 +367,13 @@ def signal_handler(signum, frame):
365367
execution_output=term_out,
366368
additional_instructions=current_additional_instructions,
367369
api_keys=llm_api_keys,
368-
timeout=timeout,
370+
timeout=api_timeout,
369371
auth_headers=auth_headers,
370372
)
371373
summary_panel.set_step(step=steps)
372374
summary_panel.update_token_counts(usage=eval_and_next_solution_response["usage"])
373375
status_response = get_optimization_run_status(
374-
run_id=run_id, include_history=True, timeout=timeout, auth_headers=auth_headers
376+
run_id=run_id, include_history=True, timeout=api_timeout, auth_headers=auth_headers
375377
)
376378
# No need to update the plan panel since we have finished the optimization
377379
# Get the optimization run status for

weco/utils.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,20 +157,21 @@ def truncate_output(output: str, max_lines: int = DEFAULT_MAX_LINES, max_chars:
157157
return output
158158

159159

160-
def run_evaluation(eval_command: str) -> str:
160+
def run_evaluation(eval_command: str, timeout: int | None = None) -> str:
161161
"""Run the evaluation command on the code and return the output."""
162162

163163
# Run the eval command as is
164-
result = subprocess.run(eval_command, shell=True, capture_output=True, text=True, check=False)
165-
166-
# Combine stdout and stderr for complete output
167-
output = result.stderr if result.stderr else ""
168-
if result.stdout:
169-
if len(output) > 0:
170-
output += "\n"
171-
output += result.stdout
172-
173-
return truncate_output(output)
164+
try:
165+
result = subprocess.run(eval_command, shell=True, capture_output=True, text=True, check=False, timeout=timeout)
166+
# Combine stdout and stderr for complete output
167+
output = result.stderr if result.stderr else ""
168+
if result.stdout:
169+
if len(output) > 0:
170+
output += "\n"
171+
output += result.stdout
172+
return truncate_output(output)
173+
except subprocess.TimeoutExpired:
174+
return f"Evaluation timed out after {'an unspecified duration' if timeout is None else f'{timeout} seconds'}."
174175

175176

176177
# Update Check Function

0 commit comments

Comments
 (0)