"""v2 统计聚合器(V2Stats)—— token 计量与账单(纯标准库)。 T9:每请求 API token 记账;聚合快路径命中率、回合数分布、熔断次数、累计 token/成本。 配合 /metrics 对外透出(论文 E1 token 经济学数据来源之一)。 """ from __future__ import annotations import threading from typing import Any, Dict, List, Optional class V2Stats: """线程安全的 v2 运行统计。""" def __init__(self): self._lock = threading.Lock() self._total = 0 self._fast_path = 0 self._breach = 0 self._by_status: Dict[str, int] = {} self._rounds: List[int] = [] self._api_input_tokens = 0 self._api_output_tokens = 0 self._api_cost_usd = 0.0 self._by_model: Dict[str, Dict[str, Any]] = {} self._recent: List[Dict[str, Any]] = [] def record(self, result) -> None: """记录一次 PipelineResult。""" with self._lock: self._total += 1 if getattr(result, "fast_path", False): self._fast_path += 1 status = getattr(result, "status", "?") self._by_status[status] = self._by_status.get(status, 0) + 1 self._rounds.append(getattr(result, "rounds_used", 0)) if "breach" in " ".join(getattr(result, "route", [])): self._breach += 1 self._api_input_tokens += getattr(result, "api_input_tokens", 0) self._api_output_tokens += getattr(result, "api_output_tokens", 0) self._api_cost_usd += getattr(result, "cost_est", 0.0) # 按模型分账(token / 成本 / 次数) model = getattr(result, "model_used", None) or "unknown" in_tok = getattr(result, "api_input_tokens", 0) out_tok = getattr(result, "api_output_tokens", 0) bucket = self._by_model.setdefault(model, { "requests": 0, "input_tokens": 0, "output_tokens": 0, "cost_est_usd": 0.0, }) bucket["requests"] += 1 bucket["input_tokens"] += in_tok bucket["output_tokens"] += out_tok bucket["cost_est_usd"] = round(bucket["cost_est_usd"] + getattr(result, "cost_est", 0.0), 6) self._recent.append({ "request_id": getattr(result, "request_id", ""), "status": status, "fast_path": getattr(result, "fast_path", False), "api_input_tokens": getattr(result, "api_input_tokens", 0), "api_output_tokens": getattr(result, "api_output_tokens", 0), "rounds_used": getattr(result, "rounds_used", 0), }) if len(self._recent) > 200: self._recent = self._recent[-200:] def summary(self) -> Dict[str, Any]: with self._lock: total = self._total rounds = self._rounds return { "total_requests": total, "fast_path_rate": round(self._fast_path / total, 4) if total else 0.0, "status_distribution": dict(self._by_status), "breach_count": self._breach, "rounds_used": { "avg": round(sum(rounds) / len(rounds), 2) if rounds else 0.0, "max": max(rounds) if rounds else 0, "distribution": _histogram(rounds), }, "api_tokens": { "input": self._api_input_tokens, "output": self._api_output_tokens, "total": self._api_input_tokens + self._api_output_tokens, "cost_est_usd": round(self._api_cost_usd, 6), }, "by_model": { m: {**b, "cost_est_usd": round(b["cost_est_usd"], 6)} for m, b in self._by_model.items() }, } def _histogram(values: List[int], max_bucket: int = 10) -> Dict[str, int]: out: Dict[str, int] = {} for v in values: key = str(v) if v <= max_bucket else f">{max_bucket}" out[key] = out.get(key, 0) + 1 return out