# EzVibe v2 架构设计文档 > 本文档描述 EzVibe 系统当前(v1.x)的整体架构设计。 --- ## 1. 系统概览 EzVibe 是一个运行在用户桌面上的 AI 桌宠系统,核心目标是**健康监测与提醒**(久坐提醒、喝水提醒)。 **技术栈**: - Python 3.10+ - PySide6 / PyQt6(Qt 图形界面) - SQLite + NumPy(本地持久化 + 向量检索) - Ollama / OpenAI(LLM 推理) - pynput(全局键鼠监听) **运行模式**: - 完整 GUI 模式(`python main.py`) - Headless Dummy 模式(`python main.py --dummy`) - API Server 模式(`python main.py --server`) --- ## 2. 分层架构 ``` ┌─────────────────────────────────────────────┐ │ 表现层 (UI) │ │ ui/pet_window.py │ │ - PetWindow / DummyPetWindow │ │ - 情绪动画渲染、右键菜单、托盘 │ └────────────────────┬────────────────────────┘ │ ┌────────────────────▼────────────────────────┐ │ 智能层 (Agent) │ │ │ │ ┌─────────┐ ┌─────────┐ ┌──────────────┐ │ │ │ brain │ │ emotion │ │ scheduler │ │ │ │ .py │ │ .py │ │ .py │ │ │ │ LLM决策 │ │ 状态机 │ │ 行为调度 │ │ │ └────┬────┘ └────┬────┘ └──────┬───────┘ │ │ └───────┬──┴────────────┘ │ │ ┌──────▼──────┐ │ │ │ memory │ │ │ │ .py │ │ │ │ RAG向量记忆 │ │ │ └─────────────┘ │ └────────────────────┬────────────────────────┘ │ ┌────────────────────▼────────────────────────┐ │ 感知层 (Perception) │ │ perception/keyboard_mouse_monitor.py │ │ - 键鼠事件监听、活跃度检测 │ │ - ScreenCapture(截图+OCR) │ └─────────────────────────────────────────────┘ ``` --- ## 3. 模块职责 ### 3.1 表现层 — `ui/pet_window.py` **职责**: - 渲染桌宠窗口(无边框、置顶、透明背景) - 情绪状态驱动动画(idle/happy/focused/annoyed/sleepy) - 提醒通知弹窗(健康喝水/伸展提醒) - 拖拽移动、右键菜单、最小化到托盘 **关键类**: - `PetWindow`:PySide6/PyQt6 实现,延迟构造避免导入冲突 - `DummyPetWindow`:headless 测试替代,不依赖 Qt **图像资源**: ``` assets/pet/ ├── idle/ ├── happy/ ├── focused/ ├── annoyed/ └── sleepy/ ``` 每个情绪目录下存放 16 帧 PNG 动画(180×180px)。无图像时回退到 emoji 显示。 **设计亮点**: - `_make_pet_window_class()` 使用 `type()` 动态构造类,避免模块级导入 QtWidgets - `create_pet_window()` 工厂函数,支持 `force_dummy` 强制降级 --- ### 3.2 智能层 — `agent/` #### 3.2.1 `brain.py` — AgentBrain(LLM 推理引擎) **职责**: - 整合记忆上下文 + 情绪状态 + 用户输入 - 调用 LLM(Ollama/OpenAI/Dummy)生成回复 - 决策是否触发主动行为 - 管理会话历史(短期上下文窗口) **LLM 后端适配器(策略模式)**: ``` LLMBackend (基类) ├── OllamaBackend — http://localhost:11434 ├── OpenAIBackend — OpenAI API / 兼容第三方 └── DummyLLMBackend — 测试用固定回复 ``` **核心方法**: - `think(user_input)`:接收用户输入,返回 `{"text", "emotion_state", "action"}` - `decide_action()`:异步决策是否触发主动行为 - `_parse_action()`:从 LLM 回复中解析 `[ACTION: type:description]` 标签 **System Prompt 模板**: ```python DEFAULT_SYSTEM_PROMPT = """你是「EzVibe」,一个运行在用户桌面上的 AI 桌宠。 【当前情绪状态】{emotion_display} 【性格设定】友善、活泼,偶尔会犯懒或闹小脾气... 【主动行为能力】可以提醒喝水/休息,或做出可爱小动作。 请用自然对话风格回复。如果想触发主动行为,在回复末尾加上: [ACTION: :] """ ``` #### 3.2.2 `emotion.py` — EmotionEngine(情绪状态机) **状态集合**:`S = {idle, happy, focused, annoyed, sleepy}` **核心机制**: - 5×5 状态转移矩阵 P - 上下文调制因子(事件驱动增益) - 蒙特卡洛随机采样 - 最小驻留时间限制(防止抖动) **状态转移矩阵**(行=当前状态,列=目标状态): ``` → idle happy focused annoyed sleepy idle │ 0.4 0.2 0.2 0.1 0.1 happy │ 0.2 0.5 0.1 0.1 0.1 focused │ 0.1 0.2 0.5 0.1 0.1 annoyed │ 0.1 0.1 0.1 0.6 0.1 sleepy │ 0.2 0.1 0.1 0.1 0.5 ``` **事件增益**:`ContextBoost.EVENT_BOOSTS` ```python "long_work_session" → sleepy +2.0, focused +0.8 "user_focused" → focused +2.5 "reminder_ignored" → annoyed +3.0 "user_praise" → happy +3.0 "user_healthy_action" → happy +2.0 ``` **关键方法**: - `update(event)`:根据事件触发状态转移 - `tick()`:时钟推进,触发 `time_passes` 事件 - `force_state(state)`:强制设置状态(测试用) #### 3.2.3 `scheduler.py` — BehaviorScheduler(行为调度器) **行为优先级定义**: - `PRIORITY_HIGHEST = 0`:健康/高危提醒(打断所有) - `PRIORITY_USER_INPUT = 1`:用户主动输入响应 - `PRIORITY_PROACTIVE = 2`:系统主动闲聊/行为 - `PRIORITY_LLM_TRIGGER = 3`:LLM 自触发 **内置行为**: | 行为 | 优先级 | 冷却 | 说明 | |------|--------|------|------| | `remind_water` | 0 | 1800s | 喝水提醒 | | `remind_stretch` | 0 | 3600s | 久坐伸展提醒 | | `idle_nudge` | 2 | 300s | 空闲闲聊触发 | | `happy_nudge` | 2 | 600s | 开心时主动搭话 | **核心逻辑**: - 活跃度限制:极度专注(activity < 0.15)时禁止 P2/P3 打扰 - 情绪调制:focused 状态概率×0.2,sleepy 状态概率×0.3 - 提醒队列:`Reminder` 延迟执行任务 #### 3.2.4 `memory.py` — VectorMemory(向量记忆系统) **数据模型**: ```python @dataclass class MemoryEntry: text: str # 记忆文本 embedding: list[float] # 向量嵌入 tags: list[str] # 标签 metadata: dict # 元数据 created_at: float # 时间戳 id: str | None # UUID ``` **存储层**:`SQLite` + `MemoryStore` ```sql CREATE TABLE memories ( id TEXT PRIMARY KEY, text TEXT NOT NULL, embedding BLOB NOT NULL, -- numpy 序列化 tags TEXT NOT NULL DEFAULT '[]', metadata TEXT NOT NULL DEFAULT '{}', created_at REAL NOT NULL ); ``` **嵌入适配器**: ``` make_embedder(backend) ├── OllamaEmbedder — nomic-embed-text ├── OpenAIEmbedder — text-embedding-3-small └── DummyEmbedder — TF-IDF(开发/测试用) ``` **RAG 工作流**: 1. Query → Embedding 2. 向量检索 Top-k 3. 格式化上下文 4. 注入 System Prompt --- ### 3.3 感知层 — `perception/` #### 3.3.1 `keyboard_mouse_monitor.py` — KeyboardMouseMonitor **职责**:全局键盘/鼠标监听,使用 pynput 全局钩子。 **核心组件**: - `ActivityDetector`:滑动窗口活跃度统计 - `KeyboardMouseMonitor`:pynput listener + queue - `ScreenCapture`:mss 截图 + pytesseract OCR **活跃度计算**: ```python baseline = window_seconds / 60.0 * 30.0 # 30 events/min = normal activity = min(raw / baseline, 1.0) ``` **阈值定义**: - activity < 0.15:极度专注(可能深度工作/游戏) - activity < 0.3:比较专注 - 0.3 ≤ activity < 0.7:适度活跃 - activity ≥ 0.7:非常活跃 **全局单例**: ```python _global_monitor: KeyboardMouseMonitor | None = None def get_global_monitor(...) → KeyboardMouseMonitor def stop_global_monitor() → None ``` --- ## 4. 主入口 — `main.py` ### 4.1 EzVibeApp 初始化流程 ```python def _init_components(self): 1. EmotionEngine() 2. VectorMemory(embedder_backend="dummy") # 避免依赖 Ollama 3. AgentBrain(llm_backend=...) 4. KeyboardMouseMonitor + ScreenCapture 5. BehaviorScheduler(emotion_engine, activity_detector) 6. PetWindow(延迟到 QApplication 之后) ``` ### 4.2 运行模式 **Dummy 模式**(headless 测试): ```python def _run_dummy(self): # 初始化后直接打印状态摘要,模拟行为触发 ``` **Qt 模式**: ```python def _run_qt(self): app = QtWidgets.QApplication(sys.argv) self._window = create_pet_window(...) # QApplication 之后才能创建 # QTimer 20ms 驱动 asyncio 轮询(临时方案,待改进) timer.timeout.connect(lambda: self._loop.run_until_complete(asyncio.sleep(0))) app.exec() ``` --- ## 5. API 服务器 — `api/server.py` FastAPI 服务器,提供 REST/WebSocket 接口: | 端点 | 方法 | 说明 | |------|------|------| | `/status` | GET | 返回系统状态 | | `/emotion` | GET/POST | 获取/设置情绪状态 | | `/remind` | POST | 添加提醒 | | `/memory/search` | POST | RAG 检索 | | `/memory/add` | POST | 添加记忆 | | `/ws` | WebSocket | 实时事件流 | --- ## 6. 数据流 ### 6.1 用户交互流程 ``` 用户输入文本 ↓ AgentBrain.think() ↓ ┌─ 注入情绪状态到 System Prompt ├─ RAG 检索相关记忆(VectorMemory.search) ├─ 追加对话历史 └─ 调用 LLM(Ollama/OpenAI/Dummy) ↓ 解析 [ACTION: ...] 标签 ↓ 返回 {"text": "...", "action": {...}, "emotion_state": "..."} ↓ PetWindow 显示回复 + 动画 ``` ### 6.2 健康监测流程 ``` 定时调度(每 10s) ↓ BehaviorScheduler.check_and_trigger() ↓ ┌─ 冷却时间检查 ├─ 活跃度限制检查(极度专注时禁止打扰) ├─ 概率触发(结合情绪调制) └─ 执行 action_fn() ↓ show_reminder() → 气泡通知 ↓ 记录到记忆(VectorMemory.add) ``` ### 6.3 感知数据流 ``` pynput 全局钩子(独立线程) ↓ queue.Queue 传递事件 ↓ _worker_loop 消费队列 ↓ ActivityDetector.record() 记录到滑动窗口 ↓ get_activity() → 0.0~1.0 活跃度 ``` --- ## 7. 文件结构 ``` EzVibe/ ├── main.py # 主入口,EzVibeApp ├── requirements.txt # 依赖列表 ├── setup_project.py # 项目初始化脚本 │ ├── agent/ │ ├── __init__.py │ ├── brain.py # AgentBrain + LLM后端 │ ├── emotion.py # EmotionEngine 状态机 │ ├── scheduler.py # BehaviorScheduler + ActivityDetector │ ├── memory.py # VectorMemory + MemoryStore + VectorEngine │ └── test_*.py # 单元测试 │ ├── perception/ │ ├── __init__.py │ ├── keyboard_mouse_monitor.py # KeyboardMouseMonitor + ActivityDetector │ └── test_perception.py │ ├── ui/ │ ├── __init__.py │ ├── pet_window.py # PetWindow + DummyPetWindow │ └── test_pet_window.py │ ├── api/ │ ├── __init__.py │ └── server.py # FastAPI 服务器 │ ├── data/ # SQLite 数据库目录 ├── assets/ # 桌宠图像资源 │ └── docs/ ├── v2_design/ │ ├── ARCHITECTURE.md # 本文档 │ ├── ARCHITECTURE_REVIEW.md # 架构评审 │ ├── EVOLUTION_ROADMAP.md # 演进路线 │ ├── HEALTH_TRACKER_SPEC.md # HealthTracker 规格 │ └── DESIGN_DECISIONS.md # 设计决策 └── *.md # 其他文档 ``` --- ## 8. 已知限制与改进方向 | 问题 | 现状 | 改进方向 | |------|------|----------| | Qt + asyncio 融合 | QTimer 轮询(不稳定) | 引入 qasync | | OCR 性能消耗 | mss + pytesseract 每分钟执行 | 轻量级焦点窗口提取 | | LLM 延迟 | 同步等待 5 秒 | 解耦动作与文案,Fallback 缓存 | | annoyed 状态屏蔽 P0 | 完全禁止打扰 | 柔性降级(安静模式) | | 健康数据缺乏结构化 | RAG 模糊检索 | HealthTracker 结构化数据库 | | 奖惩机制无闭环 | 单向提醒 | 隐式确认 + 正向反馈 | 详见 `ARCHITECTURE_REVIEW.md`。