weco 0.1.10__py3-none-any.whl → 0.2.0__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/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from .client import WecoAI
2
- from .functional import abuild, aquery, batch_query, build, query
3
-
4
- __all__ = ["WecoAI", "build", "abuild", "query", "aquery", "batch_query"]
1
+ # DO NOT EDIT
2
+ __pkg_version__ = "0.2.0"
3
+ __api_version__ = "v1"
4
+ __base_url__ = f"https://api.aide.weco.ai/{__api_version__}"
weco/api.py ADDED
@@ -0,0 +1,89 @@
1
+ from typing import Dict, Any
2
+ import rich
3
+ import requests
4
+ from weco import __pkg_version__, __base_url__
5
+ import sys
6
+
7
+
8
+ def handle_api_error(e: requests.exceptions.HTTPError, console: rich.console.Console) -> None:
9
+ """Extract and display error messages from API responses."""
10
+ try:
11
+ error_data = e.response.json()
12
+ error_message = error_data.get("detail", str(e))
13
+ console.print(f"[bold red]Server Error:[/] {error_message}")
14
+ except Exception:
15
+ # If we can't parse the JSON, just show the original error
16
+ console.print(f"[bold red]Server Error:[/] {str(e)}")
17
+ sys.exit(1)
18
+
19
+
20
+ def start_optimization_session(
21
+ console: rich.console.Console,
22
+ source_code: str,
23
+ evaluation_command: str,
24
+ metric_name: str,
25
+ maximize: bool,
26
+ steps: int,
27
+ code_generator_config: Dict[str, Any],
28
+ evaluator_config: Dict[str, Any],
29
+ search_policy_config: Dict[str, Any],
30
+ additional_instructions: str = None,
31
+ api_keys: Dict[str, Any] = {},
32
+ ) -> Dict[str, Any]:
33
+ """Start the optimization session."""
34
+ with console.status("[bold green]Starting Optimization..."):
35
+ try:
36
+ response = requests.post(
37
+ f"{__base_url__}/sessions",
38
+ json={
39
+ "source_code": source_code,
40
+ "additional_instructions": additional_instructions,
41
+ "objective": {"evaluation_command": evaluation_command, "metric_name": metric_name, "maximize": maximize},
42
+ "optimizer": {
43
+ "steps": steps,
44
+ "code_generator": code_generator_config,
45
+ "evaluator": evaluator_config,
46
+ "search_policy": search_policy_config,
47
+ },
48
+ "metadata": {"client_name": "cli", "client_version": __pkg_version__, **api_keys},
49
+ },
50
+ )
51
+ response.raise_for_status()
52
+ return response.json()
53
+ except requests.exceptions.HTTPError as e:
54
+ handle_api_error(e=e, console=console)
55
+
56
+
57
+ def evaluate_feedback_then_suggest_next_solution(
58
+ console: rich.console.Console,
59
+ session_id: str,
60
+ execution_output: str,
61
+ additional_instructions: str = None,
62
+ api_keys: Dict[str, Any] = {},
63
+ ) -> Dict[str, Any]:
64
+ """Evaluate the feedback and suggest the next solution."""
65
+ try:
66
+ response = requests.post(
67
+ f"{__base_url__}/sessions/{session_id}/suggest",
68
+ json={
69
+ "execution_output": execution_output,
70
+ "additional_instructions": additional_instructions,
71
+ "metadata": {**api_keys},
72
+ },
73
+ )
74
+ response.raise_for_status()
75
+ return response.json()
76
+ except requests.exceptions.HTTPError as e:
77
+ handle_api_error(e=e, console=console)
78
+
79
+
80
+ def get_optimization_session_status(
81
+ console: rich.console.Console, session_id: str, include_history: bool = False
82
+ ) -> Dict[str, Any]:
83
+ """Get the current status of the optimization session."""
84
+ try:
85
+ response = requests.get(f"{__base_url__}/sessions/{session_id}", params={"include_history": include_history})
86
+ response.raise_for_status()
87
+ return response.json()
88
+ except requests.exceptions.HTTPError as e:
89
+ handle_api_error(e=e, console=console)
weco/cli.py ADDED
@@ -0,0 +1,333 @@
1
+ import argparse
2
+ import sys
3
+ import pathlib
4
+ import math
5
+ from rich.console import Console
6
+ from rich.live import Live
7
+ from rich.panel import Panel
8
+ from rich.traceback import install
9
+ from .api import start_optimization_session, evaluate_feedback_then_suggest_next_solution, get_optimization_session_status
10
+ from .panels import (
11
+ SummaryPanel,
12
+ PlanPanel,
13
+ Node,
14
+ MetricTreePanel,
15
+ EvaluationOutputPanel,
16
+ SolutionPanels,
17
+ create_optimization_layout,
18
+ create_end_optimization_layout,
19
+ )
20
+ from .utils import (
21
+ read_api_keys_from_env,
22
+ read_additional_instructions,
23
+ read_from_path,
24
+ write_to_path,
25
+ run_evaluation,
26
+ smooth_update,
27
+ format_number,
28
+ )
29
+
30
+ install(show_locals=True)
31
+ console = Console()
32
+
33
+
34
+ def main() -> None:
35
+ """Main function for the Weco CLI."""
36
+ parser = argparse.ArgumentParser(
37
+ description="[bold cyan]Weco CLI[/]", formatter_class=argparse.RawDescriptionHelpFormatter
38
+ )
39
+ parser.add_argument("--source", type=str, required=True, help="Path to the Python source code (e.g. optimize.py)")
40
+ parser.add_argument(
41
+ "--eval-command", type=str, required=True, help="Command to run for evaluation (e.g. 'python eval.py --arg1=val1')"
42
+ )
43
+ parser.add_argument("--metric", type=str, required=True, help="Metric to optimize")
44
+ parser.add_argument("--maximize", type=bool, required=True, help="Maximize the metric")
45
+ parser.add_argument("--steps", type=int, required=True, help="Number of steps to run")
46
+ parser.add_argument("--model", type=str, required=True, help="Model to use for optimization")
47
+ parser.add_argument(
48
+ "--additional-instructions",
49
+ default=None,
50
+ type=str,
51
+ help="Description of additional instruction or path to a file containing additional instructions",
52
+ )
53
+ args = parser.parse_args()
54
+
55
+ try:
56
+ with console.status("[bold green]Loading Modules..."):
57
+ # Define optimization session config
58
+ evaluation_command = args.eval_command
59
+ metric_name = args.metric
60
+ maximize = args.maximize
61
+ steps = args.steps
62
+ code_generator_config = {"model": args.model}
63
+ evaluator_config = {"model": args.model}
64
+ search_policy_config = {
65
+ "num_drafts": max(1, math.ceil(0.15 * steps)), # 15% of steps
66
+ "debug_prob": 0.5,
67
+ "max_debug_depth": max(1, math.ceil(0.1 * steps)), # 10% of steps
68
+ }
69
+ # Read additional instructions
70
+ additional_instructions = read_additional_instructions(additional_instructions=args.additional_instructions)
71
+ # Read source code
72
+ source_fp = pathlib.Path(args.source)
73
+ source_code = read_from_path(fp=source_fp, is_json=False)
74
+ # Read API keys
75
+ api_keys = read_api_keys_from_env()
76
+
77
+ # Initialize panels
78
+ summary_panel = SummaryPanel(total_steps=steps, model=args.model)
79
+ plan_panel = PlanPanel()
80
+ solution_panels = SolutionPanels()
81
+ eval_output_panel = EvaluationOutputPanel()
82
+ tree_panel = MetricTreePanel(maximize=maximize)
83
+ layout = create_optimization_layout()
84
+ end_optimization_layout = create_end_optimization_layout()
85
+
86
+ # Start optimization session
87
+ session_response = start_optimization_session(
88
+ console=console,
89
+ source_code=source_code,
90
+ evaluation_command=evaluation_command,
91
+ metric_name=metric_name,
92
+ maximize=maximize,
93
+ steps=steps,
94
+ code_generator_config=code_generator_config,
95
+ evaluator_config=evaluator_config,
96
+ search_policy_config=search_policy_config,
97
+ additional_instructions=additional_instructions,
98
+ api_keys=api_keys,
99
+ )
100
+
101
+ # Define the runs directory (.runs/<session-id>)
102
+ session_id = session_response["session_id"]
103
+ runs_dir = pathlib.Path(".runs") / session_id
104
+ runs_dir.mkdir(parents=True, exist_ok=True)
105
+
106
+ # Save the original code (.runs/<session-id>/original.py)
107
+ runs_copy_source_fp = runs_dir / "original.py"
108
+ write_to_path(fp=runs_copy_source_fp, content=source_code)
109
+
110
+ # Write the code string to the source file path
111
+ # Do this after the original code is saved
112
+ write_to_path(fp=source_fp, content=session_response["code"])
113
+
114
+ # Update the panels with the initial solution
115
+ # Add session id now that we have it
116
+ summary_panel.session_id = session_id
117
+ # Set the step of the progress bar
118
+ summary_panel.set_step(step=0)
119
+ # Update the token counts
120
+ summary_panel.update_token_counts(usage=session_response["usage"])
121
+ # Update the plan
122
+ plan_panel.update(plan=session_response["plan"])
123
+ # Build the metric tree
124
+ tree_panel.build_metric_tree(
125
+ nodes=[
126
+ {
127
+ "solution_id": session_response["solution_id"],
128
+ "parent_id": None,
129
+ "code": session_response["code"],
130
+ "step": 0,
131
+ "metric_value": None,
132
+ "is_buggy": False,
133
+ }
134
+ ]
135
+ )
136
+ # Set the current solution as unevaluated since we haven't run the evaluation function and fed it back to the model yet
137
+ tree_panel.set_unevaluated_node(node_id=session_response["solution_id"])
138
+ # Update the solution panels with the initial solution and get the panel displays
139
+ solution_panels.update(
140
+ current_node=Node(
141
+ id=session_response["solution_id"], parent_id=None, code=session_response["code"], metric=None, is_buggy=False
142
+ ),
143
+ best_node=None,
144
+ )
145
+ current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=0)
146
+ # Define the refresh rate
147
+ refresh_rate = 4
148
+ with Live(layout, refresh_per_second=refresh_rate, screen=True) as live:
149
+ # Update the entire layout
150
+ smooth_update(
151
+ live=live,
152
+ layout=layout,
153
+ sections_to_update=[
154
+ ("summary", summary_panel.get_display()),
155
+ ("plan", plan_panel.get_display()),
156
+ ("tree", tree_panel.get_display()),
157
+ ("current_solution", current_solution_panel),
158
+ ("best_solution", best_solution_panel),
159
+ ("eval_output", eval_output_panel.get_display()),
160
+ ],
161
+ transition_delay=0.1, # Slightly longer delay for initial display
162
+ )
163
+
164
+ # Run evaluation on the initial solution
165
+ term_out = run_evaluation(eval_command=args.eval_command)
166
+
167
+ # Update the evaluation output panel
168
+ eval_output_panel.update(output=term_out)
169
+ smooth_update(
170
+ live=live,
171
+ layout=layout,
172
+ sections_to_update=[("eval_output", eval_output_panel.get_display())],
173
+ transition_delay=0.1,
174
+ )
175
+
176
+ for step in range(1, steps):
177
+ # Evaluate the current output and get the next solution
178
+ eval_and_next_solution_response = evaluate_feedback_then_suggest_next_solution(
179
+ console=console,
180
+ session_id=session_id,
181
+ execution_output=term_out,
182
+ additional_instructions=additional_instructions,
183
+ api_keys=api_keys,
184
+ )
185
+ # Save next solution (.runs/<session-id>/step_<step>.py)
186
+ write_to_path(fp=runs_dir / f"step_{step}.py", content=eval_and_next_solution_response["code"])
187
+
188
+ # Get the optimization session status for
189
+ # the best solution, its score, and the history to plot the tree
190
+ status_response = get_optimization_session_status(console=console, session_id=session_id, include_history=True)
191
+
192
+ # Update the step of the progress bar
193
+ summary_panel.set_step(step=step)
194
+ # Update the token counts
195
+ summary_panel.update_token_counts(usage=eval_and_next_solution_response["usage"])
196
+ # Update the plan
197
+ plan_panel.update(plan=eval_and_next_solution_response["plan"])
198
+ # Build the metric tree
199
+ tree_panel.build_metric_tree(nodes=status_response["history"])
200
+ # Set the current solution as unevaluated since we haven't run the evaluation function and fed it back to the model yet
201
+ tree_panel.set_unevaluated_node(node_id=eval_and_next_solution_response["solution_id"])
202
+
203
+ # Update the solution panels with the next solution and best solution (and score)
204
+ # Figure out if we have a best solution so far
205
+ if status_response["best_result"] is not None:
206
+ best_solution_node = Node(
207
+ id=status_response["best_result"]["solution_id"],
208
+ parent_id=status_response["best_result"]["parent_id"],
209
+ code=status_response["best_result"]["code"],
210
+ metric=status_response["best_result"]["metric_value"],
211
+ is_buggy=status_response["best_result"]["is_buggy"],
212
+ )
213
+ else:
214
+ best_solution_node = None
215
+
216
+ # Create a node for the current solution
217
+ current_solution_node = None
218
+ for node in status_response["history"]:
219
+ if node["solution_id"] == eval_and_next_solution_response["solution_id"]:
220
+ current_solution_node = Node(
221
+ id=node["solution_id"],
222
+ parent_id=node["parent_id"],
223
+ code=node["code"],
224
+ metric=node["metric_value"],
225
+ is_buggy=node["is_buggy"],
226
+ )
227
+ if current_solution_node is None:
228
+ raise ValueError("Current solution node not found in history")
229
+ # Update the solution panels with the current and best solution
230
+ solution_panels.update(current_node=current_solution_node, best_node=best_solution_node)
231
+ current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=step)
232
+
233
+ # Clear evaluation output since we are running a evaluation on a new solution
234
+ eval_output_panel.clear()
235
+
236
+ # Update displays with smooth transitions
237
+ smooth_update(
238
+ live=live,
239
+ layout=layout,
240
+ sections_to_update=[
241
+ ("summary", summary_panel.get_display()),
242
+ ("plan", plan_panel.get_display()),
243
+ ("tree", tree_panel.get_display()),
244
+ ("current_solution", current_solution_panel),
245
+ ("best_solution", best_solution_panel),
246
+ ("eval_output", eval_output_panel.get_display()),
247
+ ],
248
+ transition_delay=0.08, # Slightly longer delay for more noticeable transitions
249
+ )
250
+
251
+ # Run evaluation on the current solution
252
+ term_out = run_evaluation(eval_command=args.eval_command)
253
+ eval_output_panel.update(output=term_out)
254
+
255
+ # Update evaluation output with a smooth transition
256
+ smooth_update(
257
+ live=live,
258
+ layout=layout,
259
+ sections_to_update=[("eval_output", eval_output_panel.get_display())],
260
+ transition_delay=0.1, # Slightly longer delay for evaluation results
261
+ )
262
+
263
+ # Ensure we pass evaluation results for the last step's generated solution
264
+ eval_and_next_solution_response = evaluate_feedback_then_suggest_next_solution(
265
+ console=console,
266
+ session_id=session_id,
267
+ execution_output=term_out,
268
+ additional_instructions=additional_instructions,
269
+ api_keys=api_keys,
270
+ )
271
+
272
+ # Update the progress bar
273
+ summary_panel.set_step(step=steps)
274
+ # Update the token counts
275
+ summary_panel.update_token_counts(usage=eval_and_next_solution_response["usage"])
276
+ # No need to update the plan panel since we have finished the optimization
277
+ # Get the optimization session status for
278
+ # the best solution, its score, and the history to plot the tree
279
+ status_response = get_optimization_session_status(console=console, session_id=session_id, include_history=True)
280
+ # Build the metric tree
281
+ tree_panel.build_metric_tree(nodes=status_response["history"])
282
+ # No need to set any solution to unevaluated since we have finished the optimization
283
+ # and all solutions have been evaluated
284
+ # No neeed to update the current solution panel since we have finished the optimization
285
+ # We only need to update the best solution panel
286
+ # Figure out if we have a best solution so far
287
+ if status_response["best_result"] is not None:
288
+ best_solution_node = Node(
289
+ id=status_response["best_result"]["solution_id"],
290
+ parent_id=status_response["best_result"]["parent_id"],
291
+ code=status_response["best_result"]["code"],
292
+ metric=status_response["best_result"]["metric_value"],
293
+ is_buggy=status_response["best_result"]["is_buggy"],
294
+ )
295
+ else:
296
+ best_solution_node = None
297
+ solution_panels.update(current_node=None, best_node=best_solution_node)
298
+ _, best_solution_panel = solution_panels.get_display(current_step=steps)
299
+
300
+ # Update the end optimization layout
301
+ end_optimization_layout["summary"].update(summary_panel.get_display())
302
+ end_optimization_layout["tree"].update(tree_panel.get_display())
303
+ end_optimization_layout["best_solution"].update(best_solution_panel)
304
+
305
+ # Save optimization results
306
+ # If the best solution does not exist or is has not been measured at the end of the optimization
307
+ # save the original solution as the best solution
308
+ best_solution_code = best_solution_node.code
309
+ best_solution_score = best_solution_node.metric
310
+ if best_solution_code is None or best_solution_score is None:
311
+ best_solution_content = (
312
+ f"# Weco could not find a better solution\n\n{read_from_path(fp=runs_copy_source_fp, is_json=False)}"
313
+ )
314
+ else:
315
+ # Format score for the comment
316
+ best_score_str = (
317
+ format_number(best_solution_score)
318
+ if best_solution_score is not None and isinstance(best_solution_score, (int, float))
319
+ else "N/A"
320
+ )
321
+ best_solution_content = f"# Best solution from Weco with a score of {best_score_str}\n\n{best_solution_code}"
322
+
323
+ # Save best solution to .runs/<session-id>/best.py
324
+ write_to_path(fp=runs_dir / "best.py", content=best_solution_content)
325
+
326
+ # write the best solution to the source file
327
+ write_to_path(fp=source_fp, content=best_solution_content)
328
+
329
+ console.print(end_optimization_layout)
330
+
331
+ except Exception as e:
332
+ console.print(Panel(f"[bold red]Error: {str(e)}", title="[bold red]Error", border_style="red"))
333
+ sys.exit(1)