Spaces:
Running
Running
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| PREDICTOR_SCOPES = {"global_ema", "per_tool_ema"} | |
| class _Estimate: | |
| mean_s: float | |
| count: int = 0 | |
| class OnlineToolGapPredictor: | |
| """Tiny online tool-duration predictor used by adaptive KV tiering. | |
| The predictor deliberately stays transparent: it keeps an exponentially | |
| weighted moving average (EWMA) of observed tool-call durations. In | |
| ``per_tool_ema`` mode each tool family gets its own estimate, backed by a | |
| global EWMA until enough observations have arrived. No future tool duration | |
| is visible at decision time. | |
| """ | |
| def __init__( | |
| self, | |
| *, | |
| initial_mean_s: float, | |
| alpha: float = 0.30, | |
| min_observations: int = 2, | |
| scope: str = "per_tool_ema", | |
| ) -> None: | |
| if scope not in PREDICTOR_SCOPES: | |
| raise ValueError(f"Unsupported predictor scope: {scope}") | |
| self.scope = scope | |
| self.alpha = min(max(float(alpha), 0.01), 1.0) | |
| self.min_observations = max(int(min_observations), 1) | |
| self.global_estimate = _Estimate(max(float(initial_mean_s), 0.0), 0) | |
| self.per_tool: dict[str, _Estimate] = {} | |
| def _update(estimate: _Estimate, value_s: float, alpha: float) -> None: | |
| value_s = max(float(value_s), 0.0) | |
| if estimate.count == 0: | |
| estimate.mean_s = value_s | |
| else: | |
| estimate.mean_s = alpha * value_s + (1.0 - alpha) * estimate.mean_s | |
| estimate.count += 1 | |
| def predict(self, tool_kind: str) -> tuple[float, str, int]: | |
| if self.scope == "per_tool_ema": | |
| estimate = self.per_tool.get(tool_kind) | |
| if estimate is not None and estimate.count >= self.min_observations: | |
| return estimate.mean_s, "tool", estimate.count | |
| return self.global_estimate.mean_s, "global", self.global_estimate.count | |
| def observe(self, tool_kind: str, duration_s: float) -> None: | |
| self._update(self.global_estimate, duration_s, self.alpha) | |
| estimate = self.per_tool.setdefault(tool_kind, _Estimate(self.global_estimate.mean_s, 0)) | |
| self._update(estimate, duration_s, self.alpha) | |
| def snapshot(self) -> dict: | |
| return { | |
| "scope": self.scope, | |
| "alpha": self.alpha, | |
| "min_observations": self.min_observations, | |
| "global": {"mean_s": self.global_estimate.mean_s, "count": self.global_estimate.count}, | |
| "per_tool": { | |
| key: {"mean_s": value.mean_s, "count": value.count} | |
| for key, value in sorted(self.per_tool.items()) | |
| }, | |
| } | |