DEVessi commited on
Commit
37b054e
·
verified ·
1 Parent(s): e9c3076

Upload folder using huggingface_hub

Browse files
.gitignore ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Virtual environments
2
+ venv/
3
+ .venv/
4
+ env/
5
+ ENV/
6
+
7
+ # Python
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+ *.so
12
+ *.egg
13
+ *.egg-info/
14
+ .eggs/
15
+ build/
16
+ dist/
17
+
18
+ # Node
19
+ node_modules/
20
+ npm-debug.log
21
+ yarn-error.log
22
+
23
+ # IDEs and Editors
24
+ .vscode/
25
+ .idea/
26
+ *.swp
27
+ *.swo
28
+ .DS_Store
29
+
30
+ # Application Specific / Temp
31
+ *.log
32
+ validation_output.txt
33
+ _cli_path.txt
34
+ _path.txt
Dockerfile CHANGED
@@ -1,69 +1,69 @@
1
- # Hugging Face Spaces Dockerfile for Self-Healing DevOps Sandbox
2
- FROM ghcr.io/meta-pytorch/openenv-base:latest AS builder
3
-
4
- USER root
5
-
6
- # Install Node.js 20 & utilities needed by the sandbox
7
- RUN apt-get update && \
8
- apt-get install -y --no-install-recommends git curl bash && \
9
- curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
10
- apt-get install -y nodejs && \
11
- rm -rf /var/lib/apt/lists/*
12
-
13
- WORKDIR /server_app
14
-
15
- # Copy environment code
16
- COPY . /server_app/env
17
- WORKDIR /server_app/env
18
-
19
- # Ensure uv is available
20
- RUN if ! command -v uv >/dev/null 2>&1; then \
21
- curl -LsSf https://astral.sh/uv/install.sh | sh && \
22
- mv /root/.local/bin/uv /usr/local/bin/uv && \
23
- mv /root/.local/bin/uvx /usr/local/bin/uvx; \
24
- fi
25
-
26
- # Install dependencies using uv sync
27
- RUN --mount=type=cache,target=/root/.cache/uv \
28
- if [ -f uv.lock ]; then \
29
- uv sync --no-install-project --no-editable; \
30
- else \
31
- uv sync --no-editable; \
32
- fi
33
-
34
- # Final runtime stage
35
- FROM ghcr.io/meta-pytorch/openenv-base:latest
36
-
37
- USER root
38
-
39
- # Re-install Node.js 20 in the runtime image
40
- RUN apt-get update && \
41
- apt-get install -y --no-install-recommends git curl bash psmisc && \
42
- curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
43
- apt-get install -y nodejs && \
44
- rm -rf /var/lib/apt/lists/*
45
-
46
- WORKDIR /server_app
47
-
48
- # Copy the virtual environment from builder
49
- COPY --from=builder /server_app/env/.venv /server_app/.venv
50
-
51
- # Copy the environment code
52
- COPY --from=builder /server_app/env /server_app/env
53
-
54
- # Ensure the backup folder for reset() is available and perfectly clean
55
- # The environment script will do `cp -r /app_backup/* /app/` during every reset
56
- RUN mkdir -p /app_backup && \
57
- cp -r /server_app/env/simulated_app/* /app_backup/ && \
58
- mkdir -p /app && \
59
- chmod -R 777 /app /app_backup
60
-
61
- # Export Paths
62
- ENV PATH="/server_app/.venv/bin:$PATH"
63
- ENV PYTHONPATH="/server_app/env:$PYTHONPATH"
64
-
65
- # Run the FastAPI server on port 7860 for HuggingFace Spaces
66
- ENV ENABLE_WEB_INTERFACE=true
67
- EXPOSE 7860
68
-
69
- CMD ["sh", "-c", "cd /server_app/env && uvicorn server.app:app --host 0.0.0.0 --port 7860"]
 
1
+ # Hugging Face Spaces Dockerfile for Self-Healing DevOps Sandbox
2
+ FROM ghcr.io/meta-pytorch/openenv-base:latest AS builder
3
+
4
+ USER root
5
+
6
+ # Install Node.js 20 & utilities needed by the sandbox
7
+ RUN apt-get update && \
8
+ apt-get install -y --no-install-recommends git curl bash && \
9
+ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
10
+ apt-get install -y nodejs && \
11
+ rm -rf /var/lib/apt/lists/*
12
+
13
+ WORKDIR /server_app
14
+
15
+ # Copy environment code
16
+ COPY . /server_app/env
17
+ WORKDIR /server_app/env
18
+
19
+ # Ensure uv is available
20
+ RUN if ! command -v uv >/dev/null 2>&1; then \
21
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
22
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
23
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
24
+ fi
25
+
26
+ # Install dependencies using uv sync
27
+ RUN --mount=type=cache,target=/root/.cache/uv \
28
+ if [ -f uv.lock ]; then \
29
+ uv sync --no-install-project --no-editable; \
30
+ else \
31
+ uv sync --no-editable; \
32
+ fi
33
+
34
+ # Final runtime stage
35
+ FROM ghcr.io/meta-pytorch/openenv-base:latest
36
+
37
+ USER root
38
+
39
+ # Re-install Node.js 20 in the runtime image
40
+ RUN apt-get update && \
41
+ apt-get install -y --no-install-recommends git curl bash psmisc && \
42
+ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
43
+ apt-get install -y nodejs && \
44
+ rm -rf /var/lib/apt/lists/*
45
+
46
+ WORKDIR /server_app
47
+
48
+ # Copy the virtual environment from builder
49
+ COPY --from=builder /server_app/env/.venv /server_app/.venv
50
+
51
+ # Copy the environment code
52
+ COPY --from=builder /server_app/env /server_app/env
53
+
54
+ # Ensure the backup folder for reset() is available and perfectly clean
55
+ # The environment script will do `cp -r /app_backup/* /app/` during every reset
56
+ RUN mkdir -p /app_backup && \
57
+ cp -r /server_app/env/simulated_app/* /app_backup/ && \
58
+ mkdir -p /app && \
59
+ chmod -R 777 /app /app_backup
60
+
61
+ # Export Paths
62
+ ENV PATH="/server_app/.venv/bin:$PATH"
63
+ ENV PYTHONPATH="/server_app/env:$PYTHONPATH"
64
+
65
+ # Run the FastAPI server on port 7860 for HuggingFace Spaces
66
+ ENV ENABLE_WEB_INTERFACE=true
67
+ EXPOSE 7860
68
+
69
+ CMD ["sh", "-c", "cd /server_app/env && uvicorn server.app:app --host 0.0.0.0 --port 7860"]
inference.py CHANGED
@@ -10,14 +10,6 @@ Baseline inference script for the Self-Healing DevOps Sandbox.
10
 
11
  Uses an LLM (via the OpenAI-compatible API) to diagnose and fix a broken
12
  Node.js backend running inside a Docker container.
13
-
14
- Usage:
15
- export OPENAI_API_KEY="sk-..."
16
- python baseline.py
17
-
18
- # Or with a custom endpoint (e.g., local vLLM):
19
- export OPENAI_BASE_URL="http://localhost:8080/v1"
20
- python baseline.py
21
  """
22
 
23
  import json
@@ -30,14 +22,20 @@ except ImportError:
30
  print("ERROR: 'openai' package is required. Install with: pip install openai")
31
  sys.exit(1)
32
 
33
- from devops_sandbox import BashAction, DevopsSandboxEnv
 
34
 
35
  # ---------------------------------------------------------------------------
36
  # Configuration
37
  # ---------------------------------------------------------------------------
 
 
 
 
38
  ENV_URL = os.getenv("DEVOPS_SANDBOX_URL", "http://localhost:8000")
39
- MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
40
- MAX_TURNS = int(os.getenv("MAX_TURNS", "30"))
 
41
 
42
  SYSTEM_PROMPT = """\
43
  You are an expert DevOps engineer and Node.js developer.
@@ -59,10 +57,8 @@ EXPECTED FINAL STATE:
59
  - GET /api/data → 200 with JSON containing "records" array
60
  """
61
 
62
-
63
  def extract_command(llm_response: str) -> str:
64
  """Extract a bash command from the LLM's response (JSON or raw text)."""
65
- # Try JSON parsing first
66
  try:
67
  data = json.loads(llm_response.strip())
68
  if isinstance(data, dict) and "command" in data:
@@ -70,10 +66,9 @@ def extract_command(llm_response: str) -> str:
70
  except (json.JSONDecodeError, TypeError):
71
  pass
72
 
73
- # Try extracting from markdown code block
74
  if "```" in llm_response:
75
  lines = llm_response.split("```")
76
- for block in lines[1::2]: # odd indices are code blocks
77
  code = block.strip()
78
  if code.startswith("json"):
79
  code = code[4:].strip()
@@ -91,36 +86,29 @@ def extract_command(llm_response: str) -> str:
91
  if first_line:
92
  return first_line
93
 
94
- # Fallback: treat entire response as a command
95
  cmd = llm_response.strip().strip("`").strip()
96
  if cmd.startswith("{"):
97
- # One more try
98
  try:
99
  return json.loads(cmd)["command"]
100
  except Exception:
101
  pass
102
  return cmd
103
 
104
-
105
  def main():
106
- print("=" * 60)
107
- print(" Self-Healing DevOps Sandbox Baseline Agent")
108
- print("=" * 60)
109
 
110
- client = OpenAI()
111
 
 
112
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
113
 
114
  with DevopsSandboxEnv(base_url=ENV_URL).sync() as env:
115
- # Reset the environment
116
- print("\n[*] Resetting environment...")
117
  result = env.reset()
118
  obs = result.observation
 
 
119
 
120
- print(f"\n[INIT] Task prompt:\n{obs.stdout[:500]}...")
121
- print(f"[INIT] Score: {obs.grader_score} | Feedback: {obs.grader_feedback}")
122
-
123
- # Add initial observation to messages
124
  messages.append({
125
  "role": "user",
126
  "content": (
@@ -132,72 +120,66 @@ def main():
132
  ),
133
  })
134
 
135
- for turn in range(1, MAX_TURNS + 1):
136
- print(f"\n{'─' * 40}")
137
- print(f"Turn {turn}/{MAX_TURNS}")
138
- print(f"{'─' * 40}")
139
 
140
- # Get LLM response
141
  try:
142
  response = client.chat.completions.create(
143
- model=MODEL,
144
  messages=messages,
145
  temperature=0.2,
146
  max_tokens=256,
147
  )
148
  llm_text = response.choices[0].message.content or ""
149
  except Exception as e:
150
- print(f"[ERROR] LLM call failed: {e}")
 
151
  break
152
 
153
- # Extract command
154
  command = extract_command(llm_text)
155
  if not command:
156
- print("[WARN] Could not extract command from LLM response")
157
  command = "ls -la /app"
158
 
159
- print(f"[CMD] {command}")
 
 
 
 
 
 
160
 
161
- # Execute in environment
162
- result = env.step(BashAction(command=command))
163
- obs = result.observation
 
 
164
 
165
- stdout_preview = obs.stdout[:300] if obs.stdout else "(empty)"
166
- stderr_preview = obs.stderr[:200] if obs.stderr else "(none)"
167
- print(f"[OUT] {stdout_preview}")
168
- if obs.stderr:
169
- print(f"[ERR] {stderr_preview}")
170
- print(f"[SCORE] {obs.grader_score:.2f} | {obs.grader_feedback}")
171
 
172
- # Add to conversation
173
  messages.append({"role": "assistant", "content": llm_text})
174
  messages.append({
175
  "role": "user",
176
  "content": (
177
  f"Command output:\n"
178
- f"stdout:\n```\n{obs.stdout}\n```\n"
179
- f"stderr:\n```\n{obs.stderr}\n```\n"
180
- f"Current score: {obs.grader_score}/1.0\n"
181
- f"Grader feedback: {obs.grader_feedback}\n\n"
182
  f"What command should I run next?"
183
  ),
184
  })
185
 
186
- # Check if done
187
- if result.done:
188
- print(f"\n{'=' * 60}")
189
- if obs.grader_score >= 1.0:
190
- print(" ✅ ALL BUGS FIXED — PERFECT SCORE!")
191
- else:
192
- print(f" Episode ended. Final score: {obs.grader_score:.2f}/1.0")
193
- print(f"{'=' * 60}")
194
  break
195
- else:
196
- print(f"\n[!] Max turns ({MAX_TURNS}) reached.")
197
- print(f" Final score: {obs.grader_score:.2f}/1.0")
198
-
199
- print("\n[*] Done.")
200
 
 
 
 
201
 
202
  if __name__ == "__main__":
203
  main()
 
10
 
11
  Uses an LLM (via the OpenAI-compatible API) to diagnose and fix a broken
12
  Node.js backend running inside a Docker container.
 
 
 
 
 
 
 
 
13
  """
14
 
15
  import json
 
22
  print("ERROR: 'openai' package is required. Install with: pip install openai")
23
  sys.exit(1)
24
 
25
+ from client import DevopsSandboxEnv
26
+ from models import BashAction
27
 
28
  # ---------------------------------------------------------------------------
29
  # Configuration
30
  # ---------------------------------------------------------------------------
31
+ API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
32
+ MODEL_NAME = os.getenv("MODEL_NAME") or "gpt-4o-mini"
33
+ HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
34
+
35
  ENV_URL = os.getenv("DEVOPS_SANDBOX_URL", "http://localhost:8000")
36
+ TASK_NAME = os.getenv("MY_ENV_V4_TASK", "devops_sandbox")
37
+ BENCHMARK = os.getenv("MY_ENV_V4_BENCHMARK", "devops_sandbox")
38
+ MAX_TURNS = int(os.getenv("MAX_TURNS", "8"))
39
 
40
  SYSTEM_PROMPT = """\
41
  You are an expert DevOps engineer and Node.js developer.
 
57
  - GET /api/data → 200 with JSON containing "records" array
58
  """
59
 
 
60
  def extract_command(llm_response: str) -> str:
61
  """Extract a bash command from the LLM's response (JSON or raw text)."""
 
62
  try:
63
  data = json.loads(llm_response.strip())
64
  if isinstance(data, dict) and "command" in data:
 
66
  except (json.JSONDecodeError, TypeError):
67
  pass
68
 
 
69
  if "```" in llm_response:
70
  lines = llm_response.split("```")
71
+ for block in lines[1::2]:
72
  code = block.strip()
73
  if code.startswith("json"):
74
  code = code[4:].strip()
 
86
  if first_line:
87
  return first_line
88
 
 
89
  cmd = llm_response.strip().strip("`").strip()
90
  if cmd.startswith("{"):
 
91
  try:
92
  return json.loads(cmd)["command"]
93
  except Exception:
94
  pass
95
  return cmd
96
 
 
97
  def main():
98
+ if not HF_TOKEN:
99
+ pass # we can let it fail or use empty key depending on endpoint
 
100
 
101
+ client = OpenAI(api_key=HF_TOKEN or "dummy_key", base_url=API_BASE_URL)
102
 
103
+ # Note: openenv evaluation specifically needs exactly 3 things: [START], [STEP] logs, [END]
104
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
105
 
106
  with DevopsSandboxEnv(base_url=ENV_URL).sync() as env:
 
 
107
  result = env.reset()
108
  obs = result.observation
109
+
110
+ print(f"[START] task={TASK_NAME} env={BENCHMARK} model={MODEL_NAME}", flush=True)
111
 
 
 
 
 
112
  messages.append({
113
  "role": "user",
114
  "content": (
 
120
  ),
121
  })
122
 
123
+ rewards = []
124
+ is_done = False
125
+ steps_taken = 0
126
+ final_score = 0.0
127
 
128
+ for turn in range(1, MAX_TURNS + 1):
129
  try:
130
  response = client.chat.completions.create(
131
+ model=MODEL_NAME,
132
  messages=messages,
133
  temperature=0.2,
134
  max_tokens=256,
135
  )
136
  llm_text = response.choices[0].message.content or ""
137
  except Exception as e:
138
+ err_msg = str(e).replace('"', "'")
139
+ # Need to emit an empty step on failure? Usually not, just end.
140
  break
141
 
 
142
  command = extract_command(llm_text)
143
  if not command:
 
144
  command = "ls -la /app"
145
 
146
+ error_msg = "null"
147
+ try:
148
+ result = env.step(BashAction(command=command))
149
+ obs = result.observation
150
+ except Exception as e:
151
+ obs = env.state # Mock failed obs
152
+ error_msg = str(e).replace('\n', ' ')
153
 
154
+ steps_taken += 1
155
+ reward_val = obs.reward if hasattr(obs, 'reward') else getattr(obs, 'grader_score', 0.0)
156
+ rewards.append(f"{reward_val:.2f}")
157
+ is_done = result.done if hasattr(result, 'done') else getattr(obs, 'done', False)
158
+ done_str = "true" if is_done else "false"
159
 
160
+ action_str = command.replace('\n', ' ; ')
161
+ print(f"[STEP] step={steps_taken} action={action_str} reward={reward_val:.2f} done={done_str} error={error_msg}", flush=True)
 
 
 
 
162
 
 
163
  messages.append({"role": "assistant", "content": llm_text})
164
  messages.append({
165
  "role": "user",
166
  "content": (
167
  f"Command output:\n"
168
+ f"stdout:\n```\n{getattr(obs, 'stdout', '')}\n```\n"
169
+ f"stderr:\n```\n{getattr(obs, 'stderr', '')}\n```\n"
170
+ f"Current score: {getattr(obs, 'grader_score', 0.0)}/1.0\n"
171
+ f"Grader feedback: {getattr(obs, 'grader_feedback', '')}\n\n"
172
  f"What command should I run next?"
173
  ),
174
  })
175
 
176
+ final_score = getattr(obs, 'grader_score', 0.0)
177
+ if getattr(obs, 'grader_score', 0.0) >= 1.0 or getattr(obs, 'done', False) or result.done:
 
 
 
 
 
 
178
  break
 
 
 
 
 
179
 
180
+ success_str = "true" if final_score >= 1.0 else "false"
181
+ rewards_str = ",".join(rewards) if rewards else "0.00"
182
+ print(f"[END] success={success_str} steps={steps_taken} score={final_score:.2f} rewards={rewards_str}", flush=True)
183
 
184
  if __name__ == "__main__":
185
  main()
move.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+
4
+ src = r"e:\programs2\openenv(RL)\devops_sandbox"
5
+ dst = r"e:\programs2\openenv(RL)"
6
+
7
+ for item in os.listdir(src):
8
+ s = os.path.join(src, item)
9
+ d = os.path.join(dst, item)
10
+ if os.path.exists(d):
11
+ if os.path.isdir(d):
12
+ shutil.rmtree(d, ignore_errors=True)
13
+ else:
14
+ os.remove(d)
15
+ shutil.move(s, d)
16
+
17
+ print("Moved successfully")
server/# Self-Healing DevOps Sandbox - a Huggin.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Self-Healing DevOps Sandbox - a Hugging Face Space by DEVessi
2
+
3
+
4
+ # https://huggingface.co/[Spaces](https://huggingface.co/spaces)https://huggingface.co/[DEVessi](https://huggingface.co/DEVessi) DEVessi / [devops_sandbox](https://huggingface.co/spaces/DEVessi/devops_sandbox) like 0 Building
5
+ [Spaces](https://huggingface.co/spaces)
6
+ [DEVessi](https://huggingface.co/DEVessi)
7
+ [devops_sandbox](https://huggingface.co/spaces/DEVessi/devops_sandbox)
8
+ [App](https://huggingface.co/spaces/DEVessi/devops_sandbox)
9
+ [Files Files](https://huggingface.co/spaces/DEVessi/devops_sandbox/tree/main)
10
+ [Community](https://huggingface.co/spaces/DEVessi/devops_sandbox/discussions)
server/devops_sandbox_environment.py CHANGED
@@ -209,11 +209,19 @@ class DevOpsSandbox(Environment):
209
  def _reset_filesystem(self) -> None:
210
  """Replace the current working /app with the pristine /app_backup."""
211
  # Ensure we don't accidentally wipe out the whole host on windows if paths are wrong
212
- if os.path.exists(self._app_dir):
213
- shutil.rmtree(self._app_dir, ignore_errors=True)
214
-
215
  os.makedirs(self._app_dir, exist_ok=True)
216
 
 
 
 
 
 
 
 
 
 
 
 
217
  # Copy from backup to app dir
218
  if os.path.exists(self._app_backup_dir):
219
  for item in os.listdir(self._app_backup_dir):
 
209
  def _reset_filesystem(self) -> None:
210
  """Replace the current working /app with the pristine /app_backup."""
211
  # Ensure we don't accidentally wipe out the whole host on windows if paths are wrong
 
 
 
212
  os.makedirs(self._app_dir, exist_ok=True)
213
 
214
+ # Clean contents of /app instead of deleting /app itself
215
+ for item in os.listdir(self._app_dir):
216
+ item_path = os.path.join(self._app_dir, item)
217
+ if os.path.isdir(item_path):
218
+ shutil.rmtree(item_path, ignore_errors=True)
219
+ else:
220
+ try:
221
+ os.remove(item_path)
222
+ except OSError:
223
+ pass
224
+
225
  # Copy from backup to app dir
226
  if os.path.exists(self._app_backup_dir):
227
  for item in os.listdir(self._app_backup_dir):