feat(sense): T-G3 标签+校准(labeler 四规则推导 + split-conformal + last-good)

- labeler.py:derive_true_tier 纯函数(规则0 人工优先 / plan_multi->T3 /
  失败->下一档(T3保持) / 成功->executed / 空结果跳过)+ derive_true_tiers
  批量回填 + 180d 留存清理
- calibrate.py:compute_thresholds(τ1/τ3 分组扫描——并列分数整组判定,
  精度 >= 1-α 的最大覆盖阈值;<min_labels -> ok=False 沿用 last-good);
  save_thresholds 工件落盘+登记;load_active 回退链 active->同kind扫描->last-good->保守值
- store:all_observations/list_artifacts 支撑方法
- 测试 +9(四规则/批量清理/覆盖达标/标签不足/工件往返与回退),全量 395 passed
This commit is contained in:
tzt
2026-09-05 13:53:45 +08:00
parent 51f1de2154
commit 324c419c35
5 changed files with 321 additions and 1 deletions
+60
View File
@@ -0,0 +1,60 @@
"""夜间 true_tier 推导(T-G3,§4 四条规则,确定性)+ 留存清理。
规则(对每条 true_tier 为空的观察,按序判定):
0. human_override 非空 -> 以人工为准(规则 4,优先级最高)。
1. features.plan_multiconsumer=pipeline 发出 brief 的多步信号)-> true=T3。
2. outcome∈{escalated,failed,user_retry,timeout} -> true = executed 的下一档
T1→T2→T3T3 保持 T3)。
3. outcome∈{ok,verified} -> true = executedT1 成功即 T1T2/T3 同理)。
4. 其余(outcome 为空等)-> 跳过,等下一轮。
"""
from __future__ import annotations
import json
import time
from typing import Any, Dict, Optional
NEXT_TIER = {"T1": "T2", "T2": "T3", "T3": "T3"}
SUCCESS = ("ok", "verified")
FAILURE = ("escalated", "failed", "user_retry", "timeout")
def _features(row: Dict[str, Any]) -> Dict[str, Any]:
try:
obj = json.loads(row.get("features") or "{}")
return obj if isinstance(obj, dict) else {}
except json.JSONDecodeError:
return {}
def derive_true_tier(row: Dict[str, Any]) -> Optional[str]:
"""单行推导(纯函数,测试友好)。返回 None = 暂不判定。"""
if row.get("human_override"):
return str(row["human_override"])
outcome = row.get("outcome") or ""
executed = row.get("executed_tier") or "T2"
feats = _features(row)
if feats.get("plan_multi"):
return "T3" # 规则 1pipeline brief 信号)
if outcome in FAILURE: # 规则 2
return NEXT_TIER.get(executed, "T3")
if outcome in SUCCESS: # 规则 3
return executed
return None # 规则 4:等待结果
def derive_true_tiers(store, now: Optional[float] = None,
retention_days: int = 180) -> int:
"""批量推导(§7 签名):返回回填条数;顺带 180d 留存清理。"""
now = time.time() if now is None else now
rows = store.all_observations()
count = 0
for row in rows:
if row.get("true_tier"):
continue
true_tier = derive_true_tier(row)
if true_tier:
store.set_true_tier(row["request_id"], true_tier)
count += 1
store.purge_older_than(now - retention_days * 86400)
return count