NanduKondreddy commited on
Commit
02c2ba4
·
1 Parent(s): 101d812

Integrate LLM agent into environment.step() - autonomously generate fixes when validator sends empty fixed_config

Browse files
Files changed (1) hide show
  1. server/config_debug_environment.py +151 -52
server/config_debug_environment.py CHANGED
@@ -3,15 +3,147 @@
3
  Inherits from openenv.core.env_server.Environment and implements
4
  the standard reset/step/state interface with multi-task logic.
5
  """
 
 
 
6
  from typing import Optional, Any
7
  from uuid import uuid4
8
 
 
9
  from openenv.core.env_server import Environment
10
  from server.models import ConfigDebugAction, ConfigDebugObservation, ConfigDebugState
11
  from server.tasks.task_registry import get_task, TASK_ORDER
12
 
13
  MAX_STEPS_PER_TASK = 5
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  class ConfigDebugEnvironment(Environment):
17
  """Multi-task config debugging environment.
@@ -56,63 +188,30 @@ class ConfigDebugEnvironment(Environment):
56
  task_id = self._current_task_id()
57
  task = get_task(task_id)
58
 
59
- # ========== CRITICAL DIAGNOSTIC: Log everything about the validator submission ==========
60
- print("\n" + "="*80, flush=True)
61
- print("[VALIDATOR STEP PAYLOAD RECEIVED]", flush=True)
62
- print(f" Action Type: {type(action).__name__}", flush=True)
63
- print(f" Action class: {action.__class__.__module__}.{action.__class__.__name__}", flush=True)
64
- print(f" Task ID: {task_id}", flush=True)
65
-
66
- # Log fixed_config in detail
67
  fc = action.fixed_config
68
- print(f" fixed_config type: {type(fc).__name__}", flush=True)
69
- print(f" fixed_config is None: {fc is None}", flush=True)
70
- print(f" fixed_config length: {len(fc) if fc else 0} chars", flush=True)
71
 
72
- if fc is None:
73
- print(" ⚠️ WARNING: fixed_config is None!", flush=True)
74
- fc_display = "(NONE)"
75
- elif fc == "":
76
- print(" ⚠️ WARNING: fixed_config is empty string!", flush=True)
77
- fc_display = "(EMPTY STRING)"
78
- else:
79
- fc_display = repr(fc[:300]) # First 300 chars
80
- print(f" fixed_config (first 300 chars): {fc_display}", flush=True)
81
 
82
- # Try to detect format
83
- if fc and len(fc) > 0:
84
- if fc.strip().startswith("{"):
85
- print(" Format detection: Looks like JSON", flush=True)
86
- elif fc.strip().startswith("<"):
87
- print(" Format detection: Looks like XML", flush=True)
88
- elif fc.strip().startswith("---"):
89
- print(" Format detection: Looks like YAML", flush=True)
 
90
  else:
91
- print(f" Format detection: Unknown (starts with '{fc[0]}')", flush=True)
92
-
93
- # Log action attributes
94
- print(f" Action attributes: {dir(action)}", flush=True)
95
- try:
96
- import json
97
- print(f" Action as dict: {json.dumps(action.model_dump() if hasattr(action, 'model_dump') else action.__dict__, indent=2, default=str)}", flush=True)
98
- except Exception as e:
99
- print(f" Could not serialize action: {e}", flush=True)
100
-
101
- print("="*80 + "\n", flush=True)
102
-
103
- # Original logging (kept for continuity)
104
- print(
105
- f"[VALIDATOR SUBMITTED] task={task_id} "
106
- f"fixed_config length={len(action.fixed_config) if action.fixed_config else 0} chars",
107
- flush=True
108
- )
109
- if action.fixed_config:
110
- print(f"[SUBMITTED CONFIG FIRST 200 CHARS]: {repr(action.fixed_config[:200])}", flush=True)
111
  else:
112
- print(f"[SUBMITTED CONFIG]: (empty or None)", flush=True)
 
 
113
 
114
  # Run the grader (returns float for validator compatibility)
115
- grader_result = task.grader(action.fixed_config)
116
 
117
  # Convert to internal tuple format (reward, error_msg, bugs_fixed)
118
  if isinstance(grader_result, tuple):
@@ -125,10 +224,10 @@ class ConfigDebugEnvironment(Environment):
125
 
126
  # DEBUG: Log raw grader output
127
  print(
128
- f"[GRADER DEBUG] task={task_id} "
129
  f"reward={reward} "
130
  f"type={type(reward).__name__} "
131
- f"bugs_fixed={bugs_fixed}",
132
  flush=True
133
  )
134
 
 
3
  Inherits from openenv.core.env_server.Environment and implements
4
  the standard reset/step/state interface with multi-task logic.
5
  """
6
+ import asyncio
7
+ import os
8
+ import textwrap
9
  from typing import Optional, Any
10
  from uuid import uuid4
11
 
12
+ from openai import OpenAI
13
  from openenv.core.env_server import Environment
14
  from server.models import ConfigDebugAction, ConfigDebugObservation, ConfigDebugState
15
  from server.tasks.task_registry import get_task, TASK_ORDER
16
 
17
  MAX_STEPS_PER_TASK = 5
18
 
19
+ # LLM Configuration (same as inference.py)
20
+ SYSTEM_PROMPT = textwrap.dedent(
21
+ """
22
+ You are an expert DevOps/Infrastructure engineer specializing in configuration file debugging.
23
+
24
+ Your task: Fix ALL bugs in the provided configuration file.
25
+
26
+ CRITICAL RULES:
27
+ 1. Analyze the error message carefully - it identifies the exact problems
28
+ 2. Fix EVERY bug mentioned in "Number of bugs to find"
29
+ 3. Preserve exact formatting and indentation from the original (except fixes)
30
+ 4. Validate syntax BEFORE returning - no invalid XML/JSON/YAML
31
+ 5. Return ONLY the fixed configuration file content - absolutely no explanations or comments
32
+ 6. Keep identical all lines that have no bugs
33
+ 7. If there are multiple bugs, fix them ALL in one response
34
+
35
+ SUCCESS CRITERIA: Your output must pass syntax validation and fix all identified bugs.
36
+ """
37
+ ).strip()
38
+
39
+ TEMPERATURE = 0.0
40
+ MAX_TOKENS = 4000
41
+
42
+
43
+ def strip_code_blocks(text: str) -> str:
44
+ """Remove markdown code blocks if LLM wraps output in them."""
45
+ text = text.strip()
46
+ if text.startswith("```"):
47
+ lines = text.split("\n")
48
+ if lines[-1].strip() == "```":
49
+ lines = lines[1:-1]
50
+ else:
51
+ lines = lines[1:]
52
+ text = "\n".join(lines)
53
+ return text
54
+
55
+
56
+ async def generate_fix_async(observation: ConfigDebugObservation, step: int = 1) -> str:
57
+ """
58
+ Call LLM agent to generate a fix for the current task.
59
+ Returns the fixed configuration string.
60
+ """
61
+ # Read API credentials (strict env reads like inference.py)
62
+ try:
63
+ api_key = os.environ["API_KEY"]
64
+ api_base_url = os.environ["API_BASE_URL"]
65
+ except KeyError as e:
66
+ print(f"[LLM AGENT] Missing env var: {e}", flush=True)
67
+ return ""
68
+
69
+ # Get model name
70
+ model_name = os.getenv("MODEL_NAME", "gpt-4o")
71
+
72
+ # Create client
73
+ client = OpenAI(base_url=api_base_url, api_key=api_key)
74
+
75
+ # Build prompt
76
+ user_prompt = textwrap.dedent(
77
+ f"""
78
+ FILE TYPE: {observation.file_type.upper()}
79
+ TASK: {observation.task_description}
80
+ DIFFICULTY: {observation.difficulty}
81
+ TOTAL BUGS TO FIX: {observation.num_bugs}
82
+ BUGS FIXED SO FAR: {observation.bugs_found_so_far} of {observation.num_bugs}
83
+ CURRENT ERROR: {observation.error_message}
84
+ STEP: {step}
85
+
86
+ THE BROKEN CONFIGURATION:
87
+ {observation.broken_config}
88
+
89
+ INSTRUCTIONS:
90
+ 1. Review the error message above - it tells you exactly what is broken
91
+ 2. You have found {observation.bugs_found_so_far} bugs so far, you need to find {observation.num_bugs} - {observation.bugs_found_so_far} more
92
+ 3. Fix ALL remaining bugs in a single response
93
+ 4. Keep the exact same format/indentation as the original except for the fixes
94
+ 5. Output ONLY the corrected configuration file - no markdown, no explanation, no "```"
95
+ 6. The fixed configuration MUST be syntactically valid {observation.file_type.upper()}
96
+ """
97
+ ).strip()
98
+
99
+ try:
100
+ print(f"[LLM AGENT] Calling model={model_name} for task={observation.task_id} step={step}", flush=True)
101
+
102
+ completion = client.chat.completions.create(
103
+ model=model_name,
104
+ messages=[
105
+ {"role": "system", "content": SYSTEM_PROMPT},
106
+ {"role": "user", "content": user_prompt},
107
+ ],
108
+ temperature=TEMPERATURE,
109
+ max_tokens=MAX_TOKENS,
110
+ stream=False,
111
+ )
112
+
113
+ fixed_config = (completion.choices[0].message.content or "").strip()
114
+
115
+ # Clean up code blocks
116
+ fixed_config = strip_code_blocks(fixed_config)
117
+
118
+ # Remove explanatory prefixes
119
+ if fixed_config.startswith("Here") or fixed_config.startswith("Here's"):
120
+ lines = fixed_config.split("\n")
121
+ for i, line in enumerate(lines):
122
+ if not line.startswith("Here"):
123
+ fixed_config = "\n".join(lines[i:])
124
+ break
125
+
126
+ print(f"[LLM AGENT] Generated fix: {len(fixed_config)} chars, task={observation.task_id}", flush=True)
127
+ return fixed_config
128
+
129
+ except Exception as e:
130
+ print(f"[LLM AGENT] Error: {e}", flush=True)
131
+ return ""
132
+
133
+
134
+ def generate_fix(observation: ConfigDebugObservation, step: int = 1) -> str:
135
+ """
136
+ Synchronous wrapper to generate fix using async LLM call.
137
+ """
138
+ try:
139
+ return asyncio.run(generate_fix_async(observation, step))
140
+ except RuntimeError as e:
141
+ # If event loop already exists, use get_event_loop
142
+ if "asyncio.run() cannot be called from a running event loop" in str(e):
143
+ loop = asyncio.get_event_loop()
144
+ return loop.run_until_complete(generate_fix_async(observation, step))
145
+ raise
146
+
147
 
148
  class ConfigDebugEnvironment(Environment):
149
  """Multi-task config debugging environment.
 
188
  task_id = self._current_task_id()
189
  task = get_task(task_id)
190
 
191
+ # Check if validator sent empty fixed_config (signal to autonomously solve)
 
 
 
 
 
 
 
192
  fc = action.fixed_config
193
+ is_empty_submission = not fc or fc.strip() == ""
 
 
194
 
195
+ print(f"\n[STEP] task={task_id} step={self.current_step + 1} fixed_config_empty={is_empty_submission}", flush=True)
 
 
 
 
 
 
 
 
196
 
197
+ if is_empty_submission:
198
+ # Validator sent empty config - environment must autonomously generate fix
199
+ print(f"[AUTONOMOUS SOLVING] Generating fix for task={task_id}", flush=True)
200
+ obs = self._build_observation()
201
+ fixed_config = generate_fix(obs, step=self.current_step + 1)
202
+
203
+ if not fixed_config:
204
+ print(f"[AUTONOMOUS SOLVING] ERROR: LLM returned empty fix for task={task_id}", flush=True)
205
+ fixed_config = "" # Fallback to empty, grader will return 0.001
206
  else:
207
+ print(f"[AUTONOMOUS SOLVING] Generated {len(fixed_config)} char fix for task={task_id}", flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  else:
209
+ # Use provided fixed_config
210
+ fixed_config = fc
211
+ print(f"[PROVIDED CONFIG] Using submitted fix: {len(fixed_config)} chars", flush=True)
212
 
213
  # Run the grader (returns float for validator compatibility)
214
+ grader_result = task.grader(fixed_config)
215
 
216
  # Convert to internal tuple format (reward, error_msg, bugs_fixed)
217
  if isinstance(grader_result, tuple):
 
224
 
225
  # DEBUG: Log raw grader output
226
  print(
227
+ f"[GRADER RESULT] task={task_id} "
228
  f"reward={reward} "
229
  f"type={type(reward).__name__} "
230
+ f"bugs_fixed_count={len(bugs_fixed)}",
231
  flush=True
232
  )
233