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):
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~5s | Embedding API 费用 | 高 | 片段化 |
| 直接注入 messages | 0ms | 无额外费用 | 低 | 完整自然 |
| 摘要后注入 | +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 个关键层:
- 人格注入层: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 版权协议,转载请附上原文出处链接及本声明。