22 KiB
22 KiB
私聊拟人性增强分阶段计划
总体目标
- 在尽量保留当前 maibot 主干能力与最近更新的前提下,增强私聊中的连续感、活人感与主动性。
- 优先修复“私聊对象信息容易遗忘”“聊天断开后像失忆”“上下文窗口过于保守”这三类核心问题。
- 再逐步增加“定时跟进”“主动私聊”“关系阶段推进”等能力。
- 落地时坚持“平行新增、契约尽量不变”:
- 不破坏现有
reply语义 - 新能力优先通过新工具、新配置、新任务表、新 metadata 增量扩展
- 尽量不改现有 WebUI 聊天消息结构与事件结构
- 不破坏现有
借鉴来源
1. MoFox-Core
- 主要借鉴:
- 私聊对象关系信息常态注入
- 主动前护栏判断
WAITING / IDLE状态机思路- 发完后等待回复、超时再判断是否继续发言的活人感逻辑
- 不直接照搬:
- AFC / KFC 全套 runtime
- 强耦合插件系统
- 其整套数据库结构
2. Muice-Chatbot
- 主要借鉴:
- 轻量主动开场/问候思路
- 随机主动话题池思路
- 不直接照搬:
- 到点直接发送固定文本
- 过于依赖固定 prompt 的主动对话实现
当前问题
- 私聊对话者信息经常会被忘记,应该被常态化注入。
- 当前上下文管理偏保守,没充分发挥大上下文模型的能力。
- 私聊 runtime 重建后不会自动回灌最近历史,聊天断开一阵子后容易出现“失忆”。
- 人物信息、关系信息、共同经历主要依赖临时检索,缺少稳定默认上下文。
- planner 查到的信息不能稳定传递到 replyer,导致回复阶段再次遗忘。
当前准备增加的能力
- 定时跟进机制:允许 LLM 在当前私聊中创建未来跟进任务,到点后重新唤醒 planner 判断要不要主动开口。
- 主动私聊发送工具:新增不依赖
msg_id的send_private_message,作为replyer的新发送出口,让主动聊天不再伪装成回复旧消息。 - 到点前护栏机制:加入静默时段、最小主动间隔、可选每日上限,避免技术上能发但体验上打扰。
- 阶段以及主线机制:允许你在私聊中像玩 galgame 一样,和小麦进行逐步推进的互动。
- 为后续私聊状态机预留扩展:后面再补“发完等回复、超时再判断”的活人感机制。
兼容性原则
前后端契约
- 现有
reply工具保持原语义、原参数、原发送形态不变。 - 新增
send_private_message与schedule_private_followup时,采用与现有 builtin tool 平行的注册、执行、记录方式。 - 现有聊天消息
WsMessage / ChatMessage / MessageSegment契约尽量不变:- 主动私聊发出的消息仍然是普通 bot 可见消息
- 不新增专用
MessageSegment.type作为前提
- 现有工具记录结构尽量不变:
- 继续复用现有 tool record / tool result 链路
- 新增内容尽量放在
tool_name、tool_data、metadata 中
配置与 WebUI
- 配置面优先平行新增独立配置块,默认值完整,保证旧配置不改也能运行。
- WebUI V1 不应成为主阻塞项:
- 新功能优先复用现有聊天渲染与工具渲染
- 后续再考虑做任务管理页、状态展示页等增强 UI
阶段 0:开发期稳定化
本阶段要解决的问题
- 当前准备进入连续多阶段改造,如果开发过程中误触自动更新,容易导致本体与 WebUI 版本漂移。
- 一旦前后端版本不一致,后续上下文、工具、调度器相关改造会很难排障。
- private-main 开发期间需要一个尽量稳定、可复现的运行基线。
本阶段要增加/修改的点
☐断开 WebUI 自动更新检测☐断开本体自动更新检测☐避免开发期自动拉取或自动提示更新☐保留显式手动恢复/更新路径
如何做
- 找到当前 WebUI 与本体的更新检查入口:
- 启动时自动检查
- 定时轮询检查
- 页面内自动提示
- 开发期优先做“禁用自动检查”,而不是修改版本号或伪造远端结果。
- 尽量采用可逆方案:
- 配置开关
- 环境变量
- 显式开发模式判断
- 如果本体和 WebUI 的更新逻辑是分开的,也应分别断开,避免只关一边。
借鉴来源
- 不依赖
MoFox-Core或Muice-Chatbot。 - 这是当前 private-main 连续改造前的工程防护阶段。
验收标准
- 启动本体时,不再主动发起自动更新检查。
- 启动 WebUI 时,不再自动弹更新提示或自动轮询更新状态。
- 本体日志和 WebUI 调试输出中,不再出现自动更新任务被触发的痕迹。
- 若后续需要更新,仍存在显式、可控、可手动触发的更新路径。
阶段 1:修复私聊遗忘问题
本阶段要解决的问题
- 私聊对象信息经常被忘记。
- 聊天断开后再回来,像从空白状态重新开始。
- planner 查到的信息不稳定,replyer 容易再次遗忘。
本阶段要增加/修改的点
☐私聊对象信息常态注入☐runtime 重建时自动回灌最近历史☐planner 到 replyer 的信息继承增强☐上下文选择策略从“纯按条数裁切”调整为“更适合大上下文模型”的策略
本阶段目标预算
- 以“大上下文真正可用,但不盲目塞满”为原则,先把私聊 planner 的目标输入预算定在 256K token 级别。
- 不建议把整整 256K 都让历史消息吃满,应预留系统提示、工具定义、注入块、输出空间。
- 第一版建议按下面的思路分配:
- 总目标预算:
256K - 预留给系统提示 / 工具定义 / injected reminders / 输出余量:
32K ~ 64K - 可用于“历史消息 + 人物信息块 + 共同事件块”的主预算:
160K ~ 220K
- 总目标预算:
- replyer 阶段不必与 planner 一样大,可以显著更小:
- 优先依赖 planner 已整理出的参考信息
- replyer 可先控制在
32K ~ 64K级别
本阶段要先定下的几个原则
- 历史回灌 不是滑动窗口本身,而是滑动窗口的前置步骤。
- token 滑窗 不应只做“超了就 pop”,而应做“固定高优先级块保底 + 历史候选池按 token 预算裁切”。
- 人物信息块、当前约定、最近共同事件 的优先级应高于普通旧聊天。
- planner 与 replyer 的上下文策略不必完全一样:
- planner 负责大窗理解
- replyer 负责在较小上下文内稳定生成
- 必须保留降级路径:
- 即使 tokenizer 暂时不可用,也能退回近似估算模式
- 但默认目标是本地 token 预算裁切,而不是继续硬按条数
如何做
- 为当前私聊补一个轻量信息聚合入口:
- 优先从现有
person_info - 再从 A_memorix 的人物画像/关系证据中补充
- 统一格式化为稳定上下文块,默认注入 planner
- 优先从现有
- 在 runtime 重建时,按
session_id从消息库回灌最近一段真实聊天历史,而不是从空_chat_history开始。 - replyer 生成阶段不再过度依赖临时工具结果,而是显式承接 planner 已整理出的稳定参考信息。
- 调整上下文窗口策略:
- 不再只用固定条数近似控制
- 引入更大的私聊历史回放范围
- 优先保留“人物关系/最近共同事件/当前约定”这类高价值上下文
1.1 runtime 历史重建
- 触发时机:
- 私聊 runtime 首次创建时
- 私聊 runtime 被回收后再次恢复时
- 定时跟进任务唤醒私聊 runtime 时
- 基本流程:
- 根据
session_id查询最近一段真实消息 - 将消息重新构造成 Maisaka 使用的
LLMContextMessage - 写回
_chat_history - 再叠加本轮新收到的消息与 injected reminders
- 根据
- 第一版建议:
- 先取最近
200 ~ 1000条私聊真实消息作为“候选历史池” - 不是全部送给模型,而是交给后续 token 滑窗裁切
- 先取最近
- 注意:
- 历史重建应优先复用现有消息入库格式和
SessionBackedMessage构建逻辑 - 不要单独造一套“历史专用消息对象”
- 历史重建应优先复用现有消息入库格式和
1.2 本地 tokenizer / token 预算器
- 当前项目里已有 token 使用统计,但那是模型返回 usage 后的结果,不足以做请求前选窗。
- 因此本阶段需要新增一个“请求前 token 估算器”抽象层。
- 目标不是一开始追求 100% 精确,而是先把“按 token 预算裁切”跑通。
- 建议方案:
- 新增统一的
context_token_counter/prompt_token_estimator - 输入为即将送给模型的消息列表或其序列化结果
- 输出为估算 token 数
- 新增统一的
- tokenizer 选择建议:
- 优先选择与当前 OpenAI-compatible 请求栈兼容的本地 tokenizer 方案
- 若误差验证可接受,可直接作为私聊上下文预算器
- 若误差过大,再考虑换成更贴近目标模型的 tokenizer
- 降级方案:
- tokenizer 初始化失败时,可暂时退回“字符数 / 中文字数近似预算”
- 但只作为 fallback,不作为主路径
1.3 token 滑动窗口
- 目标不是“把 256K 塞满”,而是“尽量稳定地用到 256K 级别能力”。
- 建议采用“两层预算”:
- 第一层:固定保底块
- 第二层:历史滑窗块
- 固定保底块建议包含:
- 系统提示
- 当前私聊对象信息块
- 当前约定 / 当前跟进原因
- 最近共同事件摘要
- 必要的工具定义
- 历史滑窗块建议逻辑:
- 从最近消息开始向前累加
- 每加入一条,重新累加 token
- 超预算则停止
- 保证最近几轮真实对话优先进入窗口
- 不建议简单地“全拼后从头一直 pop 到合法”作为唯一策略。
- 更推荐:
- 先固定高优先级块
- 再对普通历史做从近到远的预算填充
1.4 高优先级上下文保底
- 下面这些信息不应与普通旧聊天放在同一优先级:
- 当前私聊对象是谁
- 你和对方目前是什么关系
- 近期共同事件
- 最近承诺/约定
- 当前定时跟进原因
- 这些内容即使在历史极长的情况下,也应尽量保底进入窗口。
- 这部分内容应从:
person_info- A_memorix 的人物画像 / 关系证据 / episode
- 当前会话内最近承诺 统一汇总出来
1.5 planner → replyer 信息继承
- 目前 replyer 容易再次遗忘,核心原因之一是它并不稳定承接 planner 已经查到和整理过的信息。
- 第一版建议:
- planner 输出时显式生成“稳定参考信息块”
- replyer 直接消费这块,而不是只依赖再看一遍历史
- 这样即使 replyer 窗口比 planner 小很多,也能保持人物信息连续性。
1.6 配置建议
- 本阶段建议新增但保持默认兼容的配置项,例如:
chat.private_context_rebuild_enabledchat.private_context_rebuild_recent_limitchat.private_context_token_budgetchat.private_context_reserved_tokenschat.private_context_inject_relation_infochat.private_context_recent_events_limitchat.replyer_context_token_budget
- 默认值应尽量保守,避免对现有用户造成突然的延迟或成本上升。
借鉴来源
MoFox-Core- 借鉴其“关系信息 + 印象 + 偏好 +聊天流印象”统一拼装进私聊上下文的思路
Muice-Chatbot- 基本不借鉴实现,只参考“最近历史 + 旧记忆回注入”的轻量骨架
验收标准
- 同一个私聊会话在沉默一段时间后再次收到消息,bot 不应明显忘记对方是谁。
- 重建后的 runtime 能带上最近一段真实聊天历史,而不是只看最后一条消息。
- 私聊中不需要用户重复提醒,bot 也能较稳定记住:
- 对方称呼
- 近期正在聊的事
- 最近约定/承诺
- 在日志或调试信息里,能看到“重建历史条数”“对象信息注入块”的明确痕迹。
- 能在日志或调试信息里看到:
- 候选历史条数
- 最终入窗条数
- 估算 token 数
- 被预算裁掉的原因
- 在高上下文测试下,planner 能稳定工作在
256K级别预算附近,而不是仍然被 60 条历史限制住。 - replyer 即使使用更小窗口,也不应明显丢失 planner 已确认的人物信息。
阶段 2:定时跟进 V1
本阶段要解决的问题
- 当前私聊没有“我之后再来找你”的能力。
- 即使用户和 bot 约好了未来某个时间点,系统也无法主动在那时重新思考。
- 需要主动发言时,只能强行伪装成
reply(msg_id=...),不自然。 - 如果直接让 planner 裸写
message_text并发送,会让主动私聊和普通回复走成两套文案体系。
本阶段要增加/修改的点
☐新 builtin tool:schedule_private_followup☐新 builtin tool:send_private_message☐私聊定时跟进任务表☐后台调度器☐runtime 的“带 reminder 唤醒 planner”入口
如何做
schedule_private_followup- 只允许在当前私聊里创建未来任务
- 记录触发时间、跟进原因、当时承诺话术
- 后台调度器
- 轮询
pending任务 - 到点后 claim 任务并唤醒当前私聊 runtime
- 轮询
- runtime 新入口
- 以
<system-reminder>形式注入“这不是用户新消息,而是一次到点跟进触发” - planner 基于当前上下文重新判断要不要发
- 以
send_private_message- 不直接裸发
message_text - 而是作为
replyer的新发送语义出口 - 复用现有人设、表达习惯、replyer 文案生成链路
- 最终仍复用现有
send_service/ Platform IO / 写库 / 历史同步
- 不直接裸发
借鉴来源
MoFox-Core- 借鉴“主动思考不是直接发消息,而是先重进上下文再判断”的理念
Muice-Chatbot- 明确不采用其“到点直接发固定文本”的路径
- 当前 maibot 架构
- 延续“planner 负责想、replyer 负责写”的职责分离,不让主动私聊成为例外
验收标准
- 私聊中可以成功创建一条未来跟进任务。
- 到点后系统会唤醒 planner,而不是直接发写死文本。
- planner 可以根据当前情况选择:
- 主动发一条消息
- 什么都不发,直接结束
- 再次约下一次跟进
- 主动发出的消息是普通 bot 消息,不带旧消息引用。
- 主动发出的文本风格应与普通
reply()路径保持一致,不应明显像另一套文案系统。 - 发送成功后仍会进入:
- 现有消息存储
- Maisaka 历史同步
- memory automation
send_private_message 设计细化
工具职责
- 在当前私聊会话中主动发出一条可见消息。
- 不依赖
msg_id。 - 不默认带引用回复。
- 语义上是“主动开口”,但文案生成仍应走
replyer,而不是绕开 replyer 直接裸发。
参数设计建议
- 不建议把主参数设计成裸
message_text。 - 更推荐让 planner 提供“为什么说”,让 replyer 负责“具体怎么说”。
- 第一版建议参数:
proactive_reason- 类型:
string - 含义:这次主动发言的直接理由,作为 replyer 的“最新推理”
- 类型:
reference_info- 类型:
string - 含义:本轮主动发言依赖的事实性参考信息
- 类型:
trigger_source- 类型:
string - 含义:触发来源,例如
scheduled_followup
- 类型:
assistant_commitment_text- 类型:
string - 含义:若这次主动发言是在延续先前承诺,可作为风格一致性参考
- 类型:
实现建议
send_private_message应复用当前reply()已有的 replyer 生成链路。- 推荐执行路径:
- planner 调用
send_private_message - tool 内部获取当前会话 replyer
- 以“主动发言场景”调用
generate_reply_with_context(...) - 此时
reply_message=None reply_reason=proactive_reasonreference_info中整理注入reference_info + assistant_commitment_text + trigger_source- replyer 生成文本后,再复用现有发送链路发出
- planner 调用
返回建议
- 成功时至少返回:
session_idgenerated_message_textsent_message_idmaisaka_source_kind
- 失败时返回明确原因:
- 非私聊
- replyer 生成失败
- 发送失败
阶段 3:主动私聊护栏与轻量主动能力
本阶段要解决的问题
- 即使有定时跟进,系统也缺少“主动前的工程护栏”。
- 后续如果要做更常态化的主动私聊,没有最小打扰控制会很容易打扰用户。
本阶段要增加/修改的点
☐静默时段☐最小主动间隔☐可选每日上限☐skip reason 与 guardrail snapshot☐轻量主动开场/问候 fallback
如何做
- 在“任务到期”与“真正唤醒 planner”之间插入规则层:
- 私聊校验
- 状态校验
- 静默时段判断
- 最小主动间隔判断
- 可选每日上限判断
- 对被跳过的任务记录:
skip_reasonguardrail_snapshot_json
- 为未来“非约定型主动私聊”预留轻量主动开场能力:
- 轻问候
- 轻话题冒泡
- 但此时仍不做完整主动聊天系统
借鉴来源
MoFox-Core- 重点借鉴其主动思考调度器中的工程护栏思路
Muice-Chatbot- 借鉴其轻量问候/轻话题池思路,仅作为 fallback
验收标准
- 明显不适合打扰的时段,任务会被安全跳过或延后,而不是强行唤醒 planner。
- 日志中能明确看到“为什么没发”的 skip reason。
- 即使以后加入更强主动私聊,这一层规则也能直接复用。
阶段 4:私聊状态机活人感增强
本阶段要解决的问题
- 当前主动私聊即使能发,也缺少“发完之后等你回复”的真实感。
- 系统无法区分“及时回”“晚回”“一直没回”。
本阶段要增加/修改的点
☐WAITING / IDLE私聊状态机☐主动发送后的等待态☐超时后再判断是否继续等、追问或闭嘴☐连续超时计数与打扰保护
如何做
- 在阶段 2 的基础上,扩展主动发送后的状态记录:
- 最近一次主动发送时间
- 是否正在等待回复
- 等待配置
- 连续超时计数
- 收到用户消息时区分:
reply_in_timereply_latenew_message
- 等待超时后,再重新进行一轮轻量判断:
- 继续等
- 轻追问
- 结束等待,不再打扰
借鉴来源
MoFox-Core- 这是最核心的借鉴来源,重点是其
WAITING / IDLE与超时后再思考的设计
- 这是最核心的借鉴来源,重点是其
Muice-Chatbot- 基本不借鉴
验收标准
- 主动发言后,系统能进入等待态,而不是把这件事完全忘掉。
- 用户及时回复和很久后才回复时,系统的反应能出现可区分差异。
- 多次未回复后,系统会自动降低继续打扰的倾向。
阶段 5:关系阶段与主线推进
本阶段要解决的问题
- 当前即使记住了人、能主动聊,也还没有真正的关系推进结构。
- 缺少“朋友 → 更亲密阶段”的可持续演进逻辑。
本阶段要增加/修改的点
☐关系阶段状态☐阶段推进条件☐近期主线事件/共同事件摘要☐更适合私聊恋爱化推进的上下文注入
如何做
- 在现有
person_info/ A_memorix 证据层之上,增加一层更明确的关系状态表达。 - 阶段推进仍然尽量保持轻规则,不急着做全硬编码 romance engine。
- 优先做:
- 阶段状态
- 最近共同事件摘要
- 对阶段敏感的 prompt 注入
- 后续再看是否要加入更强规则。
借鉴来源
MoFox-Core- 借鉴其“关系分数 + 关系阶段 + 印象文案”的分层思路
- 不直接采用其当前阶段命名作为最终产品逻辑
Muice-Chatbot- 不足以支撑这一阶段
验收标准
- 私聊中的称呼、关心方式、主动发言方式,会随关系阶段出现可感知变化。
- 最近共同事件能够被作为关系推进依据,而不是每次都从零开始。
- 系统在长期私聊中呈现出更稳定的关系连续性,而不是只有短期记忆连续性。
配置与 WebUI 影响评估
配置面
- 配置面预计是“小改”,不是大改。
- 建议新增独立配置块,例如:
private_followup.enabledprivate_followup.scheduler_interval_secondsprivate_followup.quiet_hours_startprivate_followup.quiet_hours_endprivate_followup.min_interval_between_proactive_secondsprivate_followup.max_daily_followups_per_sessionprivate_followup.inject_relation_infoprivate_followup.recent_shared_events_limit
- 原则:
- 默认值完整
- 老配置不动也能跑
- 优先改模板并递增版本号
WebUI 面
- V1 到 V3 原则上不需要大改聊天协议。
- 聊天渲染层:
send_private_message发送的仍然是普通 bot 消息- 不要求新增
MessageSegment.type
- 工具渲染层:
- 新工具复用现有 tool record 展示链路
- 前端最多看到新的
tool_name
- 可选增强:
- 定时跟进任务列表
- 任务取消/重试管理页
- 主动护栏状态可视化
- 私聊状态机状态展示
分阶段验收顺序建议
- 第 0 阶段先稳定开发环境,避免自动更新打断改造过程。
- 第一阶段先解决遗忘与历史重建,不急着上主动聊天。
- 第二阶段打通定时跟进 V1。
- 第三阶段补主动护栏与轻量主动能力。
- 第四阶段再做
WAITING / IDLE状态机。 - 第五阶段最后做关系阶段与主线推进。
当前最推荐的开发顺序
- 断开 WebUI / 本体自动更新检测
- 私聊对象信息常态注入
- runtime 历史重建
- planner → replyer 信息继承增强
schedule_private_followupsend_private_messagesend_private_message的 replyer 驱动实现- 后台调度器与 runtime 唤醒入口
- 到点前护栏
WAITING / IDLE扩展- 阶段/主线推进