Jaswanth1210 commited on
Commit
e8f5d33
·
1 Parent(s): e934496

Add client SDK, fix openenv.yaml format, add model aliases

Browse files

- Add client.py: typed Python SDK wrapping the HTTP API
- Fix openenv.yaml to spec_version 1 format (matches reference envs)
- Add CloudSenseAction/CloudSenseObservation aliases in models
- Add root __init__.py exporting client and models

Files changed (4) hide show
  1. __init__.py +23 -0
  2. client.py +110 -0
  3. env/models.py +5 -0
  4. openenv.yaml +5 -60
__init__.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CloudSense — RL benchmark for FinOps AI agents on cloud cost optimization."""
2
+
3
+ from client import CloudSenseClient
4
+ from env.models import (
5
+ ActionType,
6
+ CloudAction,
7
+ CloudObservation,
8
+ CloudResource,
9
+ CloudSenseAction,
10
+ CloudSenseObservation,
11
+ StepResult,
12
+ )
13
+
14
+ __all__ = [
15
+ "CloudSenseClient",
16
+ "ActionType",
17
+ "CloudAction",
18
+ "CloudObservation",
19
+ "CloudResource",
20
+ "CloudSenseAction",
21
+ "CloudSenseObservation",
22
+ "StepResult",
23
+ ]
client.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CloudSense Python SDK — typed client for the CloudSense RL environment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import requests
6
+ from env.models import (
7
+ ActionType,
8
+ CloudAction,
9
+ CloudObservation,
10
+ CloudSenseAction,
11
+ CloudSenseObservation,
12
+ StepResult,
13
+ )
14
+
15
+
16
+ class CloudSenseClient:
17
+ """Typed HTTP client for the CloudSense environment API."""
18
+
19
+ def __init__(self, base_url: str = "http://localhost:7860"):
20
+ self.base_url = base_url.rstrip("/")
21
+ self._session = requests.Session()
22
+
23
+ # ── Core environment API ────────────────────────────────────────
24
+
25
+ def reset(self, task_id: str = "startup-cleanup") -> CloudObservation:
26
+ """Reset the environment to a new episode."""
27
+ resp = self._session.post(
28
+ f"{self.base_url}/reset",
29
+ params={"task_id": task_id},
30
+ timeout=30,
31
+ )
32
+ resp.raise_for_status()
33
+ return CloudObservation(**resp.json())
34
+
35
+ def step(self, action: CloudAction) -> StepResult:
36
+ """Execute an action and return the result."""
37
+ resp = self._session.post(
38
+ f"{self.base_url}/step",
39
+ json=action.model_dump(),
40
+ timeout=30,
41
+ )
42
+ resp.raise_for_status()
43
+ data = resp.json()
44
+ return StepResult(
45
+ observation=CloudObservation(**data["observation"]),
46
+ reward=data["reward"],
47
+ done=data["done"],
48
+ info=data.get("info", {}),
49
+ )
50
+
51
+ def state(self) -> dict:
52
+ """Get the current environment state."""
53
+ resp = self._session.get(f"{self.base_url}/state", timeout=10)
54
+ resp.raise_for_status()
55
+ return resp.json()
56
+
57
+ def close(self) -> None:
58
+ """Close the current episode."""
59
+ resp = self._session.post(f"{self.base_url}/close", timeout=10)
60
+ resp.raise_for_status()
61
+
62
+ # ── Discovery ───────────────────────────────────────────────────
63
+
64
+ def health(self) -> dict:
65
+ """Check environment health."""
66
+ resp = self._session.get(f"{self.base_url}/health", timeout=10)
67
+ resp.raise_for_status()
68
+ return resp.json()
69
+
70
+ def tasks(self) -> list[dict]:
71
+ """List available tasks."""
72
+ resp = self._session.get(f"{self.base_url}/tasks", timeout=10)
73
+ resp.raise_for_status()
74
+ return resp.json()
75
+
76
+ # ── Convenience ─────────────────────────────────────────────────
77
+
78
+ def rightsize(
79
+ self, resource_id: str, instance_type: str, reasoning: str = "", **config
80
+ ) -> StepResult:
81
+ """Shortcut: rightsize a resource."""
82
+ new_config = {"instance_type": instance_type, **config}
83
+ return self.step(
84
+ CloudAction(
85
+ action_type=ActionType.rightsize_resource,
86
+ resource_id=resource_id,
87
+ new_config=new_config,
88
+ reasoning=reasoning,
89
+ )
90
+ )
91
+
92
+ def terminate(self, resource_id: str, reasoning: str = "") -> StepResult:
93
+ """Shortcut: terminate a resource."""
94
+ return self.step(
95
+ CloudAction(
96
+ action_type=ActionType.terminate_resource,
97
+ resource_id=resource_id,
98
+ reasoning=reasoning,
99
+ )
100
+ )
101
+
102
+ def skip(self, resource_id: str, reasoning: str = "") -> StepResult:
103
+ """Shortcut: skip a resource."""
104
+ return self.step(
105
+ CloudAction(
106
+ action_type=ActionType.skip_resource,
107
+ resource_id=resource_id,
108
+ reasoning=reasoning,
109
+ )
110
+ )
env/models.py CHANGED
@@ -91,3 +91,8 @@ class StepResult(BaseModel):
91
  reward: float
92
  done: bool
93
  info: dict = Field(default_factory=dict)
 
 
 
 
 
 
91
  reward: float
92
  done: bool
93
  info: dict = Field(default_factory=dict)
94
+
95
+
96
+ # Aliases following OpenEnv naming convention
97
+ CloudSenseAction = CloudAction
98
+ CloudSenseObservation = CloudObservation
openenv.yaml CHANGED
@@ -1,61 +1,6 @@
 
1
  name: cloudsense
2
- description: "CloudSense: RL benchmark for training FinOps AI agents on cloud cost optimization with real AWS pricing and blast radius mechanics"
3
- version: "1.0.0"
4
- author: "CloudSense Team"
5
-
6
- environment:
7
- type: docker
8
- image: cloudsense
9
- port: 7860
10
-
11
- tasks:
12
- - id: startup-cleanup
13
- difficulty: easy
14
- description: "Optimize a small startup's dev/staging AWS account with 6 obviously oversized or unused resources. Total spend ~$627/mo, optimal ~$240/mo."
15
- max_steps: 10
16
- tags: ["finops", "cost-optimization", "beginner"]
17
-
18
- - id: mid-size-audit
19
- difficulty: medium
20
- description: "Audit a mid-size company's mixed prod/dev AWS account with 15 resources. Must identify which resources to optimize and which to leave alone. Total spend ~$3,487/mo, optimal ~$1,700/mo."
21
- max_steps: 20
22
- tags: ["finops", "cost-optimization", "intermediate", "production-safety"]
23
-
24
- - id: enterprise-finops
25
- difficulty: hard
26
- description: "Full FinOps review of an enterprise AWS account with 40 resources across prod/staging/dev. Complex dependencies, blast radius considerations, reserved instance management, and multi-service optimization. Total spend ~$14,230/mo, optimal ~$7,100/mo."
27
- max_steps: 45
28
- tags: ["finops", "cost-optimization", "advanced", "blast-radius", "dependencies"]
29
-
30
- endpoints:
31
- reset: POST /reset?task_id={task_id}
32
- step: POST /step
33
- state: GET /state
34
- health: GET /health
35
- close: POST /close
36
-
37
- action_space:
38
- type: json
39
- schema:
40
- action_type: "ActionType enum"
41
- resource_id: "string"
42
- new_config: "optional dict"
43
- reasoning: "string"
44
-
45
- observation_space:
46
- type: json
47
- schema:
48
- task_id: "string"
49
- goal: "string"
50
- account_id: "string"
51
- resources: "list of CloudResource dicts"
52
- monthly_cost_current: "float"
53
- monthly_cost_optimized: "float"
54
- total_possible_savings: "float"
55
- actions_taken: "list of action dicts"
56
- warnings: "list of strings"
57
- step_number: "int"
58
- max_steps: "int"
59
- last_reward: "float"
60
- last_action_error: "optional string"
61
- info: "dict with blast_radius etc."
 
1
+ spec_version: 1
2
  name: cloudsense
3
+ type: environment
4
+ runtime: docker
5
+ app: server.app:app
6
+ port: 7860