feat: Phase 1+2 架构进化 — 连续思考链/主动消息决策/情感状态机/离线自主思考 (86文件)
Phase 1 (基础设施): - ThinkChain 思考链连续性 + 差异化思考提示词 (persistent) - AutonomousToolPolicy 工具安全策略 (safe/unsafe/conditional) - MessageScheduler 自适应消息节奏 (Idle/Available/Busy) - SessionEnrichmentStore 渐进式上下文丰富 (5层) - ConversationBus 事件总线 + ResponseCache (dedup) - pkg/logger 统一日志 + 所有 handler 替换 fmt.Printf - NPE 守卫/链路优化/数据库表修复/Go workspace Phase 2 (人格交互): - EmotionState/EmotionTracker 情感状态机 (5种心情, 情绪衰减) - ProactiveGuard 主动消息多维决策 (静默时段/紧急度/频率/校验) - Gateway↔ai-core 在线状态感知链路 (presence notification) - 离线思考频率控制 + 重连问候 + 离线消息排队 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
package background
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProactiveDecision represents a decision about whether to send a proactive message.
|
||||
type ProactiveDecision struct {
|
||||
ShouldSend bool `json:"should_send"`
|
||||
Urgency string `json:"urgency"` // low, medium, high
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// ProactiveGuard evaluates whether a proactive message should be sent
|
||||
// based on time-of-day, urgency, rate limits, and user state.
|
||||
type ProactiveGuard struct {
|
||||
// Quiet hours: no non-urgent messages during this window
|
||||
QuietHoursStart int // 0-23, default 23
|
||||
QuietHoursEnd int // 0-23, default 7
|
||||
|
||||
// Min gap between proactive messages, by urgency
|
||||
MinGapByUrgency map[string]time.Duration
|
||||
|
||||
// Max proactive messages per hour
|
||||
MaxMessagesPerHour int
|
||||
|
||||
// Track recent send times for rate limiting
|
||||
recentSends []time.Time
|
||||
}
|
||||
|
||||
// DefaultProactiveGuard returns a guard with sensible defaults.
|
||||
func DefaultProactiveGuard() *ProactiveGuard {
|
||||
return &ProactiveGuard{
|
||||
QuietHoursStart: 23,
|
||||
QuietHoursEnd: 7,
|
||||
MinGapByUrgency: map[string]time.Duration{
|
||||
"low": 30 * time.Minute,
|
||||
"medium": 10 * time.Minute,
|
||||
"high": 2 * time.Minute,
|
||||
},
|
||||
MaxMessagesPerHour: 3,
|
||||
}
|
||||
}
|
||||
|
||||
// IsQuietHour returns true if the given time falls within quiet hours.
|
||||
func (g *ProactiveGuard) IsQuietHour(now time.Time) bool {
|
||||
hour := now.Hour()
|
||||
if g.QuietHoursStart < g.QuietHoursEnd {
|
||||
return hour >= g.QuietHoursStart && hour < g.QuietHoursEnd
|
||||
}
|
||||
// Overnight quiet hours (e.g., 23:00 - 07:00)
|
||||
return hour >= g.QuietHoursStart || hour < g.QuietHoursEnd
|
||||
}
|
||||
|
||||
// Evaluate checks whether a proactive message should be sent.
|
||||
func (g *ProactiveGuard) Evaluate(now time.Time, lastProactiveTime time.Time, urgency string, userState string) ProactiveDecision {
|
||||
// 1. Quiet hours: only high urgency messages pass
|
||||
if g.IsQuietHour(now) && urgency != "high" {
|
||||
return ProactiveDecision{
|
||||
ShouldSend: false,
|
||||
Urgency: urgency,
|
||||
Reason: "当前处于安静时段(23:00-07:00),仅紧急消息可推送",
|
||||
}
|
||||
}
|
||||
|
||||
// 2. User state check: don't disturb if user is resting/busy
|
||||
if userState == "resting" || userState == "busy" || userState == "sleeping" {
|
||||
if urgency != "high" {
|
||||
return ProactiveDecision{
|
||||
ShouldSend: false,
|
||||
Urgency: urgency,
|
||||
Reason: "开拓者正在休息/忙碌,不打扰",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Rate limit by urgency
|
||||
minGap, ok := g.MinGapByUrgency[urgency]
|
||||
if !ok {
|
||||
minGap = g.MinGapByUrgency["low"]
|
||||
}
|
||||
if !lastProactiveTime.IsZero() && now.Sub(lastProactiveTime) < minGap {
|
||||
return ProactiveDecision{
|
||||
ShouldSend: false,
|
||||
Urgency: urgency,
|
||||
Reason: "距上次主动消息时间过短(" + minGap.String() + " 最小间隔)",
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Hourly rate limit
|
||||
g.pruneOldSends(now)
|
||||
if len(g.recentSends) >= g.MaxMessagesPerHour {
|
||||
return ProactiveDecision{
|
||||
ShouldSend: false,
|
||||
Urgency: urgency,
|
||||
Reason: "本小时主动消息已达上限",
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Content length validation (caller should also check)
|
||||
return ProactiveDecision{
|
||||
ShouldSend: true,
|
||||
Urgency: urgency,
|
||||
Reason: "",
|
||||
}
|
||||
}
|
||||
|
||||
// RecordSend records a proactive message send for rate limiting.
|
||||
func (g *ProactiveGuard) RecordSend(now time.Time) {
|
||||
g.recentSends = append(g.recentSends, now)
|
||||
g.pruneOldSends(now)
|
||||
}
|
||||
|
||||
// pruneOldSends removes sends older than 1 hour.
|
||||
func (g *ProactiveGuard) pruneOldSends(now time.Time) {
|
||||
cutoff := now.Add(-1 * time.Hour)
|
||||
valid := g.recentSends[:0]
|
||||
for _, t := range g.recentSends {
|
||||
if t.After(cutoff) {
|
||||
valid = append(valid, t)
|
||||
}
|
||||
}
|
||||
g.recentSends = valid
|
||||
}
|
||||
|
||||
// ExtractUrgencyFromContent tries to infer urgency from the proactive message content.
|
||||
func ExtractUrgencyFromContent(content string) string {
|
||||
lower := strings.ToLower(content)
|
||||
|
||||
// High urgency indicators
|
||||
highIndicators := []string{"紧急", "立刻", "马上", "危险", "警告", "报警", "异常", "urgent", "alert"}
|
||||
for _, kw := range highIndicators {
|
||||
if strings.Contains(lower, kw) {
|
||||
return "high"
|
||||
}
|
||||
}
|
||||
|
||||
// Medium urgency indicators
|
||||
mediumIndicators := []string{"建议", "提醒", "注意", "该", "要", "应该", "记得", "别忘了"}
|
||||
for _, kw := range mediumIndicators {
|
||||
if strings.Contains(lower, kw) {
|
||||
return "medium"
|
||||
}
|
||||
}
|
||||
|
||||
return "low"
|
||||
}
|
||||
|
||||
// ValidateProactiveMessage performs post-extraction validation on a message.
|
||||
func ValidateProactiveMessage(content string) (valid bool, reason string) {
|
||||
runes := []rune(content)
|
||||
if len(runes) == 0 {
|
||||
return false, "消息为空"
|
||||
}
|
||||
if len(runes) > 500 {
|
||||
return false, "消息过长(>500字符)"
|
||||
}
|
||||
|
||||
// Check for prohibited patterns (should not tell user they're resting when they're active)
|
||||
prohibited := []string{
|
||||
"系统检测到", "根据分析", "经检测", "后台监控",
|
||||
}
|
||||
for _, p := range prohibited {
|
||||
if strings.Contains(content, p) {
|
||||
return false, "包含机械语言: " + p
|
||||
}
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// DetermineUserState checks conversation history for user state indicators.
|
||||
func DetermineUserState(lastUserMsg string) string {
|
||||
lower := strings.ToLower(lastUserMsg)
|
||||
restIndicators := []string{"睡", "休息", "躺", "困", "累", "晚安", "午安", "小憩"}
|
||||
busyIndicators := []string{"忙", "工作", "开会", "出去", "走了", "拜拜", "再见", "回头", "晚点"}
|
||||
|
||||
for _, kw := range restIndicators {
|
||||
if strings.Contains(lower, kw) {
|
||||
return "resting"
|
||||
}
|
||||
}
|
||||
for _, kw := range busyIndicators {
|
||||
if strings.Contains(lower, kw) {
|
||||
return "busy"
|
||||
}
|
||||
}
|
||||
return "active"
|
||||
}
|
||||
|
||||
// logDecision logs the proactive decision for debugging.
|
||||
func logDecision(d ProactiveDecision) {
|
||||
if d.ShouldSend {
|
||||
log.Printf("[主动消息决策] 允许推送 (紧急程度=%s)", d.Urgency)
|
||||
} else {
|
||||
log.Printf("[主动消息决策] 阻止推送 (紧急程度=%s, 原因=%s)", d.Urgency, d.Reason)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user