Files
Timmy-time-dashboard/src/dashboard/routes/energy.py
Claude (Opus 4.6) 6b2e6d9e8c
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled
[claude] feat: Agent Energy Budget Monitoring (#1009) (#1267)
2026-03-24 01:35:49 +00:00

122 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Energy Budget Monitoring routes.
Exposes the energy budget monitor via REST API so the dashboard and
external tools can query power draw, efficiency scores, and toggle
low power mode.
Refs: #1009
"""
import logging
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from config import settings
from infrastructure.energy.monitor import energy_monitor
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/energy", tags=["energy"])
class LowPowerRequest(BaseModel):
"""Request body for toggling low power mode."""
enabled: bool
class InferenceEventRequest(BaseModel):
"""Request body for recording an inference event."""
model: str
tokens_per_second: float
@router.get("/status")
async def energy_status():
"""Return the current energy budget status.
Returns the live power estimate, efficiency score (010), recent
inference samples, and whether low power mode is active.
"""
if not getattr(settings, "energy_budget_enabled", True):
return {
"enabled": False,
"message": "Energy budget monitoring is disabled (ENERGY_BUDGET_ENABLED=false)",
}
report = await energy_monitor.get_report()
return {**report.to_dict(), "enabled": True}
@router.get("/report")
async def energy_report():
"""Detailed energy budget report with all recent samples.
Same as /energy/status but always includes the full sample history.
"""
if not getattr(settings, "energy_budget_enabled", True):
raise HTTPException(status_code=503, detail="Energy budget monitoring is disabled")
report = await energy_monitor.get_report()
data = report.to_dict()
# Override recent_samples to include the full window (not just last 10)
data["recent_samples"] = [
{
"timestamp": s.timestamp,
"model": s.model,
"tokens_per_second": round(s.tokens_per_second, 1),
"estimated_watts": round(s.estimated_watts, 2),
"efficiency": round(s.efficiency, 3),
"efficiency_score": round(s.efficiency_score, 2),
}
for s in list(energy_monitor._samples)
]
return {**data, "enabled": True}
@router.post("/low-power")
async def set_low_power_mode(body: LowPowerRequest):
"""Enable or disable low power mode.
In low power mode the cascade router is advised to prefer the
configured energy_low_power_model (see settings).
"""
if not getattr(settings, "energy_budget_enabled", True):
raise HTTPException(status_code=503, detail="Energy budget monitoring is disabled")
energy_monitor.set_low_power_mode(body.enabled)
low_power_model = getattr(settings, "energy_low_power_model", "qwen3:1b")
return {
"low_power_mode": body.enabled,
"preferred_model": low_power_model if body.enabled else None,
"message": (
f"Low power mode {'enabled' if body.enabled else 'disabled'}. "
+ (f"Routing to {low_power_model}." if body.enabled else "Routing restored to default.")
),
}
@router.post("/record")
async def record_inference_event(body: InferenceEventRequest):
"""Record an inference event for efficiency tracking.
Called after each LLM inference completes. Updates the rolling
efficiency score and may auto-activate low power mode if watts
exceed the configured threshold.
"""
if not getattr(settings, "energy_budget_enabled", True):
return {"recorded": False, "message": "Energy budget monitoring is disabled"}
if body.tokens_per_second <= 0:
raise HTTPException(status_code=422, detail="tokens_per_second must be positive")
sample = energy_monitor.record_inference(body.model, body.tokens_per_second)
return {
"recorded": True,
"efficiency_score": round(sample.efficiency_score, 2),
"estimated_watts": round(sample.estimated_watts, 2),
"low_power_mode": energy_monitor.low_power_mode,
}