Spaces:
Runtime error
Runtime error
File size: 7,869 Bytes
205f6c7 13558c9 205f6c7 13558c9 205f6c7 13558c9 205f6c7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | """
DebugOps inference script
=
Runs the LLM agent (or heuristic fallback) against all three tasks and
emits structured stdout logs in the exact required format:
[START] task=<name> env=debugops model=<model>
[STEP] step=<n> action=<a> reward=<r> done=<bool> error=<null|err>
[END] success=<bool> steps=<n> score=<s> rewards=<comma-list>
Environment variables
API_BASE_URL LLM endpoint (default: HuggingFace router)
MODEL_NAME Model ID (default: Qwen/Qwen2.5-72B-Instruct)
HF_TOKEN API key (also checked as OPENAI_API_KEY or API_KEY)
"""
from __future__ import annotations
import os
import sys
from typing import Any, Dict, List
try:
from openai import OpenAI
_OPENAI_AVAILABLE = True
except ImportError:
_OPENAI_AVAILABLE = False
from grader.grader import evaluate_episode
# Configuration
API_KEY = os.getenv("HF_TOKEN") or os.getenv("OPENAI_API_KEY") or os.getenv("API_KEY")
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
VALID_ACTIONS = ["restart_api", "restart_db", "restart_cache", "scale_up", "noop"]
# OpenAI client (spec requires OpenAI client for all LLM calls)
_client: Any = None
if _OPENAI_AVAILABLE and API_KEY:
_client = OpenAI(api_key=API_KEY, base_url=API_BASE_URL)
# Heuristic fallback agent (used when no LLM key or on API error)
def _fallback_agent(state: Dict[str, Any], action_history: List[str]) -> str:
"""
Rule-based agent driven by log keywords and fix_progress from state.
Uses fix_progress from the observation to know exactly which step to issue next.
"""
logs = " ".join(state.get("logs", [])).lower()
metrics = state.get("metrics", {})
progress = state.get("fix_progress", 0)
# Known fix sequences keyed by log signal (ordered by specificity)
_log_to_seq = [
("timeout", ["scale_up", "restart_api"]),
("upstream", ["scale_up", "restart_api"]),
("heap", ["restart_api", "restart_db"]),
("oom", ["restart_api", "restart_db"]),
("memory", ["restart_api", "restart_db"]),
("db pool", ["restart_db", "scale_up"]),
("connections", ["restart_db", "scale_up"]),
("cache miss", ["restart_cache", "scale_up"]),
("cache", ["restart_cache", "scale_up"]),
("db", ["restart_db", "scale_up"]),
]
# Identify sequence from logs
seq = None
for keyword, candidate_seq in _log_to_seq:
if keyword in logs:
seq = candidate_seq
break
if seq and progress < len(seq):
# Issue the next step in the correct sequence
return seq[progress]
# Metric-based fallback
if metrics.get("cpu", 0) > 80 and "scale_up" not in action_history:
return "scale_up"
if metrics.get("error_rate", 0) > 0.5 and "restart_api" not in action_history:
return "restart_api"
# Try any action not yet used twice
for a in ["restart_api", "restart_db", "restart_cache", "scale_up"]:
if action_history.count(a) < 2:
return a
return "noop"
# LLM call (OpenAI client as required)
def _call_llm(
state: Dict[str, Any],
action_history: List[str],
reward_history: List[float],
) -> str:
if _client is None:
return _fallback_agent(state, action_history)
# Build a human-readable step history with outcome signals
step_lines = []
for i, (a, r) in enumerate(zip(action_history, reward_history)):
outcome = "✓ progress made" if r > 20 else ("✗ wrong / no effect" if r < -5 else "~ neutral")
step_lines.append(f" step {i}: {a:18s} reward={r:>8.1f} [{outcome}]")
history_block = "\n".join(step_lines) if step_lines else " (none yet)"
services_degraded = [s for s, h in state["services"].items() if h == "degraded"]
metrics = state["metrics"]
prompt = f"""You are an expert SRE triaging a production incident. Your goal is to resolve it in as few steps as possible.
SYSTEM STATE (step {state['time_step']})
Degraded services : {services_degraded if services_degraded else 'none'}
Metrics : latency={metrics.get('latency', 0):.0f}ms error_rate={metrics.get('error_rate', 0):.2%} cpu={metrics.get('cpu', 0):.0f}%
Metric trend : {state.get('metric_trend', 'unknown')}
Fix progress : {state.get('fix_progress', 0)} step(s) completed correctly so far
SYSTEM LOGS
{chr(10).join(' ' + l for l in state['logs'])}
ACTION HISTORY & OUTCOMES
{history_block}
INSTRUCTIONS
Root causes have multi-step fix sequences that MUST be performed in order.
A positive reward means the last action was a correct step — continue the sequence.
A negative reward means the last action was wrong — try something different.
Do NOT repeat an action that already got a negative reward.
If fix_progress increased after your last action, continue to the NEXT step in the sequence.
Choose ONE action from: restart_api, restart_db, restart_cache, scale_up, noop
Respond with ONLY the action name."""
try:
response = _client.chat.completions.create(
model=MODEL_NAME,
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=20,
timeout=15,
)
action = response.choices[0].message.content.strip().lower()
action = action.split()[0] if action else "noop"
# strip any punctuation
action = "".join(c for c in action if c.isalnum() or c == "_")
if action not in VALID_ACTIONS:
return _fallback_agent(state, action_history)
return action
except Exception:
return _fallback_agent(state, action_history)
def run_episode(task_name: str = "simple") -> None:
if task_name == "simple":
from tasks.task_simple import create_env
max_steps = 15
elif task_name == "multi_service":
from tasks.task_multi_service import create_env
max_steps = 12
elif task_name == "critical":
from tasks.task_critical import create_env
max_steps = 10
else:
raise ValueError(f"Unknown task: {task_name!r}")
env = create_env()
state = env.reset()
print(
f"[START] task={task_name} env=debugops model={MODEL_NAME}",
flush=True,
)
action_history: List[str] = []
reward_history: List[float] = []
episode_logs: List[Dict[str, Any]] = []
done = False
step = 0
while not done and step < max_steps:
action = _call_llm(state, action_history, reward_history)
try:
next_state, reward, done, info = env.step(action)
error = "null"
except Exception as exc:
next_state, reward, done, info = state, 0.0, True, {}
error = type(exc).__name__
action_history.append(action)
reward_history.append(reward)
episode_logs.append({"reward": reward, "info": info})
print(
f"[STEP] step={step} action={action} reward={round(reward, 3)} "
f"done={str(done).lower()} error={error}",
flush=True,
)
# Early exit on explicit success flag
if info.get("success", False):
done = True
state = next_state
step += 1
result = evaluate_episode(episode_logs, max_steps=max_steps)
score = result["score"] # already in [0, 1]
success = result["resolved"]
print(
f"[END] success={str(success).lower()} steps={step} "
f"score={round(score, 3)} "
f"rewards={','.join(str(round(e['reward'], 3)) for e in episode_logs)}",
flush=True,
)
if __name__ == "__main__":
for task in ["simple", "multi_service", "critical"]:
run_episode(task) |