PRANAV05092003 commited on
Commit
e7eb0fa
·
1 Parent(s): 580a50b

Final Commit

Browse files
Files changed (9) hide show
  1. Dockerfile +2 -2
  2. README.md +1 -1
  3. openenv.yaml +1 -1
  4. pyproject.toml +8 -3
  5. requirements.txt +1 -1
  6. server.py +9 -75
  7. server/__init__.py +4 -0
  8. server/app.py +396 -0
  9. uv.lock +0 -0
Dockerfile CHANGED
@@ -18,6 +18,6 @@ ENV PORT=7860
18
  EXPOSE 7860
19
 
20
  HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
21
- CMD python -c "import requests; requests.get('http://localhost:7860/').raise_for_status()"
22
 
23
- CMD ["python", "server.py"]
 
18
  EXPOSE 7860
19
 
20
  HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
21
+ CMD python -c "import requests; requests.get('http://localhost:7860/health').raise_for_status()"
22
 
23
+ CMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -157,7 +157,7 @@ Uses Pydantic models:
157
 
158
  ```bash
159
  pip install -r requirements.txt
160
- python server.py
161
  ```
162
 
163
  ---
 
157
 
158
  ```bash
159
  pip install -r requirements.txt
160
+ uvicorn server.app:app --host 0.0.0.0 --port 7860
161
  ```
162
 
163
  ---
openenv.yaml CHANGED
@@ -4,7 +4,7 @@ description: >
4
  Autonomous Code Refactoring Environment - an RL environment where an
5
  agent improves Python code quality using AST-level transformations.
6
  author: "Nikhil Pratap Singh, Pranav Mangal, Ananya Gupta"
7
- entrypoint: "openenv_interface:OpenEnvRefactorEnv"
8
  tags:
9
  - openenv
10
 
 
4
  Autonomous Code Refactoring Environment - an RL environment where an
5
  agent improves Python code quality using AST-level transformations.
6
  author: "Nikhil Pratap Singh, Pranav Mangal, Ananya Gupta"
7
+ entrypoint: "server.app:app"
8
  tags:
9
  - openenv
10
 
pyproject.toml CHANGED
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
  [project]
6
  name = "acre-openenv"
7
  version = "1.0.0"
8
- description = "Autonomous Code Refactoring Environment using OpenEnv"
9
  authors = [
10
  { name = "Pranav Mangal" }
11
  ]
@@ -14,12 +14,17 @@ dependencies = [
14
  "uvicorn",
15
  "pydantic",
16
  "openai",
17
- "gymnasium"
 
18
  ]
19
 
 
 
 
20
  [tool.setuptools.packages.find]
21
  where = ["."]
 
22
 
23
  [tool.openenv]
24
- entry_point = "server.py"
25
 
 
5
  [project]
6
  name = "acre-openenv"
7
  version = "1.0.0"
8
+ description = "Autonomous Code Refactoring Environment"
9
  authors = [
10
  { name = "Pranav Mangal" }
11
  ]
 
14
  "uvicorn",
15
  "pydantic",
16
  "openai",
17
+ "gymnasium",
18
+ "openenv-core>=0.2.0"
19
  ]
20
 
21
+ [project.scripts]
22
+ server = "server.app:app"
23
+
24
  [tool.setuptools.packages.find]
25
  where = ["."]
26
+ include = ["acre*", "server*"]
27
 
28
  [tool.openenv]
29
+ entry_point = "server.app:app"
30
 
requirements.txt CHANGED
@@ -5,7 +5,7 @@ gymnasium
5
  stable-baselines3
6
  radon>=6.0.1
7
  openai>=1.0.0
8
- openenv>=0.1.13
9
  requests>=2.31.0
10
  pydantic>=2.0.0
11
  typing_extensions>=4.0.0
 
 
5
  stable-baselines3
6
  radon>=6.0.1
7
  openai>=1.0.0
 
8
  requests>=2.31.0
9
  pydantic>=2.0.0
10
  typing_extensions>=4.0.0
11
+ openenv-core>=0.2.0
server.py CHANGED
@@ -1,84 +1,18 @@
1
  """
2
- ACRE OpenEnv HTTP server.
3
-
4
- Endpoints (all required by OpenEnv spec):
5
- GET / — health check (must return HTTP 200)
6
- POST /reset — reset environment, returns observation + info
7
- POST /step — take one step, returns obs/reward/done/info
8
- GET /state — full current state snapshot
9
- GET /tasks — list all tasks with initial code
10
- POST /tasks/{task_id}/grade — grade code for a specific task
11
  """
 
12
  from __future__ import annotations
13
 
14
- import difflib
15
  import os
16
- import re
17
- import json
18
- import sys
19
- from typing import Optional
20
-
21
  import uvicorn
22
- import numpy as np
23
- from fastapi import FastAPI, HTTPException
24
- from fastapi.middleware.cors import CORSMiddleware
25
- from fastapi.responses import HTMLResponse, JSONResponse
26
- from openai import OpenAI
27
-
28
- PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
29
- if PROJECT_ROOT not in sys.path:
30
- sys.path.insert(0, PROJECT_ROOT)
31
-
32
- try:
33
- from stable_baselines3 import PPO
34
- except Exception:
35
- PPO = None # type: ignore[assignment]
36
-
37
- from acre.tasks.task_registry import TaskRegistry
38
- from models import (
39
- ActionModel,
40
- CompatibilityHealthResponse,
41
- GradeRequest,
42
- GradeResponse,
43
- HealthResponse,
44
- OptimizationStep,
45
- OptimizeRequest,
46
- OptimizeResponse,
47
- ResetRequest,
48
- ResetResponse,
49
- StateResponse,
50
- StepRequest,
51
- StepResponse,
52
- TaskInfo,
53
- TasksResponse,
54
- )
55
- from openenv_interface import OpenEnvRefactorEnv
56
-
57
- DEFAULT_API_BASE_URL = os.getenv("API_BASE_URL", "https://api.openai.com/v1")
58
- DEFAULT_MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4o-mini")
59
- DEFAULT_RL_MODEL_PATH = os.getenv("RL_MODEL_PATH", "acre_agent.zip")
60
-
61
- # ---------------------------------------------------------------------------
62
- # App setup
63
- # ---------------------------------------------------------------------------
64
-
65
- app = FastAPI(
66
- title="ACRE — Autonomous Code Refactoring Environment",
67
- description="OpenEnv-compatible RL environment for Python code refactoring.",
68
- version="1.0.0",
69
- )
70
-
71
- app.add_middleware(
72
- CORSMiddleware,
73
- allow_origins=["*"],
74
- allow_methods=["*"],
75
- allow_headers=["*"],
76
- )
77
 
78
- # Global singletons
79
- registry = TaskRegistry()
80
- _env: Optional[OpenEnvRefactorEnv] = None
81
- _rl_model_cache: dict[str, object] = {}
82
 
83
 
84
  def get_env() -> OpenEnvRefactorEnv:
@@ -704,4 +638,4 @@ def optimize(req: OptimizeRequest) -> OptimizeResponse:
704
 
705
  if __name__ == "__main__":
706
  port = int(os.getenv("PORT", 7860))
707
- uvicorn.run(app, host="0.0.0.0", port=port)
 
1
  """
2
+ Legacy server runner.
3
+
4
+ OpenEnv validation expects the FastAPI app to be importable at:
5
+ server.app:app
6
+
7
+ This file is kept as a thin runner for local execution and Docker CMD.
 
 
 
8
  """
9
+
10
  from __future__ import annotations
11
 
 
12
  import os
 
 
 
 
 
13
  import uvicorn
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ from server.app import app # noqa: F401 (re-export for convenience)
 
 
 
16
 
17
 
18
  def get_env() -> OpenEnvRefactorEnv:
 
638
 
639
  if __name__ == "__main__":
640
  port = int(os.getenv("PORT", 7860))
641
+ uvicorn.run("server.app:app", host="0.0.0.0", port=port)
server/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ """
2
+ Server package for OpenEnv/Hugging Face entrypoint.
3
+ """
4
+
server/app.py ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ OpenEnv / Hugging Face importable entrypoint.
3
+
4
+ OpenEnv validation expects an importable FastAPI app at:
5
+ server.app:app
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import difflib
11
+ import json
12
+ import os
13
+ import re
14
+ import sys
15
+ from typing import Optional
16
+
17
+ import numpy as np
18
+ from fastapi import FastAPI, HTTPException
19
+ from fastapi.middleware.cors import CORSMiddleware
20
+ from fastapi.responses import HTMLResponse, JSONResponse
21
+ from openai import OpenAI
22
+
23
+ # Ensure project root is importable when executed in Spaces/Docker.
24
+ PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
25
+ if PROJECT_ROOT not in sys.path:
26
+ sys.path.insert(0, PROJECT_ROOT)
27
+
28
+ try:
29
+ from stable_baselines3 import PPO
30
+ except Exception:
31
+ PPO = None # type: ignore[assignment]
32
+
33
+ from acre.tasks.task_registry import TaskRegistry
34
+ from models import (
35
+ ActionModel,
36
+ CompatibilityHealthResponse,
37
+ GradeRequest,
38
+ GradeResponse,
39
+ HealthResponse,
40
+ OptimizationStep,
41
+ OptimizeRequest,
42
+ OptimizeResponse,
43
+ ResetRequest,
44
+ ResetResponse,
45
+ StateResponse,
46
+ StepRequest,
47
+ StepResponse,
48
+ TaskInfo,
49
+ TasksResponse,
50
+ )
51
+ from openenv_interface import OpenEnvRefactorEnv
52
+
53
+ DEFAULT_API_BASE_URL = os.getenv("API_BASE_URL", "https://api.openai.com/v1")
54
+ DEFAULT_MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4o-mini")
55
+ DEFAULT_RL_MODEL_PATH = os.getenv("RL_MODEL_PATH", "acre_agent.zip")
56
+
57
+ app = FastAPI(
58
+ title="ACRE — Autonomous Code Refactoring Environment",
59
+ description="OpenEnv-compatible RL environment for Python code refactoring.",
60
+ version="1.0.0",
61
+ )
62
+
63
+ app.add_middleware(
64
+ CORSMiddleware,
65
+ allow_origins=["*"],
66
+ allow_methods=["*"],
67
+ allow_headers=["*"],
68
+ )
69
+
70
+ registry = TaskRegistry()
71
+ _env: Optional[OpenEnvRefactorEnv] = None
72
+ _rl_model_cache: dict[str, object] = {}
73
+
74
+
75
+ def get_env() -> OpenEnvRefactorEnv:
76
+ global _env
77
+ if _env is None:
78
+ _env = OpenEnvRefactorEnv(registry=registry)
79
+ return _env
80
+
81
+
82
+ def _state_response() -> StateResponse:
83
+ return get_env().state()
84
+
85
+
86
+ def _choose_action_heuristic(code: str, task_id: Optional[str]) -> int:
87
+ has_generic = re.search(r"\b(x|tmp|i)\b", code) is not None
88
+ has_if_false = re.search(r"\bif\s+False\b", code) is not None
89
+ has_if_true = re.search(r"\bif\s+True\b", code) is not None
90
+ has_append_loop = ".append(" in code and "for " in code
91
+ has_double_not = "not not" in code
92
+ has_add_call = "add(" in code
93
+
94
+ if task_id == "rename_variables":
95
+ if has_generic:
96
+ return 0
97
+ if has_if_false or "unused" in code:
98
+ return 1
99
+ if has_append_loop:
100
+ return 2
101
+ if has_if_true or has_double_not:
102
+ return 3
103
+ return 4
104
+
105
+ if task_id == "remove_dead_code":
106
+ if has_if_false or "unused" in code:
107
+ return 1
108
+ if has_append_loop:
109
+ return 2
110
+ if has_if_true or has_double_not:
111
+ return 3
112
+ if has_generic:
113
+ return 0
114
+ return 4
115
+
116
+ if has_generic:
117
+ return 0
118
+ if has_append_loop:
119
+ return 2
120
+ if has_if_false or has_if_true or has_double_not:
121
+ return 3
122
+ if has_add_call:
123
+ return 4
124
+ return 1
125
+
126
+
127
+ def _choose_action_llm(
128
+ *,
129
+ code: str,
130
+ task_id: Optional[str],
131
+ step_index: int,
132
+ max_steps: int,
133
+ api_base_url: str,
134
+ model_name: str,
135
+ api_token: str,
136
+ ) -> tuple[int, str, str]:
137
+ if not api_token.strip():
138
+ return _choose_action_heuristic(code, task_id), "empty token -> heuristic", "heuristic"
139
+
140
+ client = OpenAI(base_url=api_base_url, api_key=api_token)
141
+ messages = [
142
+ {
143
+ "role": "system",
144
+ "content": (
145
+ "You are a code-refactoring action selector. Return ONLY compact JSON: "
146
+ '{"action": <0-4>, "reason": "..."}.\n'
147
+ "Actions: 0=rename_variable,1=remove_dead_code,2=simplify_loop,3=optimize_condition,4=inline_function"
148
+ ),
149
+ },
150
+ {
151
+ "role": "user",
152
+ "content": (
153
+ f"task_id={task_id or 'auto'}\n"
154
+ f"step={step_index}/{max_steps}\n"
155
+ "Current code:\n"
156
+ f"```python\n{code}\n```"
157
+ ),
158
+ },
159
+ ]
160
+ try:
161
+ resp = client.chat.completions.create(
162
+ model=model_name,
163
+ messages=messages,
164
+ temperature=0.0,
165
+ max_tokens=120,
166
+ )
167
+ raw = (resp.choices[0].message.content or "").strip()
168
+ m = re.search(r"\{.*\}", raw, flags=re.DOTALL)
169
+ blob = m.group(0) if m else raw
170
+ parsed = json.loads(blob)
171
+ action = int(parsed.get("action", -1))
172
+ reason = str(parsed.get("reason", "llm-selected action"))
173
+ if 0 <= action <= 4:
174
+ return action, reason, "llm"
175
+ except Exception as exc:
176
+ return _choose_action_heuristic(code, task_id), f"llm error -> heuristic: {exc}", "heuristic"
177
+
178
+ return _choose_action_heuristic(code, task_id), "invalid llm output -> heuristic", "heuristic"
179
+
180
+
181
+ def _choose_action_rl(observation: list[float], model_path: str) -> tuple[Optional[int], str, str]:
182
+ if PPO is None:
183
+ return None, "stable-baselines3 unavailable", "rl"
184
+ if not os.path.exists(model_path):
185
+ return None, f"rl model not found: {model_path}", "rl"
186
+
187
+ try:
188
+ model = _rl_model_cache.get(model_path)
189
+ if model is None:
190
+ model = PPO.load(model_path)
191
+ _rl_model_cache[model_path] = model
192
+
193
+ obs = np.asarray(observation, dtype=np.float32)
194
+ action, _ = model.predict(obs, deterministic=True)
195
+ action_i = int(action)
196
+ if 0 <= action_i <= 4:
197
+ return action_i, "rl policy action", "rl"
198
+ return None, f"invalid rl action: {action_i}", "rl"
199
+ except Exception as exc:
200
+ return None, f"rl failure: {exc}", "rl"
201
+
202
+
203
+ def _demo_html() -> str:
204
+ # Import the existing UI HTML from root server.py if present, else fallback.
205
+ try:
206
+ import server as legacy_server # type: ignore
207
+ return str(getattr(legacy_server, "_demo_html")())
208
+ except Exception:
209
+ return "<html><body><h1>ACRE</h1><p>UI unavailable.</p></body></html>"
210
+
211
+
212
+ @app.get("/", response_class=HTMLResponse)
213
+ def root() -> HTMLResponse:
214
+ return HTMLResponse(content=_demo_html())
215
+
216
+
217
+ @app.get("/health", response_model=CompatibilityHealthResponse)
218
+ def health_compat() -> CompatibilityHealthResponse:
219
+ return CompatibilityHealthResponse(status="healthy", service="acre-env")
220
+
221
+
222
+ @app.get("/demo")
223
+ def demo() -> JSONResponse:
224
+ from inference import run_all_tasks
225
+
226
+ return JSONResponse(content={"results": run_all_tasks()})
227
+
228
+
229
+ @app.get("/ui", response_class=HTMLResponse)
230
+ def demo_ui() -> HTMLResponse:
231
+ return HTMLResponse(content=_demo_html())
232
+
233
+
234
+ @app.post("/reset", response_model=ResetResponse)
235
+ def reset(req: ResetRequest = ResetRequest()) -> ResetResponse:
236
+ env = get_env()
237
+ try:
238
+ obs = env.reset(seed=req.seed, task_id=req.task_id, code=req.code)
239
+ except ValueError as exc:
240
+ raise HTTPException(status_code=404, detail=str(exc)) from exc
241
+ return ResetResponse(
242
+ observation=obs,
243
+ observation_vector=obs.to_vector(),
244
+ info=env.last_reset_info,
245
+ task_id=req.task_id,
246
+ state=_state_response(),
247
+ )
248
+
249
+
250
+ @app.post("/step", response_model=StepResponse)
251
+ def step(req: StepRequest) -> StepResponse:
252
+ env = get_env()
253
+ if not (0 <= req.action <= 4):
254
+ raise HTTPException(status_code=400, detail="action must be 0–4")
255
+
256
+ obs, reward, done, info = env.step(req.action)
257
+ action_name = str(info.get("action_name", env.action_meanings.get(req.action, "unknown")))
258
+ return StepResponse(
259
+ action=ActionModel(action=req.action, action_name=action_name),
260
+ observation=obs,
261
+ observation_vector=obs.to_vector(),
262
+ reward=reward,
263
+ done=done,
264
+ terminated=done,
265
+ truncated=False,
266
+ info=info,
267
+ state=_state_response(),
268
+ )
269
+
270
+
271
+ @app.get("/state", response_model=StateResponse)
272
+ def state() -> StateResponse:
273
+ return _state_response()
274
+
275
+
276
+ @app.get("/tasks", response_model=TasksResponse)
277
+ def list_tasks() -> TasksResponse:
278
+ return TasksResponse(tasks=[TaskInfo.model_validate(t) for t in registry.list_tasks()])
279
+
280
+
281
+ @app.post("/tasks/{task_id}/grade", response_model=GradeResponse)
282
+ def grade(task_id: str, req: GradeRequest) -> GradeResponse:
283
+ task = registry.get_task(task_id)
284
+ if task is None:
285
+ raise HTTPException(status_code=404, detail=f"Task '{task_id}' not found")
286
+ score = task.grade_against_expected(req.code)
287
+ return GradeResponse(task_id=task_id, score=round(score, 4), passed=score >= 0.8)
288
+
289
+
290
+ @app.post("/optimize", response_model=OptimizeResponse)
291
+ def optimize(req: OptimizeRequest) -> OptimizeResponse:
292
+ code = req.code.strip("\n")
293
+ if not code.strip():
294
+ raise HTTPException(status_code=400, detail="code must be non-empty")
295
+
296
+ env = get_env()
297
+ try:
298
+ env.reset(task_id=req.task_id, code=code)
299
+ except ValueError as exc:
300
+ raise HTTPException(status_code=404, detail=str(exc)) from exc
301
+
302
+ steps: list[OptimizationStep] = []
303
+ cumulative_reward = 0.0
304
+
305
+ for step_idx in range(1, req.max_steps + 1):
306
+ state_now = env.state()
307
+ current_code = state_now.current_code
308
+ obs_list = [float(x) for x in state_now.observation_vector]
309
+
310
+ action: int
311
+ reason: str
312
+ source: str
313
+
314
+ if req.use_rl:
315
+ rl_action, rl_reason, rl_source = _choose_action_rl(
316
+ observation=obs_list,
317
+ model_path=req.rl_model_path or DEFAULT_RL_MODEL_PATH,
318
+ )
319
+ if rl_action is not None:
320
+ action, reason, source = rl_action, rl_reason, rl_source
321
+ elif req.fallback_to_llm and req.use_llm:
322
+ action, reason, source = _choose_action_llm(
323
+ code=current_code,
324
+ task_id=req.task_id,
325
+ step_index=step_idx,
326
+ max_steps=req.max_steps,
327
+ api_base_url=req.api_base_url or DEFAULT_API_BASE_URL,
328
+ model_name=req.model_name or DEFAULT_MODEL_NAME,
329
+ api_token=req.api_token or "",
330
+ )
331
+ reason = f"{rl_reason}; {reason}"
332
+ else:
333
+ action = _choose_action_heuristic(current_code, req.task_id)
334
+ reason = f"{rl_reason}; heuristic fallback"
335
+ source = "heuristic"
336
+ elif req.use_llm:
337
+ action, reason, source = _choose_action_llm(
338
+ code=current_code,
339
+ task_id=req.task_id,
340
+ step_index=step_idx,
341
+ max_steps=req.max_steps,
342
+ api_base_url=req.api_base_url or DEFAULT_API_BASE_URL,
343
+ model_name=req.model_name or DEFAULT_MODEL_NAME,
344
+ api_token=req.api_token or "",
345
+ )
346
+ else:
347
+ action = _choose_action_heuristic(current_code, req.task_id)
348
+ reason = "heuristic policy"
349
+ source = "heuristic"
350
+
351
+ _, reward, done, info = env.step(action)
352
+ state_now = env.state()
353
+ cumulative_reward += float(reward.raw)
354
+ steps.append(
355
+ OptimizationStep(
356
+ step=step_idx,
357
+ action=action,
358
+ action_name=info.get("action_name", "unknown"),
359
+ reason=reason,
360
+ source=source,
361
+ reward=float(reward.raw),
362
+ normalized_reward=float(reward.normalized),
363
+ changed=bool(info.get("changed", False)),
364
+ complexity=float(state_now.complexity),
365
+ )
366
+ )
367
+ if done:
368
+ break
369
+
370
+ final_code = str(env.state().current_code)
371
+ diff_lines = difflib.unified_diff(
372
+ code.splitlines(),
373
+ final_code.splitlines(),
374
+ fromfile="original.py",
375
+ tofile="optimized.py",
376
+ lineterm="",
377
+ )
378
+ diff_text = "\n".join(diff_lines)
379
+
380
+ task_score: Optional[float] = None
381
+ if req.task_id:
382
+ task = registry.get_task(req.task_id)
383
+ if task is None:
384
+ raise HTTPException(status_code=404, detail=f"Task '{req.task_id}' not found")
385
+ task_score = round(task.grade(final_code), 4)
386
+
387
+ return OptimizeResponse(
388
+ original_code=code,
389
+ optimized_code=final_code,
390
+ diff=diff_text,
391
+ steps=steps,
392
+ cumulative_reward=round(cumulative_reward, 4),
393
+ task_id=req.task_id,
394
+ task_score=task_score,
395
+ )
396
+
uv.lock ADDED
The diff for this file is too large to render. See raw diff