Files
mai-bot/计划.md
2026-05-11 23:20:19 +08:00

13 KiB
Raw Blame History

私聊定时跟进 V1 计划

当前结论

  • 本方案只支持私聊,不支持群聊。
  • 旧草案中的“到点直接发送预先写死的 message_text”不再作为 V1 主方案。
  • V1 改为“定时器负责唤醒当前私聊会话的正常思考流程”,由 LLM 基于到点时的当前上下文决定是否主动发言。
  • 为了支持“主动发言但不强行回复某条旧消息”,需要新增一个与 reply 平行的主动私聊发送工具。
  • 平台出口、消息存储、Maisaka 上下文写回、Napcat 适配等链路应尽量复用现有实现,不新造一条发送通道。

目标

  • 让 LLM 可以在当前私聊里创建一条未来触发的“定时跟进任务”。
  • 到点后不直接发固定文本,而是重新进入该私聊的正常 planner 思考流程。
  • 到点思考时直接复用现有上下文窗口、原有 planner prompt、原有工具机制。
  • 如果 planner 判断现在适合主动发言,则调用新的主动私聊发送工具发出消息。
  • 如果 planner 判断现在不适合打扰,则可以不发言并直接结束本轮。

明确不做

  • 不做群聊版定时跟进。
  • 不做跨会话发送;当前私聊中创建的任务,只能唤醒当前私聊,也只能向当前私聊主动发言。
  • 不做“任意指定目标用户/目标 session 的消息发送器”。
  • 不做复杂多意图分类,不引入冗余的 intent_key
  • 不伪造一条新的用户入站消息来驱动到点逻辑。
  • 不把主动 follow-up 强行伪装成 reply(msg_id=...)

核心方案

1. 定时器从“文本发送器”改为“流程进入器”

  • 当前用户在私聊中表达未来某个时间点需要 bot 主动跟进。
  • Planner 调用新的定时跟进工具,写入一条未来任务。
  • 到点后,后台调度器不直接发消息,而是唤醒对应私聊会话的 Maisaka planner。
  • planner 结合当前上下文重新判断:
    • 是否应该主动发言
    • 现在该说什么
    • 是否应该放弃本次跟进
    • 是否应该重新约下一个时间点

2. 新增主动私聊发送工具

  • 保留现有 reply
    • 语义仍然是“针对某条已有消息进行回复”
    • 仍然依赖 msg_id
  • 新增 send_private_message
    • 语义是“在当前私聊里主动发出一条可见消息”
    • 不依赖 msg_id
    • 不默认引用旧消息
  • 这样可以避免 QQ/Napcat 侧展示成“永远在回复某条旧消息”,更符合真实私聊中的自然主动聊天。

3. 发送出口继续复用现有通道

  • send_private_message 不新造平台出口。
  • 它和 reply 一样,最终复用现有:
    • send_service
    • Platform IO
    • 现有 driver / adapter
    • Napcat 出口
  • 这样发送成功后,仍可复用现有:
    • 消息写库
    • memory automation
    • Maisaka 历史同步

端到端流转链路

1. 用户触发

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

2. Planner 决策

  • Planner 分析当前局势后,可直接调用定时跟进工具。
  • 工具应为 planner 直接可见的 action tool不走 deferred tools。
  • 同一轮里planner 仍可继续:
    • 调用 reply
    • 正常回复用户
    • 告知用户已经记下这次未来跟进

3. 工具执行

  • 暂定工具名:schedule_private_followup
  • 工具接收参数后,直接写入任务存储。
  • 工具只允许操作当前私聊会话,不允许 LLM 指定别的 session。
  • tool result 与其他工具一样,复用现有 tool call 记录与上下文链路。

4. 后台调度器轮询

  • 独立后台调度器定期扫描到期任务。
  • 扫描条件:
    • status = pending
    • send_at_ts <= now
  • 任务取出后,不直接发送,而是进入“会话唤醒”流程。

5. 会话唤醒

  • 调度器根据 session_id 定位或恢复对应私聊会话。
  • 如果对应 runtime 当前不在内存中,应先恢复/创建该会话的 Maisaka runtime。
  • 调度器调用一个新的运行时入口,例如:
    • trigger_scheduled_followup(task)
    • 或同等语义的方法
  • 这个入口负责以“额外注入消息”的形式进入 planner而不是伪造用户消息。

6. 到点后的 planner 行为

  • 唤醒后的 planner 继续使用项目原有的主 prompt。
  • 同时在请求尾部追加一条 injected_user_messages,内容使用项目已有的 <system-reminder> 风格。
  • planner 基于当前上下文重新分析后,可以:
    • 调用 send_private_message
    • 调用 reply(仅当它明确需要围绕某条旧消息回复时)
    • 调用 finish
    • 再次调用 schedule_private_followup

7. 主动发送

  • 当 planner 判断应主动发言时,调用 send_private_message
  • send_private_message 直接复用现有 send_service.text_to_stream_with_message(...)
  • 发送参数应满足:
    • stream_id = 当前 ToolExecutionContext.session_id
    • storage_message = True
    • sync_to_maisaka_history = True
  • maisaka_source_kind 建议使用新的主动发送来源标记,例如:
    • proactive_send
    • 若后续需要区分定时唤醒来源,可再在 metadata 中补充 trigger_source=scheduled_followup

8. 任务完成

  • 本次唤醒流程成功结束后,原任务应被标记为完成。
  • “完成”不等于“一定发了消息”。
  • 只要本次唤醒和 planner 处理已成功结束,即可完成任务,并记录最终动作结果。

工具设计

工具 1schedule_private_followup

工具职责

  • 为当前私聊创建一条未来触发的定时跟进任务。
  • 任务到点后唤醒当前私聊的 planner而不是直接发送固定文本。

工具参数

  • send_at
    • 类型:string
    • 含义:触发时间
    • 建议:由 LLM 提供标准化后的绝对时间字符串
  • followup_reason
    • 类型:string
    • 含义:这次未来跟进的原因/事项摘要
    • 作用:到点时注入 reminder帮助 planner 理解为什么会被唤醒
  • assistant_commitment_text
    • 类型:string
    • 含义当前这轮里bot 对用户作出的那句“未来会来找你/提醒你/跟进你”的承诺话术
    • 作用:到点时也注入给 planner帮助它和之前的承诺保持一致
  • replace_existing
    • 类型:boolean
    • 含义:是否覆盖当前私聊中尚未执行的旧跟进任务
    • 默认建议:false

工具隐含上下文

  • 不要求 LLM 传 session_id
  • 不要求 LLM 传 user_id
  • 后端直接从当前 ToolExecutionContext.session_id 取目标私聊
  • 如果当前不是私聊,工具直接失败

工具返回

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

工具 2send_private_message

工具职责

  • 在当前私聊会话中主动发送一条可见消息。
  • 不依赖 msg_id
  • 不默认带引用回复。

工具参数

  • message_text
    • 类型:string
    • 含义:要主动发送给当前私聊对象的文本内容

工具隐含上下文

  • 不要求 LLM 传 session_id
  • 不允许 LLM 指定别的目标会话
  • 后端直接使用当前 ToolExecutionContext.session_id

工具返回

  • 成功时至少返回:
    • session_id
    • message_text
    • sent_message_id
  • 失败时返回明确原因:
    • 非私聊
    • 文本为空
    • 发送失败

工具关系

  • reply:回应某条已有消息
  • send_private_message:主动发言,不依赖某条已有消息
  • schedule_private_followup:创建未来跟进任务,负责到点后唤醒 planner

任务数据模型

建议字段

  • id
  • session_id
  • send_at_ts
  • status
  • followup_reason
  • assistant_commitment_text
  • created_at_ts
  • updated_at_ts
  • created_by_tool_call_id
  • cancelled_by_tool_call_id
  • triggered_at_ts
  • completed_at_ts
  • completion_action
  • sent_message_id
  • last_error
  • replace_existing

状态

  • pending
  • running
  • completed
  • cancelled
  • failed

状态语义

  • pending:已创建,等待到点唤醒
  • running:调度器已取出,正在执行本次唤醒
  • completed:本次唤醒流程已成功结束,无论是否真的发出消息
  • cancelled:被显式取消或被新任务覆盖
  • failed:本次唤醒流程执行失败

完成动作建议

  • sent_private_message
  • skipped
  • rescheduled
  • unknown

覆盖规则

V1 定义

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

这样做的原因

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

到点注入给 planner 的提示方式

复用原则

  • 继续复用原有 prompts/zh-CN/maisaka_chat.prompt
  • 不复制一整份新 prompt
  • 不把 reminder 伪装成真实聊天历史消息
  • 复用现有 injected_user_messages 机制

风格要求

  • 注入内容应沿用项目现有 <system-reminder>...</system-reminder> 风格
  • 它在请求里以 user 身份追加,但语义上明确说明“不是用户刚刚发来的新消息”
  • 应提醒 planner
    • 这是一次定时跟进触发
    • 当前为什么会被唤醒
    • 你之前对用户说过什么
    • 可以主动发言,也可以不发言
    • 除非明确需要回应某条旧消息,否则不要默认调用 reply

提示文案打样

<system-reminder>
以下内容不是用户刚刚发来的新消息,而是当前私聊的一次定时跟进触发。

触发时间:{trigger_at}

你之前在这个私聊中设置过一次定时跟进。
当时记录的跟进原因:
{followup_reason}

当时你已经对用户说过:
{assistant_commitment_text}

请结合当前上下文重新分析现在是否适合主动发言:
- 如果仍然适合,就继续正常分析,并在需要时调用 send_private_message。
- 如果当前话题已经变化、用户已经提到过同类内容、约定已经失效,或者现在不适合打扰,就不要强行发送,直接 finish。
- 不要把这段提醒当成用户的新发言。
- 除非你明确是在回应历史中的某条具体消息,否则不要默认调用 reply。
</system-reminder>

与现有链路的复用点

Prompt 与上下文

  • 复用 Maisaka planner 主 prompt
  • 复用现有上下文窗口选择逻辑
  • 复用 injected_user_messages 追加尾部提醒

发送

  • send_private_message 复用 send_service.text_to_stream_with_message(...)
  • 继续复用现有 Platform IO 和 Napcat 出口

存储

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

上下文写回

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

Tool call 记录

  • 创建任务时,复用现有 Planner tool 执行记录链路
  • 到点唤醒不伪造一条新的 LLM tool call
  • 到点执行本质上属于调度器唤醒会话,而不是伪造用户发言

需要补的实现模块

1. 新内置工具

  • 新增 schedule_private_followup builtin tool
  • 新增 send_private_message builtin tool

2. 任务存储

  • 新增一张私聊定时跟进任务表
  • 提供最小操作:
    • create
    • cancel pending by session
    • list due pending
    • mark running
    • mark completed
    • mark failed

3. 后台调度器

  • 独立异步循环
  • 固定间隔扫描 due tasks
  • 负责 claim 任务、唤醒会话、更新状态

4. Maisaka 运行时入口

  • 为运行时新增一个“带 reminder 进入 planner 一轮”的入口
  • 该入口负责把 <system-reminder> 注入到 planner 请求尾部

5. 主程序启动

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

最小验证路径

验证目标

  • 确认创建任务、到点唤醒、重新思考、主动发送、写库、上下文同步都能走通

建议路径

  1. 在私聊中让 bot 创建一条几分钟后的 schedule_private_followup
  2. 当轮正常 reply 给用户,说明之后会再来跟进
  3. 到点后由调度器唤醒当前私聊的 planner
  4. planner 收到 <system-reminder> 后重新分析
  5. 若判断合适,调用 send_private_message
  6. 发送应复用现有 Napcat / Platform IO 出口成功发出
  7. 发出的消息应进入:
    • 现有消息存储
    • Maisaka 上下文历史
    • 现有 memory automation

当前建议的落地顺序

  • 第一步:先把工具边界定下来:schedule_private_followupsend_private_message
  • 第二步:把任务表与任务存储定下来
  • 第三步:补运行时“带 reminder 唤醒 planner”入口
  • 第四步:补后台调度器并接入主程序
  • 第五步:打通主动发送、写库、上下文同步
  • 第六步:再考虑取消/查看任务工具