Deepikachintamreddy commited on
Commit
c7b11e5
·
1 Parent(s): 529657f

fix: correct step format, per-task output, tuple grader returns

Browse files
inference.py CHANGED
@@ -1,16 +1,7 @@
1
  """
2
  Inference Script - ConfigDebugEnv
3
  ===================================
4
- MANDATORY
5
- - Before submitting, ensure the following variables are defined in your environment configuration:
6
- API_BASE_URL The API endpoint for the LLM.
7
- MODEL_NAME The model identifier to use for inference.
8
- HF_TOKEN Your Hugging Face / API key.
9
- IMAGE_NAME The name of the local image to use for the environment if you are using
10
- from_docker_image() method
11
-
12
- STDOUT FORMAT
13
- - The script emits [START], [STEP], and [END] PER TASK:
14
  [START] task=<task_id> env=<benchmark> model=<model_name>
15
  [STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
16
  [END] success=<true|false> steps=<n> score=<score> rewards=<r1,r2,...,rn>
@@ -28,14 +19,16 @@ MAX_STEPS_PER_TASK = 5
28
  TEMPERATURE = 0.1
29
  MAX_TOKENS = 2000
30
 
31
- SYSTEM_PROMPT = textwrap.dedent(
32
- """
33
  You are an expert DevOps engineer specializing in configuration file debugging.
34
  You will be given a broken configuration file and must fix ALL bugs in it.
35
  Return ONLY the fixed configuration file content.
36
  No explanations, no markdown formatting, no code blocks. Just the raw fixed configuration.
37
- """
38
- ).strip()
 
 
 
39
 
40
 
41
  def log_start(task: str, env: str, model: str) -> None:
@@ -43,22 +36,13 @@ def log_start(task: str, env: str, model: str) -> None:
43
 
44
 
45
  def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
46
- error_val = error if error else "null"
47
- done_val = str(done).lower()
48
- reward = max(0.01, min(0.99, reward))
49
- print(
50
- f"[STEP] step={step} action={action} reward={reward:.2f} done={done_val} error={error_val}",
51
- flush=True,
52
- )
53
 
54
 
55
  def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
56
- score = max(0.01, min(0.99, score))
57
- rewards_str = ",".join(f"{max(0.01, min(0.99, r)):.2f}" for r in rewards)
58
- print(
59
- f"[END] success={str(success).lower()} steps={steps} score={score:.3f} rewards={rewards_str}",
60
- flush=True,
61
- )
62
 
63
 
64
  def strip_code_blocks(text: str) -> str:
@@ -73,38 +57,45 @@ def strip_code_blocks(text: str) -> str:
73
  return text
74
 
75
 
76
- def build_user_prompt(obs: dict, step: int, history: List[str]) -> str:
77
- history_block = "\n".join(history[-4:]) if history else "None"
78
- return textwrap.dedent(
79
- f"""
80
- Fix the following broken {obs.get('file_type', 'config')} configuration file.
81
 
82
- Task: {obs.get('task_description', '')}
83
- Difficulty: {obs.get('difficulty', '')}
84
- Number of bugs to find: {obs.get('num_bugs', 0)}
85
- Bugs fixed so far: {obs.get('bugs_found_so_far', 0)}
86
- Error message: {obs.get('error_message', '')}
87
- Step: {step}
 
 
88
 
89
- Previous attempts:
90
- {history_block}
 
 
 
 
 
 
 
91
 
92
- Broken configuration:
93
- {obs.get('broken_config', '')}
94
 
95
- Return ONLY the fixed configuration file content.
96
- """
97
- ).strip()
98
 
 
99
 
100
- def get_model_fix(client: OpenAI, obs: dict, step: int, history: List[str], model_name: str) -> str:
101
- user_prompt = build_user_prompt(obs, step, history)
102
  try:
103
  completion = client.chat.completions.create(
104
  model=model_name,
105
  messages=[
106
  {"role": "system", "content": SYSTEM_PROMPT},
107
- {"role": "user", "content": user_prompt},
108
  ],
109
  temperature=TEMPERATURE,
110
  max_tokens=MAX_TOKENS,
@@ -112,133 +103,106 @@ def get_model_fix(client: OpenAI, obs: dict, step: int, history: List[str], mode
112
  )
113
  text = (completion.choices[0].message.content or "").strip()
114
  return strip_code_blocks(text) if text else ""
115
- except Exception as exc:
116
- print(f"[DEBUG] Model request failed: {exc}", flush=True)
117
  return ""
118
 
119
 
120
  class HTTPEnvClient:
121
- def __init__(self, base_url: str):
122
  import httpx
123
  self.base_url = base_url.rstrip("/")
124
  self.http = httpx.AsyncClient(timeout=60.0)
125
 
126
  async def reset(self):
127
- resp = await self.http.post(f"{self.base_url}/reset")
128
- resp.raise_for_status()
129
- return resp.json()
130
-
131
- async def step(self, action_data: dict):
132
- resp = await self.http.post(f"{self.base_url}/step", json=action_data)
133
- resp.raise_for_status()
134
- return resp.json()
 
 
 
 
135
 
136
  async def close(self):
137
  await self.http.aclose()
138
 
139
 
140
- async def main() -> None:
141
  api_key = os.getenv("HF_TOKEN") or os.getenv("API_KEY") or ""
142
  api_base_url = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
143
  model_name = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-72B-Instruct"
144
- benchmark = os.getenv("CONFIG_DEBUG_BENCHMARK", "config_debug_env")
145
 
146
  client = OpenAI(base_url=api_base_url, api_key=api_key)
147
-
148
  env_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:7860"
149
  env = HTTPEnvClient(env_url)
150
 
151
  try:
152
- # Reset environment — starts at task1
153
  result = await env.reset()
154
- obs = result.get("observation", {})
155
- state = result.get("state", obs)
156
-
157
- current_task_id = obs.get("task_id", "unknown")
158
  task_step = 0
159
- task_rewards: List[float] = []
160
- task_history: List[str] = []
161
-
162
- # Emit [START] for the first task
163
- log_start(task=current_task_id, env=benchmark, model=model_name)
164
-
165
- while True:
166
- # Check if episode is fully done
167
- is_done = state.get("is_done", False) if isinstance(state, dict) else False
168
- if is_done:
169
- # End the current task
170
- if task_rewards:
171
- task_score = sum(task_rewards) / len(task_rewards)
172
- else:
173
- task_score = 0.01
174
- task_score = max(0.01, min(0.99, task_score))
175
- log_end(
176
- success=task_score >= 0.5,
177
- steps=task_step,
178
- score=task_score,
179
- rewards=task_rewards if task_rewards else [0.01],
180
- )
181
- break
182
 
 
 
 
 
183
  task_step += 1
184
 
185
- # Get LLM fix
186
- fixed_config = get_model_fix(client, obs, task_step, task_history, model_name)
187
-
188
- # Step the environment
189
- step_result = await env.step({"fixed_config": fixed_config})
190
- obs = step_result.get("observation", {})
191
- state = step_result.get("state", obs)
192
- reward = step_result.get("reward", 0.01)
193
- reward = max(0.01, min(0.99, float(reward) if reward is not None else 0.01))
194
- info = step_result.get("info", {})
195
- new_task_id = obs.get("task_id", current_task_id)
196
- task_done = info.get("task_done", False)
197
- is_done = state.get("is_done", False) if isinstance(state, dict) else False
198
- error = info.get("error_message") if info.get("error_message") != "All checks passed!" else None
199
 
 
 
 
 
200
  task_rewards.append(reward)
201
 
202
- action_summary = f"fix({current_task_id})"
203
- log_step(
204
- step=task_step,
205
- action=action_summary,
206
- reward=reward,
207
- done=is_done,
208
- error=error,
209
- )
210
 
 
211
  task_history.append(f"Step {task_step}: reward {reward:.2f}")
212
 
213
- # Did this task just finish?
214
- if task_done or new_task_id != current_task_id:
215
- # Emit [END] for the completed task
216
- if task_rewards:
217
- task_score = sum(task_rewards) / len(task_rewards)
218
- else:
219
- task_score = 0.01
220
- task_score = max(0.01, min(0.99, task_score))
221
- log_end(
222
- success=task_score >= 0.5,
223
- steps=task_step,
224
- score=task_score,
225
- rewards=task_rewards,
226
- )
227
 
228
  if is_done:
229
  break
230
 
231
- # Move to next task — emit new [START]
232
- current_task_id = new_task_id
233
  task_step = 0
234
  task_rewards = []
235
  task_history = []
236
- log_start(task=current_task_id, env=benchmark, model=model_name)
 
 
237
 
238
  except Exception as e:
239
- print(f"[DEBUG] Error during inference: {e}", flush=True)
240
- # Emit a safe [END] so the validator has something to parse
241
- log_end(success=False, steps=0, score=0.01, rewards=[0.01])
242
  finally:
243
  try:
244
  await env.close()
@@ -250,4 +214,4 @@ if __name__ == "__main__":
250
  try:
251
  asyncio.run(main())
252
  except Exception:
253
- print(f"[END] success=false steps=0 score=0.01 rewards=0.01", flush=True)
 
1
  """
2
  Inference Script - ConfigDebugEnv
3
  ===================================
4
+ STDOUT FORMAT - emits [START], [STEP], [END] PER TASK:
 
 
 
 
 
 
 
 
 
5
  [START] task=<task_id> env=<benchmark> model=<model_name>
6
  [STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
7
  [END] success=<true|false> steps=<n> score=<score> rewards=<r1,r2,...,rn>
 
19
  TEMPERATURE = 0.1
20
  MAX_TOKENS = 2000
21
 
22
+ SYSTEM_PROMPT = textwrap.dedent("""
 
23
  You are an expert DevOps engineer specializing in configuration file debugging.
24
  You will be given a broken configuration file and must fix ALL bugs in it.
25
  Return ONLY the fixed configuration file content.
26
  No explanations, no markdown formatting, no code blocks. Just the raw fixed configuration.
27
+ """).strip()
28
+
29
+
30
+ def clamp(v: float) -> float:
31
+ return max(0.01, min(0.99, v))
32
 
33
 
34
  def log_start(task: str, env: str, model: str) -> None:
 
36
 
37
 
38
  def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
39
+ print(f"[STEP] step={step} action={action} reward={clamp(reward):.2f} done={str(done).lower()} error={error or 'null'}", flush=True)
 
 
 
 
 
 
40
 
41
 
42
  def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
43
+ s = clamp(score)
44
+ rs = ",".join(f"{clamp(r):.2f}" for r in rewards) if rewards else "0.01"
45
+ print(f"[END] success={str(success).lower()} steps={steps} score={s:.3f} rewards={rs}", flush=True)
 
 
 
46
 
47
 
48
  def strip_code_blocks(text: str) -> str:
 
57
  return text
58
 
59
 
60
+ def get_obs_field(data: dict, field: str, default=None):
61
+ """Get field from response - handles both nested and flat formats."""
62
+ obs = data.get("observation", data)
63
+ return obs.get(field, data.get(field, default))
64
+
65
 
66
+ def get_model_fix(client, obs_data, step, history, model_name):
67
+ file_type = get_obs_field(obs_data, "file_type", "config")
68
+ desc = get_obs_field(obs_data, "task_description", "")
69
+ difficulty = get_obs_field(obs_data, "difficulty", "")
70
+ num_bugs = get_obs_field(obs_data, "num_bugs", 0)
71
+ bugs_found = get_obs_field(obs_data, "bugs_found_so_far", 0)
72
+ error_msg = get_obs_field(obs_data, "error_message", "")
73
+ broken = get_obs_field(obs_data, "broken_config", "")
74
 
75
+ history_block = "\n".join(history[-4:]) if history else "None"
76
+ prompt = f"""Fix the following broken {file_type} configuration file.
77
+
78
+ Task: {desc}
79
+ Difficulty: {difficulty}
80
+ Number of bugs to find: {num_bugs}
81
+ Bugs fixed so far: {bugs_found}
82
+ Error message: {error_msg}
83
+ Step: {step}
84
 
85
+ Previous attempts:
86
+ {history_block}
87
 
88
+ Broken configuration:
89
+ {broken}
 
90
 
91
+ Return ONLY the fixed configuration file content."""
92
 
 
 
93
  try:
94
  completion = client.chat.completions.create(
95
  model=model_name,
96
  messages=[
97
  {"role": "system", "content": SYSTEM_PROMPT},
98
+ {"role": "user", "content": prompt},
99
  ],
100
  temperature=TEMPERATURE,
101
  max_tokens=MAX_TOKENS,
 
103
  )
104
  text = (completion.choices[0].message.content or "").strip()
105
  return strip_code_blocks(text) if text else ""
106
+ except Exception as e:
107
+ print(f"[DEBUG] LLM error: {e}", flush=True)
108
  return ""
109
 
110
 
111
  class HTTPEnvClient:
112
+ def __init__(self, base_url):
113
  import httpx
114
  self.base_url = base_url.rstrip("/")
115
  self.http = httpx.AsyncClient(timeout=60.0)
116
 
117
  async def reset(self):
118
+ r = await self.http.post(f"{self.base_url}/reset")
119
+ r.raise_for_status()
120
+ return r.json()
121
+
122
+ async def step(self, fixed_config: str):
123
+ """Send step with correct OpenEnv format: {"action": {"fixed_config": "..."}}"""
124
+ r = await self.http.post(
125
+ f"{self.base_url}/step",
126
+ json={"action": {"fixed_config": fixed_config}},
127
+ )
128
+ r.raise_for_status()
129
+ return r.json()
130
 
131
  async def close(self):
132
  await self.http.aclose()
133
 
134
 
135
+ async def main():
136
  api_key = os.getenv("HF_TOKEN") or os.getenv("API_KEY") or ""
137
  api_base_url = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
138
  model_name = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-72B-Instruct"
139
+ benchmark = "config_debug_env"
140
 
141
  client = OpenAI(base_url=api_base_url, api_key=api_key)
 
142
  env_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:7860"
143
  env = HTTPEnvClient(env_url)
144
 
145
  try:
 
146
  result = await env.reset()
147
+ current_task = get_obs_field(result, "task_id", "unknown")
 
 
 
148
  task_step = 0
149
+ task_rewards = []
150
+ task_history = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
+ log_start(task=current_task, env=benchmark, model=model_name)
153
+
154
+ for global_step in range(1, 50):
155
+ fixed_config = get_model_fix(client, result, task_step + 1, task_history, model_name)
156
  task_step += 1
157
 
158
+ try:
159
+ step_result = await env.step(fixed_config)
160
+ except Exception as step_err:
161
+ print(f"[DEBUG] Step failed: {step_err}", flush=True)
162
+ log_step(task_step, f"fix({current_task})", 0.01, False, str(step_err))
163
+ task_rewards.append(0.01)
164
+ # End this task on step failure
165
+ log_end(False, task_step, 0.01, task_rewards)
166
+ break
 
 
 
 
 
167
 
168
+ reward = get_obs_field(step_result, "reward", 0.01)
169
+ if reward is None:
170
+ reward = 0.01
171
+ reward = clamp(float(reward))
172
  task_rewards.append(reward)
173
 
174
+ new_task = get_obs_field(step_result, "task_id", current_task)
175
+ is_done = get_obs_field(step_result, "done", False)
176
+ error = get_obs_field(step_result, "error_message", None)
177
+ if error == "All checks passed!":
178
+ error = None
 
 
 
179
 
180
+ log_step(task_step, f"fix({current_task})", reward, bool(is_done), error)
181
  task_history.append(f"Step {task_step}: reward {reward:.2f}")
182
 
183
+ # Detect task transition
184
+ task_changed = (new_task != current_task) and (new_task != "unknown")
185
+
186
+ if task_changed or is_done:
187
+ # End current task
188
+ task_score = clamp(sum(task_rewards) / len(task_rewards)) if task_rewards else 0.01
189
+ log_end(task_score >= 0.5, task_step, task_score, task_rewards)
 
 
 
 
 
 
 
190
 
191
  if is_done:
192
  break
193
 
194
+ # Start next task
195
+ current_task = new_task
196
  task_step = 0
197
  task_rewards = []
198
  task_history = []
199
+ log_start(task=current_task, env=benchmark, model=model_name)
200
+
201
+ result = step_result
202
 
203
  except Exception as e:
204
+ print(f"[DEBUG] Fatal error: {e}", flush=True)
205
+ log_end(False, 0, 0.01, [0.01])
 
206
  finally:
207
  try:
208
  await env.close()
 
214
  try:
215
  asyncio.run(main())
216
  except Exception:
217
+ print("[END] success=false steps=0 score=0.01 rewards=0.01", flush=True)
server/config_debug_environment.py CHANGED
@@ -56,25 +56,34 @@ class ConfigDebugEnvironment(Environment):
56
  task_id = self._current_task_id()
57
  task = get_task(task_id)
58
 
59
- # Run the grader - now returns FLOAT ONLY for validator compatibility
60
  grader_result = task.grader(action.fixed_config)
61
-
62
- # Handle result gracefully (should be float)
63
- if isinstance(grader_result, float):
64
- reward = grader_result
65
- elif isinstance(grader_result, tuple) and len(grader_result) > 0:
66
- # Fallback for legacy tuple format
67
- reward = grader_result[0]
 
 
 
 
 
 
 
68
  else:
69
- reward = 0.01 # Safe default
70
-
 
 
71
  reward = max(0.01, min(0.99, reward))
72
 
73
  self.current_step += 1
74
  self._global_step += 1
75
- self.bugs_found_so_far = 0 # Default since grader no longer returns this
76
  self.previous_reward = round(reward, 4)
77
- self.current_error_message = "" # Default since grader no longer returns this
78
 
79
  # Check if task is complete
80
  task_done = reward >= 0.99 or self.current_step >= MAX_STEPS_PER_TASK
@@ -120,7 +129,6 @@ class ConfigDebugEnvironment(Environment):
120
  is_done=self._done,
121
  tasks_completed=list(self.tasks_completed),
122
  tasks_remaining=tasks_remaining,
123
- # Enhanced RL signals
124
  bugs_found_so_far=self.bugs_found_so_far,
125
  current_error_message=self.current_error_message,
126
  progress_ratio=round(progress_ratio, 2),
@@ -153,4 +161,4 @@ class ConfigDebugEnvironment(Environment):
153
  previous_reward=self.previous_reward,
154
  done=self._done,
155
  reward=self.previous_reward,
156
- )
 
56
  task_id = self._current_task_id()
57
  task = get_task(task_id)
58
 
59
+ # Run the grader - returns (reward, error_message, bugs_fixed) tuple
60
  grader_result = task.grader(action.fixed_config)
61
+
62
+ # Parse grader result
63
+ if isinstance(grader_result, tuple) and len(grader_result) >= 3:
64
+ reward = float(grader_result[0])
65
+ error_message = str(grader_result[1])
66
+ bugs_fixed = list(grader_result[2])
67
+ elif isinstance(grader_result, tuple) and len(grader_result) >= 1:
68
+ reward = float(grader_result[0])
69
+ error_message = ""
70
+ bugs_fixed = []
71
+ elif isinstance(grader_result, (int, float)):
72
+ reward = float(grader_result)
73
+ error_message = ""
74
+ bugs_fixed = []
75
  else:
76
+ reward = 0.01
77
+ error_message = "Grader returned unexpected format"
78
+ bugs_fixed = []
79
+
80
  reward = max(0.01, min(0.99, reward))
81
 
82
  self.current_step += 1
83
  self._global_step += 1
84
+ self.bugs_found_so_far = len(bugs_fixed)
85
  self.previous_reward = round(reward, 4)
86
+ self.current_error_message = error_message
87
 
88
  # Check if task is complete
89
  task_done = reward >= 0.99 or self.current_step >= MAX_STEPS_PER_TASK
 
129
  is_done=self._done,
130
  tasks_completed=list(self.tasks_completed),
131
  tasks_remaining=tasks_remaining,
 
132
  bugs_found_so_far=self.bugs_found_so_far,
133
  current_error_message=self.current_error_message,
134
  progress_ratio=round(progress_ratio, 2),
 
161
  previous_reward=self.previous_reward,
162
  done=self._done,
163
  reward=self.previous_reward,
164
+ )
server/tasks/task_registry.py CHANGED
@@ -4,7 +4,7 @@ from server.tasks import task1_json, task2_yaml, task3_dockerfile
4
  from server.tasks import task4_compose, task5_k8s, task6_github_actions, task7_nginx
5
 
6
  # INTERNAL USE: Import directly from raw grader files (return tuples)
7
- # grader_api.py returns float-only (for validator/openenv.yaml)
8
  from server.graders.json_grader import grade_task1
9
  from server.graders.yaml_grader import grade_task2
10
  from server.graders.dockerfile_grader import grade_task3
@@ -15,7 +15,7 @@ from server.graders.nginx_grader import grade_task7
15
 
16
 
17
  def _clamp_grader(fn):
18
- """Wrap raw grader to clamp reward to (0.01, 0.99)."""
19
  def wrapper(submitted_config: str) -> Tuple[float, str, List[str]]:
20
  reward, error_msg, bugs_fixed = fn(submitted_config)
21
  reward = max(0.01, min(0.99, float(reward)))
@@ -64,4 +64,4 @@ def get_task(task_id: str) -> TaskInfo:
64
 
65
 
66
  def get_all_task_ids() -> List[str]:
67
- return list(TASK_ORDER)
 
4
  from server.tasks import task4_compose, task5_k8s, task6_github_actions, task7_nginx
5
 
6
  # INTERNAL USE: Import directly from raw grader files (return tuples)
7
+ # grader_api.py is separate and returns float-only (for openenv.yaml validator)
8
  from server.graders.json_grader import grade_task1
9
  from server.graders.yaml_grader import grade_task2
10
  from server.graders.dockerfile_grader import grade_task3
 
15
 
16
 
17
  def _clamp_grader(fn):
18
+ """Wrap raw grader to clamp reward to (0.01, 0.99) while preserving tuple return."""
19
  def wrapper(submitted_config: str) -> Tuple[float, str, List[str]]:
20
  reward, error_msg, bugs_fixed = fn(submitted_config)
21
  reward = max(0.01, min(0.99, float(reward)))
 
64
 
65
 
66
  def get_all_task_ids() -> List[str]:
67
+ return list(TASK_ORDER)