Files
mai-bot/计划.md

6.6 KiB
Raw Blame History

私聊定时消息 V1 计划

目标

  • 只支持私聊,不支持群聊。
  • 由 LLM 在当前轮直接写好未来要发送的文本。
  • 到点后不再唤醒 LLM不再二次规划直接发送。
  • 尽量复用现有消息发送、消息存储、Maisaka 上下文、tool call 记录链路。

明确不做

  • 不做群聊定时发言。
  • 不做“到点后再让 LLM 判断该不该发”。
  • 不做 intent_key 这类没有明确行为差异的分类字段。
  • 不做复杂任务编排,只做“一条任务,到点发一条文本消息”。

端到端流转链路

1. 用户触发

  • 用户在私聊中表达未来某个时间点需要 bot 主动发一条消息。
  • 当前消息照常进入现有接收链路:
    • 消息接收
    • HeartFlow runtime
    • Timing Gate
    • Planner

2. Planner 决策

  • Planner 在分析当前局势后,直接调用新的定时消息工具。
  • 工具由 Planner 直接可见,不走 deferred tools。
  • 理由:
    • 这是明确的主流程动作,不是低频扩展工具。
    • 用户说“明天早上提醒我”时Planner 应当能直接执行,不应先 tool_search

3. 工具执行

  • 新工具暂定名:schedule_private_message
  • 工具接收参数后,直接写入任务存储。
  • 工具返回成功结果给当前 Planner。
  • 该 tool result 和其他工具一样,走现有 tool call 记录与上下文写回链路。
  • Planner 下一轮可继续:
    • 调用 reply
    • 告知用户“已经帮你定好了”
    • 或继续补充说明

4. 调度器轮询

  • 独立后台调度器定期扫描到期任务。
  • 扫描条件:
    • status = pending
    • send_at <= now
    • chat_type = private
  • 调度器取出任务后尝试执行发送。

5. 到点发送

  • 调度器直接调用现有 send_service.text_to_stream_with_message(...)
  • 发送参数要求:
    • text = 任务里保存的 message_text
    • stream_id = 任务对应的私聊 session_id
    • storage_message = True
    • sync_to_maisaka_history = True
    • maisaka_source_kind = "scheduled_send"
  • 这样发送后会自动:
    • 走现有平台发送链路
    • 写入现有消息存储
    • 触发现有 memory automation
    • 在 runtime 仍存活时同步写入 Maisaka 上下文

6. 发送后的上下文表现

  • 如果该 session 的 runtime 当前仍在 heartflow_manager.heartflow_chat_list 中:
    • 发送完成后,消息会立刻追加进 _chat_history
  • 如果 runtime 当前不在内存中:
    • 这条消息仍会进入现有消息存储
    • 之后会话重新活跃时,至少数据库层面仍保留这条 bot 已发送消息
  • V1 先不为“runtime 不在内存时立即补上下文”额外新造链路
  • 先复用现有发送与存储通道

工具设计

工具名

  • schedule_private_message

工具职责

  • 为当前私聊创建一条未来发送任务
  • 可选择覆盖当前私聊中尚未执行的旧任务

工具参数

  • send_at
    • 类型:string
    • 含义:发送时间
    • 约定:先存标准化后的绝对时间字符串,内部落库再转时间戳
  • message_text
    • 类型:string
    • 含义:到点时直接发送的文本
  • replace_existing
    • 类型:boolean
    • 含义:是否覆盖当前私聊里尚未执行的旧任务
    • 默认建议:false

工具隐含上下文

  • 不要求 LLM 传 session_id
  • 工具从当前 ToolExecutionContext / runtime 中直接拿当前私聊 session_id
  • 如果当前不是私聊,工具直接失败

工具返回

  • 成功时至少返回:
    • task_id
    • session_id
    • send_at
    • message_text
    • replace_existing
    • 若发生覆盖,返回被取消的旧任务 ID 列表
  • 失败时返回明确原因:
    • 非私聊
    • 时间非法
    • 文本为空
    • 存储失败

任务数据模型

建议字段

  • id
  • session_id
  • chat_type
  • message_text
  • send_at_ts
  • status
  • created_at_ts
  • updated_at_ts
  • created_by_tool_call_id
  • cancelled_by_tool_call_id
  • sent_message_id
  • sent_at_ts
  • last_error
  • replace_existing

状态

  • pending
  • sent
  • cancelled
  • failed

状态语义

  • pending:已创建,等待发送
  • sent:已成功发出
  • cancelled:被显式取消或被新任务覆盖
  • failed:尝试发送失败,保留错误信息

覆盖规则

V1 定义

  • replace_existing = true 时:
    • 将当前 session_id 下所有 pending 任务置为 cancelled
    • 再创建当前新任务
  • replace_existing = false 时:
    • 不取消旧任务
    • 直接新增当前任务

这样做的原因

  • 不引入模糊分类
  • 不猜“哪个旧任务和哪个新任务算同类”
  • 规则简单、确定、易解释

取消与改期

V1 建议

  • 不单独做“改期”底层能力
  • 改期等价于:
    • 取消旧任务
    • 新建新任务
  • 后续可再补专用工具:
    • cancel_scheduled_private_message
    • list_scheduled_private_messages

当前阶段

  • 当前文档先只覆盖“创建并发送”的最小闭环
  • 取消工具可以作为下一步扩展

到点前的最小状态校验

  • 任务当前仍是 pending
  • 任务所属 chat_type = private
  • 当前时间已到 send_at_ts
  • 任务尚未发送过
  • 任务未被取消

明确不作为校验项的内容

  • 不检查“用户后来是否又聊天”
  • 不让 LLM 到点时重新读上下文判断
  • 不根据最近聊天内容自动反悔

与现有链路的复用点

发送

  • 复用 send_service.text_to_stream_with_message(...)

存储

  • 复用现有消息发送成功后的消息存储链路

上下文

  • 复用 sync_to_maisaka_history=True
  • 复用 runtime 的 append_sent_message_to_chat_history(...)

Tool call 记录

  • 创建任务时,复用现有 Planner tool 执行记录链路
  • 到点执行时,不强行伪造一条新的 LLM tool call
  • 到点执行属于调度器行为,不属于当轮 Planner 推理

需要补的实现模块

1. 新内置工具

  • 新增 schedule_private_message builtin tool

2. 任务存储

  • 新增一张定时消息任务表
  • 提供最小 CRUD
    • create
    • cancel pending by session
    • list due pending
    • mark sent
    • mark failed

3. 后台调度器

  • 独立异步循环
  • 固定间隔扫描 due tasks
  • 执行发送并更新状态

4. 主程序启动

  • 在现有后台任务体系中注册该调度器

当前建议的落地顺序

  • 第一步:先把工具接口和任务表定下来
  • 第二步:把任务创建打通
  • 第三步:把后台调度发送打通
  • 第四步:确认发送后消息能按现有链路进入上下文
  • 第五步:再考虑取消/查看任务工具