Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- Dockerfile +38 -0
- README.md +120 -10
- __init__.py +27 -0
- __pycache__/__init__.cpython-313.pyc +0 -0
- __pycache__/main.cpython-313.pyc +0 -0
- __pycache__/models.cpython-313.pyc +0 -0
- client.py +56 -0
- env/__init__.py +6 -0
- env/__pycache__/__init__.cpython-313.pyc +0 -0
- env/__pycache__/core.cpython-313.pyc +0 -0
- env/core.py +268 -0
- inference.py +316 -0
- inference_results.json +39 -0
- main.py +47 -0
- models.py +49 -0
- openenv.yaml +8 -0
- openenv_cloud_ops_env.egg-info/PKG-INFO +10 -0
- openenv_cloud_ops_env.egg-info/SOURCES.txt +7 -0
- openenv_cloud_ops_env.egg-info/dependency_links.txt +1 -0
- openenv_cloud_ops_env.egg-info/entry_points.txt +2 -0
- openenv_cloud_ops_env.egg-info/requires.txt +5 -0
- openenv_cloud_ops_env.egg-info/top_level.txt +1 -0
- pyproject.toml +23 -0
- requirements.txt +5 -0
- server/__init__.py +5 -0
- server/app.py +24 -0
- test_env.py +29 -0
- uv.lock +0 -0
Dockerfile
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CloudOps Optimizer Environment Dockerfile
|
| 2 |
+
|
| 3 |
+
ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
|
| 4 |
+
FROM ${BASE_IMAGE} AS builder
|
| 5 |
+
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
COPY . /app/env/
|
| 9 |
+
|
| 10 |
+
WORKDIR /app/env
|
| 11 |
+
|
| 12 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 13 |
+
git \
|
| 14 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 15 |
+
|
| 16 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 17 |
+
uv sync --no-install-project --no-editable
|
| 18 |
+
|
| 19 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 20 |
+
uv sync --no-editable
|
| 21 |
+
|
| 22 |
+
FROM ${BASE_IMAGE}
|
| 23 |
+
|
| 24 |
+
WORKDIR /app
|
| 25 |
+
|
| 26 |
+
COPY --from=builder /app/env/.venv /app/.venv
|
| 27 |
+
COPY --from=builder /app/env /app/env
|
| 28 |
+
|
| 29 |
+
ENV PATH="/app/.venv/bin:$PATH"
|
| 30 |
+
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 31 |
+
ENV PORT=7860
|
| 32 |
+
|
| 33 |
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
| 34 |
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/health')" || exit 1
|
| 35 |
+
|
| 36 |
+
EXPOSE 7860
|
| 37 |
+
|
| 38 |
+
CMD ["python", "main.py"]
|
README.md
CHANGED
|
@@ -1,10 +1,120 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CloudOps Optimizer Environment
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
**CloudOps Optimizer** is a real-world simulation of cloud infrastructure cost and performance optimization. The agent acts as a Cloud Site Reliability Engineer (SRE) optimizing a fleet of virtual cloud instances to meet Service Level Agreement (SLA) requirements while minimizing monthly costs.
|
| 6 |
+
|
| 7 |
+
## Why This Matters
|
| 8 |
+
|
| 9 |
+
- **Real-world utility**: Every company using AWS/Azure/GCP struggles with "Cloud Waste". Training agents to right-size instances is a multi-million dollar problem.
|
| 10 |
+
- **Not a toy**: Unlike chatbots or simple games, this environment requires quantitative reasoning about cost vs performance tradeoffs.
|
| 11 |
+
|
| 12 |
+
## Environment Description
|
| 13 |
+
|
| 14 |
+
### Observation Space
|
| 15 |
+
|
| 16 |
+
The agent receives structured data including:
|
| 17 |
+
- **Inventory**: List of cloud resources (id, type, cpu_usage, mem_usage, monthly_cost)
|
| 18 |
+
- **Metrics**: Real-time performance (avg_latency_ms, error_rate, throughput_rps)
|
| 19 |
+
- **SLA**: Target constraints (max_latency_ms, max_budget, min_uptime_pct)
|
| 20 |
+
- **Task Info**: task_id, task_name, difficulty, current step
|
| 21 |
+
|
| 22 |
+
### Action Space
|
| 23 |
+
|
| 24 |
+
The agent sends text commands in format: `change [resource_id] to [instance_type]`
|
| 25 |
+
|
| 26 |
+
Available instance types:
|
| 27 |
+
- `t3.nano`: $3.60/mo, capacity 1.0
|
| 28 |
+
- `t3.small`: $11.50/mo, capacity 2.0
|
| 29 |
+
- `t3.medium`: $23.00/mo, capacity 4.0
|
| 30 |
+
- `m5.large`: $70.00/mo, capacity 8.0
|
| 31 |
+
- `m5.xlarge`: $140.00/mo, capacity 16.0
|
| 32 |
+
|
| 33 |
+
## Tasks & Grading
|
| 34 |
+
|
| 35 |
+
| Task | Difficulty | Description | Grading |
|
| 36 |
+
|------|------------|-------------|---------|
|
| 37 |
+
| Right-Sizing | Easy | Reduce an overpriced server without breaking SLA | Score = reward value (0-1) |
|
| 38 |
+
| Latency Fix | Medium | Resolve performance bottleneck under budget | Score = reward value (0-1) |
|
| 39 |
+
| Balance Optimization | Hard | Optimize multi-server cluster with tight constraints | Score = reward value (0-1) |
|
| 40 |
+
|
| 41 |
+
### Reward Function
|
| 42 |
+
|
| 43 |
+
The reward provides **continuous signals** over the trajectory:
|
| 44 |
+
|
| 45 |
+
```
|
| 46 |
+
R = cost_reward + performance_reward
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
Where:
|
| 50 |
+
- **Cost Reward (0-0.5)**: Higher as cost approaches budget
|
| 51 |
+
- **Performance Reward (0-0.5)**: Higher as latency stays under SLA
|
| 52 |
+
|
| 53 |
+
**Partial Progress**: Agent receives incremental rewards for each improvement.
|
| 54 |
+
**Penalties**: System crash (CPU > 110%) results in 0 reward and episode end.
|
| 55 |
+
|
| 56 |
+
## Setup & Usage
|
| 57 |
+
|
| 58 |
+
### Prerequisites
|
| 59 |
+
- Python 3.10+
|
| 60 |
+
- OpenAI API key (HF_TOKEN)
|
| 61 |
+
|
| 62 |
+
### Local Installation
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
# Install dependencies
|
| 66 |
+
pip install -e .
|
| 67 |
+
|
| 68 |
+
# Run baseline inference
|
| 69 |
+
export HF_TOKEN=your_huggingface_token
|
| 70 |
+
python inference.py
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### Docker Execution
|
| 74 |
+
|
| 75 |
+
```bash
|
| 76 |
+
docker build -t cloud-ops-env .
|
| 77 |
+
docker run -p 8000:8000 cloud-ops-env
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### API Endpoints
|
| 81 |
+
|
| 82 |
+
- `POST /reset` - Reset environment with optional task_id
|
| 83 |
+
- `POST /step` - Execute action
|
| 84 |
+
- `GET /state` - Get current state
|
| 85 |
+
- `GET /health` - Health check
|
| 86 |
+
|
| 87 |
+
## Baseline Results
|
| 88 |
+
|
| 89 |
+
Model: Qwen/Qwen2.5-72B-Instruct
|
| 90 |
+
|
| 91 |
+
| Task | Score | Steps |
|
| 92 |
+
|------|-------|-------|
|
| 93 |
+
| Right-Sizing (Easy) | 0.125 | 1 |
|
| 94 |
+
| Latency Fix (Medium) | 0.000 | 1 |
|
| 95 |
+
| Balance (Hard) | 0.000 | 1 |
|
| 96 |
+
|
| 97 |
+
**Average: 0.042**
|
| 98 |
+
|
| 99 |
+
Note: Baseline scores indicate the model needs better prompting to handle the optimization tradeoffs. The environment correctly penalizes overshooting budget (easy) and undersizing (medium/hard causing crashes).
|
| 100 |
+
|
| 101 |
+
## Files
|
| 102 |
+
|
| 103 |
+
- `openenv.yaml` - OpenEnv specification
|
| 104 |
+
- `models.py` - Pydantic models (Observation, Action, Reward)
|
| 105 |
+
- `env/core.py` - Environment logic with state machine
|
| 106 |
+
- `server/app.py` - FastAPI server
|
| 107 |
+
- `inference.py` - Baseline inference script
|
| 108 |
+
- `Dockerfile` - Container build
|
| 109 |
+
|
| 110 |
+
## Spec Compliance
|
| 111 |
+
|
| 112 |
+
- [x] Typed Pydantic models
|
| 113 |
+
- [x] reset() returns Observation
|
| 114 |
+
- [x] step(action) returns (Observation, Reward, done, info)
|
| 115 |
+
- [x] state() returns current state
|
| 116 |
+
- [x] openenv.yaml with metadata
|
| 117 |
+
- [x] openenv validate passes
|
| 118 |
+
- [x] 3 tasks with deterministic graders (0.0-1.0)
|
| 119 |
+
- [x] Partial reward signals
|
| 120 |
+
- [x] Strict [START]/[STEP]/[END] log format in inference.py
|
__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CloudOps Optimizer Environment for OpenEnv.
|
| 2 |
+
|
| 3 |
+
A real-world simulation of cloud infrastructure cost and performance optimization.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from models import (
|
| 7 |
+
ObservationModel as Observation,
|
| 8 |
+
ActionModel as Action,
|
| 9 |
+
Reward as Reward,
|
| 10 |
+
Resource,
|
| 11 |
+
Metrics,
|
| 12 |
+
SLA,
|
| 13 |
+
)
|
| 14 |
+
from env.core import CloudOpsEnvironment, TASKS
|
| 15 |
+
|
| 16 |
+
__version__ = "1.0.0"
|
| 17 |
+
|
| 18 |
+
__all__ = [
|
| 19 |
+
"Observation",
|
| 20 |
+
"Action",
|
| 21 |
+
"Reward",
|
| 22 |
+
"Resource",
|
| 23 |
+
"Metrics",
|
| 24 |
+
"SLA",
|
| 25 |
+
"CloudOpsEnvironment",
|
| 26 |
+
"TASKS",
|
| 27 |
+
]
|
__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (642 Bytes). View file
|
|
|
__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (2.7 kB). View file
|
|
|
__pycache__/models.cpython-313.pyc
ADDED
|
Binary file (3.74 kB). View file
|
|
|
client.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CloudOps Optimizer Environment Client.
|
| 2 |
+
|
| 3 |
+
Provides the async EnvClient for connecting to the server.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from typing import Any, Dict, Optional
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from openenv.core.env_client import EnvClient
|
| 10 |
+
from openenv.core.client_types import StepResult
|
| 11 |
+
except ImportError:
|
| 12 |
+
from openenv.core.env_client import EnvClient
|
| 13 |
+
from openenv.core.client_types import StepResult
|
| 14 |
+
|
| 15 |
+
from models import Observation as ObsModel, Action as ActModel
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class CloudOpsClient(EnvClient[ActModel, ObsModel, Dict[str, Any]]):
|
| 19 |
+
"""Async client for the CloudOps Optimizer Environment."""
|
| 20 |
+
|
| 21 |
+
def _step_payload(self, action: ActModel) -> Dict[str, Any]:
|
| 22 |
+
return action.model_dump()
|
| 23 |
+
|
| 24 |
+
def _parse_result(self, payload: Dict[str, Any]) -> "StepResult[ObsModel]":
|
| 25 |
+
obs_data = payload.get("observation", payload)
|
| 26 |
+
reward = payload.get("reward", 0.0)
|
| 27 |
+
done = payload.get("done", False)
|
| 28 |
+
info = payload.get("info", {})
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
observation = ObsModel.model_validate(obs_data)
|
| 32 |
+
except Exception:
|
| 33 |
+
observation = ObsModel(
|
| 34 |
+
inventory=[],
|
| 35 |
+
metrics=payload.get("metrics", {"avg_latency_ms": 0, "error_rate": 0, "throughput_rps": 0}),
|
| 36 |
+
sla=payload.get("sla", {"max_latency_ms": 0, "max_budget": 0, "min_uptime_pct": 0}),
|
| 37 |
+
echoed_message="Error parsing observation",
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
return StepResult(
|
| 41 |
+
observation=observation,
|
| 42 |
+
reward=reward,
|
| 43 |
+
done=done,
|
| 44 |
+
info=info,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
def _parse_state(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 48 |
+
return payload
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def get_client(base_url: str = "http://localhost:7860") -> CloudOpsClient:
|
| 52 |
+
"""Create a CloudOps client."""
|
| 53 |
+
return CloudOpsClient(base_url=base_url)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
__all__ = ["CloudOpsClient", "get_client"]
|
env/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CloudOps Environment package."""
|
| 2 |
+
|
| 3 |
+
from env.core import CloudOpsEnvironment
|
| 4 |
+
from models import Observation, Action, Reward, Resource, Metrics, SLA
|
| 5 |
+
|
| 6 |
+
__all__ = ["CloudOpsEnvironment", "Observation", "Action", "Reward", "Resource", "Metrics", "SLA"]
|
env/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (428 Bytes). View file
|
|
|
env/__pycache__/core.cpython-313.pyc
ADDED
|
Binary file (12.5 kB). View file
|
|
|
env/core.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import random
|
| 3 |
+
import re
|
| 4 |
+
from typing import Any, Dict, Optional, Tuple
|
| 5 |
+
from uuid import uuid4
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
|
| 8 |
+
from models import (
|
| 9 |
+
Observation as ObsModel,
|
| 10 |
+
Action as ActModel,
|
| 11 |
+
Reward as RewModel,
|
| 12 |
+
Resource,
|
| 13 |
+
Metrics,
|
| 14 |
+
SLA,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
INSTANCE_DATA = {
|
| 19 |
+
"t3.nano": {"cost": 3.6, "capacity": 1.0},
|
| 20 |
+
"t3.small": {"cost": 11.5, "capacity": 2.0},
|
| 21 |
+
"t3.medium": {"cost": 23.0, "capacity": 4.0},
|
| 22 |
+
"m5.large": {"cost": 70.0, "capacity": 8.0},
|
| 23 |
+
"m5.xlarge": {"cost": 140.0,"capacity": 16.0},
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@dataclass
|
| 28 |
+
class TaskConfig:
|
| 29 |
+
task_id: str
|
| 30 |
+
name: str
|
| 31 |
+
difficulty: str
|
| 32 |
+
description: str
|
| 33 |
+
initial_resources: list
|
| 34 |
+
sla: dict
|
| 35 |
+
load: float
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
TASKS = {
|
| 39 |
+
"easy": TaskConfig(
|
| 40 |
+
task_id="easy_right_sizing",
|
| 41 |
+
name="Right-Sizing",
|
| 42 |
+
difficulty="easy",
|
| 43 |
+
description="Reduce an overpriced server without breaking the SLA",
|
| 44 |
+
initial_resources=[
|
| 45 |
+
{"id": "srv-1", "type": "m5.xlarge", "cpu_usage": 2.0, "mem_usage": 2.0, "monthly_cost": 140.0}
|
| 46 |
+
],
|
| 47 |
+
sla={"max_latency_ms": 200.0, "max_budget": 30.0, "min_uptime_pct": 99.0},
|
| 48 |
+
load=2.0
|
| 49 |
+
),
|
| 50 |
+
"medium": TaskConfig(
|
| 51 |
+
task_id="medium_latency_fix",
|
| 52 |
+
name="Latency Fix",
|
| 53 |
+
difficulty="medium",
|
| 54 |
+
description="Resolve performance bottleneck while staying under budget",
|
| 55 |
+
initial_resources=[
|
| 56 |
+
{"id": "srv-1", "type": "t3.nano", "cpu_usage": 98.0, "mem_usage": 90.0, "monthly_cost": 3.6}
|
| 57 |
+
],
|
| 58 |
+
sla={"max_latency_ms": 100.0, "max_budget": 60.0, "min_uptime_pct": 99.9},
|
| 59 |
+
load=12.0
|
| 60 |
+
),
|
| 61 |
+
"hard": TaskConfig(
|
| 62 |
+
task_id="hard_balance",
|
| 63 |
+
name="Balance Optimization",
|
| 64 |
+
difficulty="hard",
|
| 65 |
+
description="Optimize a mixed cluster under tight budget constraints",
|
| 66 |
+
initial_resources=[
|
| 67 |
+
{"id": "srv-1", "type": "m5.large", "cpu_usage": 40.0, "mem_usage": 30.0, "monthly_cost": 70.0},
|
| 68 |
+
{"id": "srv-2", "type": "t3.nano", "cpu_usage": 90.0, "mem_usage": 80.0, "monthly_cost": 3.6}
|
| 69 |
+
],
|
| 70 |
+
sla={"max_latency_ms": 150.0, "max_budget": 35.0, "min_uptime_pct": 99.9},
|
| 71 |
+
load=25.0
|
| 72 |
+
),
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@dataclass
|
| 77 |
+
class EpisodeState:
|
| 78 |
+
task_config: TaskConfig
|
| 79 |
+
resources: list
|
| 80 |
+
current_load: float
|
| 81 |
+
initial_cost: float
|
| 82 |
+
initial_latency: float
|
| 83 |
+
steps: int = 0
|
| 84 |
+
crashed: bool = False
|
| 85 |
+
episode_id: str = field(default_factory=lambda: str(uuid4()))
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class CloudOpsEnvironment:
|
| 89 |
+
"""Cloud Infrastructure Optimization Environment.
|
| 90 |
+
|
| 91 |
+
The agent acts as a Cloud SRE optimizing cost and performance.
|
| 92 |
+
"""
|
| 93 |
+
|
| 94 |
+
def __init__(self, max_steps: int = 12):
|
| 95 |
+
self._max_steps = max_steps
|
| 96 |
+
self._ep: Optional[EpisodeState] = None
|
| 97 |
+
|
| 98 |
+
def reset(
|
| 99 |
+
self,
|
| 100 |
+
seed: Optional[int] = None,
|
| 101 |
+
episode_id: Optional[str] = None,
|
| 102 |
+
task_id: Optional[str] = None,
|
| 103 |
+
**kwargs: Any,
|
| 104 |
+
) -> ObsModel:
|
| 105 |
+
if seed is not None:
|
| 106 |
+
random.seed(seed)
|
| 107 |
+
|
| 108 |
+
task_key = task_id or random.choice(["easy", "medium", "hard"])
|
| 109 |
+
if task_key not in TASKS:
|
| 110 |
+
task_key = "easy"
|
| 111 |
+
|
| 112 |
+
task = TASKS[task_key]
|
| 113 |
+
|
| 114 |
+
resources = [
|
| 115 |
+
Resource(**r) for r in task.initial_resources
|
| 116 |
+
]
|
| 117 |
+
|
| 118 |
+
initial_cost = sum(r.monthly_cost for r in resources)
|
| 119 |
+
initial_latency, _, _ = self._calculate_metrics(task.load, resources)
|
| 120 |
+
|
| 121 |
+
self._ep = EpisodeState(
|
| 122 |
+
task_config=task,
|
| 123 |
+
resources=resources,
|
| 124 |
+
current_load=task.load,
|
| 125 |
+
initial_cost=initial_cost,
|
| 126 |
+
initial_latency=initial_latency,
|
| 127 |
+
steps=0,
|
| 128 |
+
crashed=False,
|
| 129 |
+
episode_id=episode_id or str(uuid4()),
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
return self._build_observation("Environment ready. Analyze and optimize.")
|
| 133 |
+
|
| 134 |
+
def step(self, action: ActModel, **kwargs: Any) -> Tuple[ObsModel, RewModel, bool, Dict]:
|
| 135 |
+
if self._ep is None:
|
| 136 |
+
return self._error_obs("Environment not reset")
|
| 137 |
+
|
| 138 |
+
self._ep.steps += 1
|
| 139 |
+
msg = action.message.lower()
|
| 140 |
+
|
| 141 |
+
message = self._parse_and_execute(msg)
|
| 142 |
+
latency, error_rate, utilization = self._calculate_metrics(
|
| 143 |
+
self._ep.current_load,
|
| 144 |
+
self._ep.resources
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
if utilization > 1.1:
|
| 148 |
+
self._ep.crashed = True
|
| 149 |
+
obs = self._build_observation("SYSTEM CRASH: Resource exhaustion!")
|
| 150 |
+
reward = RewModel(value=0.0, reason="System crashed due to resource exhaustion")
|
| 151 |
+
return obs, reward, True, {"reason": "crash"}
|
| 152 |
+
|
| 153 |
+
reward = self._calculate_reward(latency, error_rate)
|
| 154 |
+
|
| 155 |
+
done = (
|
| 156 |
+
reward.value >= 0.98 or
|
| 157 |
+
self._ep.steps >= self._max_steps
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
obs = self._build_observation(message)
|
| 161 |
+
return obs, reward, done, {}
|
| 162 |
+
|
| 163 |
+
def _parse_and_execute(self, msg: str) -> str:
|
| 164 |
+
match = re.search(r"change\s+([a-z0-9-]+)\s+to\s+([a-z0-9.]+)", msg)
|
| 165 |
+
if match:
|
| 166 |
+
res_id, new_type = match.groups()
|
| 167 |
+
if new_type not in INSTANCE_DATA:
|
| 168 |
+
return f"Error: Unknown instance type '{new_type}'. Available: {', '.join(INSTANCE_DATA.keys())}"
|
| 169 |
+
|
| 170 |
+
for r in self._ep.resources:
|
| 171 |
+
if r.id == res_id:
|
| 172 |
+
r.type = new_type
|
| 173 |
+
r.monthly_cost = INSTANCE_DATA[new_type]["cost"]
|
| 174 |
+
return f"Changed {res_id} to {new_type}"
|
| 175 |
+
|
| 176 |
+
return f"Error: Resource '{res_id}' not found"
|
| 177 |
+
|
| 178 |
+
if "resize" in msg or "scale" in msg or "upgrade" in msg or "downgrade" in msg:
|
| 179 |
+
return "Use format: 'change [resource_id] to [instance_type]'"
|
| 180 |
+
|
| 181 |
+
return "Command not recognized. Use 'change [resource_id] to [instance_type]'"
|
| 182 |
+
|
| 183 |
+
def _calculate_metrics(self, load: float, resources: list) -> Tuple[float, float, float]:
|
| 184 |
+
total_cap = sum(INSTANCE_DATA[r.type]["capacity"] for r in resources)
|
| 185 |
+
utilization = load / (total_cap + 1e-6)
|
| 186 |
+
|
| 187 |
+
latency = 50 * (1 + math.exp(utilization * 2 - 2))
|
| 188 |
+
error_rate = 0.0 if utilization < 0.9 else (utilization - 0.9) * 2.0
|
| 189 |
+
|
| 190 |
+
return latency, error_rate, utilization
|
| 191 |
+
|
| 192 |
+
def _calculate_reward(self, latency: float, error_rate: float) -> RewModel:
|
| 193 |
+
total_cost = sum(r.monthly_cost for r in self._ep.resources)
|
| 194 |
+
budget = self._ep.task_config.sla["max_latency_ms"]
|
| 195 |
+
|
| 196 |
+
cost_ratio = total_cost / budget
|
| 197 |
+
cost_reward = 0.5 * (1.0 / (1.0 + max(0, cost_ratio - 1)))
|
| 198 |
+
|
| 199 |
+
lat_ratio = latency / budget
|
| 200 |
+
perf_reward = 0.5 * (1.0 / (1.0 + max(0, lat_ratio - 1)))
|
| 201 |
+
|
| 202 |
+
total_reward = cost_reward + perf_reward
|
| 203 |
+
|
| 204 |
+
initial_latency = self._ep.initial_latency
|
| 205 |
+
initial_cost = self._ep.initial_cost
|
| 206 |
+
cost_change = ((total_cost - initial_cost) / initial_cost) * 100 if initial_cost > 0 else 0
|
| 207 |
+
lat_change = ((latency - initial_latency) / initial_latency) * 100 if initial_latency > 0 else 0
|
| 208 |
+
|
| 209 |
+
return RewModel(
|
| 210 |
+
value=min(1.0, max(0.0, total_reward)),
|
| 211 |
+
reason=f"Cost: ${total_cost:.1f}/mo, Latency: {latency:.1f}ms",
|
| 212 |
+
cost_change_pct=cost_change,
|
| 213 |
+
latency_change_pct=lat_change,
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
def _build_observation(self, message: str) -> ObsModel:
|
| 217 |
+
if self._ep is None:
|
| 218 |
+
return self._error_obs()
|
| 219 |
+
|
| 220 |
+
latency, error_rate, _ = self._calculate_metrics(
|
| 221 |
+
self._ep.current_load,
|
| 222 |
+
self._ep.resources
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
for r in self._ep.resources:
|
| 226 |
+
r.cpu_usage = min(100.0, self._ep.current_load / INSTANCE_DATA[r.type]["capacity"] * 100)
|
| 227 |
+
r.mem_usage = min(100.0, r.cpu_usage * 0.9)
|
| 228 |
+
|
| 229 |
+
metrics = Metrics(
|
| 230 |
+
avg_latency_ms=latency,
|
| 231 |
+
error_rate=error_rate,
|
| 232 |
+
throughput_rps=100.0
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
sla = SLA(**self._ep.task_config.sla)
|
| 236 |
+
|
| 237 |
+
return ObsModel(
|
| 238 |
+
inventory=self._ep.resources,
|
| 239 |
+
metrics=metrics,
|
| 240 |
+
sla=sla,
|
| 241 |
+
echoed_message=message,
|
| 242 |
+
task_id=self._ep.task_config.task_id,
|
| 243 |
+
task_name=self._ep.task_config.name,
|
| 244 |
+
difficulty=self._ep.task_config.difficulty,
|
| 245 |
+
step=self._ep.steps,
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
def _error_obs(self, message: str = "Error: Environment not initialized") -> ObsModel:
|
| 249 |
+
return ObsModel(
|
| 250 |
+
inventory=[],
|
| 251 |
+
metrics=Metrics(avg_latency_ms=0, error_rate=0, throughput_rps=0),
|
| 252 |
+
sla=SLA(max_latency_ms=0, max_budget=0, min_uptime_pct=0),
|
| 253 |
+
echoed_message=message,
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
@property
|
| 257 |
+
def state(self) -> Dict[str, Any]:
|
| 258 |
+
if self._ep is None:
|
| 259 |
+
return {}
|
| 260 |
+
return {
|
| 261 |
+
"episode_id": self._ep.episode_id,
|
| 262 |
+
"task_id": self._ep.task_config.task_id,
|
| 263 |
+
"steps": self._ep.steps,
|
| 264 |
+
"crashed": self._ep.crashed,
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
Environment = CloudOpsEnvironment
|
inference.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Baseline Inference Script for CloudOps Optimizer Environment.
|
| 4 |
+
|
| 5 |
+
Uses OpenAI Client + HTTP calls to the server to run a model against the environment.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
python inference.py
|
| 9 |
+
|
| 10 |
+
Environment Variables:
|
| 11 |
+
API_BASE_URL: The API endpoint (default: https://router.huggingface.co/v1)
|
| 12 |
+
MODEL_NAME: The model identifier (default: Qwen/Qwen2.5-72B-Instruct)
|
| 13 |
+
HF_TOKEN: Your Hugging Face / API key (required)
|
| 14 |
+
SERVER_URL: The environment server URL (default: http://localhost:7860)
|
| 15 |
+
|
| 16 |
+
Expected format for STDOUT:
|
| 17 |
+
[START] task=<task_name> env=<benchmark> model=<model_name>
|
| 18 |
+
[STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
|
| 19 |
+
[END] success=<true|false> steps=<n> score=<score> rewards=<r1,r2,...,rn>
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import json
|
| 23 |
+
import os
|
| 24 |
+
import re
|
| 25 |
+
import textwrap
|
| 26 |
+
import time
|
| 27 |
+
import requests
|
| 28 |
+
from typing import List, Optional
|
| 29 |
+
|
| 30 |
+
from openai import OpenAI
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
|
| 34 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
|
| 35 |
+
HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_TOKEN")
|
| 36 |
+
SERVER_URL = os.getenv("SERVER_URL", "http://localhost:7860")
|
| 37 |
+
|
| 38 |
+
MAX_STEPS = 8
|
| 39 |
+
MAX_TOKENS = 256
|
| 40 |
+
TEMPERATURE = 0.7
|
| 41 |
+
SUCCESS_SCORE_THRESHOLD = 0.5
|
| 42 |
+
BENCHMARK = "cloud_ops_env"
|
| 43 |
+
|
| 44 |
+
SYSTEM_PROMPT = textwrap.dedent(
|
| 45 |
+
"""
|
| 46 |
+
You are an expert Cloud SRE (Site Reliability Engineer). Your goal is to optimize cloud infrastructure
|
| 47 |
+
to meet the SLA requirements while minimizing costs.
|
| 48 |
+
|
| 49 |
+
Available instance types (cost per month, capacity):
|
| 50 |
+
- t3.nano: $3.60, capacity 1.0
|
| 51 |
+
- t3.small: $11.50, capacity 2.0
|
| 52 |
+
- t3.medium: $23.00, capacity 4.0
|
| 53 |
+
- m5.large: $70.00, capacity 8.0
|
| 54 |
+
- m5.xlarge: $140.00, capacity 16.0
|
| 55 |
+
|
| 56 |
+
Command format: "change [resource_id] to [instance_type]"
|
| 57 |
+
Example: "change srv-1 to t3.small"
|
| 58 |
+
|
| 59 |
+
You must output ONLY the command, nothing else."""
|
| 60 |
+
).strip()
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def log_start(task: str, env: str, model: str) -> None:
|
| 64 |
+
print(f"[START] task={task} env={env} model={model}", flush=True)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
|
| 68 |
+
error_val = error if error else "null"
|
| 69 |
+
done_val = str(done).lower()
|
| 70 |
+
print(
|
| 71 |
+
f"[STEP] step={step} action={action} reward={reward:.2f} done={done_val} error={error_val}",
|
| 72 |
+
flush=True,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
|
| 77 |
+
rewards_str = ",".join(f"{r:.2f}" for r in rewards)
|
| 78 |
+
print(f"[END] success={str(success).lower()} steps={steps} score={score:.3f} rewards={rewards_str}", flush=True)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def reset_env(task: str) -> dict:
|
| 82 |
+
"""Reset the environment via HTTP."""
|
| 83 |
+
resp = requests.get(f"{SERVER_URL}/reset", params={"task": task})
|
| 84 |
+
resp.raise_for_status()
|
| 85 |
+
return resp.json()
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def step_env(message: str) -> dict:
|
| 89 |
+
"""Send action to environment via HTTP."""
|
| 90 |
+
resp = requests.post(f"{SERVER_URL}/step", json={"message": message})
|
| 91 |
+
resp.raise_for_status()
|
| 92 |
+
return resp.json()
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def build_user_prompt(obs_data: dict) -> str:
|
| 96 |
+
inventory = obs_data.get("inventory", [])
|
| 97 |
+
metrics = obs_data.get("metrics", {})
|
| 98 |
+
sla = obs_data.get("sla", {})
|
| 99 |
+
|
| 100 |
+
inv_str = "\n".join([
|
| 101 |
+
f" {r['id']}: {r['type']} - ${r['monthly_cost']}/mo, CPU: {r['cpu_usage']:.1f}%"
|
| 102 |
+
for r in inventory
|
| 103 |
+
])
|
| 104 |
+
|
| 105 |
+
prompt = f"""Current Infrastructure:
|
| 106 |
+
{inv_str}
|
| 107 |
+
|
| 108 |
+
Metrics:
|
| 109 |
+
- Latency: {metrics.get('avg_latency_ms', 0):.1f}ms
|
| 110 |
+
- Error Rate: {metrics.get('error_rate', 0):.3f}
|
| 111 |
+
|
| 112 |
+
SLA Requirements:
|
| 113 |
+
- Max Latency: {sla.get('max_latency_ms', 0)}ms
|
| 114 |
+
- Max Budget: ${sla.get('max_budget', 0)}/mo
|
| 115 |
+
|
| 116 |
+
Task: {obs_data.get('task_name', 'Optimize')} ({obs_data.get('difficulty', 'easy')})
|
| 117 |
+
|
| 118 |
+
Provide your next command:"""
|
| 119 |
+
|
| 120 |
+
return prompt
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def call_model(client: OpenAI, user_prompt: str, history: List[dict]) -> str:
|
| 124 |
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 125 |
+
messages.extend(history)
|
| 126 |
+
messages.append({"role": "user", "content": user_prompt})
|
| 127 |
+
|
| 128 |
+
try:
|
| 129 |
+
completion = client.chat.completions.create(
|
| 130 |
+
model=MODEL_NAME,
|
| 131 |
+
messages=messages,
|
| 132 |
+
temperature=TEMPERATURE,
|
| 133 |
+
max_tokens=MAX_TOKENS,
|
| 134 |
+
stream=False,
|
| 135 |
+
)
|
| 136 |
+
text = (completion.choices[0].message.content or "").strip()
|
| 137 |
+
|
| 138 |
+
# Extract just the command if model adds explanation
|
| 139 |
+
lines = text.split('\n')
|
| 140 |
+
for line in lines:
|
| 141 |
+
line = line.strip()
|
| 142 |
+
if line.startswith('change '):
|
| 143 |
+
return line
|
| 144 |
+
return text if text else "change srv-1 to t3.small"
|
| 145 |
+
except Exception as exc:
|
| 146 |
+
print(f"[DEBUG] Model request failed: {exc}", flush=True)
|
| 147 |
+
return "change srv-1 to t3.small"
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
TASKS = {
|
| 151 |
+
"easy": {"task_id": "easy_right_sizing", "name": "Right-Sizing", "difficulty": "easy"},
|
| 152 |
+
"medium": {"task_id": "medium_latency_fix", "name": "Latency Fix", "difficulty": "medium"},
|
| 153 |
+
"hard": {"task_id": "hard_balance", "name": "Balance Optimization", "difficulty": "hard"},
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def run_task(client: OpenAI, task_key: str, verbose: bool = False) -> dict:
|
| 158 |
+
"""Run inference on a single task via HTTP."""
|
| 159 |
+
task = TASKS[task_key]
|
| 160 |
+
task_name = task["name"]
|
| 161 |
+
|
| 162 |
+
history: List[dict] = []
|
| 163 |
+
rewards: List[float] = []
|
| 164 |
+
steps_taken = 0
|
| 165 |
+
score = 0.0
|
| 166 |
+
success = False
|
| 167 |
+
error_msg = None
|
| 168 |
+
|
| 169 |
+
log_start(task=task_name, env=BENCHMARK, model=MODEL_NAME)
|
| 170 |
+
|
| 171 |
+
try:
|
| 172 |
+
result = reset_env(task_key)
|
| 173 |
+
obs_data = result.get("observation", {})
|
| 174 |
+
|
| 175 |
+
done = result.get("done", False)
|
| 176 |
+
|
| 177 |
+
for step in range(1, MAX_STEPS + 1):
|
| 178 |
+
if done:
|
| 179 |
+
break
|
| 180 |
+
|
| 181 |
+
user_prompt = build_user_prompt(obs_data)
|
| 182 |
+
response_text = call_model(client, user_prompt, history)
|
| 183 |
+
history.append({"role": "assistant", "content": response_text})
|
| 184 |
+
|
| 185 |
+
action_str = response_text[:50] + "..." if len(response_text) > 50 else response_text
|
| 186 |
+
|
| 187 |
+
try:
|
| 188 |
+
result = step_env(response_text)
|
| 189 |
+
|
| 190 |
+
reward = result.get("reward", 0.0)
|
| 191 |
+
done = result.get("done", False)
|
| 192 |
+
error_msg = None
|
| 193 |
+
obs_data = result.get("observation", {})
|
| 194 |
+
|
| 195 |
+
info = result.get("info", {})
|
| 196 |
+
if info.get("reason") == "crash":
|
| 197 |
+
done = True
|
| 198 |
+
reward = 0.0
|
| 199 |
+
error_msg = "system_crash"
|
| 200 |
+
|
| 201 |
+
except Exception as exc:
|
| 202 |
+
error_msg = str(exc)
|
| 203 |
+
reward = 0.0
|
| 204 |
+
done = True
|
| 205 |
+
obs_data = {}
|
| 206 |
+
|
| 207 |
+
rewards.append(reward)
|
| 208 |
+
steps_taken = step
|
| 209 |
+
|
| 210 |
+
log_step(step=step, action=action_str, reward=reward, done=done, error=error_msg)
|
| 211 |
+
|
| 212 |
+
if done:
|
| 213 |
+
break
|
| 214 |
+
|
| 215 |
+
max_reward = MAX_STEPS * 1.0
|
| 216 |
+
score = sum(rewards) / max_reward if max_reward > 0 else 0.0
|
| 217 |
+
score = min(max(score, 0.0), 1.0)
|
| 218 |
+
success = score >= SUCCESS_SCORE_THRESHOLD
|
| 219 |
+
|
| 220 |
+
except Exception as exc:
|
| 221 |
+
error_msg = str(exc)
|
| 222 |
+
print(f"[DEBUG] Task execution error: {exc}", flush=True)
|
| 223 |
+
finally:
|
| 224 |
+
log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
|
| 225 |
+
|
| 226 |
+
return {
|
| 227 |
+
"task_id": task["task_id"],
|
| 228 |
+
"task_name": task_name,
|
| 229 |
+
"score": score,
|
| 230 |
+
"success": success,
|
| 231 |
+
"steps": steps_taken,
|
| 232 |
+
"rewards": rewards,
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def main():
|
| 237 |
+
print("=" * 60)
|
| 238 |
+
print("CloudOps Optimizer — Baseline Inference")
|
| 239 |
+
print("=" * 60)
|
| 240 |
+
print(f"API URL : {API_BASE_URL}")
|
| 241 |
+
print(f"Model : {MODEL_NAME}")
|
| 242 |
+
print(f"Server : {SERVER_URL}")
|
| 243 |
+
print()
|
| 244 |
+
|
| 245 |
+
if not HF_TOKEN:
|
| 246 |
+
print("ERROR: HF_TOKEN not set")
|
| 247 |
+
return
|
| 248 |
+
|
| 249 |
+
# Test server connection
|
| 250 |
+
try:
|
| 251 |
+
resp = requests.get(f"{SERVER_URL}/health", timeout=5)
|
| 252 |
+
if resp.status_code != 200:
|
| 253 |
+
print(f"ERROR: Server returned {resp.status_code}")
|
| 254 |
+
return
|
| 255 |
+
print("Server connection: OK")
|
| 256 |
+
except Exception as e:
|
| 257 |
+
print(f"ERROR: Cannot connect to server at {SERVER_URL}")
|
| 258 |
+
print(f" Make sure server is running: python main.py")
|
| 259 |
+
return
|
| 260 |
+
|
| 261 |
+
client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
|
| 262 |
+
|
| 263 |
+
task_keys = ["easy", "medium", "hard"]
|
| 264 |
+
results = []
|
| 265 |
+
|
| 266 |
+
for task_key in task_keys:
|
| 267 |
+
task = TASKS[task_key]
|
| 268 |
+
print(f"Running task: {task['name']} ({task['difficulty']})...")
|
| 269 |
+
try:
|
| 270 |
+
r = run_task(client, task_key, verbose=False)
|
| 271 |
+
results.append(r)
|
| 272 |
+
print(f" score={r['score']:.4f} steps={r['steps']}")
|
| 273 |
+
except Exception as exc:
|
| 274 |
+
print(f" ERROR: {exc}")
|
| 275 |
+
results.append({
|
| 276 |
+
"task_id": task["task_id"],
|
| 277 |
+
"task_name": task["name"],
|
| 278 |
+
"score": 0.0,
|
| 279 |
+
"success": False,
|
| 280 |
+
"steps": 0,
|
| 281 |
+
"rewards": [],
|
| 282 |
+
})
|
| 283 |
+
|
| 284 |
+
print("\n" + "=" * 60)
|
| 285 |
+
print("SUMMARY")
|
| 286 |
+
print("=" * 60)
|
| 287 |
+
total = 0.0
|
| 288 |
+
for r in results:
|
| 289 |
+
marker = {"easy": "[E]", "medium": "[M]", "hard": "[H]"}.get(r["task_id"].split("_")[0], "?")
|
| 290 |
+
print(f"{marker} {r['task_id']:30s} score={r['score']:.4f}")
|
| 291 |
+
total += r['score']
|
| 292 |
+
|
| 293 |
+
avg = total / len(results) if results else 0.0
|
| 294 |
+
print("-" * 40)
|
| 295 |
+
print(f"Average score: {avg:.4f}")
|
| 296 |
+
print()
|
| 297 |
+
|
| 298 |
+
output_path = "inference_results.json"
|
| 299 |
+
with open(output_path, "w") as f:
|
| 300 |
+
json.dump(
|
| 301 |
+
{
|
| 302 |
+
"model": MODEL_NAME,
|
| 303 |
+
"api_url": API_BASE_URL,
|
| 304 |
+
"server_url": SERVER_URL,
|
| 305 |
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
| 306 |
+
"average_score": avg,
|
| 307 |
+
"results": results,
|
| 308 |
+
},
|
| 309 |
+
f,
|
| 310 |
+
indent=2,
|
| 311 |
+
)
|
| 312 |
+
print(f"Results saved to: {output_path}")
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
if __name__ == "__main__":
|
| 316 |
+
main()
|
inference_results.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "Qwen/Qwen2.5-72B-Instruct",
|
| 3 |
+
"api_url": "https://router.huggingface.co/v1",
|
| 4 |
+
"server_url": "http://localhost:7860",
|
| 5 |
+
"timestamp": "2026-04-05 01:52:15",
|
| 6 |
+
"average_score": 0.041666666666666664,
|
| 7 |
+
"results": [
|
| 8 |
+
{
|
| 9 |
+
"task_id": "easy_right_sizing",
|
| 10 |
+
"task_name": "Right-Sizing",
|
| 11 |
+
"score": 0.125,
|
| 12 |
+
"success": false,
|
| 13 |
+
"steps": 1,
|
| 14 |
+
"rewards": [
|
| 15 |
+
1.0
|
| 16 |
+
]
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"task_id": "medium_latency_fix",
|
| 20 |
+
"task_name": "Latency Fix",
|
| 21 |
+
"score": 0.0,
|
| 22 |
+
"success": false,
|
| 23 |
+
"steps": 1,
|
| 24 |
+
"rewards": [
|
| 25 |
+
0.0
|
| 26 |
+
]
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"task_id": "hard_balance",
|
| 30 |
+
"task_name": "Balance Optimization",
|
| 31 |
+
"score": 0.0,
|
| 32 |
+
"success": false,
|
| 33 |
+
"steps": 1,
|
| 34 |
+
"rewards": [
|
| 35 |
+
0.0
|
| 36 |
+
]
|
| 37 |
+
}
|
| 38 |
+
]
|
| 39 |
+
}
|
main.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uvicorn
|
| 2 |
+
from fastapi import FastAPI, HTTPException
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
from env.core import CloudOpsEnvironment
|
| 5 |
+
from models import Action as ActionModel
|
| 6 |
+
|
| 7 |
+
app = FastAPI(title="CloudOps Optimizer")
|
| 8 |
+
env = CloudOpsEnvironment()
|
| 9 |
+
|
| 10 |
+
class ActionRequest(BaseModel):
|
| 11 |
+
message: str
|
| 12 |
+
|
| 13 |
+
@app.get("/health")
|
| 14 |
+
async def health():
|
| 15 |
+
return {"status": "ok"}
|
| 16 |
+
|
| 17 |
+
@app.get("/reset")
|
| 18 |
+
async def reset(task: str = "easy"):
|
| 19 |
+
try:
|
| 20 |
+
obs = env.reset(task_id=task)
|
| 21 |
+
return {"observation": obs.model_dump(), "done": False}
|
| 22 |
+
except Exception as e:
|
| 23 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 24 |
+
|
| 25 |
+
@app.post("/step")
|
| 26 |
+
async def step(action: ActionRequest):
|
| 27 |
+
try:
|
| 28 |
+
action_obj = ActionModel(message=action.message)
|
| 29 |
+
obs, reward, done, info = env.step(action_obj)
|
| 30 |
+
|
| 31 |
+
reward_val = reward.value if hasattr(reward, 'value') else reward
|
| 32 |
+
|
| 33 |
+
return {
|
| 34 |
+
"observation": obs.model_dump(),
|
| 35 |
+
"reward": reward_val,
|
| 36 |
+
"done": done,
|
| 37 |
+
"info": info
|
| 38 |
+
}
|
| 39 |
+
except Exception as e:
|
| 40 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 41 |
+
|
| 42 |
+
@app.get("/state")
|
| 43 |
+
async def state():
|
| 44 |
+
return env.state
|
| 45 |
+
|
| 46 |
+
if __name__ == "__main__":
|
| 47 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
models.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from typing import List, Optional
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class Resource(BaseModel):
|
| 6 |
+
id: str = Field(description="Unique resource identifier")
|
| 7 |
+
type: str = Field(description="Instance type (e.g., t3.small, m5.large)")
|
| 8 |
+
cpu_usage: float = Field(description="CPU usage percentage")
|
| 9 |
+
mem_usage: float = Field(description="Memory usage percentage")
|
| 10 |
+
monthly_cost: float = Field(description="Monthly cost in USD")
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class Metrics(BaseModel):
|
| 14 |
+
avg_latency_ms: float = Field(description="Average latency in milliseconds")
|
| 15 |
+
error_rate: float = Field(description="Error rate (0-1)")
|
| 16 |
+
throughput_rps: float = Field(description="Requests per second")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class SLA(BaseModel):
|
| 20 |
+
max_latency_ms: float = Field(description="Maximum allowed latency")
|
| 21 |
+
max_budget: float = Field(description="Maximum monthly budget in USD")
|
| 22 |
+
min_uptime_pct: float = Field(description="Minimum uptime percentage")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Observation(BaseModel):
|
| 26 |
+
inventory: List[Resource] = Field(description="List of active cloud resources")
|
| 27 |
+
metrics: Metrics = Field(description="Current system metrics")
|
| 28 |
+
sla: SLA = Field(description="Service Level Agreement requirements")
|
| 29 |
+
echoed_message: str = Field(default="System ready", description="Feedback from last action")
|
| 30 |
+
task_id: str = Field(default="easy", description="Current task identifier")
|
| 31 |
+
task_name: str = Field(default="Right-Sizing", description="Human-readable task name")
|
| 32 |
+
difficulty: str = Field(default="easy", description="Task difficulty level")
|
| 33 |
+
step: int = Field(default=0, description="Current step number")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class Action(BaseModel):
|
| 37 |
+
message: str = Field(description="Agent's command to modify infrastructure")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class Reward(BaseModel):
|
| 41 |
+
value: float = Field(description="Reward value between 0 and 1")
|
| 42 |
+
reason: str = Field(default="", description="Explanation of the reward")
|
| 43 |
+
cost_change_pct: float = Field(default=0.0, description="Percentage change in cost")
|
| 44 |
+
latency_change_pct: float = Field(default=0.0, description="Percentage change in latency")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
ObservationModel = Observation
|
| 48 |
+
ActionModel = Action
|
| 49 |
+
RewardModel = Reward
|
openenv.yaml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: cloud_ops_env
|
| 2 |
+
version: 1.0.0
|
| 3 |
+
description: A real-world simulation of cloud infrastructure cost and performance optimization.
|
| 4 |
+
runtime: fastapi
|
| 5 |
+
app: main:app
|
| 6 |
+
port: 7860
|
| 7 |
+
spec_version: "1"
|
| 8 |
+
type: space
|
openenv_cloud_ops_env.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: openenv-cloud-ops-env
|
| 3 |
+
Version: 1.0.0
|
| 4 |
+
Summary: CloudOps Optimizer - A real-world simulation of cloud infrastructure cost and performance optimization
|
| 5 |
+
Requires-Python: >=3.10
|
| 6 |
+
Requires-Dist: openenv-core[core]>=0.2.2
|
| 7 |
+
Requires-Dist: fastapi>=0.115.0
|
| 8 |
+
Requires-Dist: pydantic>=2.0.0
|
| 9 |
+
Requires-Dist: uvicorn[standard]>=0.24.0
|
| 10 |
+
Requires-Dist: requests>=2.31.0
|
openenv_cloud_ops_env.egg-info/SOURCES.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pyproject.toml
|
| 2 |
+
openenv_cloud_ops_env.egg-info/PKG-INFO
|
| 3 |
+
openenv_cloud_ops_env.egg-info/SOURCES.txt
|
| 4 |
+
openenv_cloud_ops_env.egg-info/dependency_links.txt
|
| 5 |
+
openenv_cloud_ops_env.egg-info/entry_points.txt
|
| 6 |
+
openenv_cloud_ops_env.egg-info/requires.txt
|
| 7 |
+
openenv_cloud_ops_env.egg-info/top_level.txt
|
openenv_cloud_ops_env.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
openenv_cloud_ops_env.egg-info/entry_points.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
server = cloud_ops_env.server.app:main
|
openenv_cloud_ops_env.egg-info/requires.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openenv-core[core]>=0.2.2
|
| 2 |
+
fastapi>=0.115.0
|
| 3 |
+
pydantic>=2.0.0
|
| 4 |
+
uvicorn[standard]>=0.24.0
|
| 5 |
+
requests>=2.31.0
|
openenv_cloud_ops_env.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
pyproject.toml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=45", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "openenv-cloud-ops-env"
|
| 7 |
+
version = "1.0.0"
|
| 8 |
+
description = "CloudOps Optimizer - A real-world simulation of cloud infrastructure cost and performance optimization"
|
| 9 |
+
requires-python = ">=3.10"
|
| 10 |
+
dependencies = [
|
| 11 |
+
"openenv-core[core]>=0.2.2",
|
| 12 |
+
"fastapi>=0.115.0",
|
| 13 |
+
"pydantic>=2.0.0",
|
| 14 |
+
"uvicorn[standard]>=0.24.0",
|
| 15 |
+
"requests>=2.31.0",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
[project.scripts]
|
| 19 |
+
server = "cloud_ops_env.server.app:main"
|
| 20 |
+
|
| 21 |
+
[tool.setuptools.packages.find]
|
| 22 |
+
where = ["."]
|
| 23 |
+
include = ["cloud_ops_env*"]
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
pydantic
|
| 4 |
+
openai
|
| 5 |
+
requests
|
server/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Server package for CloudOps Environment."""
|
| 2 |
+
|
| 3 |
+
from cloud_ops_env.server.app import app, main
|
| 4 |
+
|
| 5 |
+
__all__ = ["app", "main"]
|
server/app.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
try:
|
| 2 |
+
from openenv.core.env_server.http_server import create_app
|
| 3 |
+
except ImportError:
|
| 4 |
+
from openenv.core.env_server.http_server import create_app
|
| 5 |
+
|
| 6 |
+
from models import ObservationModel, ActionModel
|
| 7 |
+
from env.core import CloudOpsEnvironment
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
app = create_app(
|
| 11 |
+
CloudOpsEnvironment,
|
| 12 |
+
ActionModel,
|
| 13 |
+
ObservationModel,
|
| 14 |
+
env_name="cloud_ops_env",
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
import uvicorn
|
| 20 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
if __name__ == "__main__":
|
| 24 |
+
main()
|
test_env.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Test CloudOps Environment."""
|
| 3 |
+
import sys
|
| 4 |
+
sys.path.insert(0, 'D:/scaler')
|
| 5 |
+
|
| 6 |
+
from cloud_ops_env.env.core import CloudOpsEnvironment, TASKS
|
| 7 |
+
from cloud_ops_env.models import Action
|
| 8 |
+
|
| 9 |
+
print("Testing CloudOps Environment...")
|
| 10 |
+
|
| 11 |
+
# Test easy task
|
| 12 |
+
env = CloudOpsEnvironment()
|
| 13 |
+
obs = env.reset(task_id='easy')
|
| 14 |
+
print(f"Task: {obs.task_name} ({obs.difficulty})")
|
| 15 |
+
print(f"Resources: {len(obs.inventory)}")
|
| 16 |
+
for r in obs.inventory:
|
| 17 |
+
print(f" - {r.id}: {r.type} @ ${r.monthly_cost}/mo")
|
| 18 |
+
|
| 19 |
+
# Test action
|
| 20 |
+
action = Action(message="change srv-1 to t3.small")
|
| 21 |
+
obs2, reward, done, info = env.step(action)
|
| 22 |
+
|
| 23 |
+
print(f"\nAfter action:")
|
| 24 |
+
print(f" Reward: {reward.value if hasattr(reward, 'value') else reward}")
|
| 25 |
+
print(f" Done: {done}")
|
| 26 |
+
for r in obs2.inventory:
|
| 27 |
+
print(f" - {r.id}: {r.type} @ ${r.monthly_cost}/mo")
|
| 28 |
+
|
| 29 |
+
print("\nEnvironment working correctly!")
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|