PranavKK1201 commited on
Commit
a693df5
·
1 Parent(s): dfe5268

modified tasks for compatibility and added the inference.py script

Browse files
inference.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import os
4
+ import textwrap
5
+ from typing import List, Optional
6
+
7
+ from openai import AsyncOpenAI
8
+
9
+ from AntiAtropos.client import AntiAtroposEnv
10
+ from AntiAtropos.grader import EpisodeGrader
11
+ from AntiAtropos.models import ActionType, SREAction
12
+
13
+
14
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://api.groq.com/openai/v1")
15
+ MODEL_NAME = os.getenv("MODEL_NAME", "llama-3.1-70b-versatile")
16
+ API_KEY = (
17
+ os.getenv("HF_TOKEN")
18
+ or os.getenv("OPENAI_API_KEY")
19
+ or os.getenv("API_KEY")
20
+ or os.getenv("GROQ_API_KEY")
21
+ )
22
+ LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME")
23
+ ENV_URL = os.getenv("ANTIATROPOS_ENV_URL", "http://127.0.0.1:8000")
24
+ TASK_NAME = os.getenv("ANTIATROPOS_TASK", "task-3")
25
+ BENCHMARK = os.getenv("ANTIATROPOS_BENCHMARK", "antiatropos")
26
+ ENV_MODE = os.getenv("ANTIATROPOS_MODE", "simulated")
27
+ MAX_STEPS = int(os.getenv("ANTIATROPOS_MAX_STEPS", "35"))
28
+ TEMPERATURE = float(os.getenv("ANTIATROPOS_TEMPERATURE", "0.05"))
29
+ MAX_TOKENS = int(os.getenv("ANTIATROPOS_MAX_TOKENS", "180"))
30
+ SUCCESS_SCORE_THRESHOLD = float(os.getenv("ANTIATROPOS_SUCCESS_THRESHOLD", "0.55"))
31
+
32
+ SYSTEM_PROMPT = textwrap.dedent(
33
+ """
34
+ You are an autonomous SRE controller managing a five-node microservice cluster.
35
+
36
+ Objectives:
37
+ - minimize Lyapunov energy and queue growth
38
+ - keep normalized average latency at or below 0.20
39
+ - avoid invalid actions, especially SHED_LOAD on node-0, node-1, and node-2
40
+ - scale proactively because SCALE_UP takes 5 ticks to take effect
41
+ - protect the VIP gateway node-0
42
+
43
+ Output exactly one JSON object:
44
+ {
45
+ "action_type": "SCALE_UP" | "SCALE_DOWN" | "REROUTE_TRAFFIC" | "SHED_LOAD" | "NO_OP",
46
+ "target_node_id": "node-0" | "node-1" | "node-2" | "node-3" | "node-4",
47
+ "parameter": 0.0
48
+ }
49
+ """
50
+ ).strip()
51
+
52
+
53
+ def log_start(task: str, env: str, model: str) -> None:
54
+ print(f"[START] task={task} env={env} model={model}", flush=True)
55
+
56
+
57
+ def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
58
+ error_val = error if error else "null"
59
+ done_val = str(done).lower()
60
+ print(
61
+ f"[STEP] step={step} action={action} reward={reward:.2f} done={done_val} error={error_val}",
62
+ flush=True,
63
+ )
64
+
65
+
66
+ def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
67
+ rewards_str = ",".join(f"{reward:.2f}" for reward in rewards)
68
+ print(
69
+ f"[END] success={str(success).lower()} steps={steps} score={score:.2f} rewards={rewards_str}",
70
+ flush=True,
71
+ )
72
+
73
+
74
+ def build_user_prompt(step: int, obs: dict, history: List[str]) -> str:
75
+ history_block = "\n".join(history[-4:]) if history else "None"
76
+ return textwrap.dedent(
77
+ f"""
78
+ Step: {step}
79
+ Current state:
80
+ {json.dumps(obs, separators=(",", ":"))}
81
+
82
+ Recent decisions:
83
+ {history_block}
84
+
85
+ Choose the next SRE action.
86
+ """
87
+ ).strip()
88
+
89
+
90
+ def compact_action(action: SREAction) -> str:
91
+ payload = {
92
+ "action_type": action.action_type.value,
93
+ "target_node_id": action.target_node_id,
94
+ "parameter": round(float(action.parameter), 4),
95
+ }
96
+ return json.dumps(payload, separators=(",", ":"))
97
+
98
+
99
+ def extract_json_object(text: str) -> dict:
100
+ stripped = text.strip()
101
+ if not stripped:
102
+ raise ValueError("empty model response")
103
+
104
+ start = stripped.find("{")
105
+ end = stripped.rfind("}")
106
+ if start == -1 or end == -1 or end < start:
107
+ raise ValueError("no JSON object found")
108
+ return json.loads(stripped[start : end + 1])
109
+
110
+
111
+ def parse_action(payload: dict) -> SREAction:
112
+ action_type = str(payload.get("action_type", "NO_OP")).upper()
113
+ target_node_id = str(payload.get("target_node_id", "node-0"))
114
+ parameter = float(payload.get("parameter", 0.0))
115
+ return SREAction(
116
+ action_type=ActionType(action_type),
117
+ target_node_id=target_node_id,
118
+ parameter=parameter,
119
+ )
120
+
121
+
122
+ async def get_model_action(
123
+ client: AsyncOpenAI,
124
+ step: int,
125
+ obs: dict,
126
+ history: List[str],
127
+ ) -> SREAction:
128
+ user_prompt = build_user_prompt(step, obs, history)
129
+ try:
130
+ completion = await client.chat.completions.create(
131
+ model=MODEL_NAME,
132
+ messages=[
133
+ {"role": "system", "content": SYSTEM_PROMPT},
134
+ {"role": "user", "content": user_prompt},
135
+ ],
136
+ temperature=TEMPERATURE,
137
+ max_tokens=MAX_TOKENS,
138
+ response_format={"type": "json_object"},
139
+ )
140
+ content = completion.choices[0].message.content or ""
141
+ return parse_action(extract_json_object(content))
142
+ except Exception:
143
+ return SREAction(
144
+ action_type=ActionType.NO_OP,
145
+ target_node_id="node-0",
146
+ parameter=0.0,
147
+ )
148
+
149
+
150
+ def observation_for_model(obs) -> dict:
151
+ return {
152
+ "task_id": obs.task_id,
153
+ "mode": getattr(obs.mode, "value", str(obs.mode)),
154
+ "step": obs.step,
155
+ "max_steps": obs.max_steps,
156
+ "lyapunov_energy": obs.lyapunov_energy,
157
+ "average_latency_ms": obs.average_latency_ms,
158
+ "error_rate": obs.error_rate,
159
+ "total_queue_backlog": obs.total_queue_backlog,
160
+ "sla_violations": obs.sla_violations,
161
+ "invalid_action_count": obs.invalid_action_count,
162
+ "nodes": [
163
+ {
164
+ "node_id": node.node_id,
165
+ "status": getattr(node.status, "value", str(node.status)),
166
+ "is_vip": node.is_vip,
167
+ "queue_depth": node.queue_depth,
168
+ "latency_ms": node.latency_ms,
169
+ "incoming_request_rate": node.incoming_request_rate,
170
+ "cpu_utilization": node.cpu_utilization,
171
+ }
172
+ for node in obs.nodes
173
+ ],
174
+ }
175
+
176
+
177
+ async def run_episode() -> None:
178
+ rewards: List[float] = []
179
+ steps_taken = 0
180
+ success = False
181
+ score = 0.0
182
+ env = None
183
+ client = None
184
+
185
+ log_start(TASK_NAME, BENCHMARK, MODEL_NAME)
186
+
187
+ try:
188
+ if not API_KEY:
189
+ raise RuntimeError("missing API key")
190
+
191
+ client = AsyncOpenAI(base_url=API_BASE_URL, api_key=API_KEY)
192
+ if LOCAL_IMAGE_NAME:
193
+ env = await AntiAtroposEnv.from_docker_image(LOCAL_IMAGE_NAME)
194
+ else:
195
+ env = AntiAtroposEnv(base_url=ENV_URL)
196
+ await env.__aenter__()
197
+
198
+ grader = EpisodeGrader(task_id=TASK_NAME)
199
+ history: List[str] = []
200
+
201
+ result = await env.reset(task_id=TASK_NAME, mode=ENV_MODE)
202
+ grader.record(result.observation)
203
+
204
+ for step in range(1, MAX_STEPS + 1):
205
+ if result.done:
206
+ break
207
+
208
+ obs = result.observation
209
+ action = await get_model_action(
210
+ client=client,
211
+ step=step,
212
+ obs=observation_for_model(obs),
213
+ history=history,
214
+ )
215
+ result = await env.step(action)
216
+ grader.record(result.observation)
217
+
218
+ reward = float(result.reward or 0.0)
219
+ rewards.append(reward)
220
+ steps_taken = step
221
+
222
+ ack_status = getattr(result.observation, "action_ack_status", "")
223
+ error = ack_status if ack_status.startswith(("Rejected:", "Error:")) else None
224
+ action_str = compact_action(action)
225
+ log_step(step=step, action=action_str, reward=reward, done=result.done, error=error)
226
+
227
+ history.append(
228
+ f"step={step} action={action_str} reward={reward:.2f} ack={ack_status or 'null'}"
229
+ )
230
+
231
+ if result.done:
232
+ break
233
+
234
+ score = max(0.0, min(1.0, grader.score().composite))
235
+ success = score >= SUCCESS_SCORE_THRESHOLD
236
+ except Exception:
237
+ success = False
238
+ finally:
239
+ if client is not None:
240
+ try:
241
+ await client.close()
242
+ except Exception:
243
+ pass
244
+ if env is not None:
245
+ try:
246
+ await env.close()
247
+ except Exception:
248
+ pass
249
+ log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
250
+
251
+
252
+ def main() -> None:
253
+ asyncio.run(run_episode())
254
+
255
+
256
+ if __name__ == "__main__":
257
+ main()
pyproject.toml CHANGED
@@ -30,6 +30,7 @@ dependencies = [
30
  "prometheus-api-client>=0.5.0",
31
  "prometheus-client>=0.20.0",
32
  "requests>=2.31.0",
 
33
  ]
34
 
35
  [project.optional-dependencies]
 
30
  "prometheus-api-client>=0.5.0",
31
  "prometheus-client>=0.20.0",
32
  "requests>=2.31.0",
33
+ "openai>=1.10.0",
34
  ]
35
 
36
  [project.optional-dependencies]
server/AntiAtropos_environment.py CHANGED
@@ -24,9 +24,9 @@ except ImportError:
24
  # Reward hyper-parameters (synchronized with stability.py constants)
25
  # ---------------------------------------------------------------------------
26
 
27
- ALPHA: float = 1e-5 # Massively scaled down Weight on Lyapunov energy drift ΔV(s)
28
- BETA: float = 1.0 # Weight on infrastructure cost
29
- GAMMA: float = 1.0 # Weight on per-step SLA violation indicator
30
 
31
  MAX_QUEUE_NORM = 200.0
32
  MAX_LATENCY_NORM = 1000.0
 
24
  # Reward hyper-parameters (synchronized with stability.py constants)
25
  # ---------------------------------------------------------------------------
26
 
27
+ ALPHA: float = 0.002 # Weight on Lyapunov energy drift ΔV(s) (Increased for faster feedback)
28
+ BETA: float = 0.01 # Weight on infrastructure cost (Reduced to prevent cheap-but-dead strategies)
29
+ GAMMA: float = 10.0 # Weight on per-step SLA violation indicator (Increased to force reactive scaling)
30
 
31
  MAX_QUEUE_NORM = 200.0
32
  MAX_LATENCY_NORM = 1000.0