feat(channels): 陌生人私聊正常聊天(跳概率门)+ 强制流控 6条/分与回合槽位 + 安静时段不再硬静默 + 受限车道边界行前置
- 陌生人私聊:不再走「值不值得回」的概率门,唯一闸门 = 每发送者 6 条/分滑窗 + 同会话 1 个在跑回合 - 被限流只登记不回,且留带 source 的可见告警(不静默) - 安静时段在回复侧只作信号(规则层不再静默),群聊取消夜间静默 - 锁定流控常量:FlowDirectDefault 加字面量钉桩测试(放大它 = 静默关流控,且会让额度循环跑百万轮)
This commit is contained in:
1 parent
c6f5c3fea0
commit
7ae4298871
10 files changed
+654
-118
No files matched your search
@@ -11,15 +11,17 @@ import (
|
||||
)
|
||||
|
||||
// 本文件承载渠道桥接器面向公开平台的入站「最小打扰」分级
|
||||
// (DevDocs/design/12 §6 策略一/二/五)。非主人的群聊/陌生人
|
||||
// 消息在进入完整 agent 循环前先经廉价判断,使繁忙的
|
||||
// 公开群不会把每条消息都扔进昂贵的 LLM 轮次。这些逻辑
|
||||
// 放在这里是为了让 channelbridge.go 保持在模块体积上限内;它绝不
|
||||
// 读取消息内容来判定身份——身份早已由
|
||||
// (DevDocs/design/12 §6 策略一/二)。非主人的**群聊**消息在进入完整
|
||||
// agent 循环前先经廉价判断,使繁忙的公开群不会把每条消息都扔进
|
||||
// 昂贵的 LLM 轮次;**陌生人私聊**则一律正常聊天(主人 2026-10-05 裁决①),
|
||||
// 只受强制流控约束(裁决③)。这些逻辑放在这里是为了让 channelbridge.go
|
||||
// 保持在模块体积上限内;它绝不读取消息内容来判定身份——身份早已由
|
||||
// 会话/actor 层结构化解析完毕(项目红线)。
|
||||
|
||||
// 安静时段默认值,与 backthink 服务保持一致,使桥接器与
|
||||
// 后台思考器共用同一个免打扰窗口(design/12 §6 策略五)。
|
||||
// 后台思考器共用同一个免打扰窗口(design/12 §6 策略五)。它在本车道里
|
||||
// **只作为信号**随 inQuiet 传给参与度引擎/轻量判定器,不再让规则层静默
|
||||
// (主人 2026-10-05 裁决②)。
|
||||
const (
|
||||
defaultQuietStartHour = 23
|
||||
defaultQuietEndHour = 7
|
||||
@@ -32,11 +34,10 @@ type lightVerdict int
|
||||
const (
|
||||
// verdictRespond 表示规则信号显示该消息值得回复。
|
||||
verdictRespond lightVerdict = iota
|
||||
// verdictSilent 表示规则信号显示该消息应登记但不应
|
||||
// 回复(例如安静时段内没有强信号)。
|
||||
verdictSilent
|
||||
// verdictInconclusive 表示规则无法判断;交给 LightJudge(未装配
|
||||
// 时则失败关闭为静默)。
|
||||
// verdictInconclusive 表示规则无法判断;交给参与度引擎(design/18)
|
||||
// 或注入式 LightJudge(未装配时则失败关闭为静默)。安静时段**不再**
|
||||
// 单独构成静默(主人 2026-10-05 裁决②)——它只作为信号随 inQuiet
|
||||
// 传给下游判定器,让轻思考/参与度自己权衡要不要回。
|
||||
verdictInconclusive
|
||||
)
|
||||
|
||||
@@ -130,12 +131,14 @@ func hasHighValueKeyword(text string) bool {
|
||||
}
|
||||
|
||||
// ruleLightJudge 只依据廉价规则信号判断是否值得回复
|
||||
// (设计/12 §6 策略二/五)。优先级:直接点名、提及主人的显示
|
||||
// 名、高价值关键词,其次是安静时段偏向静默;否则
|
||||
// 结论为无结论(交给注入的 LightJudge,没有则静默)。它
|
||||
// 只读取已解析的 actor 元数据与消息内容——绝不推断
|
||||
// 身份。
|
||||
func (b *Bridge) ruleLightJudge(msg adapter.UnifiedMessage, actor adapter.Actor, inQuiet bool) lightVerdict {
|
||||
// (设计/12 §6 策略二)。优先级:直接点名、提及主人的显示
|
||||
// 名、高价值关键词;除此之外一律无结论(交给参与度引擎 /
|
||||
// 注入的 LightJudge,没有则失败关闭为静默)。**安静时段不是
|
||||
// 这里的静默理由**(主人 2026-10-05 裁决②:安静时段由轻思考判断
|
||||
// 是否回复,不是硬性全静默)——该信号由调用方随 inQuiet 传给
|
||||
// 下游判定器。它只读取已解析的消息信号(提及/正文)——绝不
|
||||
// 推断身份(身份由 actor 元数据在会话层解析,红线)。
|
||||
func (b *Bridge) ruleLightJudge(msg adapter.UnifiedMessage) lightVerdict {
|
||||
if len(msg.Mentions) > 0 {
|
||||
return verdictRespond
|
||||
}
|
||||
@@ -146,20 +149,26 @@ func (b *Bridge) ruleLightJudge(msg adapter.UnifiedMessage, actor adapter.Actor,
|
||||
if hasHighValueKeyword(msg.Content) {
|
||||
return verdictRespond
|
||||
}
|
||||
if inQuiet {
|
||||
return verdictSilent
|
||||
}
|
||||
return verdictInconclusive
|
||||
}
|
||||
|
||||
// isStrangerPrivate 报告该会话是否为**陌生人私聊**(非群 + 非主人)。
|
||||
// 它只读会话层结构化解析出的标志(项目红线:身份不靠正文/提示词)。
|
||||
// 主人私聊在 onMessage/shouldRespond 之前就已被 actor 门拦下,因此
|
||||
// 这里成真的只会是陌生人私聊。
|
||||
func isStrangerPrivate(sess session.Session) bool {
|
||||
return !sess.IsGroup && !sess.Owner
|
||||
}
|
||||
|
||||
// shouldRespond 实现非主人入站消息的最小打扰分级
|
||||
// (设计/12 §6 策略一/二/五 + design/18 §2/§4/§5)。它返回该消息
|
||||
// (设计/12 §6 策略一/二 + design/18 §2/§4/§5)。它返回该消息
|
||||
// 应驱动完整 agent 循环并回复,还是只登记并静默。
|
||||
// 主人消息绝不会到这里(processFrom 以 !actor.IsOwner 为门),
|
||||
// 因此主人私聊与主人在群里的 @/高价值回复不受影响。sess 是
|
||||
// 该消息解析出的隔离群聊/陌生人会话绑定,使参与度
|
||||
// 引擎探测时能读到群的近期上下文。
|
||||
func (b *Bridge) shouldRespond(ctx context.Context, msg adapter.UnifiedMessage, actor adapter.Actor, sess session.Session) (bool, string) {
|
||||
// 引擎探测时能读到群的近期上下文;a 是收到该消息的账号适配器,
|
||||
// 用于取该账号的流控闸门(陌生人私聊的强制限流)。
|
||||
func (b *Bridge) shouldRespond(ctx context.Context, msg adapter.UnifiedMessage, actor adapter.Actor, sess session.Session, a adapter.PlatformAdapter) (bool, string) {
|
||||
// 策略一:平台/系统噪声 → 只登记,绝不进完整循环。
|
||||
if isLowValueEvent(actor) {
|
||||
b.log.Debug("non-owner low-value system event, registered only",
|
||||
@@ -167,18 +176,29 @@ func (b *Bridge) shouldRespond(ctx context.Context, msg adapter.UnifiedMessage,
|
||||
return false, "low-value system event; registered only"
|
||||
}
|
||||
|
||||
// 策略二/五:基于规则的轻量判定,感知安静时段。直接 @、
|
||||
// 提及主人显示名或高价值关键词一律回复;安静时段
|
||||
// 偏向静默。只有无结论的剩余部分才咨询
|
||||
// 参与度引擎。
|
||||
// 裁决①(主人 2026-10-05):**陌生人私聊 = 正常聊天** —— 不设安静期、
|
||||
// 不走「值不值得回」的概率门,直接进入完整回合。唯一的闸门是强制
|
||||
// 流控:同一发送者每分钟 ≤ FlowDirectDefault 条(AllowDirect 记账)
|
||||
// 与同时最多 1 个在跑的回合(processFrom 用 AcquireTurn 施加)。
|
||||
// 被限流时**不回**:调用方会把它登记进隔离会话,这里留一条带
|
||||
// source 的可见告警(绝不静默)。
|
||||
if isStrangerPrivate(sess) {
|
||||
if g := b.gateFor(a); g != nil && !g.AllowDirect(msg) {
|
||||
b.log.Warn("stranger private chat rate limited, registered only",
|
||||
"platform", msg.Platform, "channel", msg.ChannelID, "sender", msg.SenderID,
|
||||
"limit_per_min", adapter.FlowDirectDefault)
|
||||
return false, "stranger private: over the per-sender flow limit"
|
||||
}
|
||||
return true, "stranger private chat: normal reply"
|
||||
}
|
||||
|
||||
// 策略二/五:基于规则的轻量判定。直接 @、提及主人显示名或
|
||||
// 高价值关键词一律直接回;规则得不出结论的部分交给参与度
|
||||
// 引擎 / 轻量判定器。**安静时段只作为信号**(inQuiet)继续往下传,
|
||||
// 规则层不再因为它静默(裁决②)。
|
||||
inQuiet := b.inQuietHours(b.now())
|
||||
switch b.ruleLightJudge(msg, actor, inQuiet) {
|
||||
case verdictRespond:
|
||||
if b.ruleLightJudge(msg) == verdictRespond {
|
||||
return true, "rule signal: worth responding"
|
||||
case verdictSilent:
|
||||
b.log.Debug("non-owner message silenced (quiet/no signal), registered only",
|
||||
"platform", msg.Platform, "channel", msg.ChannelID, "quiet", inQuiet, "actor_role", actor.Role)
|
||||
return false, "rule signal: not worth responding"
|
||||
}
|
||||
|
||||
// 廉价规则得不出结论。参与度引擎(design/18)已配置时
|
||||
@@ -245,6 +265,18 @@ func (b *Bridge) registerSilenced(ctx context.Context, msg adapter.UnifiedMessag
|
||||
"platform", msg.Platform, "channel", msg.ChannelID, "session", sess.SessionID)
|
||||
}
|
||||
|
||||
// registerThrottled 是一次被强制流控(同一发送者已有回合在跑)拦下的
|
||||
// 非主人消息的落点:先走最小打扰的登记路径(registerSilenced)把它写进
|
||||
// 其隔离会话,再留一条**带 source 的可见告警** —— 被限流绝不静默
|
||||
// (主人 2026-10-05 裁决③)。限流路径与普通静默因此区分得开:前者
|
||||
// 一定有可见日志,后者只有 debug。
|
||||
func (b *Bridge) registerThrottled(ctx context.Context, msg adapter.UnifiedMessage, actor adapter.Actor, sess session.Session, a adapter.PlatformAdapter, reason string) {
|
||||
b.registerSilenced(ctx, msg, actor, sess, a)
|
||||
b.log.Warn("inbound message throttled by flow gate, registered only",
|
||||
"platform", msg.Platform, "channel", msg.ChannelID,
|
||||
"sender", msg.SenderID, "reason", reason)
|
||||
}
|
||||
|
||||
// inQuietHours 报告 at 是否落在配置的免打扰窗口内
|
||||
// (与 backthink 语义一致:两个非零且相等的时刻关闭该窗口,
|
||||
// 跨午夜的窗口也能正确处理)。它是 Bridge 上的纯方法,测试
|
||||
|
||||
Reference in new issue
Block a user