Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8df9d0d5db |
@@ -0,0 +1,99 @@
|
||||
// t_132cfa76 — 自动反馈样本账(AC5 判读门槛 + AC3 负样本消费侧入账)
|
||||
//
|
||||
// 背景:recall_usefulness_rate 长期只有 ~632 样本人工/自动反馈,覆盖率 1.36%,
|
||||
// 排序几乎没被校准过。本卡确立三条**机械可判**(零 LLM 自评、零人工点击)的
|
||||
// 自动样本来源,并把「多少样本才许判读」写死:
|
||||
//
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// 🔴 判读门槛(牧尘 2026-11-10 卡 t_132cfa76 AC5 拍板):
|
||||
// rail_usefulness_rate(= recall_usefulness_rate)**样本 ≥ 5000 才判读**。
|
||||
// 在此之前该指标只能看趋势方向,不得作为排序改动 A/B 的裁决依据。
|
||||
// 判读时必须连同 Wilson 95% 区间一起看(区间宽度 > 效应量 ⇒ 无结论)。
|
||||
// 机械执行:metrics 输出 recall_usefulness_judgeable = samples ≥ 5000。
|
||||
// ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// 自动信号定义(判定代码回放见卡交付 replay_auto_labels.py,7 天回放数据 out/):
|
||||
//
|
||||
// 正样本(已有链路,不新增写入点):
|
||||
// P1 downstream_use —— usage_track.go 判「被召回记忆的 ≥8 rune 独特内容逐字
|
||||
// 出现在后续写入」→ AppendHitEvent(pos)。回放 7d 命中 2043 条唯一记忆。
|
||||
// P2 inject_pass —— 插件 _auto_feedback_for_reply(token+语义双闸)→
|
||||
// /feedback/useful(source=auto)。回放 7d 唯一 105 条。
|
||||
//
|
||||
// 负样本(本卡新增消费侧自动上报,堵住「插件永不标 not_useful」的空白):
|
||||
// N1 expired_unconsumed —— usage_recall 行 consumed=2(召回后 48h 窗口内
|
||||
// 无任何下游逐字引用 = 过期未消费)。由消费扫描(CheckCommittedForUse 的
|
||||
// sweep 分支,与写侧同节拍、零常驻进程)逐条上报。
|
||||
// N2 injected_not_used —— 插件侧:注入且双闸 passes=False 且同 session 后续
|
||||
// 不再注入该 mid(回放 7d 唯一 268 条)。
|
||||
// N3 repeat_replaced —— 同 session 归一化 query 重复且该 mid 从 top-k 掉出
|
||||
// (回放 7d 唯一 43 条)。
|
||||
//
|
||||
// 可信性依据(AC4,100 例独立源交叉复跑,见 out/neg_error_rate.json):
|
||||
// 负样本判定错误率 6%(6/100,判据=同 7d 他处存在 passes=True 引用或
|
||||
// consumed=1 行 ⇒ 与负标签矛盾)< 10% 门槛。
|
||||
// ⚠️ 已知语义边界:N1-N3 是「该查询/该回合未被使用」的**上下文负样本**,
|
||||
// 不是「这条记忆全局无用」——聚合排序特征时必须按 (query_pair, mem) 计,
|
||||
// 不得把单条记忆的负样本直接当全局质量降权(hit_event.go 排除项铁律同源)。
|
||||
// 因此本文件只把自动负样本计入**观测指标** auto_*,不接进 RecordFeedback
|
||||
// (人评口径 useful_count/not_useful_count 保持纯净,人机可辨)。
|
||||
package selfoptimize
|
||||
|
||||
import "math"
|
||||
|
||||
// 自动反馈样本账:与人工 UsefulCount/NotUsefulCount 分开计数、分开判读。
|
||||
//
|
||||
// 持久化走 dashboardRedisKey / 本地 JSON 双通道,字段
|
||||
// auto_feedback_positive / auto_feedback_negative(与 useful_count 同款语义:
|
||||
// 单调累加器,跨重启存活;不参与跨天归零)。
|
||||
//
|
||||
// 门槛常量给审计/面板脚本 import 用;改门槛必须同步改本注释 + 卡 t_132cfa76。
|
||||
const (
|
||||
// UsefulnessReadGate —— rail_usefulness_rate 判读门槛(AC5 写死)。
|
||||
UsefulnessReadGate = 5000
|
||||
|
||||
// AutoNegSource* —— auto_feedback_negative 的机械来源枚举(审计分桶用)。
|
||||
AutoNegSourceExpiredUnconsumed = "expired_unconsumed" // N1
|
||||
AutoNegSourceInjectNotUsed = "inject_not_used" // N2
|
||||
AutoNegSourceRepeatReplaced = "repeat_replaced" // N3
|
||||
)
|
||||
|
||||
// RecordAutoFeedback 自动信号入账(useful=true 仅 downstream_use / inject_pass;
|
||||
// useful=false 仅 N1/N3 两类机械负样本)。
|
||||
//
|
||||
// 与 RecordFeedback 的差异:
|
||||
// - 不写 useful_count/not_useful_count(人评口径不掺机器信号)
|
||||
// - 同步追加 hit_events(pos→downstream_use;neg→replacement),
|
||||
// 由调用方负责(routes/feedback.go 已对 useful 做;not-useful auto 分支新增)
|
||||
func (d *Dashboard) RecordAutoFeedback(useful bool) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if useful {
|
||||
d.AutoFeedbackPositive++
|
||||
} else {
|
||||
d.AutoFeedbackNegative++
|
||||
}
|
||||
d.persist()
|
||||
}
|
||||
|
||||
// WilsonInterval95 —— Wilson score 区间(95%,z=1.96)。
|
||||
// 任何对外报 recall_usefulness_rate / auto_usefulness_rate 的地方必须配它输出。
|
||||
func WilsonInterval95(pos, n int) (lo, hi float64) {
|
||||
if n <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
const z = 1.96
|
||||
p := float64(pos) / float64(n)
|
||||
zz := z * z
|
||||
denom := 1 + zz/float64(n)
|
||||
centre := (p + zz/(2*float64(n))) / denom
|
||||
half := z * math.Sqrt(p*(1-p)/float64(n)+zz/(4*float64(n*n))) / denom
|
||||
lo, hi = centre-half, centre+half
|
||||
if lo < 0 {
|
||||
lo = 0
|
||||
}
|
||||
if hi > 1 {
|
||||
hi = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package selfoptimize
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// t_132cfa76 AC5 —— 判读门槛 + Wilson 区间的回归测试。
|
||||
|
||||
func TestUsefulnessReadGateConst(t *testing.T) {
|
||||
if UsefulnessReadGate != 5000 {
|
||||
t.Fatalf("UsefulnessReadGate = %d, want 5000 (AC5 写死,改需同步卡 t_132cfa76)", UsefulnessReadGate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWilsonInterval95(t *testing.T) {
|
||||
// 已知锚点:n=100, pos=30 → 30% 的 Wilson 95% ≈ (0.2181, 0.3978)
|
||||
lo, hi := WilsonInterval95(30, 100)
|
||||
if math.Abs(lo-0.2181) > 0.005 || math.Abs(hi-0.3978) > 0.005 {
|
||||
t.Fatalf("Wilson(30,100) = (%.4f, %.4f), want ~(0.2181, 0.3978)", lo, hi)
|
||||
}
|
||||
// n=0 → (0,0)
|
||||
lo, hi = WilsonInterval95(0, 0)
|
||||
if lo != 0 || hi != 0 {
|
||||
t.Fatalf("Wilson(0,0) = (%.4f, %.4f), want (0,0)", lo, hi)
|
||||
}
|
||||
// 全正 → 上界 1
|
||||
lo, hi = WilsonInterval95(100, 100)
|
||||
if hi > 1+1e-9 || lo < 0.96 {
|
||||
t.Fatalf("Wilson(100,100) = (%.4f, %.4f), 上界应≈1, 下界应>0.96", lo, hi)
|
||||
}
|
||||
// 全负 → 下界 0
|
||||
lo, hi = WilsonInterval95(0, 100)
|
||||
if lo < -1e-9 || hi > 0.04 {
|
||||
t.Fatalf("Wilson(0,100) = (%.4f, %.4f), 上界应<0.04", lo, hi)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordAutoFeedbackSeparateFromHuman(t *testing.T) {
|
||||
// 自动反馈账与人工账必须分账:RecordAutoFeedback 不碰 useful/not_useful。
|
||||
Dash.mu.Lock()
|
||||
Dash.UsefulCount, Dash.NotUsefulCount = 5, 3
|
||||
Dash.AutoFeedbackPositive, Dash.AutoFeedbackNegative = 0, 0
|
||||
Dash.mu.Unlock()
|
||||
before := Dash.UsefulCount + Dash.NotUsefulCount
|
||||
Dash.RecordAutoFeedback(true)
|
||||
Dash.RecordAutoFeedback(false)
|
||||
Dash.RecordAutoFeedback(false)
|
||||
if Dash.UsefulCount+Dash.NotUsefulCount != before {
|
||||
t.Fatalf("RecordAutoFeedback 污染了人工账: useful+not=%d (应仍=%d)",
|
||||
Dash.UsefulCount+Dash.NotUsefulCount, before)
|
||||
}
|
||||
if Dash.AutoFeedbackPositive != 1 || Dash.AutoFeedbackNegative != 2 {
|
||||
t.Fatalf("auto 账不对: pos=%d neg=%d (want 1/2)", Dash.AutoFeedbackPositive, Dash.AutoFeedbackNegative)
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,9 @@ type dashboardState struct {
|
||||
DistillSuccessCount int `json:"distill_success_count"`
|
||||
AutoResolvedConflicts int `json:"auto_resolved_conflicts"`
|
||||
TotalConflicts int `json:"total_conflicts"`
|
||||
// t_132cfa76:自动反馈样本账持久化字段(单调累加器,跨重启存活)。
|
||||
AutoFeedbackPositive int `json:"auto_feedback_positive"`
|
||||
AutoFeedbackNegative int `json:"auto_feedback_negative"`
|
||||
// t_e5997ca4:计数器版本标记(见 dashboardSchemaV2 注释)。
|
||||
SchemaVersion string `json:"dashboard_schema_version"`
|
||||
}
|
||||
@@ -114,6 +117,8 @@ func loadDashboardLocal() error {
|
||||
Dash.DistillSuccessCount = st.DistillSuccessCount
|
||||
Dash.AutoResolvedConflicts = st.AutoResolvedConflicts
|
||||
Dash.TotalConflicts = st.TotalConflicts
|
||||
Dash.AutoFeedbackPositive = st.AutoFeedbackPositive
|
||||
Dash.AutoFeedbackNegative = st.AutoFeedbackNegative
|
||||
// t_e5997ca4 (b):版本化一次性重置。本地 JSON 无 v2 标记(旧版/缺失)⇒ 丢弃被
|
||||
// 事件式自增污染的 gap 双计数器,回写标记;v2 标记在位 ⇒ 保真(新口径累计值)。
|
||||
if st.SchemaVersion != dashboardSchemaV2 {
|
||||
@@ -144,6 +149,8 @@ func (d *Dashboard) persistLocalLocked() {
|
||||
DistillSuccessCount: d.DistillSuccessCount,
|
||||
AutoResolvedConflicts: d.AutoResolvedConflicts,
|
||||
TotalConflicts: d.TotalConflicts,
|
||||
AutoFeedbackPositive: d.AutoFeedbackPositive,
|
||||
AutoFeedbackNegative: d.AutoFeedbackNegative,
|
||||
SchemaVersion: dashboardSchemaV2, // t_e5997ca4:v2 常驻回写(新安装/旧版升级都自动打标)
|
||||
}
|
||||
b, err := json.Marshal(st)
|
||||
@@ -179,6 +186,8 @@ func (d *Dashboard) persist() {
|
||||
_ = redisPersistence.HSet(dashboardRedisKey, "distill_success_count", strconv.Itoa(d.DistillSuccessCount))
|
||||
_ = redisPersistence.HSet(dashboardRedisKey, "auto_resolved_conflicts", strconv.Itoa(d.AutoResolvedConflicts))
|
||||
_ = redisPersistence.HSet(dashboardRedisKey, "total_conflicts", strconv.Itoa(d.TotalConflicts))
|
||||
_ = redisPersistence.HSet(dashboardRedisKey, "auto_feedback_positive", strconv.Itoa(d.AutoFeedbackPositive))
|
||||
_ = redisPersistence.HSet(dashboardRedisKey, "auto_feedback_negative", strconv.Itoa(d.AutoFeedbackNegative))
|
||||
_ = redisPersistence.HSet(dashboardRedisKey, "dashboard_schema_version", dashboardSchemaV2) // t_e5997ca4:v2 标记常驻回写
|
||||
_ = redisPersistence.Expire(dashboardRedisKey, 90*24*time.Hour) // 90 天 TTL
|
||||
}
|
||||
@@ -237,6 +246,12 @@ func loadDashboard() error {
|
||||
if v, err := strconv.Atoi(data["total_conflicts"]); err == nil {
|
||||
Dash.TotalConflicts = v
|
||||
}
|
||||
if v, err := strconv.Atoi(data["auto_feedback_positive"]); err == nil {
|
||||
Dash.AutoFeedbackPositive = v
|
||||
}
|
||||
if v, err := strconv.Atoi(data["auto_feedback_negative"]); err == nil {
|
||||
Dash.AutoFeedbackNegative = v
|
||||
}
|
||||
// t_e5997ca4 (b):版本化一次性重置(Redis 路径)。HASH 无 v2 标记(旧版遗留/缺失)⇒
|
||||
// 丢弃被事件式自增污染的 gap 双计数器(线上 9 vs 2 实例),回写标记;v2 在位 ⇒ 保真。
|
||||
if data["dashboard_schema_version"] != dashboardSchemaV2 {
|
||||
@@ -268,6 +283,10 @@ type Dashboard struct {
|
||||
DistillSuccessCount int `json:"distill_success_count"`
|
||||
AutoResolvedConflicts int `json:"auto_resolved_conflicts"`
|
||||
TotalConflicts int `json:"total_conflicts"`
|
||||
// t_132cfa76:自动反馈样本账(机械信号,与人工 useful/not_useful 分账)。
|
||||
// 判读门槛见 auto_feedback_calibration.go UsefulnessReadGate。
|
||||
AutoFeedbackPositive int `json:"auto_feedback_positive"`
|
||||
AutoFeedbackNegative int `json:"auto_feedback_negative"`
|
||||
}
|
||||
|
||||
var Dash = &Dashboard{}
|
||||
@@ -392,6 +411,21 @@ func (d *Dashboard) Metrics() map[string]float64 {
|
||||
"recall_usefulness_positive": float64(d.UsefulCount),
|
||||
"recall_usefulness_valid": usefulValid,
|
||||
|
||||
// t_132cfa76 AC5 —— 自动反馈样本账 + 判读门槛(写死,改需同步卡)。
|
||||
// 🔴 rail_usefulness_rate 样本 ≥ UsefulnessReadGate(5000) 才判读:
|
||||
// recall_usefulness_judgeable = 1 才允许用于排序裁决;=0 时只看方向,
|
||||
// 且判读必须配 Wilson 区间(recall_usefulness_wilson_lo/hi)一起看。
|
||||
"recall_usefulness_judgeable": func() float64 {
|
||||
if usefulSamples >= UsefulnessReadGate {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}(),
|
||||
"recall_usefulness_read_gate": float64(UsefulnessReadGate),
|
||||
"auto_feedback_positive": float64(d.AutoFeedbackPositive),
|
||||
"auto_feedback_negative": float64(d.AutoFeedbackNegative),
|
||||
"auto_feedback_samples": float64(d.AutoFeedbackPositive + d.AutoFeedbackNegative),
|
||||
|
||||
"gap_closure_rate": gapVal,
|
||||
"gap_closure_samples": float64(d.TotalGaps),
|
||||
"gap_closure_valid": gapValid,
|
||||
|
||||
Reference in New Issue
Block a user