docs(comments): 注释中文化 · internal/services/channelbridge(纯替换)
- 只翻注释/文档字符串:剥掉注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态) - 术语对齐既有译法(owner→主人、persona→角色、engage→参与度判定、quiet hours→安静时段、backoff→退避) - 复核:go build 绿 + channelbridge 包测试绿;gofmt 干净
This commit is contained in:
1 parent
61cf80d5ee
commit
093f38d986
28 files changed
+1089
-1089
No files matched your search
@@ -10,39 +10,39 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
|
||||
)
|
||||
|
||||
// This file holds the channel bridge's minimal-disturbance inbound classification
|
||||
// for public platforms (DevDocs/design/12 §6 策略一/二/五). Non-owner group/stranger
|
||||
// messages are judged cheaply before they reach the full agent loop, so a busy
|
||||
// public group does not fling every message into an expensive LLM turn. The logic
|
||||
// lives here to keep channelbridge.go under the module size bound; it never reads
|
||||
// message content to decide identity — identity is already resolved structurally by
|
||||
// the session/actor layer (a project red line).
|
||||
// 本文件承载渠道桥接器面向公开平台的入站「最小打扰」分级
|
||||
// (DevDocs/design/12 §6 策略一/二/五)。非主人的群聊/陌生人
|
||||
// 消息在进入完整 agent 循环前先经廉价判断,使繁忙的
|
||||
// 公开群不会把每条消息都扔进昂贵的 LLM 轮次。这些逻辑
|
||||
// 放在这里是为了让 channelbridge.go 保持在模块体积上限内;它绝不
|
||||
// 读取消息内容来判定身份——身份早已由
|
||||
// 会话/actor 层结构化解析完毕(项目红线)。
|
||||
|
||||
// Quiet-hour defaults, mirroring the backthink service so the bridge and the
|
||||
// background thinker share one do-not-disturb window (design/12 §6 策略五).
|
||||
// 安静时段默认值,与 backthink 服务保持一致,使桥接器与
|
||||
// 后台思考器共用同一个免打扰窗口(design/12 §6 策略五)。
|
||||
const (
|
||||
defaultQuietStartHour = 23
|
||||
defaultQuietEndHour = 7
|
||||
)
|
||||
|
||||
// lightVerdict is the outcome of a rule-based light judgement before the optional
|
||||
// injected LightJudge is consulted (design/12 §6 策略二/五).
|
||||
// lightVerdict 是规则轻量判定给出的结果,早于咨询可选的
|
||||
// 注入式 LightJudge(design/12 §6 策略二/五)。
|
||||
type lightVerdict int
|
||||
|
||||
const (
|
||||
// verdictRespond means the rule signals the message is worth a response.
|
||||
// verdictRespond 表示规则信号显示该消息值得回复。
|
||||
verdictRespond lightVerdict = iota
|
||||
// verdictSilent means the rule signals the message should be registered but not
|
||||
// answered (e.g. during quiet hours with no strong signal).
|
||||
// verdictSilent 表示规则信号显示该消息应登记但不应
|
||||
// 回复(例如安静时段内没有强信号)。
|
||||
verdictSilent
|
||||
// verdictInconclusive means the rules cannot tell; defer to the LightJudge (or
|
||||
// fail-closed to silence when none is wired).
|
||||
// verdictInconclusive 表示规则无法判断;交给 LightJudge(未装配
|
||||
// 时则失败关闭为静默)。
|
||||
verdictInconclusive
|
||||
)
|
||||
|
||||
// LightInput is the set of signals a LightJudge receives to decide response
|
||||
// worthiness. Identity is already resolved structurally by the session/actor
|
||||
// layer, so the judge weighs only 值不值得回 — it never decides who the sender is.
|
||||
// LightInput 是 LightJudge 判断是否值得回复时收到的信号集合。
|
||||
// 身份早已由会话/actor 层结构化解析完毕,
|
||||
// 因此判定器只权衡值不值得回 —— 绝不判断发送者是谁。
|
||||
type LightInput struct {
|
||||
Msg adapter.UnifiedMessage
|
||||
Actor adapter.Actor
|
||||
@@ -51,74 +51,74 @@ type LightInput struct {
|
||||
QuietStart int
|
||||
QuietEnd int
|
||||
|
||||
// Engage reports whether this is a cost-controlled participation judgment
|
||||
// (design/18): it is set only when a trigger (role-name mention / probability)
|
||||
// or an active activity window routed the message here, so the caller feeds the
|
||||
// group context and persona prompt ONCE and never on every chat message. A
|
||||
// legacy (design/12 §6) inconclusive judgment leaves it false.
|
||||
// Engage 报告这是否是一次受成本控制的参与度判定
|
||||
// (design/18):只有触发(角色名提及 / 概率)
|
||||
// 或活跃窗口把消息送到这里时才为真,因此调用方只投喂一次
|
||||
// 群上下文与角色提示词,绝不会按每条聊天消息投喂。旧的
|
||||
// (design/12 §6)无结论判定会使它为 false。
|
||||
Engage bool
|
||||
// PersonaPrompt is the current persona's identity/system-prompt block, so the
|
||||
// judge can weigh the role's tone without ever deciding identity. It carries no
|
||||
// owner secret (a project red line).
|
||||
// PersonaPrompt 是当前角色的身份/系统提示词块,使判定器
|
||||
// 能权衡角色的语气而不涉身份判定。它不含
|
||||
// 主人敏感信息(项目红线)。
|
||||
PersonaPrompt string
|
||||
// RecentContext is the group conversation's recent N messages rendered with each
|
||||
// sender's display name, plus the triggering message, so the judge can tell
|
||||
// whether the topic is worth participating in.
|
||||
// RecentContext 是群会话最近 N 条消息(带各发送者
|
||||
// 显示名)加上触发消息的渲染文本,使判定器能判断
|
||||
// 该话题是否值得参与。
|
||||
RecentContext string
|
||||
// TriggerWords are the current persona's role-name reminder words (design/18 §2),
|
||||
// passed as context so the judge knows which word was mentioned.
|
||||
// TriggerWords 是当前角色的角色名提醒词(design/18 §2),
|
||||
// 作为上下文传入,使判定器知道被提及的是哪个词。
|
||||
TriggerWords []string
|
||||
}
|
||||
|
||||
// LightDecision is a LightJudge's verdict.
|
||||
// LightDecision 是 LightJudge 的裁决。
|
||||
type LightDecision struct {
|
||||
// Respond is the legacy design/12 §6 simple verdict (respond or not). It is kept
|
||||
// for backward compatibility; the design/18 fields below carry the structured
|
||||
// participation verdict that the engage engine reads.
|
||||
// Respond 是旧的 design/12 §6 简单裁决(回不回)。保留它
|
||||
// 是为了向后兼容;下面的 design/18 字段承载结构化的
|
||||
// 参与度裁决,供参与度引擎读取。
|
||||
Respond bool
|
||||
Reason string
|
||||
|
||||
// Interesting reports whether she finds the topic interesting (design/18) — the
|
||||
// signal to enter/remain ACTIVE.
|
||||
// Interesting 报告她是否觉得该话题有意思(design/18)——
|
||||
// 进入/保持 ACTIVE 的信号。
|
||||
Interesting bool
|
||||
// WorthReply reports whether the message is worth a reply.
|
||||
// WorthReply 报告该消息是否值得回复。
|
||||
WorthReply bool
|
||||
// AvoidTopic reports whether the topic should be avoided (sensitive / privet /
|
||||
// red-line), so she does not engage even when otherwise interested.
|
||||
// AvoidTopic 报告该话题是否应回避(敏感 / 私密 /
|
||||
// 红线),使她在其他方面感兴趣时也不参与。
|
||||
AvoidTopic bool
|
||||
}
|
||||
|
||||
// LightJudge is the optional cost-controlled light-response judge (design/12 §6
|
||||
// 策略二/五). The bridge consults it only when the built-in rule judge is
|
||||
// inconclusive, so a chat message is not repeatedly sent to an expensive LLM. A
|
||||
// nil LightJudge (the default) keeps the bridge rule-only and fail-closed toward
|
||||
// silence (不打扰为第一原则). The production wiring does not supply one yet, so the
|
||||
// seam is ready but not token-consuming until it is deliberately wired.
|
||||
// LightJudge 是可选的受成本控制的轻量应答判定器(design/12 §6
|
||||
// 策略二/五)。桥接器只在内置规则判定器得不出结论时咨询它,
|
||||
// 因此聊天消息不会被反复送往昂贵的 LLM。LightJudge 为
|
||||
// nil(默认)时桥接器只用规则,并失败关闭为
|
||||
// 静默(不打扰为第一原则)。生产装配尚未提供它,因此这个
|
||||
// 接缝已就绪,但在被刻意接入前不消耗 token。
|
||||
type LightJudge interface {
|
||||
Judge(ctx context.Context, in LightInput) (LightDecision, error)
|
||||
}
|
||||
|
||||
// highValueKeywords is the small term list the rule judge matches to decide a
|
||||
// message is worth a response even when the sender was not directly addressed
|
||||
// (设计/12 §6 策略二: 关键词重要度). It is a cheap substring match — never an LLM call,
|
||||
// and never used to infer identity.
|
||||
// highValueKeywords 是规则判定器用于匹配的小词表:即使发送者
|
||||
// 未被直接点名,命中即认为该消息值得回复
|
||||
// (设计/12 §6 策略二:关键词重要度)。它是廉价子串匹配——不调用 LLM,
|
||||
// 也绝不用于推断身份。
|
||||
var highValueKeywords = []string{
|
||||
"紧急", "救命", "故障", "坏掉", "坏了", "报错", "出错", "崩溃",
|
||||
"求助", "帮帮我", "help", "urgent", "error", "bug",
|
||||
}
|
||||
|
||||
// isLowValueEvent reports whether a non-owner message is platform/system noise
|
||||
// that must be registered but never drives the full agent loop (设计/12 §6 策略一).
|
||||
// It is only consulted for non-owner messages: a system/synthetic sender is the
|
||||
// canonical low-value signal. Messages with no meaningful chat text are dropped at
|
||||
// the bridge head anyway, so this primarily gates system-actor notifications.
|
||||
// isLowValueEvent 报告非主人消息是否为平台/系统噪声:
|
||||
// 必须登记,但绝不驱动完整 agent 循环(设计/12 §6 策略一)。
|
||||
// 它只对非主人消息咨询:系统/合成发送者是最典型的
|
||||
// 低价值信号。没有实质聊天文本的消息本就会在
|
||||
// 桥接器入口被丢弃,因此它主要用于拦系统 actor 的通知。
|
||||
func isLowValueEvent(actor adapter.Actor) bool {
|
||||
return actor.IsSystem()
|
||||
}
|
||||
|
||||
// hasHighValueKeyword reports whether text carries a term worth responding to even
|
||||
// when the sender was not directly addressed. Kept tiny so the rule path stays O(1)
|
||||
// per message.
|
||||
// hasHighValueKeyword 报告文本是否含有「即使未被直接点名
|
||||
// 也值得回复」的词。词表保持极小,使规则路径对每条消息
|
||||
// 都是 O(1)。
|
||||
func hasHighValueKeyword(text string) bool {
|
||||
lower := strings.ToLower(text)
|
||||
for _, k := range highValueKeywords {
|
||||
@@ -129,12 +129,12 @@ func hasHighValueKeyword(text string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ruleLightJudge decides response-worthiness from cheap rule signals only
|
||||
// (设计/12 §6 策略二/五). Priority: a direct mention, a mention of the owner's display
|
||||
// name, a high-value keyword, then a quiet-hours bias toward silence; otherwise the
|
||||
// verdict is inconclusive (defer to the injected LightJudge, else silence). It
|
||||
// reads only already-resolved actor metadata and message content — never infers
|
||||
// identity.
|
||||
// ruleLightJudge 只依据廉价规则信号判断是否值得回复
|
||||
// (设计/12 §6 策略二/五)。优先级:直接点名、提及主人的显示
|
||||
// 名、高价值关键词,其次是安静时段偏向静默;否则
|
||||
// 结论为无结论(交给注入的 LightJudge,没有则静默)。它
|
||||
// 只读取已解析的 actor 元数据与消息内容——绝不推断
|
||||
// 身份。
|
||||
func (b *Bridge) ruleLightJudge(msg adapter.UnifiedMessage, actor adapter.Actor, inQuiet bool) lightVerdict {
|
||||
if len(msg.Mentions) > 0 {
|
||||
return verdictRespond
|
||||
@@ -152,25 +152,25 @@ func (b *Bridge) ruleLightJudge(msg adapter.UnifiedMessage, actor adapter.Actor,
|
||||
return verdictInconclusive
|
||||
}
|
||||
|
||||
// shouldRespond implements the minimal-disturbance classification for a non-owner
|
||||
// inbound message (设计/12 §6 策略一/二/五 + design/18 §2/§4/§5). It returns whether
|
||||
// the message should drive the full agent loop and reply, or be registered-and-
|
||||
// silenced. Owner messages never reach here (processFrom gates on !actor.IsOwner),
|
||||
// so owner-private chat and owner group @/high-value replies are untouched. sess is
|
||||
// the isolated group/stranger session binding the message resolved to, so the
|
||||
// participation engine can read the group's recent context when it probes.
|
||||
// shouldRespond 实现非主人入站消息的最小打扰分级
|
||||
// (设计/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) {
|
||||
// 策略一: platform/system noise → register only, never the full loop.
|
||||
// 策略一:平台/系统噪声 → 只登记,绝不进完整循环。
|
||||
if isLowValueEvent(actor) {
|
||||
b.log.Debug("non-owner low-value system event, registered only",
|
||||
"platform", msg.Platform, "channel", msg.ChannelID, "actor_role", actor.Role)
|
||||
return false, "low-value system event; registered only"
|
||||
}
|
||||
|
||||
// 策略二/五: rule-based light judgement, quiet-hours aware. A direct @, a mention
|
||||
// of the owner's display name, or a high-value keyword always responds; quiet
|
||||
// hours bias toward silence. The participation engine is only consulted on the
|
||||
// inconclusive remainder.
|
||||
// 策略二/五:基于规则的轻量判定,感知安静时段。直接 @、
|
||||
// 提及主人显示名或高价值关键词一律回复;安静时段
|
||||
// 偏向静默。只有无结论的剩余部分才咨询
|
||||
// 参与度引擎。
|
||||
inQuiet := b.inQuietHours(b.now())
|
||||
switch b.ruleLightJudge(msg, actor, inQuiet) {
|
||||
case verdictRespond:
|
||||
@@ -181,21 +181,21 @@ func (b *Bridge) shouldRespond(ctx context.Context, msg adapter.UnifiedMessage,
|
||||
return false, "rule signal: not worth responding"
|
||||
}
|
||||
|
||||
// The cheap rules are inconclusive. When the participation engine (design/18) is
|
||||
// configured it takes over and only consults the light judge on an actual trigger
|
||||
// (role-name mention / probability) or an active activity window — never on every
|
||||
// chat message, so cost stays controlled (a red line). Without engage
|
||||
// configuration the bridge keeps the pre-engage legacy behaviour: consult the
|
||||
// optional injected light judge on every inconclusive message, else fail-closed.
|
||||
// 廉价规则得不出结论。参与度引擎(design/18)已配置时
|
||||
// 由它接管,且只在真正触发(角色名提及 / 概率)或
|
||||
// 活跃窗口内才咨询轻量判定器——绝不按每条
|
||||
// 聊天消息咨询,成本因此可控(红线)。未配置参与度
|
||||
// 引擎时,桥接器保持引入引擎之前的旧行为:对每条无结论
|
||||
// 消息咨询可选的注入式轻量判定器,否则失败关闭。
|
||||
if !b.engageConfigured() {
|
||||
return b.legacyLightJudge(ctx, msg, actor, inQuiet)
|
||||
}
|
||||
return b.engageDecision(ctx, msg, actor, sess, inQuiet)
|
||||
}
|
||||
|
||||
// legacyLightJudge is the pre-design/18 inconclusive handling: consult the optional
|
||||
// injected light judge, fail-closed toward silence on error or absence
|
||||
// (不打扰为第一原则). It is only reached when the participation engine is not wired.
|
||||
// legacyLightJudge 是 design/18 之前的无结论处理:咨询可选的
|
||||
// 注入式轻量判定器,出错或缺失时失败关闭为静默
|
||||
// (不打扰为第一原则)。仅在未装配参与度引擎时才走到。
|
||||
func (b *Bridge) legacyLightJudge(ctx context.Context, msg adapter.UnifiedMessage, actor adapter.Actor, inQuiet bool) (bool, string) {
|
||||
if b.light != nil {
|
||||
d, err := b.light.Judge(ctx, LightInput{
|
||||
@@ -215,9 +215,9 @@ func (b *Bridge) legacyLightJudge(ctx context.Context, msg adapter.UnifiedMessag
|
||||
return false, "no rule signal; silenced (fail-closed)"
|
||||
}
|
||||
|
||||
// mentionsTriggerWord reports whether the message content names the current persona
|
||||
// via one of its trigger words (design/18 §2 触发②). It is a cheap substring match —
|
||||
// never an LLM call, and never used to infer who the sender is.
|
||||
// mentionsTriggerWord 报告消息内容是否通过当前角色的某个触发词
|
||||
// 提到该角色(design/18 §2 触发②)。它是廉价子串匹配——
|
||||
// 不调用 LLM,也绝不用于推断发送者是谁。
|
||||
func (b *Bridge) mentionsTriggerWord(text string) bool {
|
||||
if text == "" {
|
||||
return false
|
||||
@@ -230,21 +230,21 @@ func (b *Bridge) mentionsTriggerWord(text string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// registerSilenced persists a silenced non-owner inbound turn into its isolated
|
||||
// session's platform log so the adapter log page still renders the non-owner
|
||||
// stream (design/16 §4.6 §4), then stops — no loop, no reply. a is the account
|
||||
// adapter that received the message, so the log is tagged with the adapter config.
|
||||
// It is the 登记 side of the minimal-disturbance path.
|
||||
// registerSilenced 把一次被静默的非主人入站对话持久化进其隔离
|
||||
// 会话的平台日志,使适配器日志页仍能渲染非主人
|
||||
// 消息流(design/16 §4.6 §4),随后就此结束——不进循环、不回复。a 是
|
||||
// 收到该消息的账号适配器,日志因此带上适配器配置标签。
|
||||
// 它是最小打扰路径中「登记」的那一侧。
|
||||
func (b *Bridge) registerSilenced(ctx context.Context, msg adapter.UnifiedMessage, actor adapter.Actor, sess session.Session, a adapter.PlatformAdapter) {
|
||||
b.persistIsolated(ctx, history.RoleUser, msg.Content, deviceIDFor(msg), sess, msg.Platform, senderDisplayName(msg, actor), adapterName(a))
|
||||
b.log.Debug("non-owner message silenced, registered only",
|
||||
"platform", msg.Platform, "channel", msg.ChannelID, "session", sess.SessionID)
|
||||
}
|
||||
|
||||
// inQuietHours reports whether at falls inside the configured do-not-disturb
|
||||
// window (shared semantics with backthink: equal non-zero hours disable it, and a
|
||||
// window that wraps midnight is handled). It is a pure method on Bridge so tests
|
||||
// drive it deterministically via the Now source.
|
||||
// inQuietHours 报告 at 是否落在配置的免打扰窗口内
|
||||
// (与 backthink 语义一致:两个非零且相等的时刻关闭该窗口,
|
||||
// 跨午夜的窗口也能正确处理)。它是 Bridge 上的纯方法,测试
|
||||
// 可通过 Now 源确定性地驱动它。
|
||||
func (b *Bridge) inQuietHours(at time.Time) bool {
|
||||
h := at.Hour()
|
||||
start, end := b.quietStartHour, b.quietEndHour
|
||||
|
||||
Reference in new issue
Block a user