Skip to content

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 — NICEqueue,GPU 空闲时触发
tier6(~6GB)3 — NICEqueue,bf16
tier8(≥8GB)2 — IMPORTANTsync,始终驻留

下一篇:07-数字人 →

Familiars · 多 Agent AI 桌面应用