Files
projectAIpopular/router_system/stats.py
T
tzt a9f60159e7 feat(v1): T-R1 采纳 ai-model-router 可解释路由评分——五维加权+硬过滤带拒绝理由+置信度分差推导
- 新增 router_system/explain.py:CandidateProfile(capability/cost/latency/
  reliability/difficulty 画像)+ explain_routing(硬过滤先行、逐条人话拒绝理由、
  五维归一评分、同分按领域字典序、confidence=0.8+分差推导封顶 0.99)
- Router._explain 纯解释层装配(解释层任何异常不影响主链路,D-G4 同款纪律);
  路由胜负与既有决策完全等价,/chat 响应新增 route_explanation 字段(缓存命中为 None)
- experts:Expert.nominal_latency_ms 标称延迟元数据(mock 1/hf 300/api 800)
- stats:upgraded_by_domain 计数 + domain_reliability()(无数据给 0.9 中性先验)
- .gitignore 登记 extra/(参考项目目录不入库)
- 新增 tests/test_explain.py 8 项;全量 33 passed(基线 25)
2026-09-19 09:23:04 +08:00

75 lines
3.0 KiB
Python

"""运行指标收集(线程安全,零依赖)。"""
from __future__ import annotations
import threading
from collections import Counter, deque
from typing import Any, Deque, Dict
class Stats:
def __init__(self, window: int = 1000):
self._lock = threading.Lock()
self.requests = 0
self.domain_counter: Counter = Counter()
self.difficulty_counter: Counter = Counter()
self.upgraded = 0
self.upgraded_by_domain: Counter = Counter() # 可解释评分 reliability 维输入
self.cache_hits = 0
self.cache_levels: Counter = Counter()
self.errors = 0
self.latencies: Deque[float] = deque(maxlen=window)
self.cost_total = 0.0
self.model_usage: Counter = Counter()
def record(self, latency_ms: float, domain: str, difficulty: str,
upgraded: bool, cache_hit: bool, cache_level: str | None,
cost_est: float, model_used: str):
with self._lock:
self.requests += 1
self.domain_counter[domain] += 1
self.difficulty_counter[difficulty] += 1
if upgraded:
self.upgraded += 1
self.upgraded_by_domain[domain] += 1
if cache_hit:
self.cache_hits += 1
if cache_level:
self.cache_levels[cache_level] += 1
self.latencies.append(latency_ms)
self.cost_total += cost_est
self.model_usage[model_used] += 1
def record_error(self):
with self._lock:
self.errors += 1
def domain_reliability(self, domain: str, prior: float = 0.9) -> float:
"""领域可靠性 = 1 - 升级率(无数据时返回中性先验,可解释评分用)。"""
with self._lock:
n = self.domain_counter.get(domain, 0)
if n <= 0:
return prior
up = self.upgraded_by_domain.get(domain, 0)
return max(0.0, min(1.0, 1.0 - up / n))
def summary(self) -> Dict[str, Any]:
with self._lock:
n = self.requests
lat = list(self.latencies)
avg_lat = sum(lat) / len(lat) if lat else 0.0
p99 = sorted(lat)[int(len(lat) * 0.99) - 1] if len(lat) >= 100 else (max(lat) if lat else 0.0)
return {
"total_requests": n,
"domain_distribution": dict(self.domain_counter),
"difficulty_distribution": dict(self.difficulty_counter),
"fallback_rate": round(self.upgraded / n, 4) if n else 0.0,
"upgraded_requests": self.upgraded,
"cache_hit_rate": round(self.cache_hits / n, 4) if n else 0.0,
"cache_levels": dict(self.cache_levels),
"avg_latency_ms": round(avg_lat, 3),
"p99_latency_ms": round(p99, 3),
"total_cost_est_usd": round(self.cost_total, 6),
"model_usage": dict(self.model_usage),
"errors": self.errors,
}