feat(sense): T-X5 云端评判晋升表×conformal 双闸门——本地档自动接管(采纳 cortiq promotion)

- gateway/sense/promotion.py:PromotionTable 状态机(candidate/promoted/demoted)
  * 晋升门:n_total>=n_min 且 通过率>=promote_lb 且 soak 浸泡期满足
  * 退化:promoted 期间通过率<demote_lb 自动降级;降级后可凭数据恢复
  * 一票否决:tier=T3 观察不进通过率统计,已晋升者立即降级
  * 持久化 sense_promotion 表(CREATE IF NOT EXISTS 幂等)
- grader:live 模式对已晋升标签的 T2 决策(无 T3 信号/非 fallback)升级 T1,
  hard_gates.promotion_applied 如实标注;T1/T3/fallback 路径不受影响
- 回填链路:T1 审计抽样带 promo:<label> 标签 -> 人工 verdict approve=ok
  经 /review/{id} 提交时回填晋升表(失败不影响审核主流程)
- 双闸门语义:conformal 阈值保证单条决策风险率,晋升表保证标签级接管节奏;
  collect 数据不足(n_min 未满)不晋升,全部迁移可经 T-X4 留痕审计

pytest 474 passed(T-X4 后 466 + 8)
This commit is contained in:
tzt
2026-09-18 23:01:52 +08:00
parent 7e01dc9b16
commit 1aaa05cacc
7 changed files with 334 additions and 5 deletions
+8 -3
View File
@@ -93,11 +93,13 @@ def build_proxy_router(cfg: ProxyConfig, pool, settings_provider=None) -> APIRou
from gateway.sense.config import build_sense_config
from gateway.sense.grader import Grader
from gateway.sense.observer import get_observer
from gateway.sense.promotion import PromotionTable
from gateway.sense.store import SenseStore
scfg = build_sense_config(settings_provider().to_dict())
if scfg.enabled and scfg.mode == "live":
sstore = SenseStore.init_db(scfg.db_path)
grader = Grader(scfg, sstore, get_observer(sstore))
grader = Grader(scfg, sstore, get_observer(sstore),
promotion=PromotionTable(sstore))
qtext = "\n".join(str(m.get("content") or "")
for m in (body.get("messages") or []))
d = await grader.decide(qtext or str(body.get("model") or ""),
@@ -114,14 +116,17 @@ def build_proxy_router(cfg: ProxyConfig, pool, settings_provider=None) -> APIRou
resp = await _run_chat(body, dict(request.headers), ctx, cfg, ledger, pool)
if tier_used:
resp.headers["x-campus-tier"] = tier_used
# T1 审计抽样(§9.4):按 review.sample_rate 入队人工核
# T1 审计抽样(§9.4):按 review.sample_rate 入队人工核
# promo 标签供人工 verdict 回填晋升表(T-X5
try:
import random as _random
from gateway.sense.promotion import promotion_label
rate = float(load_config().get("review", {}).get("sample_rate", 0.1))
if _random.random() < rate:
get_review().enqueue(
request_id + "-sense", str(body.get("model") or "proxy"),
"(sense T1 审计抽样)", tags=["sense_t1"],
"(sense T1 审计抽样)",
tags=["sense_t1", "promo:" + promotion_label("proxy", "")],
reason="sense_audit")
except Exception:
pass