# EzVibe v2 演进修改记录 > 本文档记录从 EVOLUTION_ROADMAP.md 出发的所有实际代码改动。 > 对应 Phase 1 和 Phase 2 的已完成项。 --- ## Phase 1:底层重构与稳定性 ### Phase 1.1 — qasync 替换 QTimer 方案 **改动文件**:[main.py](main.py#L320-L360) **现状(旧)**: ```python timer = QTimer(app) timer.timeout.connect(_run_async) timer.setInterval(20) # 50 Hz timer.start() app.exec() ``` **目标**:引入 qasync,实现 Qt 事件循环与 asyncio 的原生融合。 **改动后**: ```python import qasync qasync_loop = qasync.QEventLoop(app) asyncio.set_event_loop(qasync_loop) async def _run_scheduler_async(): while getattr(self, "_running", False): # 异步调度循环,每 10 秒检查一次 triggered = await self._scheduler.check_and_trigger(user_activity_level=activity) for action in triggered: self._window.show_reminder(...) await asyncio.sleep(10) scheduler_task = qasync_loop.create_task(_run_scheduler_async()) with qasync_loop: qasync_loop.run_forever() ``` **依赖**:`requirements.txt` 新增 `qasync>=0.6.0` --- ### Phase 1.3 — 新增 HealthTracker 模块 **新增文件**:[agent/health_tracker.py](agent/health_tracker.py) **数据库 Schema**: ```sql CREATE TABLE health_events ( id TEXT PRIMARY KEY, event_type TEXT NOT NULL, -- 'water' | 'stand' | 'stretch' | 'screen_break' timestamp REAL NOT NULL, source TEXT NOT NULL, -- 'user_action' | 'reminder_confirmed' | 'implicit_detected' metadata TEXT NOT NULL, -- JSON created_at REAL NOT NULL ); CREATE TABLE daily_stats ( date TEXT PRIMARY KEY, -- 'YYYY-MM-DD' water_count INTEGER DEFAULT 0, stand_count INTEGER DEFAULT 0, stretch_count INTEGER DEFAULT 0, sedentary_minutes INTEGER DEFAULT 0, screen_time_minutes INTEGER DEFAULT 0, last_stand_at REAL, last_water_at REAL, updated_at REAL NOT NULL ); CREATE TABLE sedentary_alerts ( id TEXT PRIMARY KEY, triggered_at REAL NOT NULL, acknowledged INTEGER DEFAULT 0, acknowledged_at REAL, message TEXT ); ``` **核心接口**: - `record_water()` / `record_stand()` / `record_stretch()` — 记录健康事件 - `get_today_stats()` — 返回今日统计 dict - `get_consecutive_sedentary_minutes()` — 当前连续静坐分钟数 - `get_health_context_for_llm()` — 生成注入 LLM System Prompt 的健康上下文 - `trigger_sedentary_alert()` / `acknowledge_alert()` — 警报管理 - `get_weekly_report()` / `get_streak()` — 统计聚合 **与 main.py 集成**: ```python # main.py _init_components() from agent.health_tracker import HealthTracker self._health_tracker = HealthTracker(db_path="data/health.db") self._brain = AgentBrain( ..., health_tracker=self._health_tracker, ) ``` --- ## Phase 2:感知与决策升级 ### Phase 2.1 — DisplayMode 柔性降级 **改动文件**:[agent/scheduler.py](agent/scheduler.py#L35-L52) **新增枚举**: ```python class DisplayMode(Enum): NORMAL = "normal" # 正常打扰(气泡通知) QUIET = "quiet" # 安静模式(缩角落举牌子) AGGRESSIVE = "aggressive" # 强制弹窗(高优先级警告) ``` **Behavior 数据结构变更**: ```python @dataclass class Behavior: ... display_mode: DisplayMode = DisplayMode.NORMAL # 新增字段 ``` **逻辑变更**(`_is_activity_restricted`): - 原设计:`annoyed` 时完全屏蔽 P0 打扰 - 新设计:`annoyed` 时 P0 降级为 QUIET 模式,不完全屏蔽 ```python # 烦躁时 P0 提醒降级为 QUIET(不屏蔽,改展示模式) if emotion == "annoyed" and behavior.priority == 0: behavior.display_mode = DisplayMode.QUIET return False # 不再直接屏蔽,而是降级 ``` **行为触发返回时附加 display_mode**: ```python result["display_mode"] = behavior.display_mode.value ``` --- ### Phase 2.2 — LLM Prompt 工程升级(HealthTracker 上下文注入) **改动文件**:[agent/brain.py](agent/brain.py#L36-L60) **System Prompt 变更**: ```python DEFAULT_SYSTEM_PROMPT = """... 【情绪驱动行为规则】 ... {health_context} 【主动行为能力】 ... """ ``` **AgentBrain 初始化变更**: ```python def __init__( self, ..., health_tracker: Any = None, # 新增参数 ) -> None: ... self._health_tracker = health_tracker ``` **think() 方法中注入健康上下文**: ```python async def think(self, user_input: str, ...): # 2. 获取健康上下文(Phase 2.2 — HealthTracker 数据注入) health_context = "" if self._health_tracker: try: health_context = await self._health_tracker.get_health_context_for_llm() except Exception as exc: logger.warning("[Brain] 健康数据获取失败: %s", exc) health_context = "【用户健康状态】暂无数据" # 3. 构建系统提示词(注入情绪 + 健康上下文) system_prompt = self._system_prompt.format( emotion_display=emotion_display, emotion_state=emotion, health_context=health_context, # 注入 ) ``` **main.py 集成**: ```python self._health_tracker = HealthTracker(db_path="data/health.db") self._brain = AgentBrain( llm_backend=self._llm_backend, llm_config=self._llm_config, emotion_engine=self._emotion, memory=self._memory, health_tracker=self._health_tracker, # 传入 ) ``` --- ### Phase 2.3 — 解耦"动作触发"与"文案生成"(FallbackMessageCache) **改动文件**:[agent/brain.py](agent/brain.py#L196-L248) **新增类**: ```python class FallbackMessageCache: """ P0 健康提醒的 Fallback 文案缓存。 设计文档:decide_action() 等待 LLM 生成文案后才执行 UI 动作。 改进:P0 健康提醒立即执行 UI 动作,LLM 文案异步加载或使用 Fallback 缓存。 """ FALLBACK_MESSAGES: dict[str, list[str]] = { "remind_water": [ "记得喝水哦~", "该补充水分了!", "喝水时间到!", "滴—— 喝水提醒!💧", "身体需要水份~来一杯吧!", ], "remind_stretch": [ "起来伸展一下吧!", "坐了好久啦,站起来动动!", "伸个懒腰吧~身体需要活动!", "⚠️ 久坐提醒:起身动一动!", "站起来伸展一下,缓解疲劳~", ], "nudge_idle": [...], "nudge_happy": [...], } def get(self, action_type: str) -> str: messages = self.FALLBACK_MESSAGES.get(action_type, []) if not messages: return f"[{action_type}]" return self._rng.choice(messages) ``` **AgentBrain 集成**: ```python def __init__(...): ... self._fallback_cache = FallbackMessageCache() ``` **decide_action 变更**(`_decide_proactive_action`): ```python # P0 规则:高频工作 + 非烦躁状态 → 强制健康提醒 # Phase 2.3:使用 Fallback 文案缓存,立即返回不等待 LLM if activity < 0.15 and emotion != "annoyed": if self._check_cooldown("remind_health"): return { "type": "remind_stretch", "message": self._fallback_cache.get("remind_stretch"), "priority": 0, } # 喝水提醒(更低优先级) if activity < 0.4 and self._check_cooldown("remind_water"): return { "type": "remind_water", "message": self._fallback_cache.get("remind_water"), "priority": 1, } ``` --- ## 文件变更清单 | 文件 | 变更类型 | 对应 Phase | |------|----------|------------| | [requirements.txt](requirements.txt) | 修改 | 1.1 | | [main.py](main.py) | 修改 | 1.1, 1.3, 2.2 | | [agent/health_tracker.py](agent/health_tracker.py) | 新增 | 1.3 | | [agent/scheduler.py](agent/scheduler.py) | 修改 | 2.1 | | [agent/brain.py](agent/brain.py) | 修改 | 2.2, 2.3 | --- ## 运行命令 ```bash # 使用 conda ai 环境 conda activate ai # Dummy 模式(无 GUI) /home/e2hang/miniforge3/envs/ai/bin/python main.py --dummy # 完整 GUI 模式 /home/e2hang/miniforge3/envs/ai/bin/python main.py ```