AI-debugging-agent / inference.py
prashasti
Revert "Improvements"
13558c9
Raw
History Blame Contribute Delete
7.87 kB
"""
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)