返回博客列表
新功能2026-04-2730 分钟阅读

"早上好惊喜"机制揭秘:四层架构如何让 AI 主动回顾昨天、温暖开启今天

从陪伴人格注入到会话上下文回忆,解密"早上好"背后多组件协同的惊喜体验

WeClaw "早上好惊喜"机制揭秘:四层架构如何让 AI 主动回顾昨天、温暖开启今天

系列文章第 45 篇 - 从陪伴人格注入到会话上下文回忆,解密"早上好"背后多组件协同的惊喜体验


📚 专栏信息

《从零到一构建跨平台 AI 助手:WeClaw 实战指南》专栏

专栏定位:面向开发者和技术决策者的实战专栏,用真实案例和完整代码带你理解如何构建生产级 AI 应用

本文是主动陪伴系统延伸篇,将带您深入理解"早上好惊喜"这一用户体验背后的完整技术链路。


👨‍💻 作者与项目

作者简介:翁勇刚 WENG YONGGANG
新概念龙虾-WeClaw 开发团队负责人,一群专注于跨平台 AI 应用的实践者
理念:"让 AI 不只是被动工具,更是主动关怀用户的生活伙伴"


📝 摘要

本文结构概览: 本文从用户一句"早上好"触发的"惊喜回复"现象出发,逐层剖析背后的四层协同架构:陪伴人格层(COMPANION_PROMPT_MODULE)、主动关怀层(CompanionEngine + CareTopic)、每日任务推送层(DailyTaskScheduler + TaskAnalyzer)、会话上下文记忆层(Session + ContextCompressor)。通过完整代码解读和架构图,揭示"扫描昨天内容"并非专门的扫描逻辑,而是多组件各司其职、自然涌现的涌现行为。

背景:很多用户在使用 WeClaw 时发现,当早上对 AI 说"早上好",系统不仅温暖地回应,还能自然地引用昨天的对话内容,甚至推送一份精心整理的每日任务清单。这种"惊喜感"是如何实现的?

核心问题:如何在不增加额外"扫描模块"的前提下,让 AI 在问候场景中自然地回顾历史、个性化回应?

解决方案:四层架构协同——人格注入 + 主动关怀引擎 + 每日任务调度 + 会话上下文持久化,各层独立运作,最终在 LLM 推理时自然融合。

关键成果

  • 4 个核心模块各司其职,零额外"扫描"开销
  • 15+ 种关怀主题自动按时段评分触发
  • 凌晨 2 点离线分析 + 早上 7 点智能推送
  • 会话上下文支持 500 条消息 + 智能压缩

适合读者:有 Python 基础,对 AI 助手设计、会话管理、事件驱动架构感兴趣的开发者

阅读时长:约 18 分钟

关键词CompanionEngineSession上下文DailyTaskSchedulerCOMPANION_PROMPT主动关怀涌现行为


一、现象还原:一句"早上好"背后的连锁反应

1.1 场景重现:用户的真实体验

想象这个早晨场景:

  • 用户打开 WeClaw,说了一句"早上好"
  • AI 回复:"早上好呀!昨天你提到了要准备周五的汇报材料,今天要不要先列个大纲?另外今天天气不错,适合出去走走~"
  • 紧跟着推送了一份"📋 今日任务清单"

用户惊讶:"它怎么知道昨天聊了什么?它是怎么'扫描'昨天的内容的?"

1.2 真相:没有"扫描模块",只有四层协同

反直觉的事实:WeClaw 代码中不存在任何"早上好扫描昨天"的专门模块。这个"惊喜"效果是四个独立层各司其职、自然融合的结果。

┌─────────────────────────────────────────────────────────────────────┐
│                     用户说"早上好"                                   │
└───────────────────────────┬─────────────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────────────┐
        ▼                   ▼                           ▼
  ┌──────────┐   ┌──────────────────┐   ┌──────────────────────┐
  │ 第一层    │   │ 第二层            │   │ 第三层               │
  │ 人格注入  │   │ 主动关怀引擎      │   │ 每日任务推送         │
  │(始终生效) │   │(morning_greeting)│   │(DailyTaskScheduler)  │
  └────┬─────┘   └────────┬─────────┘   └──────────┬───────────┘
       │                  │                        │
       ▼                  ▼                        ▼
  ┌─────────────────────────────────────────────────────────┐
  │                    第四层:会话上下文                      │
  │         session.messages(含昨天的完整对话历史)           │
  └────────────────────────┬────────────────────────────────┘
                           │
                           ▼
  ┌─────────────────────────────────────────────────────────┐
  │                   LLM 推理融合                           │
  │   System Prompt(人格) + 上下文(记忆) + 用户输入(触发)     │
  │   → 生成"引用昨天内容 + 温暖问候"的个性化回复             │
  └─────────────────────────────────────────────────────────┘

核心洞察:所谓"扫描昨天"不是扫描,而是 LLM 通过 session 中天然存在的对话历史,在人格 Prompt 的引导下,自然地引用了过去的信息。这是一种涌现行为(Emergent Behavior)。

1.3 为什么不用专门的"晨间摘要"模块?

初学者可能会想:专门写一个"早上扫描昨天对话、生成摘要、注入 Prompt"的模块不是更可控吗?

答案是:过度工程化会破坏 LLM 的自然推理能力

方案优点缺点
专用晨间摘要模块可控性强额外 API 调用成本、延迟高、摘要可能丢失细节
四层协同涌现零额外开销、自然流畅、信息完整回复质量依赖 LLM 能力
RAG 检索历史精确可控架构复杂、检索质量不稳定

WeClaw 选择了四层协同方案,因为 LLM 本身就有强大的上下文理解和回忆能力,我们要做的是:提供正确的上下文 + 恰当的引导。


二、四层架构详解 —— 每一层做了什么

2.1 第一层:人格注入 —— COMPANION_PROMPT_MODULE

核心思想:始终在 System Prompt 中注入"陪伴人格",让 AI 以朋友而非客服的身份交流。

┌─────────────────────────────────────────┐
│         CORE_SYSTEM_PROMPT              │
│     (工具使用指南、规则、决策树)        │
├─────────────────────────────────────────┤
│       COMPANION_PROMPT_MODULE           │
│     (始终注入,陪伴人格 + 关怀规则)    │
├─────────────────────────────────────────┤
│     按需模块(assembly/mcp/pwa...)      │
├─────────────────────────────────────────┤
│           最终 System Prompt             │
└─────────────────────────────────────────┘

关键代码src/core/prompts.py):

# 主动陪伴模式 Prompt 模块 — 始终注入
COMPANION_PROMPT_MODULE = """
## 主动陪伴模式

你具备主动关怀能力。当系统触发陪伴事件时,你将以自然、温暖的方式与用户交流。

关键规则:
- 语气亲切自然,像朋友而非客服
- 已知信息不再重复询问,善于从上下文推断
- 如果用户明显忙碌或不想聊,礼貌退出
- 收集到的信息自动调用 user_profile 工具保存
- 语音模式下回复控制在30字以内
- 每次只关注一个话题,不要同时追问多件事
"""

构建流程build_system_prompt_from_intent):

def build_system_prompt_from_intent(
    intent_result: IntentResult,
    request_source: str = "local"
) -> str:
    parts = [CORE_SYSTEM_PROMPT]
    
    # ✅ 关键:始终注入陪伴模块 — 陪伴角色是 WeClaw 的核心身份
    parts.append(COMPANION_PROMPT_MODULE)
    
    # PWA 手机端特殊规则
    if request_source.startswith("pwa:"):
        parts.append(PWA_CONTEXT_PROMPT)
    
    # 按需注入 assembly/mcp 模块
    if "assembly" in intent_result.prompt_modules:
        parts.append(ASSEMBLY_TASK_PROMPT)
    if "mcp" in intent_result.prompt_modules:
        parts.append(MCP_TOOL_GUIDE_PROMPT)
    
    result = "\n\n".join(parts)
    # 动态替换日期和项目根路径
    result = result.replace("{generated_date}", today)
    result = result.replace("{project_root}", project_root_escaped)
    return result

为什么"始终注入"是关键? 因为无论用户说什么(包括"早上好"),COMPANION_PROMPT_MODULE 都在 System Prompt 中生效。它告诉 LLM:"你是朋友,善于从上下文推断"。这正是 LLM 主动引用昨天对话的驱动力。

2.2 第二层:主动关怀引擎 —— CompanionEngine

核心思想:预定义 15+ 种关怀主题,通过七层评分系统智能决策何时触发什么关怀。

┌─────────────────────────────────────────────────────┐
│                 CompanionEngine                     │
│                                                     │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────┐ │
│  │CooldownManager│  │MoodDetector  │  │Opportunity│ │
│  │(防骚扰冷却)   │  │(情绪检测)    │  │Detector   │ │
│  │              │  │              │  │(时机检测)  │ │
│  └──────┬───────┘  └──────┬───────┘  └─────┬─────┘ │
│         │                 │                │        │
│         └─────────────────┼────────────────┘        │
│                           ▼                         │
│              ┌─────────────────────┐                │
│              │InteractionOrchestrator│               │
│              │(交互编排 + EventBus) │                │
│              └─────────────────────┘                │
│                                                     │
│  ┌──────────────────────────────────┐               │
│  │      CareTopicRegistry           │               │
│  │  morning_greeting (早安问候)      │               │
│  │  mood_check (心情关怀)            │               │
│  │  health_checkin (健康打卡)        │               │
│  │  birthday_remind (生日提醒)       │               │
│  │  ... 15+ 种关怀主题               │               │
│  └──────────────────────────────────┘               │
└─────────────────────────────────────────────────────┘

早安问候主题定义src/core/companion_topics.py):

CareTopic(
    topic_id="morning_greeting",
    name="早安问候",
    category="lifestyle",
    priority=5,                  # 中等优先级
    min_interval_hours=24,       # 最少间隔 24 小时
    best_time_slots=["morning"], # 最佳时段:早晨
    prompt_template="早上好呀!今天{weather_hint},{schedule_hint}有什么计划吗?"
)

七层评分算法calculate_interaction_score):

def calculate_interaction_score(self, topic: CareTopic) -> float:
    """计算主题此刻的执行分数(0-100)。"""
    score = topic.priority * 10  # 基础分:优先级 × 10
    
    # 1. 时段匹配度 +0~20
    if current_slot in topic.best_time_slots:
        score += 20
    
    # 2. 冷却期硬性检查(不到24小时直接返回0)
    if hours < topic.min_interval_hours:
        return 0
    
    # 3. 紧急度加成 +0~30
    urgency_bonus = min((cooldown_ratio - 1) * 15, 30)
    score += urgency_bonus
    
    # 4. 特殊紧急情况(如生日临近 +30)
    # 5. 用户空闲度 +0~20(空闲5分钟+10,15分钟+10)
    # 6. 情绪调节 ±15(根据 MoodDetector 结果)
    # 7. 每日预算检查(配额用完则返回0)
    
    return max(0, min(100, score))

定时调度循环(每 30 分钟扫描一次):

async def _scheduler_loop(self) -> None:
    while True:
        await asyncio.sleep(30 * 60)  # 每30分钟
        
        current_slot = get_time_slot()  # "morning"/"afternoon"/"evening"
        topics = self._topic_registry.get_by_time_slot(current_slot)
        
        for topic in topics:
            score = self.calculate_interaction_score(topic)
            if score > 30:  # 超过阈值
                await self.on_cron_trigger(topic.topic_id)
                break  # 每次只触发一个,避免骚扰

2.3 第三层:每日任务推送 —— DailyTaskScheduler

核心思想:凌晨 2 点离线分析用户数据,早上 7 点推送个性化任务清单。

时间线:
  
  02:00                    07:00                    用户打开
    │                        │                         │
    ▼                        ▼                         ▼
┌──────────┐          ┌──────────┐          ┌──────────────────┐
│TaskAnalyzer│         │  推送    │          │check_and_push    │
│多维度分析  │────────→│  任务    │          │_on_startup()     │
│·待办事项  │          │  清单    │          │启动时补推(6-10点) │
│·家庭成员  │          └──────────┘          └──────────────────┘
│·对话记录  │
│·健康数据  │
└──────────┘

TaskAnalyzer 的四大分析维度src/core/task_analyzer.py):

"""任务智能分析引擎 — 多维度分析用户信息,生成每日任务推荐。

分析维度:
1. 待办事项分析 — 优先级排序、截止时间压力、时间周期匹配
2. 家庭成员关联分析 — 生日提醒、纪念日、子女课程接送
3. 对话记录分析 — NLP提取潜在任务、用户承诺事项
4. 健康与上下文分析 — 用药提醒、运动计划、天气影响
"""

推送消息的格式化_format_recommendation_message):

def _format_recommendation_message(self, rec) -> str:
    lines = [f"📋 今日任务清单({date} {weekday})", ""]
    
    # 按来源分组显示
    if overdue_tasks:
        lines.append("⚠️ 过期任务:")
    if today_tasks:
        lines.append("📅 今日到期:")
    if family_tasks:
        lines.append("👨‍👩‍👧 家庭相关:")
    if other_tasks:
        lines.append("📋 推荐任务:")
    
    lines.append("💬 回复「确认任务」开始今日计划")
    return "\n".join(lines)

2.4 第四层:会话上下文 —— Session + ContextCompressor

核心思想:持久化存储完整对话历史,LLM 自然具备"回忆"能力。

┌──────────────────────────────────────────────┐
│              SessionManager                  │
│                                              │
│  Session {                                   │
│    id: "session_001"                         │
│    title: "关于周五汇报的讨论"                │
│    messages: [                               │
│      {role: "user",     content: "早上好"},   │  ← 今天
│      {role: "assistant", content: "..."},     │  ← 今天
│      ─ ─ ─ ─ ─ 昨天 ─ ─ ─ ─ ─              │
│      {role: "user",     content: "帮我准备周五汇报"},│ ← 昨天
│      {role: "assistant", content: "好的..."}, │  ← 昨天
│      ...                                     │
│    ]                                         │
│  }                                           │
│                                              │
│  最大消息数: 500                              │
│  压缩策略: 辅助LLM摘要早期消息                │
└──────────────────────────────────────────────┘

会话持久化配置src/core/session.py):

# 默认最大消息数硬上限
# 【v2.31.1 提升】100 → 500,适配 DeepSeek 1M 等大上下文模型
DEFAULT_MAX_MESSAGE_COUNT = 500

@dataclass
class Session:
    id: str
    title: str = "新对话"
    created_at: datetime = field(default_factory=datetime.now)
    messages: list[dict[str, Any]] = field(default_factory=list)

关键设计决策

特性实现为什么
消息数量上限500 条适配大上下文模型(128K+)
智能截断保留完整对话轮次不拆散 user+assistant 配对
上下文压缩辅助 LLM 生成摘要比简单截断保留更多信息
持久化存储SQLite应用重启后不丢失历史

三、完整链路追踪 —— 从"早上好"到惊喜回复

3.1 请求处理全流程

用户输入: "早上好"
     │
     ▼
[1] 意图识别 (detect_intent_with_confidence)
     │  "早上好" 是 casual_chat 低权重关键词
     │  不会单独触发 casual_chat(需组合匹配)
     │  意图置信度: 0.0(无明确工具意图)
     │
     ▼
[2] System Prompt 构建 (build_system_prompt_from_intent)
     │  parts = [
     │    CORE_SYSTEM_PROMPT,      ← 工具指南 + 规则
     │    COMPANION_PROMPT_MODULE,  ← 🔑 始终注入的陪伴人格
     │  ]
     │
     ▼
[3] 获取会话上下文 (session.messages)
     │  messages = session_manager.get_messages(
     │      max_tokens=model_cfg.context_window
     │  )
     │  包含:昨天的对话 + 今天的"早上好"
     │
     ▼
[4] LLM 推理
     │  System Prompt 告诉 LLM:
     │  "语气亲切自然,像朋友" + "善于从上下文推断"
     │  
     │  LLM 看到 messages 中昨天的内容:
     │  "用户昨天说'帮我准备周五的汇报材料'"
     │  
     │  LLM 自然推理:
     │  "我应该提到昨天的话题,表示我记得"
     │
     ▼
[5] 生成回复(示例)
     │  "早上好呀!昨天你提到要准备周五的汇报材料,
     │   今天要不要先列个大纲?早晨空气清新,
     │   思路会比较清晰哦~"
     │
     ▼
[6] (并行)每日任务推送
     │  DailyTaskScheduler 在 07:00 或启动时
     │  通过 CompanionEngine.send_daily_task_message()
     │  推送 "📋 今日任务清单" 到对话栏
     │
     ▼
用户感受:"AI 帮我扫描了昨天的内容,太惊喜了!"

3.2 关键代码片段:为什么"早上好"不会触发闲聊模式?

在意图识别系统中,"早上好"被归类为 casual_chat低权重关键词

# Phase 6+: casual_chat 低权重关键词不直接参与匹配
CASUAL_LOW_WEIGHT_KEYWORDS = {
    "你好", "在吗", "早上好", "晚上好", "最近怎么样",
    "谢谢你", "感谢", "辛苦了", "做得好",
    ...
}

# 在关键词匹配时,低权重关键词被过滤掉
for intent_name, keywords in INTENT_CATEGORIES.items():
    if intent_name == "casual_chat":
        effective_keywords = [kw for kw in keywords 
                             if kw not in CASUAL_LOW_WEIGHT_KEYWORDS]

设计意图:单说"早上好"不应该触发 casual_chat 意图(那样会限制工具使用),而是让 LLM 在通用模式下自由回应,结合 COMPANION_PROMPT_MODULE 的人格引导,产出温暖且个性化的回复。

3.3 并行触发:每日任务如何"恰好"出现

DailyTaskScheduler 的启动检查机制:

async def check_and_push_on_startup(self) -> bool:
    """应用启动时检查是否需要推送。"""
    now = datetime.now()
    hour = now.hour
    
    # 只在早上 6-10 点之间检查(用户可能刚起床)
    if hour < 6 or hour > 10:
        return False
    
    # 今天已推送过则跳过
    if self._last_push_date == today:
        return False
    
    # 执行推送
    await self._push_recommendations()
    return True

时间窗口设计:6:00-10:00 的推送窗口确保:

  • 不会在凌晨打扰用户
  • 不会在下午推送"今日任务"(已经过了半天)
  • 应用启动时自动检查,无需定时任务

四、防骚扰机制 —— 惊喜不等于打扰

4.1 冷却管理器的三重保护

class CooldownManager:
    daily_budget: int = 5           # 每日最多 5 次主动交互
    min_budget: int = 2             # 最少 2 次(不能太低)
    max_budget: int = 8             # 最多 8 次(不能太高)
    consecutive_limit: int = 2      # 连续忽略 2 次就暂停
    rejection_penalty_hours: int = 4 # 被拒绝后冷却 4 小时

三重保护机制

┌─────────────────────────────────────────────────┐
│               可以主动交互吗?                    │
├─────────────────────────────────────────────────┤
│                                                 │
│  [检查1] 每日配额用完了?                        │
│     daily_count >= daily_budget → 拒绝           │
│                                                 │
│  [检查2] 被拒绝后的惩罚期?                      │
│     now < last_rejection + 4h → 拒绝             │
│                                                 │
│  [检查3] 连续被忽略了?                          │
│     consecutive_ignores >= 2 → 拒绝              │
│                                                 │
│  全部通过 → ✅ 允许交互                          │
└─────────────────────────────────────────────────┘

4.2 预算自适应调节

def adjust_budget(self, user_response: str, consecutive_ignores: int):
    """根据用户反馈动态调整配额。"""
    if user_response == "positive":
        # 正面反馈 + 没有被忽略 → 增加配额
        if consecutive_ignores == 0:
            self.daily_budget = min(self.daily_budget + 1, self.max_budget)
    elif user_response == "negative" or consecutive_ignores >= 2:
        # 负面反馈或多次忽略 → 减少配额
        self.daily_budget = max(self.daily_budget - 1, self.min_budget)

核心原则:越喜欢互动的用户得到越多的主动关怀,越反感的用户得到越少的打扰。


五、性能优化与最佳实践

5.1 为什么不用 RAG 检索历史?

方案延迟成本复杂度效果
RAG 检索+2~5sEmbedding API 费用片段化
直接注入 messages0ms无额外费用完整自然
摘要后注入+1~2s辅助 LLM 费用信息压缩

WeClaw 的策略:

  • 近期对话(最近 500 条):直接注入 messages,LLM 自然回忆
  • 更早的历史:ContextCompressor 用辅助 LLM 生成摘要,替代简单截断
  • 跨会话搜索:通过 chat_history 工具按需检索

5.2 上下文窗口管理策略

# 智能截断:保留完整对话轮次
def get_messages(self, max_tokens: int) -> list[dict]:
    """获取消息列表,自动截断以适配上下文窗口。
    
    策略:
    1. 保留 system prompt(始终)
    2. 保留最近消息(从后往前填充)
    3. 不截断 tool_calls 和对应 tool 结果(保证配对完整)
    4. 不拆散 user+assistant 对话轮次
    """

5.3 最佳实践总结

Do's(推荐做法):

  • ✅ 始终在 System Prompt 中注入人格模块,让 LLM 有"记忆动机"
  • ✅ 持久化完整对话历史,让 LLM 有"记忆素材"
  • ✅ 用冷却机制控制主动交互频率,避免骚扰
  • ✅ 用定时任务离线分析,早上推送结构化任务清单
  • ✅ 将"低权重问候词"排除在意图识别之外,保持回复自由度

Don'ts(避免做法):

  • ❌ 不要为每种问候场景写专门的处理器(过度工程化)
  • ❌ 不要把历史对话截断得太激进(丢失上下文)
  • ❌ 不要无条件推送主动消息(骚扰用户)
  • ❌ 不要在 System Prompt 中写死"引用昨天内容"的指令(太僵硬)

黄金法则

让 LLM 做 LLM 擅长的事(理解上下文、自然回忆),让工程系统做工程擅长的事(定时调度、冷却控制、持久化存储)。


六、总结与展望

6.1 核心要点回顾

本文揭示了"早上好惊喜"背后的四层协同架构:

4 个关键层

  1. 人格注入层:COMPANION_PROMPT_MODULE 始终生效,赋予 AI "朋友"人格
  2. 主动关怀层:CompanionEngine 通过七层评分决策何时触发 morning_greeting
  3. 任务推送层:DailyTaskScheduler 凌晨分析 + 早上推送结构化清单
  4. 上下文记忆层:Session 持久化 500 条消息,LLM 自然回忆

1 个核心洞察

早上好惊喜 = 人格引导 + 对话历史 + LLM推理能力 + 定时推送

没有专门的"扫描模块",是多组件协同的涌现行为

6.2 可扩展方向

短期优化

  • 在 ContextCompressor 中增加"昨日摘要"缓存,减少 Token 消耗
  • 为 morning_greeting 增加更丰富的上下文变量(天气 API、日程 API)

长期演进

  • 引入向量数据库实现跨会话长期记忆
  • 基于用户反馈训练个性化的关怀回复模型
  • 多模态关怀:语音 + 表情 + 桌面宠物联动

6.3 互动环节

思考题

  1. 如果用户每天说"早上好"的时间不固定(有时 6 点,有时 10 点),冷却机制应该如何适配?
  2. 如果 LLM 的上下文窗口只有 8K Token,四层架构需要做哪些调整?

讨论话题

你认为 AI 助手的"主动性"边界在哪里?太多的主动关怀会变成打扰,太少又显得冷漠。你的产品是怎么平衡的?


附录 A:核心文件清单

文件路径行数作用
src/core/prompts.py1896System Prompt 构建 + 意图识别 + 陪伴人格模块
src/core/companion_engine.py1439主动陪伴核心引擎(五大组件 + 七层评分)
src/core/companion_topics.py439关怀主题定义(15+ 种预定义主题)
src/core/daily_task_scheduler.py434每日任务调度(凌晨分析 + 早上推送)
src/core/task_analyzer.py727任务智能分析(四维度 NLP 分析)
src/core/session.py1100会话管理(持久化 + 智能截断 + 压缩)

总代码量:约 6,035 行
关键组件:6 个核心模块
协作关系:EventBus 事件驱动 + SQLite 状态持久化


附录 B:关联文章

  1. 第 21 篇:《主动陪伴引擎实战:五大组件如何协力决策"何时"和"说什么"》
  2. 第 22 篇:《拒绝不是失败:防骚扰冷却机制如何让陪伴更懂用户》
  3. 第 23 篇:《从工具调用推断用户档案:无感建档系统的分阶段采集策略》
  4. 第 30 篇:《会话管理系统的演进:从内存字典到 SQLite 持久化》

版权声明:本文为 CSDN 博主「yweng18」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。