Ajjack4 commited on
Commit
eb9db3e
·
1 Parent(s): 38c2d5c

add inference.py with structured logs, openai client, required env vars

Browse files
Files changed (3) hide show
  1. api.py +12 -4
  2. inference.py +228 -0
  3. requirements.txt +1 -0
api.py CHANGED
@@ -115,11 +115,19 @@ def baseline() -> dict:
115
  import subprocess
116
  import json
117
  result = subprocess.run(
118
- ["python", "baseline.py", "--output-json"],
119
- capture_output=True, text=True, timeout=300
 
120
  )
121
- if result.returncode == 0:
122
- scores = json.loads(result.stdout)
 
 
 
 
 
 
 
123
  return {"status": "success", "scores": scores}
124
  else:
125
  return {"status": "error", "message": result.stderr}
 
115
  import subprocess
116
  import json
117
  result = subprocess.run(
118
+ ["python", "inference.py"],
119
+ capture_output=True, text=True, timeout=300,
120
+ env={**__import__("os").environ},
121
  )
122
+ # Parse [END] lines from structured stdout to extract per-task scores
123
+ scores = []
124
+ for line in result.stdout.splitlines():
125
+ if line.startswith("[END] "):
126
+ try:
127
+ scores.append(json.loads(line[6:]))
128
+ except json.JSONDecodeError:
129
+ pass
130
+ if result.returncode == 0 or scores:
131
  return {"status": "success", "scores": scores}
132
  else:
133
  return {"status": "error", "message": result.stderr}
inference.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Inference script — Dev Environment Debugger (OpenEnv Hackathon)
3
+
4
+ Required environment variables:
5
+ API_BASE_URL OpenAI-compatible LLM API endpoint
6
+ MODEL_NAME Model identifier for inference
7
+ HF_TOKEN HuggingFace / API key
8
+
9
+ Optional:
10
+ ENV_BASE_URL Base URL of the deployed OpenEnv Space
11
+ (default: http://localhost:7860)
12
+ """
13
+
14
+ import asyncio
15
+ import json
16
+ import os
17
+ import sys
18
+ from typing import List, Optional
19
+
20
+ import requests
21
+ from openai import OpenAI
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Config
25
+ # ---------------------------------------------------------------------------
26
+
27
+ API_BASE_URL: str = os.environ["API_BASE_URL"]
28
+ MODEL_NAME: str = os.environ["MODEL_NAME"]
29
+ HF_TOKEN: str = os.environ["HF_TOKEN"]
30
+ ENV_BASE_URL: str = os.getenv("ENV_BASE_URL", "http://localhost:7860")
31
+
32
+ TASK_IDS = ["task1", "task2", "task3"]
33
+ BENCHMARK = "dev-env-debugger"
34
+ MAX_STEPS = 20
35
+ SUCCESS_SCORE_THRESHOLD = 0.5
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Structured logging — [START] / [STEP] / [END]
39
+ # ---------------------------------------------------------------------------
40
+
41
+ def log_start(*, task: str, env: str, model: str) -> None:
42
+ print(
43
+ f"[START] {json.dumps({'task': task, 'env': env, 'model': model})}",
44
+ flush=True,
45
+ )
46
+
47
+
48
+ def log_step(
49
+ *, step: int, action: str, reward: float, done: bool, error: Optional[str]
50
+ ) -> None:
51
+ print(
52
+ f"[STEP] {json.dumps({'step': step, 'action': action, 'reward': reward, 'done': done, 'error': error})}",
53
+ flush=True,
54
+ )
55
+
56
+
57
+ def log_end(*, success: bool, steps: int, score: float, rewards: List[float]) -> None:
58
+ print(
59
+ f"[END] {json.dumps({'success': success, 'steps': steps, 'score': score, 'rewards': rewards})}",
60
+ flush=True,
61
+ )
62
+
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # LLM agent
66
+ # ---------------------------------------------------------------------------
67
+
68
+ SYSTEM_PROMPT = """You are an expert DevOps engineer debugging a broken multi-service development environment.
69
+
70
+ Services: api, worker, database, proxy
71
+ Available actions — respond ONLY with valid JSON, nothing else:
72
+ {"type": "read_logs", "service": "<name>"}
73
+ {"type": "inspect_env", "service": "<name>"}
74
+ {"type": "edit_env", "service": "<name>", "key": "<KEY>", "value": "<value>"}
75
+ {"type": "restart_service", "service": "<name>"}
76
+ {"type": "run_healthcheck", "service": "<name>"}
77
+ {"type": "submit"}
78
+
79
+ STRATEGY:
80
+ 1. Check which services are in "error" state
81
+ 2. Read logs for broken services
82
+ 3. Inspect env vars of the broken service
83
+ 4. Edit the wrong/missing env var
84
+ 5. Restart the service
85
+ 6. Submit when ALL services show "healthy"
86
+ """
87
+
88
+
89
+ def get_model_message(
90
+ client: OpenAI, obs_text: str, history: List[dict]
91
+ ) -> str:
92
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
93
+ messages.extend(history[-10:]) # keep last 10 turns to stay within context
94
+ messages.append({"role": "user", "content": obs_text})
95
+
96
+ try:
97
+ response = client.chat.completions.create(
98
+ model=MODEL_NAME,
99
+ messages=messages,
100
+ temperature=0.2,
101
+ max_tokens=200,
102
+ )
103
+ return response.choices[0].message.content.strip()
104
+ except Exception as exc:
105
+ print(f"[DEBUG] Model request failed: {exc}", flush=True)
106
+ return '{"type": "submit"}' # safe fallback — ends episode cleanly
107
+
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # HTTP environment adapter
111
+ # ---------------------------------------------------------------------------
112
+
113
+ class EnvAdapter:
114
+ """Thin wrapper around the OpenEnv HTTP API."""
115
+
116
+ def __init__(self, base_url: str) -> None:
117
+ self.base_url = base_url.rstrip("/")
118
+
119
+ def reset(self, task_id: str) -> dict:
120
+ resp = requests.post(
121
+ f"{self.base_url}/reset", params={"task_id": task_id}, timeout=30
122
+ )
123
+ resp.raise_for_status()
124
+ return resp.json()
125
+
126
+ def step(self, action: dict) -> dict:
127
+ resp = requests.post(f"{self.base_url}/step", json=action, timeout=30)
128
+ resp.raise_for_status()
129
+ return resp.json()
130
+
131
+ def grader(self, task_id: str) -> dict:
132
+ resp = requests.get(
133
+ f"{self.base_url}/grader", params={"task_id": task_id}, timeout=30
134
+ )
135
+ resp.raise_for_status()
136
+ return resp.json()
137
+
138
+
139
+ def obs_to_text(obs: dict) -> str:
140
+ services = obs.get("services", {})
141
+ svc_lines = "\n".join(
142
+ f" {name}: {s['status']} (port: {s.get('port', 'N/A')})"
143
+ for name, s in services.items()
144
+ )
145
+ last = obs.get("last_action_result", {})
146
+ return (
147
+ f"Step {obs['step']}\n"
148
+ f"Services:\n{svc_lines}\n"
149
+ f"Last action: {'OK' if last.get('success') else 'FAIL'} — {last.get('message', '')}\n"
150
+ f"Done: {obs.get('done', False)}"
151
+ )
152
+
153
+
154
+ # ---------------------------------------------------------------------------
155
+ # Episode runner
156
+ # ---------------------------------------------------------------------------
157
+
158
+ async def run_task(client: OpenAI, env: EnvAdapter, task_id: str) -> None:
159
+ history: List[dict] = []
160
+ rewards: List[float] = []
161
+ steps_taken = 0
162
+ score = 0.0
163
+ success = False
164
+
165
+ log_start(task=task_id, env=BENCHMARK, model=MODEL_NAME)
166
+
167
+ try:
168
+ obs = env.reset(task_id)
169
+
170
+ for step in range(1, MAX_STEPS + 1):
171
+ if obs.get("done", False):
172
+ break
173
+
174
+ obs_text = obs_to_text(obs)
175
+ raw_action = get_model_message(client, obs_text, history)
176
+
177
+ error: Optional[str] = None
178
+ try:
179
+ action = json.loads(raw_action)
180
+ except json.JSONDecodeError:
181
+ error = f"Invalid JSON from model: {raw_action}"
182
+ action = {"type": "submit"}
183
+
184
+ step_data = env.step(action)
185
+ obs = step_data["observation"]
186
+ reward = float(step_data.get("reward", 0.0))
187
+ done = bool(step_data.get("done", False))
188
+
189
+ rewards.append(reward)
190
+ steps_taken = step
191
+
192
+ log_step(step=step, action=raw_action, reward=reward, done=done, error=error)
193
+
194
+ history.append({"role": "assistant", "content": raw_action})
195
+ history.append({"role": "user", "content": obs_to_text(obs)})
196
+
197
+ if done:
198
+ break
199
+
200
+ grader_data = env.grader(task_id)
201
+ grader_result = grader_data.get("grader_result", {})
202
+ score = float(grader_result.get("score", 0.0))
203
+ score = min(max(score, 0.0), 1.0)
204
+ success = score >= SUCCESS_SCORE_THRESHOLD
205
+
206
+ except Exception as exc:
207
+ print(f"[DEBUG] Task {task_id} error: {exc}", flush=True)
208
+ score = 0.0
209
+ success = False
210
+
211
+ finally:
212
+ log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
213
+
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # Entry point
217
+ # ---------------------------------------------------------------------------
218
+
219
+ async def main() -> None:
220
+ client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
221
+ env = EnvAdapter(ENV_BASE_URL)
222
+
223
+ for task_id in TASK_IDS:
224
+ await run_task(client, env, task_id)
225
+
226
+
227
+ if __name__ == "__main__":
228
+ asyncio.run(main())
requirements.txt CHANGED
@@ -3,3 +3,4 @@ uvicorn==0.30.1
3
  pydantic==2.7.1
4
  requests==2.32.3
5
  pyyaml==6.0.1
 
 
3
  pydantic==2.7.1
4
  requests==2.32.3
5
  pyyaml==6.0.1
6
+ openai==1.30.1