WeClaw “早上好惊喜“机制揭秘:四层架构如何让 AI 主动回顾昨天、温暖开启今天
系列文章第 45 篇- 从陪伴人格注入到会话上下文回忆,解密"早上好"背后多组件协同的惊喜体验
📚 专栏信息
《从零到一构建跨平台 AI 助手:WeClaw 实战指南》专栏
专栏定位:面向开发者和技术决策者的实战专栏,用真实案例和完整代码带你理解如何构建生产级 AI 应用
本文是主动陪伴系统延伸篇,将带您深入理解"早上好惊喜"这一用户体验背后的完整技术链路。
👨💻 作者与项目
作者简介:翁勇刚 WENG YONGGANG
新概念龙虾-WeClaw 开发团队负责人,一群专注于跨平台 AI 应用的实践者
理念:“让 AI 不只是被动工具,更是主动关怀用户的生活伙伴”
- 💻 项目地址:https://github.com/wyg5208/weclaw.git
- 🌐 官网地址:https://weclaw.link
- 📝 作者 CSDN:https://blog.csdn.net/yweng18
- ⭐ 欢迎 Star⭐、Fork🍴、贡献代码🤝
📝 摘要
本文结构概览:
本文从用户一句"早上好"触发的"惊喜回复"现象出发,逐层剖析背后的四层协同架构:陪伴人格层(COMPANION_PROMPT_MODULE)、主动关怀层(CompanionEngine + CareTopic)、每日任务推送层(DailyTaskScheduler + TaskAnalyzer)、会话上下文记忆层(Session + ContextCompressor)。通过完整代码解读和架构图,揭示"扫描昨天内容"并非专门的扫描逻辑,而是多组件各司其职、自然涌现的涌现行为。
背景:很多用户在使用 WeClaw 时发现,当早上对 AI 说"早上好",系统不仅温暖地回应,还能自然地引用昨天的对话内容,甚至推送一份精心整理的每日任务清单。这种"惊喜感"是如何实现的?
核心问题:如何在不增加额外"扫描模块"的前提下,让 AI 在问候场景中自然地回顾历史、个性化回应?
解决方案:四层架构协同——人格注入 + 主动关怀引擎 + 每日任务调度 + 会话上下文持久化,各层独立运作,最终在 LLM 推理时自然融合。
关键成果:
- 4 个核心模块各司其职,零额外"扫描"开销
- 15+ 种关怀主题自动按时段评分触发
- 凌晨 2 点离线分析 + 早上 7 点智能推送
- 会话上下文支持 500 条消息 + 智能压缩
适合读者:有 Python 基础,对 AI 助手设计、会话管理、事件驱动架构感兴趣的开发者
阅读时长:约 18 分钟
关键词:CompanionEngine、Session上下文、DailyTaskScheduler、COMPANION_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):
defbuild_system_prompt_from_intent(intent_result:IntentResult,request_source:str="local")->str:parts=[CORE_SYSTEM_PROMPT]# ✅ 关键:始终注入陪伴模块 — 陪伴角色是 WeClaw 的核心身份parts.append(COMPANION_PROMPT_MODULE)# PWA 手机端特殊规则ifrequest_source.startswith("pwa:"):parts.append(PWA_CONTEXT_PROMPT)# 按需注入 assembly/mcp 模块if"assembly"inintent_result.prompt_modules:parts.append(ASSEMBLY_TASK_PROMPT)if"mcp"inintent_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)returnresult为什么"始终注入"是关键?
因为无论用户说什么(包括"早上好"),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):
defcalculate_interaction_score(self,topic:CareTopic)->float:"""计算主题此刻的执行分数(0-100)。"""score=topic.priority*10# 基础分:优先级 × 10# 1. 时段匹配度 +0~20ifcurrent_slotintopic.best_time_slots:score+=20# 2. 冷却期硬性检查(不到24小时直接返回0)ifhours<topic.min_interval_hours:return0# 3. 紧急度加成 +0~30urgency_bonus=min((cooldown_ratio-1)*15,30)score+=urgency_bonus# 4. 特殊紧急情况(如生日临近 +30)# 5. 用户空闲度 +0~20(空闲5分钟+10,15分钟+10)# 6. 情绪调节 ±15(根据 MoodDetector 结果)# 7. 每日预算检查(配额用完则返回0)returnmax(0,min(100,score))定时调度循环(每 30 分钟扫描一次):
asyncdef_scheduler_loop(self)->None:whileTrue:awaitasyncio.sleep(30*60)# 每30分钟current_slot=get_time_slot()# "morning"/"afternoon"/"evening"topics=self._topic_registry.get_by_time_slot(current_slot)fortopicintopics:score=self.calculate_interaction_score(topic)ifscore>30:# 超过阈值awaitself.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})",""]# 按来源分组显示ifoverdue_tasks:lines.append("⚠️ 过期任务:")iftoday_tasks:lines.append("📅 今日到期:")iffamily_tasks:lines.append("👨👩👧 家庭相关:")ifother_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@dataclassclassSession:id:strtitle: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={"你好","在吗","早上好","晚上好","最近怎么样","谢谢你","感谢","辛苦了","做得好",...}# 在关键词匹配时,低权重关键词被过滤掉forintent_name,keywordsinINTENT_CATEGORIES.items():ifintent_name=="casual_chat":effective_keywords=[kwforkwinkeywordsifkwnotinCASUAL_LOW_WEIGHT_KEYWORDS]设计意图:单说"早上好"不应该触发casual_chat意图(那样会限制工具使用),而是让 LLM 在通用模式下自由回应,结合 COMPANION_PROMPT_MODULE 的人格引导,产出温暖且个性化的回复。
3.3 并行触发:每日任务如何"恰好"出现
DailyTaskScheduler 的启动检查机制:
asyncdefcheck_and_push_on_startup(self)->bool:"""应用启动时检查是否需要推送。"""now=datetime.now()hour=now.hour# 只在早上 6-10 点之间检查(用户可能刚起床)ifhour<6orhour>10:returnFalse# 今天已推送过则跳过ifself._last_push_date==today:returnFalse# 执行推送awaitself._push_recommendations()returnTrue时间窗口设计:6:00-10:00 的推送窗口确保:
- 不会在凌晨打扰用户
- 不会在下午推送"今日任务"(已经过了半天)
- 应用启动时自动检查,无需定时任务
四、防骚扰机制 —— 惊喜不等于打扰
4.1 冷却管理器的三重保护
classCooldownManager: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 预算自适应调节
defadjust_budget(self,user_response:str,consecutive_ignores:int):"""根据用户反馈动态调整配额。"""ifuser_response=="positive":# 正面反馈 + 没有被忽略 → 增加配额ifconsecutive_ignores==0:self.daily_budget=min(self.daily_budget+1,self.max_budget)elifuser_response=="negative"orconsecutive_ignores>=2:# 负面反馈或多次忽略 → 减少配额self.daily_budget=max(self.daily_budget-1,self.min_budget)核心原则:越喜欢互动的用户得到越多的主动关怀,越反感的用户得到越少的打扰。
五、性能优化与最佳实践
5.1 为什么不用 RAG 检索历史?
| 方案 | 延迟 | 成本 | 复杂度 | 效果 |
|---|---|---|---|---|
| RAG 检索 | +2~5s | Embedding API 费用 | 高 | 片段化 |
| 直接注入 messages | 0ms | 无额外费用 | 低 | 完整自然 |
| 摘要后注入 | +1~2s | 辅助 LLM 费用 | 中 | 信息压缩 |
WeClaw 的策略:
- 近期对话(最近 500 条):直接注入 messages,LLM 自然回忆
- 更早的历史:ContextCompressor 用辅助 LLM 生成摘要,替代简单截断
- 跨会话搜索:通过
chat_history工具按需检索
5.2 上下文窗口管理策略
# 智能截断:保留完整对话轮次defget_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 个关键层:
- 人格注入层:COMPANION_PROMPT_MODULE 始终生效,赋予 AI "朋友"人格
- 主动关怀层:CompanionEngine 通过七层评分决策何时触发 morning_greeting
- 任务推送层:DailyTaskScheduler 凌晨分析 + 早上推送结构化清单
- 上下文记忆层:Session 持久化 500 条消息,LLM 自然回忆
1 个核心洞察:
早上好惊喜 = 人格引导 + 对话历史 + LLM推理能力 + 定时推送没有专门的"扫描模块",是多组件协同的涌现行为。
6.2 可扩展方向
短期优化:
- 在 ContextCompressor 中增加"昨日摘要"缓存,减少 Token 消耗
- 为 morning_greeting 增加更丰富的上下文变量(天气 API、日程 API)
长期演进:
- 引入向量数据库实现跨会话长期记忆
- 基于用户反馈训练个性化的关怀回复模型
- 多模态关怀:语音 + 表情 + 桌面宠物联动
6.3 互动环节
思考题:
- 如果用户每天说"早上好"的时间不固定(有时 6 点,有时 10 点),冷却机制应该如何适配?
- 如果 LLM 的上下文窗口只有 8K Token,四层架构需要做哪些调整?
讨论话题:
你认为 AI 助手的"主动性"边界在哪里?太多的主动关怀会变成打扰,太少又显得冷漠。你的产品是怎么平衡的?
附录 A:核心文件清单
| 文件路径 | 行数 | 作用 |
|---|---|---|
src/core/prompts.py | 1896 | System Prompt 构建 + 意图识别 + 陪伴人格模块 |
src/core/companion_engine.py | 1439 | 主动陪伴核心引擎(五大组件 + 七层评分) |
src/core/companion_topics.py | 439 | 关怀主题定义(15+ 种预定义主题) |
src/core/daily_task_scheduler.py | 434 | 每日任务调度(凌晨分析 + 早上推送) |
src/core/task_analyzer.py | 727 | 任务智能分析(四维度 NLP 分析) |
src/core/session.py | 1100 | 会话管理(持久化 + 智能截断 + 压缩) |
总代码量:约 6,035 行
关键组件:6 个核心模块
协作关系:EventBus 事件驱动 + SQLite 状态持久化
附录 B:关联文章
- 第 21 篇:《主动陪伴引擎实战:五大组件如何协力决策"何时"和"说什么"》
- 第 22 篇:《拒绝不是失败:防骚扰冷却机制如何让陪伴更懂用户》
- 第 23 篇:《从工具调用推断用户档案:无感建档系统的分阶段采集策略》
- 第 30 篇:《会话管理系统的演进:从内存字典到 SQLite 持久化》
版权声明:本文为 CSDN 博主「yweng18」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
