06 · 主动决策与礼物
架构剖析第六篇:Decider 本地小模型做"要不要主动做事"、APScheduler 调度系统、报告/提醒/日程/待办管线、礼物系统。 对应
ARCHITECTURE.md§7。
§7.1 为什么本地训练小模型(Decider)
| 需求 | 云端 LLM | 本地 Decider |
|---|---|---|
| 延迟 | 200-2000ms | <50ms |
| 隐私 | 数据出本机 | 全部本地 |
| 频率 | 每个信号一次 API 调用 | 无限频率 |
| 成本 | 按 token 计费 | 零边际成本 |
主动决策(要不要提醒、要不要汇报、要不要主动查资料)必须低延迟、本地、隐私。关系型 Prompt 判断不划算 → 训练一个小分类模型做门控。
§7.2 主动决策架构
事件/信号 → Assistant.evaluate_signal()
→ DeciderService.shared().evaluate(agent_id, trigger_type, ...)
→ DecisionOutcome(fire: bool, action_category: str, act_score: float)
├─fire=False → 忽略
└─fire=True → Assistant.send_proactive_message(...)
→ dialogue.tree.attach_proactive_node(content, metadata)
→ reminder_dispatch._emit(event) → inbox.jsonl → SSE
→ 同时发原生 OS 通知7.2.1 evaluate_signal 参数
python
def evaluate_signal(
self,
*,
trigger_type: str, # "new_message" / "external_info" / ...
trigger_summary: str, # 触发摘要(≤300 字符)
trigger_details: Optional[dict] = None,
memory: Optional[list[str]] = None,
user_state: Optional[dict] = None,
recent_user_messages: Optional[list[str]] = None,
):7.2.2 send_proactive_message
python
def send_proactive_message(
self,
*,
content: str,
signal_summary: str,
action_category: str,
act_score: Optional[float] = None,
decision_id: Optional[str] = None,
dialogue_id: int = 0,
title: Optional[str] = None,
fired_at: Optional[float] = None,
extra: Optional[dict] = None, # 如 {"gift": {...}} 触发前端富渲染
) -> Optional[int]:
"""返回新 node id,或 None(无对话)。
metadata 含 decision_id / signal_summary / action_category / act_score →
前端评分 UI ("对/错") POST 到 /api/feedback/proactive。
"""7.2.3 DeciderService 内部逻辑
python
# src/decider/service.py
class DeciderService:
"""阈值/冷却/危机关键词/DND 逻辑。
evaluate() 返回 DecisionOutcome,永不抛异常。
最坏情况:fire=False + source="predict_error"。
"""
@classmethod
def shared(cls) -> "DeciderService":
"""进程级单例。"""§7.3 APScheduler 调度系统
7.3.1 调度器类型
python
# src/utils/daily_tools.py
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger| 调度类型 | 用途 | 触发器 |
|---|---|---|
cron | 定时报告/清理 | CronTrigger |
schedule | 日程提醒 | 一次性 / 循环 |
todo | 待办提醒 | deadline 前 |
reminder | 通用提醒 | 60s 间隔扫描 |
7.3.2 循环日程(v13 recurrence)
python
_FREQ_VALID = {"never", "daily", "weekly", "monthly", "yearly"}
def start_of_cycle(dt: datetime, frequency: str) -> datetime:
"""返回 dt 所在周期的起始时间。
daily → 今日午夜
weekly → 本 ISO 周一午夜
monthly → 本月 1 日午夜
yearly → 今年 1 月 1 日午夜
never → dt 截断到午夜
"""ScheduleList.create_schedule 播种 last_cycle_start,DailyTools._run_recurrence_rollover 枚举出现次数。
7.3.3 Schedule 状态机
pending → active → completed
→ cancelled
→ expired (deadline 过)v8__schedules_status.sql 添加状态列。
§7.4 提醒派发(src/utils/reminder_dispatch.py)
7.4.1 架构
python
def _inbox_path(workspace_route: str) -> Path:
return Path(workspace_route) / "workspace" / "reminders" / "inbox.jsonl"
def _emit(workspace_route: str, event: dict) -> None:
"""追加一条提醒事件到 inbox JSONL。bridge 尾随此文件。"""
event = {**event, "ts": time.time()}
inbox = _inbox_path(workspace_route)
with inbox.open("a", encoding="utf-8") as f:
f.write(json.dumps(event, ensure_ascii=False) + "\n")
_maybe_native(event)7.4.2 提醒 ≠ 聊天消息
| 维度 | 提醒 | 聊天消息 |
|---|---|---|
| 对话树 | 不进(无 Q/A 节点) | 进 |
| LLM | 不消耗 | 消耗 |
| 历史 | 不进 ConversationSession.history | 进 |
| 前端渲染 | ReminderToast 覆盖层 | 聊天气泡 |
| SSE 流 | /api/reminders/stream | /api/chat/stream |
7.4.3 原生通知
python
def _maybe_native(event: dict) -> None:
"""为到期提醒发原生 OS 通知。始终发。
提醒必须到达用户,无论 app 窗口是否可见。
inbox 始终携带 payload;原生窗口通道在 worker 线程触发,
慢的 PowerShell/osascript 永远不阻塞 DB 扫描。
双重触发防护:renderer 已附加时跳过 daemon toast →
避免两个相同 OS 通知。
"""7.4.4 通知去重
使用 client_presence 的 _active 计数器判断 renderer 是否在线:
- renderer 在线 → renderer 路径处理 → daemon 跳过原生 toast
- renderer 离线 → daemon 发原生 toast
§7.5 DailyTools
python
# src/utils/daily_tools.py
class DailyTools:
"""日程/待办/提醒/报告工具集合。
集成 APScheduler,提供:
- create_schedule / update_schedule / list_schedules
- create_todo / update_todo / list_todos
- create_reminder / list_reminders
- _run_recurrence_rollover() # 60s 间隔扫描循环日程
"""7.5.1 报告生成(已弃用)
python
# src/models/assistant.py
def write_report(self, type: str = "day", timestamp=None) -> str:
"""Deprecated: 每 agent 的 daily/weekly/monthly/annual 报告
已被 manager_review skill 替代。
此 stub 保留使残余 import 失败而非静默。
"""
raise RuntimeError(
"Assistant.write_report is deprecated — use the manager_review "
"skill (triggered per-agent by dialogue-turn threshold)."
)7.5.2 manager_review skill
workspace/manager/skills/manager_review/SKILL.md按 agent 对话轮次阈值触发,替代旧的 cron 报告。
§7.6 礼物管线
7.6.1 架构
用户发送礼物 → /api/gift/... → gift.py 路由处理
→ 兑换码验证
→ token/coin 转账
→ 公会结算7.6.2 礼物触发主动消息
python
# Assistant.send_proactive_message 的 extra 参数
extra = {"gift": {...}} # 前端 key on 此字段切换富渲染气泡send_proactive_message 的 extra dict 合并到 metadata,前端渲染器检查 metadata.gift 存在 → 切换自定义聊天气泡。
§7.7 Decider VRAM 管理
Decider 模型在 VRAM 门控中的位置:
| 条件 | 优先级 | 类别 |
|---|---|---|
| mode0(无 GPU) | N/A | 用云端 LLM 2-call hack |
| tier4(~4GB) | 3 — NICE | queue,GPU 空闲时触发 |
| tier6(~6GB) | 3 — NICE | queue,bf16 |
| tier8(≥8GB) | 2 — IMPORTANT | sync,始终驻留 |
下一篇:07-数字人 →
