Commit ·
eb895b1
1
Parent(s): 2794920
inference fixed, port changed to 7860
Browse files- Dockerfile +4 -4
- README.md +5 -5
- inference.py +28 -11
- server/app.py +1 -1
- server/static/index.html +2 -2
- server/tasks/pipeline_build_deploy.py +3 -3
- server/tasks/pipeline_full.py +3 -3
- server/tasks/task_1_build_errors.py +1 -1
- smoke_test.py +2 -2
Dockerfile
CHANGED
|
@@ -12,11 +12,11 @@ COPY baseline_runner.py .
|
|
| 12 |
COPY inference.py .
|
| 13 |
COPY openenv.yaml .
|
| 14 |
|
| 15 |
-
# HuggingFace Spaces expects port
|
| 16 |
-
EXPOSE
|
| 17 |
|
| 18 |
# Health check
|
| 19 |
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
|
| 20 |
-
CMD python -c "import requests; requests.get('http://localhost:
|
| 21 |
|
| 22 |
-
CMD ["python", "-m", "uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "
|
|
|
|
| 12 |
COPY inference.py .
|
| 13 |
COPY openenv.yaml .
|
| 14 |
|
| 15 |
+
# HuggingFace Spaces expects port 7860
|
| 16 |
+
EXPOSE 7860
|
| 17 |
|
| 18 |
# Health check
|
| 19 |
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
|
| 20 |
+
CMD python -c "import requests; requests.get('http://localhost:7860/')" || exit 1
|
| 21 |
|
| 22 |
+
CMD ["python", "-m", "uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -4,7 +4,7 @@ emoji: 🔧
|
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: green
|
| 6 |
sdk: docker
|
| 7 |
-
app_port:
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
|
@@ -296,12 +296,12 @@ This ensures:
|
|
| 296 |
|
| 297 |
```bash
|
| 298 |
# 1. Start an episode
|
| 299 |
-
curl -X POST http://localhost:
|
| 300 |
-H "Content-Type: application/json" \
|
| 301 |
-d '{"task_id": "k8s_pod_failures", "scenario_id": "oom_killed"}'
|
| 302 |
|
| 303 |
# 2. Fix the memory limit (any reasonable value works — simulator validates structurally)
|
| 304 |
-
curl -X POST http://localhost:
|
| 305 |
-H "Content-Type: application/json" \
|
| 306 |
-d '{
|
| 307 |
"action": {
|
|
@@ -325,7 +325,7 @@ curl -X POST http://localhost:8000/step \
|
|
| 325 |
|
| 326 |
```bash
|
| 327 |
pip install -r requirements.txt
|
| 328 |
-
python -m uvicorn server.app:app --host 0.0.0.0 --port
|
| 329 |
```
|
| 330 |
|
| 331 |
### Run Tests
|
|
@@ -338,7 +338,7 @@ pytest tests/ -v
|
|
| 338 |
|
| 339 |
```bash
|
| 340 |
docker build -t cloud-native-devops-env .
|
| 341 |
-
docker run -p
|
| 342 |
```
|
| 343 |
|
| 344 |
### Baseline Inference (with LLM)
|
|
|
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: green
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
|
|
|
| 296 |
|
| 297 |
```bash
|
| 298 |
# 1. Start an episode
|
| 299 |
+
curl -X POST http://localhost:7860/reset \
|
| 300 |
-H "Content-Type: application/json" \
|
| 301 |
-d '{"task_id": "k8s_pod_failures", "scenario_id": "oom_killed"}'
|
| 302 |
|
| 303 |
# 2. Fix the memory limit (any reasonable value works — simulator validates structurally)
|
| 304 |
+
curl -X POST http://localhost:7860/step \
|
| 305 |
-H "Content-Type: application/json" \
|
| 306 |
-d '{
|
| 307 |
"action": {
|
|
|
|
| 325 |
|
| 326 |
```bash
|
| 327 |
pip install -r requirements.txt
|
| 328 |
+
python -m uvicorn server.app:app --host 0.0.0.0 --port 7860
|
| 329 |
```
|
| 330 |
|
| 331 |
### Run Tests
|
|
|
|
| 338 |
|
| 339 |
```bash
|
| 340 |
docker build -t cloud-native-devops-env .
|
| 341 |
+
docker run -p 7860:7860 cloud-native-devops-env
|
| 342 |
```
|
| 343 |
|
| 344 |
### Baseline Inference (with LLM)
|
inference.py
CHANGED
|
@@ -56,7 +56,7 @@ from openai import OpenAI
|
|
| 56 |
API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
|
| 57 |
MODEL_NAME = os.getenv("MODEL_NAME") or "meta-llama/Llama-3.1-70B-Instruct"
|
| 58 |
API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
|
| 59 |
-
ENV_URL = os.getenv("ENV_URL", "http://localhost:
|
| 60 |
LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME")
|
| 61 |
BENCHMARK = "cloud_native_devops"
|
| 62 |
MAX_STEPS = 8 # leave 2 steps buffer before env hard-limit of 10
|
|
@@ -72,18 +72,29 @@ Your job is to:
|
|
| 72 |
|
| 73 |
When you identify a fix, respond with a JSON object in this exact format:
|
| 74 |
{
|
|
|
|
| 75 |
"reasoning": "Brief explanation of the bug and fix",
|
| 76 |
"edits": [
|
| 77 |
{
|
| 78 |
"file_path": "path/to/file",
|
| 79 |
-
"
|
| 80 |
-
"
|
|
|
|
| 81 |
}
|
| 82 |
]
|
| 83 |
}
|
| 84 |
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
{
|
|
|
|
| 87 |
"reasoning": "Create missing ConfigMap manifest",
|
| 88 |
"edits": [
|
| 89 |
{
|
|
@@ -95,10 +106,10 @@ To create a new file (e.g. a missing ConfigMap), use an empty old_content:
|
|
| 95 |
}
|
| 96 |
|
| 97 |
If you believe all issues are fixed and want to submit, respond with:
|
| 98 |
-
{"
|
| 99 |
|
| 100 |
If you need a hint, respond with:
|
| 101 |
-
{"
|
| 102 |
|
| 103 |
Rules:
|
| 104 |
- Match old_content EXACTLY as it appears in the file (whitespace matters)
|
|
@@ -217,20 +228,26 @@ def parse_llm_response(text: str) -> Dict[str, Any]:
|
|
| 217 |
|
| 218 |
def build_action(parsed: Dict[str, Any]) -> Dict[str, Any]:
|
| 219 |
"""Convert parsed LLM response to environment action format."""
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
| 221 |
return {"action_type": "submit"}
|
| 222 |
-
if parsed.get("action") == "hint":
|
| 223 |
return {"action_type": "request_hint"}
|
| 224 |
|
| 225 |
edits = parsed.get("edits", [])
|
| 226 |
-
if not edits:
|
| 227 |
return {"action_type": "submit"}
|
| 228 |
|
|
|
|
|
|
|
| 229 |
return {
|
| 230 |
-
"action_type":
|
| 231 |
"edits": [
|
| 232 |
{
|
| 233 |
"file_path": e.get("file_path", ""),
|
|
|
|
| 234 |
"old_content": e.get("old_content", ""),
|
| 235 |
"new_content": e.get("new_content", ""),
|
| 236 |
}
|
|
@@ -384,7 +401,7 @@ def main():
|
|
| 384 |
print(f"[DEBUG] Environment status: {health.get('status', 'unknown')}", flush=True)
|
| 385 |
except Exception as e:
|
| 386 |
print(f"[DEBUG] Cannot connect to environment at {ENV_URL}: {e}", flush=True)
|
| 387 |
-
print("[DEBUG] Start the server first: python -m uvicorn server.app:app --host 0.0.0.0 --port
|
| 388 |
sys.exit(1)
|
| 389 |
|
| 390 |
client = create_client()
|
|
|
|
| 56 |
API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
|
| 57 |
MODEL_NAME = os.getenv("MODEL_NAME") or "meta-llama/Llama-3.1-70B-Instruct"
|
| 58 |
API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
|
| 59 |
+
ENV_URL = os.getenv("ENV_URL", "http://localhost:7860")
|
| 60 |
LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME")
|
| 61 |
BENCHMARK = "cloud_native_devops"
|
| 62 |
MAX_STEPS = 8 # leave 2 steps buffer before env hard-limit of 10
|
|
|
|
| 72 |
|
| 73 |
When you identify a fix, respond with a JSON object in this exact format:
|
| 74 |
{
|
| 75 |
+
"action_type": "YOUR_CHOSEN_ACTION_TYPE",
|
| 76 |
"reasoning": "Brief explanation of the bug and fix",
|
| 77 |
"edits": [
|
| 78 |
{
|
| 79 |
"file_path": "path/to/file",
|
| 80 |
+
"line_number": 5, // Only needed for replace_line, add_line, delete_line, add_block
|
| 81 |
+
"old_content": "exactly broken", // Only needed for edit_file, delete_block
|
| 82 |
+
"new_content": "corrected block" // Not needed for delete_line, delete_block
|
| 83 |
}
|
| 84 |
]
|
| 85 |
}
|
| 86 |
|
| 87 |
+
Available action_type values for edits:
|
| 88 |
+
- "edit_file" (requires old_content and new_content)
|
| 89 |
+
- "replace_line" (requires line_number and new_content)
|
| 90 |
+
- "add_line" (requires line_number and new_content)
|
| 91 |
+
- "delete_line" (requires line_number)
|
| 92 |
+
- "add_block" (requires line_number and new_content)
|
| 93 |
+
- "delete_block" (requires old_content)
|
| 94 |
+
|
| 95 |
+
To create a new file (e.g. a missing ConfigMap), use "edit_file" with empty old_content:
|
| 96 |
{
|
| 97 |
+
"action_type": "edit_file",
|
| 98 |
"reasoning": "Create missing ConfigMap manifest",
|
| 99 |
"edits": [
|
| 100 |
{
|
|
|
|
| 106 |
}
|
| 107 |
|
| 108 |
If you believe all issues are fixed and want to submit, respond with:
|
| 109 |
+
{"action_type": "submit"}
|
| 110 |
|
| 111 |
If you need a hint, respond with:
|
| 112 |
+
{"action_type": "request_hint"}
|
| 113 |
|
| 114 |
Rules:
|
| 115 |
- Match old_content EXACTLY as it appears in the file (whitespace matters)
|
|
|
|
| 228 |
|
| 229 |
def build_action(parsed: Dict[str, Any]) -> Dict[str, Any]:
|
| 230 |
"""Convert parsed LLM response to environment action format."""
|
| 231 |
+
action_type = parsed.get("action_type")
|
| 232 |
+
|
| 233 |
+
# Backwards compatibility and standard aliases
|
| 234 |
+
if parsed.get("action") == "submit" or action_type == "submit":
|
| 235 |
return {"action_type": "submit"}
|
| 236 |
+
if parsed.get("action") == "hint" or action_type == "request_hint":
|
| 237 |
return {"action_type": "request_hint"}
|
| 238 |
|
| 239 |
edits = parsed.get("edits", [])
|
| 240 |
+
if not edits and not action_type:
|
| 241 |
return {"action_type": "submit"}
|
| 242 |
|
| 243 |
+
action_str = action_type if action_type else "edit_file"
|
| 244 |
+
|
| 245 |
return {
|
| 246 |
+
"action_type": action_str,
|
| 247 |
"edits": [
|
| 248 |
{
|
| 249 |
"file_path": e.get("file_path", ""),
|
| 250 |
+
"line_number": e.get("line_number"),
|
| 251 |
"old_content": e.get("old_content", ""),
|
| 252 |
"new_content": e.get("new_content", ""),
|
| 253 |
}
|
|
|
|
| 401 |
print(f"[DEBUG] Environment status: {health.get('status', 'unknown')}", flush=True)
|
| 402 |
except Exception as e:
|
| 403 |
print(f"[DEBUG] Cannot connect to environment at {ENV_URL}: {e}", flush=True)
|
| 404 |
+
print("[DEBUG] Start the server first: python -m uvicorn server.app:app --host 0.0.0.0 --port 7860", flush=True)
|
| 405 |
sys.exit(1)
|
| 406 |
|
| 407 |
client = create_client()
|
server/app.py
CHANGED
|
@@ -234,7 +234,7 @@ async def run_baseline(request: Optional[BaselineRequest] = None):
|
|
| 234 |
|
| 235 |
|
| 236 |
def main():
|
| 237 |
-
uvicorn.run(app, host="0.0.0.0", port=
|
| 238 |
|
| 239 |
|
| 240 |
if __name__ == "__main__":
|
|
|
|
| 234 |
|
| 235 |
|
| 236 |
def main():
|
| 237 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 238 |
|
| 239 |
|
| 240 |
if __name__ == "__main__":
|
server/static/index.html
CHANGED
|
@@ -428,12 +428,12 @@
|
|
| 428 |
</div>
|
| 429 |
<div class="code-block">
|
| 430 |
<span class="comment"># 1. Get a broken K8s deployment with OOMKilled pods</span>
|
| 431 |
-
<span class="cmd">curl</span> -X POST <span class="url">http://localhost:
|
| 432 |
-H <span class="flag">"Content-Type: application/json"</span> \
|
| 433 |
-d '{"task_id": "k8s_pod_failures", "scenario_id": "oom_killed"}'
|
| 434 |
|
| 435 |
<span class="comment"># 2. Bump the memory limit from 64Mi to 256Mi</span>
|
| 436 |
-
<span class="cmd">curl</span> -X POST <span class="url">http://localhost:
|
| 437 |
-H <span class="flag">"Content-Type: application/json"</span> \
|
| 438 |
-d '{"action": {"action_type": "edit_file", "edits": [{"file_path": "k8s/deployment.yaml", "old_content": "memory: \"64Mi\"", "new_content": "memory: \"256Mi\""}]}}'
|
| 439 |
|
|
|
|
| 428 |
</div>
|
| 429 |
<div class="code-block">
|
| 430 |
<span class="comment"># 1. Get a broken K8s deployment with OOMKilled pods</span>
|
| 431 |
+
<span class="cmd">curl</span> -X POST <span class="url">http://localhost:7860/reset</span> \
|
| 432 |
-H <span class="flag">"Content-Type: application/json"</span> \
|
| 433 |
-d '{"task_id": "k8s_pod_failures", "scenario_id": "oom_killed"}'
|
| 434 |
|
| 435 |
<span class="comment"># 2. Bump the memory limit from 64Mi to 256Mi</span>
|
| 436 |
+
<span class="cmd">curl</span> -X POST <span class="url">http://localhost:7860/step</span> \
|
| 437 |
-H <span class="flag">"Content-Type: application/json"</span> \
|
| 438 |
-d '{"action": {"action_type": "edit_file", "edits": [{"file_path": "k8s/deployment.yaml", "old_content": "memory: \"64Mi\"", "new_content": "memory: \"256Mi\""}]}}'
|
| 439 |
|
server/tasks/pipeline_build_deploy.py
CHANGED
|
@@ -128,7 +128,7 @@ class PipelineBuildDeployTask(BaseTask):
|
|
| 128 |
"COPY requirements.txt .\n"
|
| 129 |
"RUN pip install -r requirements.txt\n"
|
| 130 |
"COPY . .\n"
|
| 131 |
-
"EXPOSE
|
| 132 |
'CMD ["python", "app.py"]\n'
|
| 133 |
),
|
| 134 |
},
|
|
@@ -275,7 +275,7 @@ class PipelineBuildDeployTask(BaseTask):
|
|
| 275 |
"WORKDIR /app\n"
|
| 276 |
"COPY . .\n"
|
| 277 |
"RUN echo $APP_VERSION > /app/version.txt\n"
|
| 278 |
-
"EXPOSE
|
| 279 |
'CMD ["python", "app.py"]\n'
|
| 280 |
),
|
| 281 |
},
|
|
@@ -341,7 +341,7 @@ class PipelineBuildDeployTask(BaseTask):
|
|
| 341 |
"COPY requirements.txt .\n"
|
| 342 |
"RUN pip install -r requirements.txt\n"
|
| 343 |
"COPY . .\n"
|
| 344 |
-
"EXPOSE
|
| 345 |
'CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]\n'
|
| 346 |
),
|
| 347 |
},
|
|
|
|
| 128 |
"COPY requirements.txt .\n"
|
| 129 |
"RUN pip install -r requirements.txt\n"
|
| 130 |
"COPY . .\n"
|
| 131 |
+
"EXPOSE 7860\n"
|
| 132 |
'CMD ["python", "app.py"]\n'
|
| 133 |
),
|
| 134 |
},
|
|
|
|
| 275 |
"WORKDIR /app\n"
|
| 276 |
"COPY . .\n"
|
| 277 |
"RUN echo $APP_VERSION > /app/version.txt\n"
|
| 278 |
+
"EXPOSE 7860\n"
|
| 279 |
'CMD ["python", "app.py"]\n'
|
| 280 |
),
|
| 281 |
},
|
|
|
|
| 341 |
"COPY requirements.txt .\n"
|
| 342 |
"RUN pip install -r requirements.txt\n"
|
| 343 |
"COPY . .\n"
|
| 344 |
+
"EXPOSE 7860\n"
|
| 345 |
'CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]\n'
|
| 346 |
),
|
| 347 |
},
|
server/tasks/pipeline_full.py
CHANGED
|
@@ -299,8 +299,8 @@ class PipelineFullTask(BaseTask):
|
|
| 299 |
"COPY requirements.txt .\n"
|
| 300 |
"RUN pip install -r requirements.txt\n"
|
| 301 |
"COPY . .\n"
|
| 302 |
-
"EXPOSE
|
| 303 |
-
'CMD ["gunicorn", "app:app", "-b", "0.0.0.0:
|
| 304 |
),
|
| 305 |
},
|
| 306 |
{
|
|
@@ -330,7 +330,7 @@ class PipelineFullTask(BaseTask):
|
|
| 330 |
" - name: api\n"
|
| 331 |
" image: ghcr.io/myorg/myapp:latest\n"
|
| 332 |
" ports:\n"
|
| 333 |
-
" - containerPort:
|
| 334 |
" resources:\n"
|
| 335 |
" limits:\n"
|
| 336 |
' memory: "64Mi"\n'
|
|
|
|
| 299 |
"COPY requirements.txt .\n"
|
| 300 |
"RUN pip install -r requirements.txt\n"
|
| 301 |
"COPY . .\n"
|
| 302 |
+
"EXPOSE 7860\n"
|
| 303 |
+
'CMD ["gunicorn", "app:app", "-b", "0.0.0.0:7860"]\n'
|
| 304 |
),
|
| 305 |
},
|
| 306 |
{
|
|
|
|
| 330 |
" - name: api\n"
|
| 331 |
" image: ghcr.io/myorg/myapp:latest\n"
|
| 332 |
" ports:\n"
|
| 333 |
+
" - containerPort: 7860\n"
|
| 334 |
" resources:\n"
|
| 335 |
" limits:\n"
|
| 336 |
' memory: "64Mi"\n'
|
server/tasks/task_1_build_errors.py
CHANGED
|
@@ -70,7 +70,7 @@ class DockerfileSyntaxTask(BaseTask):
|
|
| 70 |
"COPY requirements.txt .\n"
|
| 71 |
"RUN pip install -r requirements.txt\n"
|
| 72 |
"COPY . .\n"
|
| 73 |
-
"EXPOSE
|
| 74 |
'CMD ["python", "app.py"]'
|
| 75 |
),
|
| 76 |
},
|
|
|
|
| 70 |
"COPY requirements.txt .\n"
|
| 71 |
"RUN pip install -r requirements.txt\n"
|
| 72 |
"COPY . .\n"
|
| 73 |
+
"EXPOSE 7860\n"
|
| 74 |
'CMD ["python", "app.py"]'
|
| 75 |
),
|
| 76 |
},
|
smoke_test.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
Usage:
|
| 4 |
.\\.venv\\Scripts\\python.exe smoke_test.py
|
| 5 |
-
.\\.venv\\Scripts\\python.exe smoke_test.py --mode live --base-url http://127.0.0.1:
|
| 6 |
|
| 7 |
Modes:
|
| 8 |
- inprocess (default): uses FastAPI TestClient, no running server needed.
|
|
@@ -227,7 +227,7 @@ def run_smoke(client: EndpointClient) -> int:
|
|
| 227 |
def main() -> int:
|
| 228 |
parser = argparse.ArgumentParser(description="Smoke test Cloud-Native DevOps Debug FastAPI server")
|
| 229 |
parser.add_argument("--mode", choices=["inprocess", "live"], default="inprocess")
|
| 230 |
-
parser.add_argument("--base-url", default="http://127.0.0.1:
|
| 231 |
args = parser.parse_args()
|
| 232 |
|
| 233 |
if args.mode == "inprocess":
|
|
|
|
| 2 |
|
| 3 |
Usage:
|
| 4 |
.\\.venv\\Scripts\\python.exe smoke_test.py
|
| 5 |
+
.\\.venv\\Scripts\\python.exe smoke_test.py --mode live --base-url http://127.0.0.1:7860
|
| 6 |
|
| 7 |
Modes:
|
| 8 |
- inprocess (default): uses FastAPI TestClient, no running server needed.
|
|
|
|
| 227 |
def main() -> int:
|
| 228 |
parser = argparse.ArgumentParser(description="Smoke test Cloud-Native DevOps Debug FastAPI server")
|
| 229 |
parser.add_argument("--mode", choices=["inprocess", "live"], default="inprocess")
|
| 230 |
+
parser.add_argument("--base-url", default="http://127.0.0.1:7860")
|
| 231 |
args = parser.parse_args()
|
| 232 |
|
| 233 |
if args.mode == "inprocess":
|