feat(memory): T1 加保守全局速率限流(防每天数百到上千次调用失控)
主人 2026-10-07 裁定:按最优解加全局限流。 - 新增 t1RateGate(滑动窗口、可注入时钟,~70 行);默认 120 次 / 60 分钟(现网 700-1000 条新记忆/天 ≈ 25-42/小时, 取 120 留约 3-5 倍余量) - 过闸摆在「t1Enabled + 有灰区候选」两个守卫之后、发请求之前 ⇒ 不调模型就不占额度;放行才记账 - 超限语义与既有失败路径同源:跳过 T1、决定仍是 create、零改写, 只多一个 reason=t1_throttled 进留痕 + ReviewStats.T1Throttled 计数 - 配置键 t1PerWindow / t1WindowSec 放 $DATA_DIR/config/memory-tidy.json (零新 env 键,启动期读一次);坏配置回落默认 + WARN,不启动失败 - 每窗口只记一条 WARN(超限是预期行为,一天几百条会淹掉真故障) - IngestRuleVersion 仍为 d3.2:判定规则集与解析契约没变,变的只是 额度状态,reason 列已可分辨(升版会有 §17.6 记过的幂等键重复行副作用) 自研而非复用 session.NewRateLimiter 的理由(design/12 §N C.5 自己登记过 这条取舍):既有件 Allow() 内部写死 time.Now(),没有时钟注入缝, 而本切片必须能注入可控时钟;且复用会把 channel 层概念拖进 memory 包。
This commit is contained in:
1 parent
987cefdf28
commit
a684328b76
10 files changed
+782
-10
No files matched your search
@@ -31,7 +31,9 @@ import (
|
||||
// 3. **规则优先、模型兜底**:T0 是纯规则(design §15 D3-2 T0),**恒为零模型
|
||||
// 调用**;T1(灰区调一次轻量模型,§15.1 D3-2)默认**开**,但只在「T0 判不出
|
||||
// 结论且存在灰区候选」时才发出**至多一次**调用(preinsert_t1.go),并且
|
||||
// 要有模型出口才成立 —— 两者任一缺席即是结构性零模型调用。
|
||||
// 要有模型出口才成立 —— 两者任一缺席即是结构性零模型调用。每一次这样的
|
||||
// 调用还要先过**全局限速**(默认 120 次/小时,preinsert_t1_limit.go):
|
||||
// 超限即跳过 T1、回落 create + `t1_throttled`。
|
||||
// **同步入库闸(preinsert_gate.go)不碰 T1**,写入路径因此仍是零模型调用。
|
||||
//
|
||||
// 代价(如实登记):极短窗口内库里会多一行待裁定的重复条目,随后被软合并
|
||||
@@ -141,6 +143,11 @@ type PreInsertReviewer struct {
|
||||
log *slog.Logger
|
||||
now func() time.Time
|
||||
queue chan MemoryItem
|
||||
// t1Gate 是 T1 的**全局限速**闸(design/23 §15.4)。挂在实例上而不是包级
|
||||
// 变量:AGENTS §10 禁包级全局变量,而组合根只建一个审查服务 ⇒ 实例字段
|
||||
// 事实上就是「全进程共享一个窗口额度」。它与 r.now 同源,因此测试可以整体
|
||||
// 推进时钟(见 preinsert_t1_limit.go)。
|
||||
t1Gate *t1RateGate
|
||||
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
@@ -168,6 +175,11 @@ func NewPreInsertReviewer(cfg ReviewConfig, deps ReviewDeps) (*PreInsertReviewer
|
||||
log: logger.WithSource(baseTidyLog(deps.Log), "memory/review"),
|
||||
now: now,
|
||||
queue: make(chan MemoryItem, cfg.QueueSize),
|
||||
// 限流闸在 normalize **之后**构建:闸把非正的额度读成「恒放行」,因此
|
||||
// 归一前的 ≤0 一旦直达它,「回落默认」就会静默变成「无上限」
|
||||
// (design/12 §N C.5 G3 登记过的失败形态)。这里的 cfg 来自上面那行
|
||||
// normalizeReviewConfig。
|
||||
t1Gate: newT1RateGate(cfg.T1PerWindow, cfg.T1Window, now),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -243,6 +255,10 @@ func (r *PreInsertReviewer) Start(ctx context.Context) {
|
||||
"t1_off_by_config", r.cfg.DisableT1,
|
||||
"t1_timeout", r.t1Timeout().String(),
|
||||
"t1_max_gray", defaultT1MaxGray,
|
||||
// T1 的全局限速额度(已 normalize 的**生效值**,不是配置原文):
|
||||
// 「以为不限流其实限着」与反过来的误解都在启动行上即可排除。
|
||||
"t1_per_window", r.cfg.T1PerWindow,
|
||||
"t1_window", r.cfg.T1Window.String(),
|
||||
"sweep_interval", DefaultIngestReviewGCInterval.String(),
|
||||
"retention", DefaultIngestReviewRetention.String(),
|
||||
"max_reviews", DefaultIngestReviewMaxRows)
|
||||
|
||||
Reference in new issue
Block a user