chore: import private baseline from gitea state
This commit is contained in:
230
计划.md
Normal file
230
计划.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# 私聊定时消息 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. 主程序启动
|
||||
- 在现有后台任务体系中注册该调度器
|
||||
|
||||
## 当前建议的落地顺序
|
||||
- 第一步:先把工具接口和任务表定下来
|
||||
- 第二步:把任务创建打通
|
||||
- 第三步:把后台调度发送打通
|
||||
- 第四步:确认发送后消息能按现有链路进入上下文
|
||||
- 第五步:再考虑取消/查看任务工具
|
||||
Reference in New Issue
Block a user