Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .gitignore +34 -0
- Dockerfile +69 -69
- inference.py +47 -65
- move.py +17 -0
- server/# Self-Healing DevOps Sandbox - a Huggin.txt +10 -0
- server/devops_sandbox_environment.py +11 -3
.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
|
|
|
|
| 34 |
|
| 35 |
# ---------------------------------------------------------------------------
|
| 36 |
# Configuration
|
| 37 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
ENV_URL = os.getenv("DEVOPS_SANDBOX_URL", "http://localhost:8000")
|
| 39 |
-
|
| 40 |
-
|
|
|
|
| 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]:
|
| 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 |
-
|
| 107 |
-
|
| 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 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
|
| 140 |
-
|
| 141 |
try:
|
| 142 |
response = client.chat.completions.create(
|
| 143 |
-
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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 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
|
| 179 |
-
f"stderr:\n```\n{obs
|
| 180 |
-
f"Current score: {obs
|
| 181 |
-
f"Grader feedback: {obs
|
| 182 |
f"What command should I run next?"
|
| 183 |
),
|
| 184 |
})
|
| 185 |
|
| 186 |
-
|
| 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):
|