Skip to content

利用微调模型实现消息防抖,判断用户在分段发送消息的情况下是否说完一句话,合并后调用LLM

License

Notifications You must be signed in to change notification settings

advent259141/astrbot_plugin_debounce

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AstrBot 消息防抖插件

⚠️ 注意:本插件仅支持 AstrBot 4.11+ 版本使用

GitHub stars GitHub license AstrBot

使用 BERT 模型智能判断用户是否说完一句话,减少不必要的 LLM 调用


📖 简介

在实际使用聊天机器人时,用户经常会分多次发送一句话:

用户: 如果明天不下雨
用户: 我们去爬山吧

传统的聊天机器人会分别处理这两句话,导致:

  • ❌ 第一句话语义不完整,LLM 回复质量差
  • ❌ 浪费 API 调用次数和费用
  • ❌ 用户体验不佳

本插件通过训练的 BERT 模型实时判断句子完整性,只在用户说完整句话后才发送给 LLM,实现:

  • ✅ 智能防抖,自动合并未完成的消息
  • ✅ 减少 LLM 调用次数,节省成本
  • ✅ 提升对话质量和用户体验

🚀 功能特性

  • 智能判断:基于 BERT 模型,准确识别中文句子是否完整
  • 双模型支持:提供 Small(快速)和 Normal(精准)两种模型
  • 自动下载:首次使用时自动从 ModelScope 下载模型
  • 灵活配置:可调节判断阈值、超时时间等参数
  • 零感知:静默工作,不打扰用户
  • 低开销:使用 ONNX 推理,CPU 友好

📦 安装

方法一:通过 AstrBot 插件市场(推荐)

  1. 打开 AstrBot WebUI
  2. 进入「插件管理」
  3. 搜索「消息防抖」
  4. 点击「安装」

方法二:手动安装

cd AstrBot/data/plugins
git clone https://github.com/yourusername/astrbot_plugin_debounce.git

然后在 AstrBot WebUI 中重载插件列表。


⚙️ 配置说明

配置项 类型 默认值 说明
model_type 选择 small 模型类型:small(轻量快速)/ normal(精准度高)
send_threshold 浮点 0.5 完整性判断阈值(0-1),越高越严格
timeout_seconds 整数 30 超时自动发送时间(秒),设为 0 禁用
enabled 布尔 true 是否启用插件
cancel_on_new_message 布尔 true LLM 回复前收到新消息时取消回复
debug_mode 布尔 false 调试模式,输出详细日志

配置示例

{
  "model_type": "small",
  "send_threshold": 0.6,
  "timeout_seconds": 30,
  "enabled": true,
  "cancel_on_new_message": true,
  "debug_mode": false
}

💡 使用示例

场景 1:分段输入

用户: 如果明天不下雨
      ↓ 插件判断:未完整,等待...
      
用户: 我们去爬山吧
      ↓ 插件判断:完整,发送给 LLM
      
LLM:  好的!如果明天天气好,我们可以一起去爬山~

场景 2:用户补充内容(启用 cancel_on_new_message)

用户: 如果明天不下雨
      ↓ 判断:未完整,缓存

用户: 我们去爬山吧
      ↓ 判断:完整,发送给 LLM
      ⏳ LLM 正在思考中...

用户: 顺便带上野餐垫
      ↓ 检测到:用户在等待回复时又发消息
      ❌ 取消 LLM 当前回复
      ✅ 将新消息加入缓存,继续等待

用户: 和水果
      ↓ 判断:完整,合并所有内容发送
      
LLM:  好的!明天天气好的话,我们去爬山,我会准备野餐垫和水果~

场景 3:超时处理

用户: 虽然今天很累
      ↓ 等待 30 秒...
      
      ↓ 超时,自动发送
      
LLM:  虽然今天很累,但还是要注意休息哦!

🔧 模型说明

插件提供两种模型,均托管在 ModelScope

模型 大小 速度 准确率 推荐场景
Small ~10MB ⚡⚡⚡ ~90% 日常使用,CPU 环境
Normal ~100MB ⚡⚡ ~95% 高准确度需求

模型仓库

首次使用会自动下载,无需手动操作。


📊 训练数据

模型基于以下类型的中文句子训练:

标签 说明 示例
0 (WAIT) 句子未完整 "如果明天不下雨"
1 (SEND) 句子已完整 "如果明天不下雨我们去爬山"

涵盖常见的复句结构:

  • 条件句:如果...假如...
  • 转折句:虽然...尽管...
  • 因果句:因为...由于...
  • 递进句:其实...也就是说...

🐛 调试

开启调试模式后,日志会输出详细信息:

[防抖] 文本: '如果明天不下雨' | 完整概率: 0.2341 | 判定: 等待
[防抖] 文本: '如果明天不下雨 我们去爬山' | 完整概率: 0.8762 | 判定: 发送
[防抖] 完整发送: 如果明天不下雨 我们去爬山

❓ 常见问题

Q1: 插件会拦截其他插件的 LLM 调用吗?

A: 不会。插件只拦截用户发送消息触发的 LLM 请求,其他插件通过 context.llm_generate() 直接调用 LLM 不受影响。

Q2: 模型下载失败怎么办?

A:

  1. 检查网络连接
  2. 手动下载模型文件放入 models/ 目录
  3. 或切换到 small 模型(体积更小)

Q3: 如何调整判断的严格程度?

A: 修改 send_threshold

  • 值越大越严格(需要更高的完整概率)
  • 建议范围:0.8 - 0.9

Q4: 插件会影响性能吗?

A: 影响极小。ONNX 推理在 CPU 上耗时 < 10ms,可忽略不计。

Q5: 什么是"取消回复"功能?

A: 当用户在等待 LLM 回复时又发送新消息,插件会:

  • 启用时:自动取消当前 LLM 回复,合并新消息后重新发送
  • 禁用时:不取消回复,新消息会作为独立消息处理

建议保持启用,以获得更好的对话体验。


🔄 取消消息机制详解

当启用 cancel_on_new_message 时,插件会在 LLM 处理期间检测新消息,并智能取消过时的回复。

工作原理

核心钩子

插件使用三个 AstrBot 事件钩子协同工作:

钩子 执行时机 作用
on_waiting_llm_request session lock 之前 检测新消息到达,标记旧响应需丢弃
on_llm_request session lock 之后 BERT 判断完整性,管理缓冲区
on_llm_response LLM 响应返回后 检查是否需要丢弃响应

关键时序

┌─────────────────────────────────────────────────────────────────┐
│  消息1: "小面包小面包"                                            │
├─────────────────────────────────────────────────────────────────┤
│  T1: on_waiting_llm_request(消息1)                                  │
│      → 无旧任务,跳过                                             │
│                                                                 │
│  T2: on_llm_request(消息1)                                       │
│      → BERT 判断: 0.04 (未完整)                                   │
│      → buffer = ["小面包小面包"]                                  │
│      → event.stop_event() 阻止发送                               │
│      → 启动 30秒 监控任务                                         │
└─────────────────────────────────────────────────────────────────┘
                              ↓ 3秒后
┌─────────────────────────────────────────────────────────────────┐
│  消息2: "我想你了"                                               │
├─────────────────────────────────────────────────────────────────┤
│  T3: on_waiting_llm_request(消息2)                                  │
│      → 取消监控任务 ✅                                            │
│                                                                 │
│  T4: on_llm_request(消息2)                                       │
│      → 检测到 waiting_sessions 有消息1                            │
│      → buffer = ["小面包小面包", "我想你了"]                       │
│      → BERT 判断: 0.97 (完整)                                     │
│      → req.prompt = "小面包小面包 我想你了"                        │
│      → pending_llm_sessions["session"] = "小面包小面包 我想你了"   │
│      → 发送 LLM 请求 🚀                                           │
└─────────────────────────────────────────────────────────────────┘
                              ↓ 2秒后 (LLM 还在处理中)
┌─────────────────────────────────────────────────────────────────┐
│  消息3: "想和你聊聊天"                                           │
├─────────────────────────────────────────────────────────────────┤
│  T5: on_will_llm_request(消息3) ⚡ 在 session lock 之前执行!      │
│      → 检测到 pending_llm_sessions 有正在处理的请求               │
│      → discard_responses.add("session") 标记丢弃 🎯              │
│      → 恢复 "小面包小面包 我想你了" 到 buffer                      │
│      → buffer = ["小面包小面包 我想你了"]                          │
│                                                                 │
│  T6: [被 session lock 阻塞,等待消息2的LLM完成...]                 │
└─────────────────────────────────────────────────────────────────┘
                              ↓ 8秒后 (LLM 处理完成)
┌─────────────────────────────────────────────────────────────────┐
│  消息2 的 LLM 响应返回                                           │
├─────────────────────────────────────────────────────────────────┤
│  T7: on_llm_response(消息2的响应)                                 │
│      → 检测到 discard_responses 包含此会话                        │
│      → event.stop_event() 丢弃响应 🚫                            │
│      → 用户看不到这个过时的回复 ✅                                 │
└─────────────────────────────────────────────────────────────────┘
                              ↓ session lock 释放
┌─────────────────────────────────────────────────────────────────┐
│  消息3 继续处理                                                  │
├─────────────────────────────────────────────────────────────────┤
│  T8: on_llm_request(消息3)                                       │
│      → buffer = ["小面包小面包 我想你了", "想和你聊聊天"]          │
│      → BERT 判断: 0.95 (完整)                                     │
│      → req.prompt = "小面包小面包 我想你了 想和你聊聊天"           │
│      → 发送 LLM 请求 🚀                                           │
│                                                                 │
│  T9: on_llm_response(消息3的响应)                                 │
│      → 正常返回 ✅                                                │
│      → 用户看到完整的回复 🎉                                      │
└─────────────────────────────────────────────────────────────────┘

为什么需要 on_waiting_llm_request

AstrBot 使用 session lock 防止同一会话的并发 LLM 请求。这意味着:

消息2 正在调用 LLM
    ↓
消息3 到达
    ↓
消息3 的 on_llm_request 被 session lock 阻塞
    ↓
等待消息2 的 LLM 完成后才能执行
    ↓
此时检测"新消息"已经太晚了!

解决方案on_waiting_llm_request 钩子在 session lock 之前 执行,让我们能在第一时间检测到新消息并标记旧响应需要丢弃。

状态管理

状态集合 类型 作用
buffers Dict[session_id, MessageBuffer] 存储未完成的消息
waiting_sessions Set[session_id] 标记正在等待更多消息的会话
pending_llm_sessions Dict[session_id, message_text] 记录正在处理 LLM 的会话及其消息
discard_responses Set[session_id] 标记需要丢弃响应的会话
monitor_tasks Dict[session_id, Task] 超时监控任务
skip_debounce_msg_ids Set[msg_id] 跳过防抖的伪造消息 ID

消息恢复机制

当旧响应被取消时,插件会将原消息内容恢复到 buffer:

# 在 on_waiting_llm_request 中
if session_id in self.pending_llm_sessions:
    old_message = self.pending_llm_sessions[session_id]  # "小面包 我想你了"
    self.discard_responses.add(session_id)
    
    # 恢复到 buffer,与新消息合并
    buffer.messages.insert(0, old_message)
    # buffer 变为 ["小面包 我想你了", "想和你聊聊天"]

这确保用户发送的所有内容都不会丢失。


🤝 贡献

欢迎提交 Issue 和 Pull Request!

📄 许可证

Apache License 2.0

如果这个插件对你有帮助,欢迎给个 ⭐ Star!

About

利用微调模型实现消息防抖,判断用户在分段发送消息的情况下是否说完一句话,合并后调用LLM

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages