AI-debugging-agent / env /environment.py
prashasti
Initial changes for ai-debugger
205f6c7
Raw
History Blame Contribute Delete
4.35 kB
# DebugOps Environment β€” core RL environment for AI incident response.
# Follows the OpenEnv step()/reset()/state() interface.
from __future__ import annotations
import copy
from dataclasses import dataclass, field, asdict
from typing import Dict, List, Tuple, Any
from env.incident_generator import generate_incident
from env.dynamics import apply_action
from env.reward import compute_reward
# Typed observation / state model
@dataclass
class Observation:
services: Dict[str, str] # service_name -> "healthy" | "degraded"
logs: List[str] # ordered log lines (may be noisy)
metrics: Dict[str, float] # latency, error_rate, cpu
time_step: int # steps elapsed in the current episode
fix_progress: int # number of correct steps completed so far
metric_trend: str # "improving" | "degrading" | "stable"
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
# Core environment
class DebugEnv:
"""
Single-episode production incident environment.
Observation space:
services : Dict[str, str] β€” per-service health status
logs : List[str] β€” system logs (may contain noise)
metrics : Dict[str,float] β€” latency (ms), error_rate (0-1), cpu (%)
time_step : int
Action space (discrete, 5 actions):
restart_api | restart_db | restart_cache | scale_up | noop
Episode terminates when:
- state_data["resolved"] is True (success)
- t >= max_steps (timeout / failure)
"""
VALID_ACTIONS = ["restart_api", "restart_db", "restart_cache", "scale_up", "noop"]
def __init__(self, max_steps: int = 20):
self.max_steps = max_steps
self.t: int = 0
self.done: bool = False
self.success: bool = False
self.state_data: Dict[str, Any] = {}
self._prev_latency: float = 0.0
#---
def reset(self) -> Dict[str, Any]:
"""Return fresh observation; episode counter reset."""
self.t = 0
self.done = False
self.success = False
self.state_data = generate_incident()
self._prev_latency = self.state_data["metrics"]["latency"]
return self._obs()
#---
def state(self) -> Dict[str, Any]:
"""Return current observation (idempotent)."""
return self._obs()
def step(self, action: str) -> Tuple[Dict[str, Any], float, bool, Dict[str, Any]]:
"""
Apply action and advance one time-step.
Returns
-------
observation : Dict
reward : float
done : bool
info : Dict β€” latency, error_rate, progress, resolved, success
"""
if self.done:
raise RuntimeError("Episode is finished. Call reset() before stepping.")
if action not in self.VALID_ACTIONS:
raise ValueError(f"Invalid action '{action}'. Must be one of {self.VALID_ACTIONS}")
prev_state = copy.deepcopy(self.state_data)
prev_latency = self.state_data["metrics"]["latency"]
self.state_data = apply_action(self.state_data, action)
reward, info = compute_reward(prev_state, self.state_data, action, self.t)
self._prev_latency = prev_latency
self.t += 1
if self.state_data["resolved"]:
self.done = True
self.success = True
elif self.t >= self.max_steps:
self.done = True
self.success = False
info["success"] = self.success
info["time_step"] = self.t
return self._obs(), reward, self.done, info
def _obs(self) -> Dict[str, Any]:
curr_latency = self.state_data["metrics"]["latency"]
delta = curr_latency - self._prev_latency
if delta < -10:
trend = "improving"
elif delta > 10:
trend = "degrading"
else:
trend = "stable"
return Observation(
services=dict(self.state_data["services"]),
logs=list(self.state_data["logs"]),
metrics={k: round(v, 2) for k, v in self.state_data["metrics"].items()},
time_step=self.t,
fix_progress=self.state_data["fix_progress"],
metric_trend=trend,
).to_dict()