feat(sense): T-G7 审计+前端(live 分流接线 + tier 指标卡 + Settings 灰度区块)
- proxy/routes:sense.mode=live 时转发前调 Grader 分流,档位 -> 池条目 (tier_pool_hint 映射),响应头 X-Campus-Tier 透出(可解释性); T1 决策按 review.sample_rate 抽样入 ReviewQueue(sense_t1 标签, 人工核"判 T1 实为更高档");分级故障不影响代理可用性(D-G4) - /api/metrics:sense 段(enabled/mode/观察数/已标签/档位分布/shadow 一致率) - 前端:MetricsView「语义分级」卡片(观察/标签进度/一致率/晋升门提示); SettingsView「Sense 灰度控制」区块(总开关 + collect/shadow/live 切换 + 标签进度),npm run build 产物更新 - 全量 410 passed
This commit is contained in:
+49
-1
@@ -78,11 +78,59 @@ def build_proxy_router(cfg: ProxyConfig, pool) -> APIRouter:
|
||||
_limits(), time.time())
|
||||
if not _limits().acquire_slot(ctx["key_id"]):
|
||||
raise QuotaError("并发请求已达该 key 上限")
|
||||
|
||||
# 语义分析器 live 分流(D-G7:mode=live 才启用;任何异常不影响代理可用性)
|
||||
tier_used = None
|
||||
try:
|
||||
return await _run_chat(body, dict(request.headers), ctx, cfg, ledger, pool)
|
||||
from gateway.sense.config import build_sense_config
|
||||
from gateway.sense.grader import Grader
|
||||
from gateway.sense.observer import get_observer
|
||||
from gateway.sense.store import SenseStore
|
||||
scfg = build_sense_config(settings_store().to_dict())
|
||||
if scfg.enabled and scfg.mode == "live":
|
||||
sstore = SenseStore.init_db(scfg.db_path)
|
||||
grader = Grader(scfg, sstore, get_observer(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 ""),
|
||||
"proxy")
|
||||
tier_used = d.tier
|
||||
# 档位 -> 池条目(§2 消费方表):t1/t2/t3 映射模型替换请求模型
|
||||
hint = scfg.tier_pool_hint("proxy", tier_used)
|
||||
e = _entry_by_tier_hint(pool, hint)
|
||||
if e is not None:
|
||||
body = {**body, "model": e["model"]}
|
||||
except Exception:
|
||||
pass # D-G4:分级故障不影响代理可用性
|
||||
try:
|
||||
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 入队人工核
|
||||
try:
|
||||
import random as _random
|
||||
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"],
|
||||
reason="sense_audit")
|
||||
except Exception:
|
||||
pass
|
||||
return resp
|
||||
finally:
|
||||
_limits().release_slot(ctx["key_id"])
|
||||
|
||||
def _entry_by_tier_hint(pool, hint: str) -> Optional[Dict[str, Any]]:
|
||||
"""按档位名选池条目(tier_hint 优先,缺省按 tier 字段映射)。"""
|
||||
entries = pool.list().get("entries", [])
|
||||
for e in entries:
|
||||
if not e.get("enabled") or e.get("backend") == "mock":
|
||||
continue
|
||||
if (e.get("tier_hint") or e.get("tier")) == hint:
|
||||
return e
|
||||
return None
|
||||
|
||||
# ---------------- 管理面 ----------------
|
||||
def _guard_admin(request: Request) -> None:
|
||||
host = request.client.host if request.client else ""
|
||||
|
||||
Reference in New Issue
Block a user