Skip to content

Commit e1543f4

Browse files
committed
add: (inference) use local envs
1 parent 746d8e3 commit e1543f4

2 files changed

Lines changed: 117 additions & 171 deletions

File tree

inference.py

Lines changed: 116 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,25 @@
11
import asyncio
22
import os
33
import sys
4-
import textwrap
54
import json
6-
import traceback
5+
import subprocess
6+
import time
7+
import re
8+
import httpx
9+
import threading
710
from pathlib import Path
8-
from typing import List, Dict, Any
911

10-
import httpx
1112
from dotenv import load_dotenv
1213
from openai import OpenAI
1314

14-
from cloudenv.client import ClientEnvironment
15-
from cloudenv.models import CloudEnvAction, CloudEnvObservation
16-
17-
# ---------------------------
18-
# PATH FIX
19-
# ---------------------------
20-
2115
ROOT = Path(__file__).parent.resolve()
2216
if str(ROOT) not in sys.path:
2317
sys.path.insert(0, str(ROOT))
2418

2519
TASK_DIR = ROOT / "tasks"
2620

27-
# ---------------------------
28-
# ENV
29-
# ---------------------------
21+
from cloudenv.client import ClientEnvironment # noqa
22+
from cloudenv.models import CloudEnvAction, CloudEnvObservation # noqa
3023

3124
load_dotenv()
3225

@@ -38,7 +31,7 @@
3831
TEMPERATURE = float(os.getenv("TEMPERATURE", "0.3"))
3932
MAX_TOKENS = int(os.getenv("MAX_TOKENS", "300"))
4033

41-
SERVER_URL = "https://vishvam10-cloudenv.hf.space"
34+
SERVER_URL = "http://127.0.0.1:8000"
4235

4336
BENCHMARK = "cloudenv"
4437
SUCCESS_THRESHOLD = 0.5
@@ -50,101 +43,129 @@
5043

5144
TASK_ORDER = ["easy-task", "medium-task", "hard-task"]
5245

53-
# ---------------------------
54-
# PROMPT (STRICT)
55-
# ---------------------------
46+
SYSTEM_PROMPT = (
47+
(Path(__file__).parent / "system_prompt.txt").read_text().strip()
48+
)
5649

57-
SYSTEM_PROMPT = """
58-
You are a deterministic cloud pipeline repair engine.
5950

60-
Return ONLY valid JSON.
61-
No explanations.
62-
No extra text.
51+
def start_ministack():
52+
proc = subprocess.Popen(
53+
["uv", "run", "ministack"],
54+
cwd=ROOT,
55+
stdout=subprocess.DEVNULL,
56+
stderr=subprocess.DEVNULL,
57+
)
58+
time.sleep(3)
59+
return proc
60+
61+
62+
def start_server():
63+
proc = subprocess.Popen(
64+
["uv", "run", "server"],
65+
cwd=ROOT,
66+
stdout=subprocess.PIPE,
67+
stderr=subprocess.STDOUT,
68+
text=True,
69+
bufsize=1,
70+
)
6371

64-
Format:
65-
{
66-
"service": "...",
67-
"operation": "...",
68-
"instance_id": null,
69-
"payload": {}
70-
}
71-
"""
72+
def stream():
73+
for line in proc.stdout:
74+
print(f"[SERVER] {line}", end="")
7275

73-
PROMPT_PATH = Path(__file__).parent / "system_prompt.txt"
74-
if PROMPT_PATH.exists():
75-
SYSTEM_PROMPT = PROMPT_PATH.read_text().strip()
76+
threading.Thread(target=stream, daemon=True).start()
7677

77-
# ---------------------------
78-
# LOGGING
79-
# ---------------------------
78+
url = f"{SERVER_URL}/docs"
8079

80+
for _ in range(40):
81+
try:
82+
r = httpx.get(url, timeout=1)
83+
if r.status_code == 200:
84+
print("[INFO] server ready")
85+
return proc
86+
except Exception:
87+
time.sleep(1)
88+
89+
raise RuntimeError("Server failed")
8190

82-
def log_start(task: str):
91+
92+
def log_start(task):
8393
print(f"[START] task={task} env={BENCHMARK} model={MODEL_NAME}", flush=True)
8494

8595

86-
def log_step(step: int, action: str, reward: float, done: bool):
96+
def log_step(step, action, reward, done):
8797
print(
8898
f"[STEP] step={step} action={action} reward={reward:.2f} done={str(done).lower()}",
8999
flush=True,
90100
)
91101

92102

93-
def log_end(success: bool, steps: int, score: float, rewards: List[float]):
94-
rewards_str = ",".join(f"{r:.2f}" for r in rewards)
103+
def log_end(success, steps, score, rewards):
95104
print(
96-
f"[END] success={str(success).lower()} steps={steps} score={score:.3f} rewards={rewards_str}",
105+
f"[END] success={str(success).lower()} steps={steps} score={score:.3f} rewards={','.join(f'{r:.2f}' for r in rewards)}",
97106
flush=True,
98107
)
99108

100109

101-
# ---------------------------
102-
# TASK LOADER
103-
# ---------------------------
104-
105-
106-
def load_task(name: str):
110+
def load_task(name):
107111
return json.loads((TASK_DIR / f"{name}.json").read_text())
108112

109113

110-
# ---------------------------
111-
# PIPELINE REGISTER
112-
# ---------------------------
113-
114-
115-
async def register_pipeline(task_data: Dict[str, Any]):
114+
async def register_pipeline(task_data):
116115
url = f"{SERVER_URL}/api/pipelines"
116+
async with httpx.AsyncClient(timeout=20) as c:
117+
r = await c.post(url, json=task_data)
118+
if r.status_code == 400:
119+
try:
120+
d = r.json().get("detail", "")
121+
except Exception:
122+
d = ""
123+
if "already exists" in d:
124+
await c.delete(f"{url}/{task_data['pipeline_id']}")
125+
await c.post(url, json=task_data)
117126

118-
async with httpx.AsyncClient(timeout=20) as http:
119-
try:
120-
r = await http.post(url, json=task_data)
121-
122-
if r.status_code == 400:
123-
try:
124-
detail = r.json().get("detail", "")
125-
except Exception:
126-
detail = ""
127-
128-
if "already exists" in detail:
129-
await http.delete(f"{url}/{task_data['pipeline_id']}")
130-
await http.post(url, json=task_data)
131127

132-
except Exception:
133-
print("[ERROR] pipeline register failed", flush=True)
134-
traceback.print_exc()
128+
def build_prompt(step, state, broken, ideal, history):
129+
return f"""
130+
STEP {step}
131+
STATE:
132+
{json.dumps(state)[:1200]}
133+
BROKEN:
134+
{json.dumps(broken)[:1200]}
135+
IDEAL:
136+
{json.dumps(ideal)[:1200]}
137+
HISTORY:
138+
{history[-5:] if history else "None"}
139+
Return ONLY JSON.
140+
""".strip()
135141

136142

137-
# ---------------------------
138-
# ACTION PARSER
139-
# ---------------------------
143+
def call_model(step, state, broken, ideal, history):
144+
prompt = build_prompt(step, state, broken, ideal, history)
145+
try:
146+
res = client.chat.completions.create(
147+
model=MODEL_NAME,
148+
messages=[
149+
{"role": "system", "content": SYSTEM_PROMPT},
150+
{"role": "user", "content": prompt},
151+
],
152+
temperature=TEMPERATURE,
153+
max_tokens=MAX_TOKENS,
154+
)
155+
return (res.choices[0].message.content or "").strip()
156+
except Exception:
157+
return "{}"
140158

141159

142-
def parse_llm_action(raw: str, task_id: str, episode_id: str) -> CloudEnvAction:
160+
def parse_llm_action(raw, task_id, episode_id):
143161
try:
144162
raw = raw.strip()
145-
raw = raw[raw.find("{") : raw.rfind("}") + 1]
146-
data = json.loads(raw)
147-
163+
m = re.search(r"\{.*\}", raw, re.S)
164+
if not m:
165+
raise ValueError("no json")
166+
data = json.loads(m.group())
167+
if "action" in data:
168+
data = data["action"]
148169
return CloudEnvAction(
149170
task_id=task_id,
150171
episode_id=episode_id,
@@ -165,88 +186,28 @@ def parse_llm_action(raw: str, task_id: str, episode_id: str) -> CloudEnvAction:
165186
)
166187

167188

168-
# ---------------------------
169-
# LLM
170-
# ---------------------------
171-
172-
173-
def build_prompt(
174-
step: int, state: dict, broken: dict, ideal: dict, history: List[str]
175-
) -> str:
176-
return textwrap.dedent(f"""
177-
STEP {step}
178-
179-
CURRENT STATE:
180-
{json.dumps(state)[:1000]}
181-
182-
BROKEN PIPELINE:
183-
{json.dumps(broken)[:1000]}
184-
185-
IDEAL PIPELINE:
186-
{json.dumps(ideal)[:1000]}
187-
188-
HISTORY:
189-
{history[-5:] if history else "None"}
190-
191-
Return ONLY JSON action.
192-
""").strip()
193-
194-
195-
def call_model(
196-
step: int, state: dict, broken: dict, ideal: dict, history: List[str]
197-
) -> str:
198-
prompt = build_prompt(step, state, broken, ideal, history)
199-
200-
try:
201-
res = client.chat.completions.create(
202-
model=MODEL_NAME,
203-
messages=[
204-
{"role": "system", "content": SYSTEM_PROMPT},
205-
{"role": "user", "content": prompt},
206-
],
207-
temperature=TEMPERATURE,
208-
max_tokens=MAX_TOKENS,
209-
)
210-
return (res.choices[0].message.content or "").strip()
211-
212-
except Exception as e:
213-
print(f"[DEBUG] LLM error: {e}", flush=True)
214-
return "{}"
215-
216-
217-
# ---------------------------
218-
# EPISODE
219-
# ---------------------------
220-
221-
222-
async def run_episode(task_data: dict):
223-
189+
async def run_episode(task_data):
224190
pipeline_id = task_data["pipeline_id"]
225191
task_id = pipeline_id
226192
episode_id = f"{pipeline_id}-episode-1"
227193

228-
history: List[str] = []
229-
rewards: List[float] = []
230-
194+
history = []
195+
rewards = []
231196
steps = 0
232-
score = 0.0
233-
success = False
234197

235198
log_start(task_id)
236199

237-
env = None
200+
env = ClientEnvironment(base_url=SERVER_URL)
238201

239202
try:
240203
await register_pipeline(task_data)
241204

242-
env = ClientEnvironment(base_url=SERVER_URL)
243-
244205
result = await env.initialize_environment(
245206
task_id=task_id,
246207
episode_id=episode_id,
247208
)
248209

249-
obs: CloudEnvObservation = result.observation
210+
obs = result.observation
250211

251212
for step in range(1, MAX_STEPS + 1):
252213
if obs.done:
@@ -263,8 +224,8 @@ async def run_episode(task_data: dict):
263224
action = parse_llm_action(raw_action, task_id, episode_id)
264225

265226
result = await env.step(action)
266-
267227
obs = result.observation
228+
268229
reward = float(obs.reward or 0.0)
269230
done = bool(obs.done)
270231

@@ -283,34 +244,29 @@ async def run_episode(task_data: dict):
283244
success = score >= SUCCESS_THRESHOLD
284245

285246
except Exception as e:
286-
log_step(steps or 1, "error", 0.0, True)
287-
print(f"[ERROR] episode failed: {e}", flush=True)
247+
print(f"[ERROR] {e}")
288248
score = 0.0
289249
success = False
290250

291251
finally:
292-
if env:
293-
try:
294-
await env.close()
295-
except Exception:
296-
pass
252+
await env.close()
297253

298254
log_end(success, steps, score, rewards)
299255

300256

301-
# ---------------------------
302-
# MAIN
303-
# ---------------------------
304-
305-
306257
async def main():
307-
for name in TASK_ORDER:
308-
try:
258+
ministack_server = start_ministack()
259+
server = start_server()
260+
try:
261+
for name in TASK_ORDER:
309262
task = load_task(name)
310263
await run_episode(task)
311-
except Exception as e:
312-
print(f"[DEBUG] task failed {name}: {e}", flush=True)
313-
log_end(False, 0, 0.0, [])
264+
finally:
265+
server.terminate()
266+
server.wait()
267+
268+
ministack_server.terminate()
269+
ministack_server.wait()
314270

315271

316272
if __name__ == "__main__":

0 commit comments

Comments
 (0)