diff --git a/.env.example b/.env.example index 2717c74..ff0f8da 100644 --- a/.env.example +++ b/.env.example @@ -27,7 +27,7 @@ TIEBA_SYNC_INTERVAL_SECONDS=900 TIEBA_MAX_POOL_SIZE=240 TIEBA_DETAIL_FETCH_LIMIT=18 TIEBA_RECENT_SENT_LIMIT=30 -TIEBA_RANDOM_AVOID_RECENT=10 +TIEBA_RANDOM_AVOID_RECENT=30 TIEBA_PREFER_IMAGE_THREADS=true TIEBA_BROWSER_HEADLESS=true TIEBA_BROWSER_CHANNEL= diff --git a/CHANGELOG.md b/CHANGELOG.md index 18552ec..6648017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,21 @@ ## [Unreleased] +### 新增 + +- 群聊唤醒模块:新增唤醒延长、兴趣话题、相关性判定、答疑判定、无聊唤醒、兜底概率六类主动响应入口;配置集中在 `config/awakening.toml`,群内通过 `/awakening` 查看状态并管理规则开关。 +- Web 管理后台唤醒页支持按群编辑唤醒延长、兜底概率、无聊唤醒、相关性判定和答疑判定参数,保存后通知 bot 重载规则配置。 + ### 变更 - 推荐 OneBot V11 基座从 NapCat 迁移至 LLBot:更新 compose 示例模板、部署文档、README;新增迁移指南 `docs/admin/migration-napcat-to-llbot.md`。NapCat 近期因 DLL 注入特征遭腾讯高强度风控(频繁 KickedOffLine / 静默掐断),LLBot 使用 PMHQ 外部内存 Hook 规避检测 +### 修复 + +- `/llm reload` 现在会同步刷新敏感词过滤器;`config/sensitive_words.toml` 继续仅通过服务器本地文件或部署流程维护,不在 Web Admin 中回显或编辑。 +- Web Admin 唤醒参数保存后会通知 bot 重载 `config/awakening.toml`,并移除 API 响应中的服务端配置路径。 +- Web Admin 诊断页健康检查改由 bot 侧动作队列执行;动作队列启用 WAL,并会回收长时间停留在 `running` 的中断任务。 + ## [1.6.1] - 2026-05-22 ### 变更 @@ -427,7 +438,11 @@ - 初始化项目骨架:NoneBot2 + OneBot V11,规则驱动回复 - 时区猜测、复读检测、好姐姐接龙、文字 meme 回复 -[Unreleased]: https://github.com/3aKHP/QuickQuip/compare/v1.4.4...HEAD +[Unreleased]: https://github.com/3aKHP/QuickQuip/compare/v1.6.1...HEAD +[1.6.1]: https://github.com/3aKHP/QuickQuip/compare/v1.6.0...v1.6.1 +[1.6.0]: https://github.com/3aKHP/QuickQuip/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/3aKHP/QuickQuip/compare/v1.4.5...v1.5.0 +[1.4.5]: https://github.com/3aKHP/QuickQuip/compare/v1.4.4...v1.4.5 [1.4.4]: https://github.com/3aKHP/QuickQuip/compare/v1.4.3...v1.4.4 [1.4.3]: https://github.com/3aKHP/QuickQuip/compare/v1.4.2...v1.4.3 [1.4.2]: https://github.com/3aKHP/QuickQuip/compare/v1.4.1...v1.4.2 diff --git a/README.md b/README.md index 050b42a..8da87d4 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,13 @@ QuickQuip(双 Q 谐音 = QQ + Quip/妙语)是一个**轻量级、规则驱 - **金币经济系统** — 每日签到累加连击、好感度成长、金币排行,所有对战游戏共用下注和结算。参数集中在 `config/games.toml` 配置 - **节日自动化** — 内置 6 个中国传统节日(公历+农历),自动切换 bot 语气并发送 persona 口吻问候 - **轻娱乐与互动** — `/roll` 掷骰子、`/choose` 随机选择、`/fortune` 每日运势、`/vote` 投票、`/quote` 语录收藏、`/find` 群聊搜索、`/tell` 离线留言 -- **词云生成** — `/wordcloud` 按 today/week/month 四档生成群聊词云图片 +- **词云生成** — `/wordcloud` 按 today/week/month/year 四档生成群聊词云图片 - **LLM 扩展** — 兼容 OpenAI / Claude / Gemini 协议,按群切换 provider/model/persona,支持工具调用、MCP 桥接、图片理解、语音消息转写、联网搜索、故障机器人转写。详见 [docs/dev/llm-module.md](docs/dev/llm-module.md) +- **低频唤醒** — 按群配置唤醒延长、兴趣话题、相关性/答疑判定、无聊冒泡和兜底概率,所有入口受规则开关与限流保护 - **每日播报与总结** — 按群开启早/中/晚报和每日 2000 字小作文,模型级联失败自动降级 - **多贴吧随机搬运** — 多来源帖子池维护,支持随机抽取和定时同步 - **多模态能力** — 图片生成、语音合成、语音识别、歌词创作与音乐生成,统一收口 `config/generation.toml` -- **Web 管理后台** — Vue 3 SPA 仪表板:统计、规则开关、记忆编辑、对话浏览、配置在线编辑、词云生成、诊断工具、日志浏览。详见 [docs/admin/web-admin.md](docs/admin/web-admin.md) +- **Web 管理后台** — Vue 3 SPA 仪表板:统计、规则开关、唤醒管理、记忆编辑、对话浏览、配置在线编辑、词云生成、诊断工具、日志浏览。详见 [docs/admin/web-admin.md](docs/admin/web-admin.md) - **频率限制** — 滑动窗口限流保护,支持按群独立分桶(`scope = "group"`)或全局合并(`scope = "global"`) 完整命令速查:群聊见 [docs/user/group-commands.md](docs/user/group-commands.md),私聊见 [docs/user/private-commands.md](docs/user/private-commands.md)。 @@ -64,7 +65,6 @@ QuickQuip(双 Q 谐音 = QQ + Quip/妙语)是一个**轻量级、规则驱 DRIVER=~fastapi HOST=0.0.0.0 PORT=8080 - SEARCH_BACKEND=auto SEARXNG_BASE_URL=http://127.0.0.1:8888 ``` @@ -93,7 +93,11 @@ QuickQuip(双 Q 谐音 = QQ + Quip/妙语)是一个**轻量级、规则驱 复制 `config/generation.toml.example` 为 `config/generation.toml`,按注释填入 provider 和模型。不存在时图片部分回退读取 `llm.toml` 旧版配置。 -7. **可选:启用多贴吧搬运** +7. **可选:启用群聊唤醒** + + 复制 `config/awakening.toml.example` 为 `config/awakening.toml`,按群设置唤醒延长、兴趣话题、相关性判定、答疑判定和无聊唤醒参数。群内可用 `/awakening status` 查看生效状态。 + +8. **可选:启用多贴吧搬运** 安装 Playwright 浏览器: @@ -112,7 +116,7 @@ QuickQuip(双 Q 谐音 = QQ + Quip/妙语)是一个**轻量级、规则驱 首次使用前按 [部署指南](docs/admin/deployment.md) 完成贴吧登录态导出。 -8. **可选:启动 Web 管理后台** +9. **可选:启动 Web 管理后台** ```bash # 先构建前端(需 Node.js) @@ -127,7 +131,7 @@ QuickQuip(双 Q 谐音 = QQ + Quip/妙语)是一个**轻量级、规则驱 访问 `http://127.0.0.1:5104/ops/`。详见 [docs/admin/web-admin.md](docs/admin/web-admin.md)。 -9. **启动机器人** +10. **启动机器人** ```bash python bot.py @@ -181,6 +185,9 @@ quickquip/app/ ← 应用级消息管线与共享状态装配 | [docs/user/private-commands.md](docs/user/private-commands.md) | 私聊指令速查 | | [docs/admin/deployment.md](docs/admin/deployment.md) | 云端部署指南 | | [docs/admin/configuration.md](docs/admin/configuration.md) | 完整配置参考 | +| [docs/admin/web-admin.md](docs/admin/web-admin.md) | Web 管理后台 | +| [docs/admin/sensitive-filter.md](docs/admin/sensitive-filter.md) | 敏感词过滤器 | +| [docs/admin/migration-napcat-to-llbot.md](docs/admin/migration-napcat-to-llbot.md) | NapCat 迁移 LLBot | | [docs/dev/llm-module.md](docs/dev/llm-module.md) | LLM 模块详解 | | [docs/dev/regex-tutorial.md](docs/dev/regex-tutorial.md) | 正则表达式教程 | | [ROADMAP.md](ROADMAP.md) | 演进路线 | diff --git a/ROADMAP.md b/ROADMAP.md index 2f8ee27..f3ae378 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,23 +5,24 @@ --- -## 当前主线:v1.6.x 鲁棒性拐点 +## 当前主线:v1.7.0 文档与前端收口 -`v1.6.0` 完成了三轮鲁棒性加固(启动链熔断 / 运行时降级 / 语义验证),将两次已知生产事故的根因(单文件配置错误 → 全集群崩溃)转变为优雅降级。下一步重点是补齐三类敏感词扫描缺口,以及两个中大型功能。 +`v1.6.x` 已完成 LLM 鲁棒性加固与 Claude 协议线格式对齐。当前主线进入 `v1.7.0` 准备阶段:唤醒模块与 LLBot 迁移已经进入代码主线,接下来需要完成全仓文档校验、大修公开手册,并补一轮 Web Admin 前端整理后再准备发版。 ### 本轮关注点 -1. **思考块(reasoning_content)扫描**:OpenAI 兼容协议的 `reasoning_content` 是纯文本,可以接入 scrub;Claude 的 signed thinking 块带签名,scrub 后签名失效,需评估是否整块丢弃或仅在写入历史时清理。 -2. **每日总结 / 播报 / 词云链路加固**:这些路径目前未接入敏感词过滤器,但同样会把群聊原文送给 LLM。需评估是否在消息收集阶段统一过一遍。 -3. **Provider 适配评估**:MiMo `mimo-v2.5-pro` / `mimo-v2-pro` 已标记为 `non_vision_models`;后续若新增国产 provider,需统一通过 `non_vision_models` 与 `style_overrides` 接入。 -4. **本地 TTS 服务接入**:基于 `dev/plans/2026-04-23-local-http-tts-fallback-plan.md`,补充本地 HTTP TTS provider 作为 MiniMax TTS 的 offline fallback。 -5. **Provider 健康检查自动故障转移**:基于已有 `quickquip/llm/health.py`,评估定时检查、provider 健康排序、自动降级或故障转移。 +1. **全仓文档校验**:对 README、`docs/`、配置模板、部署示例、ROADMAP 和 CHANGELOG 做一次代码面事实核对。 +2. **唤醒模块文档化**:补齐 `config/awakening.toml`、`/awakening` 命令、六类唤醒规则、无聊唤醒 opt-in 数据文件和限流规则说明。 +3. **LLBot 部署说明收口**:以 LLBot 作为推荐 OneBot V11 基座,保持 compose 示例、部署指南和 NapCat 迁移指南一致。 +4. **Web Admin 前端整理**:在文档大修后检查当前标签页、导航、配置编辑和移动端体验,修掉本轮能明确定位的前端问题。 +5. **配置参考补全**:同步 `.env.example`、`config/*.example`、代码默认值与 `docs/admin/configuration.md`,减少部署者靠读源码补配置的情况。 ### 退出条件 -1. thinking 块扫描在所有启用 thinking 的 provider 上验证通过。 -2. 每日总结/播报/词云链路在消息收集阶段统一接入敏感词过滤。 -3. 关键合规事件能从日志反推到具体类别和命中位置(不需要原文)。 +1. 全仓 Markdown 内链检查通过,公开文档与当前前端、部署入口和版本主线保持一致。 +2. 用户手册、管理手册、开发手册均覆盖 v1.7.0 当前功能面,尤其是唤醒模块、LLBot、敏感词过滤、生成配置和 Web Admin 标签页。 +3. `CHANGELOG.md` 的 `[Unreleased]` 区域能准确描述待发布变化,底部比较链接与最新 tag 对齐。 +4. 前端整理项完成后通过 `npm run build` 或等价检查;Python 文档相关配置变动通过目标测试或静态校验。 --- @@ -43,6 +44,10 @@ 2. provider 健康排序 3. 自动降级或故障转移(基于网关错误类型分类,含 `Content Exists Risk` 这类合规事件) +### 合规链路继续补强 + +敏感词过滤器已经接入 LLM 输入、输出、历史和工具调用层。后续继续评估 reasoning 内容、每日总结、播报和词云等批处理路径的统一扫描策略。 + ### 测试覆盖继续补强 在当前 pytest / CI 体系基础上,继续补: @@ -59,7 +64,7 @@ ### LLM 主动发言 -冷场检测:群内超过 N 小时无消息且处于活跃时段时,bot 主动发一条话题引子(从词云高频词、每日总结或名言录取材)。 +唤醒模块已经提供低频主动响应的基础能力。后续如果继续扩展,可把冷场话题引子与词云高频词、每日总结或语录素材结合起来。 ### 群周报 / 月报 diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 4e0d8c8..caff4ee 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -18,7 +18,9 @@ services: image: initialencounter/llonebot:latest container_name: llbot entrypoint: - - /entrypoint.sh + - /bin/sh + - -c + - "(sleep 5 && echo 'nameserver 127.0.0.11' > /etc/resolv.conf && echo 'options ndots:0' >> /etc/resolv.conf) & exec /bin/llonebot-service" environment: - QUICK_LOGIN_QQ=${QQ_ACCOUNT:?请设置 QQ 号} - TZ=Asia/Shanghai @@ -28,7 +30,6 @@ services: volumes: - ./llbot-qq:/root/.config/QQ # 登录态持久化(关键) - ./llbot-data:/root/llonebot # 配置文件 + 运行时数据 - - ./llbot-entrypoint.sh:/entrypoint.sh:ro # DNS 修复 wrapper restart: unless-stopped # ── 联网搜索(SearXNG)────────────────────────────────────────────────── @@ -85,6 +86,7 @@ services: environment: WEB_ADMIN_HOST: "0.0.0.0" WEB_ADMIN_PORT: "5104" + SEARXNG_BASE_URL: "${SEARXNG_BASE_URL:-http://searxng:8080}" ports: - "127.0.0.1:5104:5104" volumes: diff --git a/docs/admin/configuration.md b/docs/admin/configuration.md index 50ab46b..851de8b 100644 --- a/docs/admin/configuration.md +++ b/docs/admin/configuration.md @@ -34,10 +34,15 @@ | 变量 | 说明 | 默认值 | |------|------|--------| -| `SEARCH_BACKEND` | 搜索后端选择:`auto` / `searxng` / `tavily` | `auto` | -| `SEARXNG_BASE_URL` | SearXNG 服务地址 | `http://127.0.0.1:8888` | +| `SEARXNG_BASE_URL` | Bot 内置 `search_web` 和 `/search` 使用的 SearXNG 服务地址 | `http://127.0.0.1:8888` | +| `SEARXNG_SAFE_SEARCH` | 传给 SearXNG 的安全搜索级别:`0` / `1` / `2` | `0` | +| `SEARXNG_LANGUAGE` | 传给 SearXNG 的搜索语言;空值时使用 `all` | `all` | +| `SEARXNG_PUBLIC_BASE_URL` | compose 中 SearXNG 对外展示的 base URL | `http://127.0.0.1:8888/` | +| `SEARXNG_BIND_ADDRESS` | compose 暴露 SearXNG 时绑定的宿主地址 | `127.0.0.1` | +| `SEARXNG_BIND_PORT` | compose 暴露 SearXNG 时绑定的宿主端口 | `8888` | +| `SEARXNG_SECRET` | SearXNG 实例密钥,用于容器环境变量 | — | -`SEARCH_BACKEND=auto` 时,若 `SEARXNG_BASE_URL` 已设置则优先使用 SearXNG。 +`search_web` 固定走项目内 SearXNG;Tavily 等外部搜索能力建议通过 MCP sidecar 暴露为工具。 ### LLM 调试 @@ -53,7 +58,13 @@ | `TIEBA_FORUM_KEYWORDS` | 多贴吧来源,逗号/分号/竖线/换行分隔 | — | | `TIEBA_FORUM_KEYWORD` | 单贴吧来源(旧字段,多来源时优先用 `FORUM_KEYWORDS`) | — | | `TIEBA_SYNC_INTERVAL_SECONDS` | 同步间隔(秒) | `900` | +| `TIEBA_MAX_POOL_SIZE` | 每个来源最多保留的帖子数,最小 `20` | `240` | +| `TIEBA_RECENT_SENT_LIMIT` | 最近发送记录保留数量,最小 `1` | `30` | +| `TIEBA_DETAIL_FETCH_LIMIT` | 单次抓取详情的帖子数量上限,最小 `1` | `18` | +| `TIEBA_RANDOM_AVOID_RECENT` | 随机抽帖时避开最近 N 条发送记录 | `30` | +| `TIEBA_PREFER_IMAGE_THREADS` | 随机抽帖时优先选择带图帖子 | `true` | | `TIEBA_BROWSER_HEADLESS` | 浏览器是否无头模式 | `true` | +| `TIEBA_BROWSER_CHANNEL` | Playwright 浏览器 channel;空值使用默认 Chromium | — | ### MCP 挂载与开关 @@ -100,6 +111,12 @@ | `memory_limit` | 单次调用注入的记忆条数上限 | — | | `memory_max_items_per_group` | 单群存储的记忆条数硬上限 | — | | `max_prompt_chars` | system prompt 最大字符数 | — | +| `tool_calling_enabled` | 是否允许工具调用 | `false` | +| `tool_max_rounds` | 单次工具调用循环最大轮数 | `8` | +| `tool_max_calls_per_round` | 单轮最多执行工具调用数 | `16` | +| `auto_memory_enabled` | 自动记忆抽取全局默认开关 | `false` | +| `auto_memory_prompt` | 自动记忆抽取自定义判定 prompt | `""` | +| `auto_memory_max_tokens` | 自动记忆抽取判定最大输出 token | `256` | ### `[triggers]` — 触发方式 @@ -115,7 +132,18 @@ | 键 | 说明 | 默认值 | |----|------|--------| | `enabled` | 是否启用自动联网 | `false` | -| `search_max_calls_per_round` | 单轮最大搜索调用数 | — | +| `search_max_calls_per_round` | 单轮最大搜索调用数,范围 1-32 | `3` | + +`[triggers.quick_judge]` — 快速判定模型: + +| 键 | 说明 | 默认值 | +|----|------|--------| +| `provider_id` | 快速判定专用 provider ID;留空使用默认 provider | `""` | +| `model` | 快速判定专用模型;留空使用 provider 默认模型 | `""` | +| `timeout` | 判定超时秒数 | `2.0` | +| `max_tokens` | 判定最大输出 token | `64` | + +快速判定用于 `context_rules` 的 `llm_context`、唤醒模块的相关性/答疑判定等短 prompt 场景。 ### `[tools]` — 工具调用 @@ -307,6 +335,66 @@ ASR 用于把 OneBot V11 `record` 语音消息转写为文字,并注入 LLM `[[music.providers]]` 和 `[[music.providers.models]]` 结构类似,额外包含 `supported_output_formats`、`lyrics_optimization` 等音乐特有字段。 +`api_key_env` 由每个 provider 自行声明;示例配置中常见的键名包括 `MINIMAX_API_KEY`、`VOLCENGINE_API_KEY` 和 OpenAI-compatible ASR 使用的 `OPENAI_API_KEY`。 + +--- + +## config/awakening.toml + +唤醒模块默认关闭,复制 `config/awakening.toml.example` 为 `config/awakening.toml` 后按需启用。配置支持全局默认值和按群覆盖。 + +### `[awakening.defaults]` + +| 键 | 说明 | 默认值 | +|----|------|--------| +| `extend_duration` | 显式触发 AI 后继续回应同一用户的秒数;`0` 关闭 | `0` | +| `fallback_probability` | 普通消息低概率触发回应的概率;`0` 关闭 | `0` | +| `boredom_silence_seconds` | 群聊沉寂多少秒后允许无聊唤醒;`0` 关闭 | `0` | +| `boredom_probability` | 无聊检查命中时发送冒泡消息的概率 | `0` | +| `boredom_check_interval` | 无聊唤醒定时检查间隔秒数 | `300` | +| `boredom_dnd_start` | 免打扰开始时间,格式 `HH:MM`,空值关闭 | `""` | +| `boredom_dnd_end` | 免打扰结束时间,格式 `HH:MM`,空值关闭 | `""` | +| `interest_topics` | 兴趣话题关键词列表,命中后触发 `awakening_interest` | `[]` | +| `relevance_threshold` | 相关性唤醒判定阈值,`>= 1` 关闭 LLM 判定 | `1.0` | +| `qa_threshold` | 答疑唤醒判定阈值,`>= 1` 关闭 LLM 判定 | `1.0` | + +### `[[awakening.group_overrides]]` + +按群覆盖任意默认值: + +```toml +[[awakening.group_overrides]] +group_id = "123456" +extend_duration = 10 +interest_topics = ["编程", "Python"] +relevance_threshold = 0.5 +qa_threshold = 0.88 +``` + +`interest_topics` 还可写在 persona TOML 的扩展字段中: + +```toml +[awakening] +interest_topics = ["角色相关关键词"] +``` + +### 群内管理 + +`/awakening status` 会展示本群六类唤醒规则的规则开关状态和已解析配置。`/awakening on ` 与 `/awakening off ` 复用规则开关系统;无聊唤醒还需要 `/awakening boredom on` 将本群加入 `data/awakening_boredom_groups.json`。 + +--- + +## config/sensitive_words.toml + +敏感词过滤器默认在词表缺失或为空时静默放行。复制 `config/sensitive_words.toml.example` 为 `config/sensitive_words.toml` 后,按部署环境填充 block/soft 两级词表。 + +| 区段 | 行为 | +|------|------| +| `[block.]` | 命中后阻断 LLM 输入、替换 LLM 输出,或在工具调用链路拒绝执行/替换结果 | +| `[soft.]` | 只记录日志,不阻断请求 | + +每个类别使用 `words = ["..."]` 定义词表。命中日志只记录类别与哈希,不记录原文。完整接入点和运维建议见 [sensitive-filter.md](sensitive-filter.md)。 + --- ## config/chat_rules.toml diff --git a/docs/admin/deployment.md b/docs/admin/deployment.md index 577d755..7d63403 100644 --- a/docs/admin/deployment.md +++ b/docs/admin/deployment.md @@ -47,14 +47,17 @@ nano .env # 填入 QQ 号、OneBot 配置和 API key 同时确认: - 根目录下的 `.env` 已存在,并填入 `OPENAI_API_KEY`、`ANTHROPIC_API_KEY`、`GEMINI_API_KEY` -- 如果仍需保留 Tavily 作为备用搜索后端,再额外填入 `TAVILY_API_KEY` +- 如启用 MCP Tavily sidecar,再额外填入 `TAVILY_API_KEY` - 根目录下的 `config/llm.toml` 已存在并填入真实 provider / model / base_url 配置 +- 如启用图片、语音、音乐或 ASR,`config/generation.toml` 已存在并填入对应 provider 与模型 +- 如启用低频唤醒,`config/awakening.toml` 已存在并填入阈值、兴趣话题和按群覆盖 +- 如启用敏感词过滤,`config/sensitive_words.toml` 已存在并填入部署侧词表 - 如使用私有部署编排,相关环境变量文件不应进入公共仓库 当前部署会把: - 根 `.env` -- `config/llm.toml` +- `config/` 目录下的运行配置(如 `llm.toml`、`generation.toml`、`awakening.toml`、`sensitive_words.toml`、`games.toml`) - `llm_about/vocab.yaml` - `llm_about/identities.yaml` - `llm_about/{群号}/vocab.yaml` @@ -85,7 +88,7 @@ docker compose up -d 补充说明: -- `config/llm.toml`、`llm_about/vocab.yaml`、`llm_about/identities.yaml` 及群级覆盖文件虽然是 bind mount,但 `quickquip` 会在进程启动时把它们读入内存 +- `config/llm.toml`、`config/awakening.toml`、`llm_about/vocab.yaml`、`llm_about/identities.yaml` 及群级覆盖文件虽然是 bind mount,但 `quickquip` 会在进程启动时把它们读入内存 - 因此部署脚本在同步文件后会额外强制重建 `quickquip` 容器,避免新 persona 或词表已经上传但运行时仍在使用旧配置 - 如果只是在线微调配置而不走部署脚本,也可以在群里手动执行 `/llm reload` @@ -151,7 +154,9 @@ compose 会同时启动 `web-admin` 容器(`python web_api.py`,默认监听 - 消息统计(各群消息数、活跃用户、规则触发次数) - 群级规则开关(toggle 开关,实时生效) - 每日总结 / 每日播报群组管理 -- `config/llm.toml` 在线编辑(保存前校验 TOML 语法) +- `config/llm.toml`、`config/generation.toml`、`config/chat_rules.toml`、`config/games.toml`、`config/awakening.toml`、`config/niuniu_text.toml`、`config/niuniu_text_safe.toml` 在线编辑(保存前校验 TOML 语法) +- 敏感词过滤器只读状态查看;`config/sensitive_words.toml` 只通过服务器本地文件或部署流程维护,不在 Web Admin 中回显或编辑 +- 记忆、对话、人格、资料、唤醒、贴吧、词云、语录、定时任务、审计、金币经济和牛牛面板 - 实时日志 / LLM Trace / 日志归档面板(直接读取 `../data/logs` 共享日志与 trace 文件) 管理界面同时有两层门: @@ -179,6 +184,7 @@ WEB_ADMIN_COOKIE_SECURE=auto | `../frontend/dist` | `/app/frontend/dist` | 只读 | > 注意:`quickquip` 容器的 `config` 和 `llm_about` 挂载仍可保持只读(`:ro`),只有 `web-admin` 需要写权限。 +> `config/sensitive_words.toml` 即使位于同一挂载目录,也不会通过 Web Admin 配置编辑器读取或写入。 ### web-admin 代码更新 @@ -219,7 +225,7 @@ docker compose down 如果未来要给 QuickQuip 接 MCP,应该把 MCP 视为 QuickQuip 自己的外部工具后端。当前项目已经支持把 Codex 里常用的 Docker 型 MCP server 镜像到 `config/llm.toml`,但部署时仍要按 QuickQuip 自己的私有环境变量来管理密钥和宿主路径。 -当前项目已经同时保留 Tavily 直连能力和 MCP 扩展能力。MCP 集成的正式约定见 [../dev/mcp-integration.md](../dev/mcp-integration.md)。 +当前项目通过 SearXNG 提供内置 `search_web` 搜索,通过 MCP 扩展接入 Tavily 等外部工具。MCP 集成的正式约定见 [../dev/mcp-integration.md](../dev/mcp-integration.md)。 ### LLBot 登录态过期 @@ -238,7 +244,7 @@ docker compose logs -f llbot # 找新的二维码 ### 端口冲突 -如果服务器上 6099 或 8080 端口已被占用,在 `docker-compose.yml` 中修改端口映射即可。QuickQuip 的 8080 端口不需要对外暴露(容器内部通信)。 +如果服务器上 3001、3080、5104 或 8888 端口已被占用,在 `docker-compose.yml` 中修改宿主机端口映射即可。QuickQuip 的 8080 端口只用于容器内部通信,不需要对外暴露。 ### LLM 配置不生效 @@ -246,7 +252,6 @@ docker compose logs -f llbot # 找新的二维码 - `config/llm.toml` 是否存在且内容正确 - `.env` 中是否填了 `OPENAI_API_KEY`、`ANTHROPIC_API_KEY`、`GEMINI_API_KEY` -- 私有环境变量文件中是否把 `SEARCH_BACKEND` 设为 `searxng` - 私有环境变量文件中是否填了 `QQ_ACCOUNT`、`GITHUB_PERSONAL_ACCESS_TOKEN` 与云端 MCP 覆盖值 - 搜索服务是否运行,QuickQuip 容器内是否能访问配置里的 `SEARXNG_BASE_URL` - `llm_about/identities.yaml` 是否存在且格式正确;如只使用群级覆盖,也确认 `llm_about/{群号}/identities.yaml` 存在 diff --git a/docs/admin/migration-napcat-to-llbot.md b/docs/admin/migration-napcat-to-llbot.md index 64eef86..2b22f2b 100644 --- a/docs/admin/migration-napcat-to-llbot.md +++ b/docs/admin/migration-napcat-to-llbot.md @@ -35,7 +35,9 @@ services: image: initialencounter/llonebot:latest container_name: llbot entrypoint: - - /entrypoint.sh + - /bin/sh + - -c + - "(sleep 5 && echo 'nameserver 127.0.0.11' > /etc/resolv.conf && echo 'options ndots:0' >> /etc/resolv.conf) & exec /bin/llonebot-service" environment: - QUICK_LOGIN_QQ=${QQ_ACCOUNT:?请设置 QQ 号} - TZ=Asia/Shanghai @@ -45,28 +47,12 @@ services: volumes: - ./llbot-qq:/root/.config/QQ # 登录态持久化(关键,切勿丢失) - ./llbot-data:/root/llonebot # 配置文件 + 运行时数据 - - ./llbot-entrypoint.sh:/entrypoint.sh:ro # DNS 修复 wrapper restart: unless-stopped ``` -**重要**:LLBot 镜像的入口脚本会将容器 DNS 指向公网服务器,导致无法解析 Docker Compose 内部服务名。需挂载修复脚本(见下方)。 +> 注意:LLBot 镜像的入口脚本会将容器 DNS 指向公网服务器,导致无法解析 Docker Compose 内部服务名。上方 `entrypoint` 已通过内联 `/bin/sh -c` 在启动前修复 DNS,无需额外文件。 -### 2. 创建 DNS 修复脚本 - -在 compose 同目录下创建 `llbot-entrypoint.sh`: - -```bash -#!/bin/sh -echo 'nameserver 127.0.0.11' > /etc/resolv.conf -echo 'options ndots:0' >> /etc/resolv.conf -exec /bin/llonebot-service -``` - -```bash -chmod +x llbot-entrypoint.sh -``` - -### 3. 创建 OneBot 配置文件 +### 2. 配置 OneBot 反向 WS LLBot 的配置为 JSON 格式,位于 `llbot-data/default_config.json`。最小配置(启用反向 WS): @@ -91,11 +77,11 @@ LLBot 的配置为 JSON 格式,位于 `llbot-data/default_config.json`。最 } ``` -> **注意**:容器首次启动时入口脚本会用内置默认配置覆盖此文件。建议先启动容器完成首次登录,再通过 WebUI(`http://<服务器IP>:3080`)配置反向 WS,或使用 `docker exec` 修改 `data/config_.json`。 +> 注意:容器首次启动时入口脚本可能用内置默认配置覆盖此文件。建议先启动容器完成首次登录,再通过 WebUI(`http://<服务器IP>:3080`)配置反向 WS,或使用 `docker exec` 修改 `data/config_.json`。 -### 4. 更新 QuickQuip 服务 +### 3. 更新 QuickQuip 服务 -在 compose 中将 QuickQuip 的 `depends_on` 和 `ONEBOT_WS_URLS` 更新为指向 LLBot: +在 compose 中将 QuickQuip 的 `depends_on` 和 `ONEBOT_WS_URLS` 更新为指向 LLBot 正向 WS: ```yaml quickquip: @@ -105,7 +91,7 @@ quickquip: ONEBOT_WS_URLS: '${ONEBOT_WS_URLS:-["ws://llbot:3001/"]}' ``` -### 5. 启动并扫码登录 +### 4. 启动并扫码登录 ```bash docker compose up -d llbot @@ -127,7 +113,7 @@ docker compose logs quickquip | grep "Bot.*connected" # 应输出: OneBot V11 | Bot <你的QQ号> connected ``` -### 6. 移除旧 NapCat 服务(验证稳定后) +### 5. 移除旧 NapCat 服务(验证稳定后) ```bash docker compose stop napcat @@ -139,9 +125,9 @@ NapCat 的登录态和数据卷(`napcat-data/`)建议在确认稳定前保 ## OneBot WS 模式说明 -LLBot 同时支持正向和反向 WebSocket。QuickQuip 的默认 `~fastapi` driver 使用**反向 WS**——LLBot 作为客户端连接到 QuickQuip 的 `/onebot/v11/ws/` 端点。这与 NapCat 时期的行为完全一致,无需调整 NoneBot 配置。 +LLBot 同时支持正向和反向 WebSocket。`docker-compose.example.yml` 默认使用**正向 WS**:QuickQuip 通过 `ONEBOT_WS_URLS='["ws://llbot:3001/"]'` 连接 LLBot 的 3001 端口。 -正向 WS(端口 3001)作为备用保留,`ONEBOT_WS_URLS` 中的默认值即指向此端口。 +如果你更希望保留 NapCat 时期常见的反向 WS 结构,也可以在 LLBot WebUI 中配置 `ws-reverse`,让 LLBot 连接 QuickQuip 的 `/onebot/v11/ws/` 端点。 ## 已知差异 diff --git a/docs/admin/sensitive-filter.md b/docs/admin/sensitive-filter.md index a6b224e..0e87fb9 100644 --- a/docs/admin/sensitive-filter.md +++ b/docs/admin/sensitive-filter.md @@ -90,9 +90,11 @@ cp config/sensitive_words.toml.example config/sensitive_words.toml - **阿里云/腾讯云内容安全 API**:覆盖更全但增加延迟和成本,对群聊 bot 而言 overkill - **测试群跑半个月,记录所有模型主动拒答**——这是质量最高的源 -### 4. 重载 +### 4. 重载与状态查看 -修改 `config/sensitive_words.toml` 后,调用 `reload_filter()` 即可热更新(不需要重启 bot)。当前没有暴露给 web admin,需要时可以加个 `/reload-sensitive` 命令。 +修改 `config/sensitive_words.toml` 后,调用 `reload_filter()` 即可热更新(不需要重启 bot)。当前没有独立的群内重载命令;在服务器本地更新词表后,可执行 `/llm reload` 或重启 bot。 + +Web Admin 提供只读状态接口 `GET /ops/api/sensitive-filter/status`,返回配置文件是否存在、是否已加载以及 block/soft/total 计数。它不会返回词表内容、分类明细或文件路径。群内 `/llm health verbose` 也会展示 `sensitive_filter` 健康项,但不会回显词表路径。 ## 接入点 @@ -109,7 +111,8 @@ cp config/sensitive_words.toml.example config/sensitive_words.toml **为什么工具结果扫描尤其重要**:搜索/抓取类工具(`search_web`、`fetch`、各类 MCP 工具)从外部源拉取内容,**用户的查询可以引导但我们无法预先审查**。一段富集敏感词的 tool_result 会作为 messages 的一部分进入下一轮 provider 请求,正是触发 DeepSeek `Content Exists Risk` / Aliyun `Content security warning` 的高危场景。 **没有接入的位置**: -- `daily_summary` / `daily_briefing` / `wordcloud` 路径——这些路径的输入是已经过滤过的群聊消息,且不直接通过 LLM 输出到群里。如需加固,可以在 `quickquip/chat/daily_summary.py` 的消息收集阶段调用同一个 `get_filter()` 做扫描 +- `daily_summary` / `daily_briefing` 会走独立的模型级联 provider 调用,不经过 `LLMService.generate_reply()` 主链路,因此当前不会复用输入/输出/历史侧过滤器;如需加固,应在 `quickquip/llm/summarize.py` 与 `quickquip/llm/briefing.py` 的请求和响应边界接入同一个 `get_filter()` +- `wordcloud` 不调用 LLM,只读取群聊消息并渲染词频图片;如需避免敏感词出现在图片中,应在 `quickquip/chat/wordcloud.py` 的分词或渲染前增加扫描/剔除 ## 性能 @@ -122,6 +125,7 @@ cp config/sensitive_words.toml.example config/sensitive_words.toml ## 不要做的事 - ❌ 把 `config/sensitive_words.toml` 提交到公开仓库 +- ❌ 通过 Web Admin 或任何浏览器页面读取、回显、编辑 `config/sensitive_words.toml` - ❌ 把命中日志记得太详细(如完整原文 + 用户 ID + 时间)——日志本身会成为合规风险 - ❌ 在群里**告知用户**触发了过滤——直接静默 + 后台日志即可,告知等于教用户绕过 - ❌ 让 LLM 自己判断"这内容能不能发"——增加成本和延迟,且模型自己也不可靠 diff --git a/docs/admin/web-admin.md b/docs/admin/web-admin.md index 01dbd86..eef2bb8 100644 --- a/docs/admin/web-admin.md +++ b/docs/admin/web-admin.md @@ -132,38 +132,47 @@ WEB_ADMIN_COOKIE_SECURE=true - 写操作会额外检查 `Origin` / `Referer` 是否与当前请求宿主一致 - FastAPI 层自身有登录态校验,不再完全依赖 nginx -这比"只有 nginx `auth_basic`,应用层完全无认证"的旧结构更符合纵深防御原则。 +外层站点访问控制和应用层 session 共同组成后台的纵深防御边界。 --- ## 功能标签页 -Web Admin 提供以下标签页(前端使用 vue-router 4 hash 模式,深链接形如 `/ops/#/stats`)。前端已升级为响应式设计,支持亮色/暗色主题切换,使用统一的设计 token 体系。当前共 24 个标签页。 +Web Admin 当前提供 25 个标签页(前端使用 vue-router 4 hash 模式,深链接形如 `/ops/#/stats`)。前端使用响应式设计、亮色/暗色主题切换和统一设计 token。 +- **概览** — 汇总运行状态、常用入口和关键指标 - **统计** — 各群消息数、活跃用户排行、规则触发 Top - **规则** — 按群启用/禁用任意规则,toggle 实时生效 - **群组** — 每日总结 / 每日播报群管理 +- **群 LLM** — 按群覆盖 provider/model/persona/前缀/历史条数等 runtime 字段;列表会同时显示近期活跃群和数据库里已有覆盖配置的群 +- **唤醒** — 按群查看并编辑唤醒参数,切换 `awakening_*` 规则和无聊唤醒 opt-in;兴趣话题由人格配置和规则开关控制 +- **限流** — 实时限流观测(按 scope 分全局/按群视图,5s 可选自动刷新) - **记忆** — 按群浏览与编辑 LLM 长期记忆 -- **总结** — 查阅/删除每日总结存档 - **对话** — 按群浏览 LLM 对话历史(含私聊/归档,支持关键词过滤、游标翻页、按单条删除) - **人格** — 在线编辑 `config/personas/*.toml`(含新建/删除,`_shared.toml` 保护) - **资料** — 在线编辑 `llm_about/vocab.yaml`、`llm_about/identities.yaml` 及群级覆盖文件(保存后执行 `/llm reload` 或重启 bot 生效) -- **群 LLM** — 按群覆盖 provider/model/persona/前缀/历史条数等 9 个 runtime 字段;列表会同时显示近期活跃群和数据库里已有覆盖配置的群,便于直接管理真实在用群组 -- **配置** — `config/llm.toml`、`config/generation.toml`、`config/chat_rules.toml` 多文件 TOML 编辑器 -- **限流** — 实时限流观测(按 scope 分全局/按群视图,5s 可选自动刷新) -- **贴吧** — 贴吧帖子池浏览(同步状态/关键词搜索/图文详情) +- **诊断** — LLM runtime 重载、MCP 重连、上下文清理、样本请求、文本规则回归测试和 LLM 健康状态 +- **MCP** — MCP 服务器状态面板(transport、连接状态、工具数量、错误信息,支持 bot 与 web-admin 共享状态文件) +- **总结** — 查阅/删除每日总结存档 - **语录** — 语录管理(按群浏览、关键词搜索、删除) -- **词云** — 词云生成(4 档时间窗、Top 词频排行、图片下载) +- **贴吧** — 贴吧帖子池浏览(同步状态/关键词搜索/图文详情/立即同步/实时抓取) +- **词云** — 词云生成(today/week/month/year 时间窗、Top 词频排行、图片下载) +- **配置** — `config/llm.toml`、`config/generation.toml`、`config/chat_rules.toml`、`config/games.toml`、`config/awakening.toml`、`config/niuniu_text.toml`、`config/niuniu_text_safe.toml` 多文件 TOML 编辑器 - **实时日志** — 当前运行日志流、连接状态与当前文件下载 - **LLM Trace** — 共享 trace 流、开关控制与最近 trace 条目 - **日志归档** — 历史轮转日志浏览、预览与下载 -- **诊断** — 样本请求与文本规则回归测试 -- **MCP** — MCP 服务器状态面板(各 server 的 transport、连接状态、工具数量、错误信息,支持 bot 与 web-admin 共享状态文件) - **定时任务** — APScheduler 定时任务面板(job ID、trigger、next_run、last_run 时间与状态、错误详情) - **审计** — Web Admin 变更审计日志(按操作类型、目标类型、操作人、日期范围等条件过滤,分页浏览) - **金币** — 金币经济面板(各群金币汇总、排行 TOP 20、账户查询、手动余额调整并记录审计日志) -- **牛牛** — 牛牛大作战面板(自然/绝对值/长度/深度四种排行、用户查询含多维度排名、操作记录追溯、文案模式管理:查看可用模式并设置群组文案) -- **配置** — 配置文件编辑器中新增加 `config/games.toml`(游戏配置),可在线编辑所有游戏参数 +- **牛牛** — 牛牛大作战面板(自然/绝对值/长度/深度四种排行、用户查询含多维度排名、操作记录追溯、文案模式管理) + +敏感词过滤器没有独立标签页。后台提供只读接口 `GET /ops/api/sensitive-filter/status`,LLM 健康检查也会汇总过滤器加载状态和词表数量。`config/sensitive_words.toml` 属于高敏部署文件,只在服务器本地维护,Web Admin 不提供内容读取或在线编辑入口。 + +### Bot 执行动作队列 + +`web_api.py` 是独立进程,不能直接复用 bot 进程里的 OneBot 连接。诊断页的运行时重载、LLM 健康检查、上下文清理、唤醒参数重载、群组页的“立即生成总结/播报”等需要 bot 进程执行的动作,会先写入 `data/web_admin_actions.db`。bot 端定时任务 `web_admin_action_queue` 每 5 秒领取并执行队列任务,结果回写到同一数据库;诊断页“最近动作”用于查看等待、执行中、成功或失败状态。动作队列数据库启用 WAL;若 bot 在领取任务后退出,后续轮询会将超时的 `running` 动作标记为失败,避免任务永久挂起。 + +普通配置文件和群级开关仍走文件持久化路径。bot 端 `web_admin_state_sync` 每 30 秒检测 `rule_switch.json`、`config/awakening.toml`、每日总结/播报群组文件、无聊唤醒群组文件的修改并重载。唤醒标签页和配置页保存 `config/awakening.toml` 后也会主动入队一次 `awakening_reload`。 --- diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 2bf4bd5..1a53d19 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -87,12 +87,12 @@ quickquip/ ├── llm/ # LLM 运行时(多 provider、工具调用循环、MCP 客户端、记忆存储、persona、身份映射、词表、健康检查;核心门面拆到 service_parts/) ├── generation/ # 多模态产出配置、模型解析、图片/语音/音乐 provider 调用 ├── tieba/ # 贴吧爬虫与帖子池 -├── search/ # 联网搜索后端(SearXNG / Tavily) +├── search/ # 项目内 SearXNG 搜索客户端 ├── adapters/ │ └── nonebot/ # NoneBot2 适配层(生命周期、消息入口、命令注册、定时任务插件;命令注册按域拆到 command_parts/) └── app/ # 应用级流水线装配(单例初始化、状态加载、游戏注册) ├── web/ # Web 管理后台 FastAPI 应用与路由 - │ └── routes/ # API 路由(统计、规则、群组、记忆、总结、对话、人格、资料、群LLM、配置、日志、限流、贴吧、词云、诊断、MCP面板、定时任务、审计、金币经济、牛牛大作战) + │ └── routes/ # API 路由(统计、规则、群组、记忆、总结、对话、人格、资料、群LLM、配置、日志、限流、贴吧、词云、诊断、敏感词状态、MCP面板、定时任务、审计、金币经济、牛牛大作战) ``` **规则**:业务逻辑只进 `quickquip/`,不进 `plugins/`。NoneBot2 相关 import 只在 `adapters/nonebot/` 里出现。 @@ -113,6 +113,10 @@ quickquip/ | `llm.toml` | 自用层(gitignore) | 真实 provider 配置,含 base_url / model 等 | | `generation.toml.example` | 分发层(追踪) | 多模态产出配置模板 | | `generation.toml` | 自用层(gitignore) | 真实图片/语音/音乐 provider 配置 | +| `awakening.toml.example` | 分发层(追踪) | 群聊唤醒模块配置模板 | +| `awakening.toml` | 自用层(gitignore) | 真实唤醒阈值、兴趣话题和按群覆盖 | +| `sensitive_words.toml.example` | 分发层(追踪) | 敏感词过滤器配置模板 | +| `sensitive_words.toml` | 自用层(gitignore) | 部署者填充的敏感词词表 | | `chat_rules.toml.example` | 分发层(追踪) | 文字回复规则格式示例 | | `chat_rules.toml` | 自用层(gitignore) | 部署专用的彩蛋规则(群内私有梗) | | `games.toml.example` | 分发层(追踪) | 游戏参数配置模板 | @@ -137,6 +141,8 @@ data/ ├── llm.db # LLM 对话历史与长期记忆(SQLite) ├── daily_summaries.db # 每日群聊总结存档(SQLite) ├── web_admin_sessions.db # Web Admin 会话记录 +├── web_admin_actions.db # Web Admin 到 bot 进程的动作队列 +├── awakening_boredom_groups.json # 已启用无聊唤醒的群列表 ├── daily_msgs/ # 每日消息原始收集({group_id}/{date}.jsonl) ├── logs/ # loguru 文件日志(保留 14 天)+ 共享 LLM trace JSONL ├── fonts/ # 词云字体文件(手动放置) @@ -155,17 +161,25 @@ docs/ ├── index.md # 文档总导航 ├── user/ # 面向群友 │ ├── group-commands.md +│ ├── group-games.md +│ ├── llm-tool-discovery.md │ ├── private-commands.md │ └── three-kingdoms-memes.md ├── admin/ # 面向部署者/管理员 │ ├── deployment.md │ ├── configuration.md +│ ├── game-config.md +│ ├── migration-napcat-to-llbot.md +│ ├── sensitive-filter.md +│ ├── tool-discovery.md │ └── web-admin.md └── dev/ # 开发者文档 ├── architecture.md # 本文件 + ├── game-framework.md ├── llm-module.md ├── mcp-integration.md - └── regex-tutorial.md + ├── regex-tutorial.md + └── tool-discovery.md ``` --- @@ -198,8 +212,11 @@ docs/ | `config/llm.toml` | 含真实 provider 配置 | | `config/llm.*.local.toml` | 同上 | | `config/generation.toml` | 含真实多模态 provider 配置 | +| `config/awakening.toml` | 含真实唤醒阈值、兴趣话题和群覆盖 | +| `config/sensitive_words.toml` | 含部署者填充的敏感词词表 | | `config/chat_rules.toml` | 含私有群梗规则 | | `config/games.toml` | 含游戏参数配置 | +| `config/niuniu_text.toml`, `config/niuniu_text_safe.toml` | 含部署者自定义牛牛文案 | | `config/personas/` | 含真实 persona 定义 | | `data/` | 运行时数据 | | 私有部署目录 | 部署脚本、真实环境变量、临时材料和个人生产结构 | diff --git a/docs/dev/llm-module.md b/docs/dev/llm-module.md index 2032e44..6ebbcf8 100644 --- a/docs/dev/llm-module.md +++ b/docs/dev/llm-module.md @@ -4,7 +4,7 @@ QuickQuip 的 LLM 模块是建立在原有规则机器人之上的**显式触发扩展层**。 -它的设计目标不是把整个群改造成全时监听的聊天机器人,而是在保留原有规则回复体系的前提下,提供一套: +它在保留原有规则回复体系的前提下,提供一套受开关、触发条件和上下文边界约束的 LLM 能力: - 可按群开关 - 可按群切换 provider / model / persona @@ -80,7 +80,7 @@ LLM 相关核心文件如下: - `quickquip/llm/inputs.py` - 负责从消息段中提取文本触发、艾特触发和图片 URL - `quickquip/search/web_search.py` - - 负责搜索后端抽象,当前支持 SearXNG / Tavily + - 负责项目内 SearXNG 搜索客户端,供 `/search` 与 `search_web` 工具使用 兼容层说明: @@ -122,7 +122,7 @@ LLM 默认只在以下场景触发: - `@机器人` - `/search ` -普通群消息不会自动进入 LLM。 +普通群消息可以通过唤醒模块进入 LLM,但所有唤醒入口默认关闭或受阈值控制,且会经过群规则开关与限流器。 当前消息流顺序: @@ -130,7 +130,13 @@ LLM 默认只在以下场景触发: 2. 读取当前群最近消息缓冲 3. 判断是否命中 LLM 显式触发 4. 如果命中 LLM,则优先走 LLM -5. 如果未命中,则继续原有规则流: +5. 如果未命中显式触发,则检查唤醒模块: + - `awakening_extend` + - `awakening_interest` + - `awakening_relevance` + - `awakening_qa` + - `awakening_fallback` +6. 如果仍未命中,则继续原有规则流: - 复读 - 接龙 - 彩蛋规则 @@ -138,9 +144,26 @@ LLM 默认只在以下场景触发: 这意味着: -- LLM 不会吞掉普通消息 +- 默认配置下 LLM 不会吞掉普通消息 - 规则系统依然是默认主流程 -- LLM 是显式调用,不是默认响应层 +- 显式调用优先级高于唤醒模块,唤醒模块优先级高于普通规则回复 + +### 3.1 唤醒模块 + +唤醒模块位于 `quickquip/chat/awakening.py`,命令入口位于 `quickquip/adapters/nonebot/awakening_plugin.py`,配置文件为 `config/awakening.toml`。 + +| 规则名 | 触发方式 | +|------|----------| +| `awakening_extend` | 显式触发后,在 `extend_duration` 秒内继续回应同一用户 | +| `awakening_interest` | 消息命中全局或 persona 里的兴趣话题 | +| `awakening_relevance` | 先做词重叠快筛,再用 `quick_judge` 判断是否延续 bot 近期回复 | +| `awakening_qa` | 先做问句快筛,再用 `quick_judge` 判断是否需要回答 | +| `awakening_boredom` | APScheduler 定时检查沉寂群,并向 opt-in 群发送低频冒泡消息 | +| `awakening_fallback` | 普通消息按配置概率兜底触发 | + +相关性与答疑判定会使用 `[triggers.quick_judge]` 指定的小模型配置;阈值 `>= 1.0` 时跳过对应 LLM 判定。 + +无聊唤醒有两层开关:先在 `config/awakening.toml` 中设置沉寂秒数、概率、检查间隔和免打扰时间,再由群管理员执行 `/awakening boredom on`,写入 `data/awakening_boredom_groups.json`。 --- @@ -210,7 +233,7 @@ LLM 自身的问答往返会写入 SQLite,用于多轮延续,但有硬限制 长期记忆当前来源非常保守: - 人工 `/remember` -- 后续如扩展自动抽取,也只允许从 LLM 已触发会话内提取 +- 自动记忆抽取开启时,仅从 LLM 已触发会话内提取稳定事实 明确不允许: @@ -313,11 +336,18 @@ LLM 自身的问答往返会写入 SQLite,用于多轮延续,但有硬限制 - `memory_limit` - `memory_max_items_per_group` - `max_prompt_chars` + - `tool_calling_enabled` + - `tool_max_rounds` + - `tool_max_calls_per_round` + - `auto_memory_enabled` + - `auto_memory_prompt` + - `auto_memory_max_tokens` - `[triggers]` - `default_prefix` - `allow_prefix` - `allow_at` - `empty_prompt_reply` + - `[triggers.quick_judge]`:唤醒模块和语境规则使用的快速判定模型 - `[tools]` - `enabled` - `discovery_mode` @@ -345,7 +375,7 @@ LLM 自身的问答往返会写入 SQLite,用于多轮延续,但有硬限制 Persona 定义已从 `llm.toml` 移出,改为 `config/personas/` 目录下每个 `.toml` 一个人格文件,`_shared.toml` 存储共享行为准则与风格规则。 -### 6.1 工具发现 +### 6.2 工具发现 工具调用开启后,QuickQuip 支持本地 `tool_search` 和 `tool_list` 元工具。该机制用于工具数量较多的场景:初始请求只暴露 `always_loaded` 中的常驻工具,模型需要其它能力时先调用 `tool_search`;搜索不到但工具可能存在时,可用 `tool_list` 查看工具组、工具名或按精确名称加载工具。工具循环会把匹配到或精确加载的真实工具加入下一轮 provider 请求。 @@ -359,7 +389,33 @@ Persona 定义已从 `llm.toml` 移出,改为 `config/personas/` 目录下每 - 真正的硬上限仍然在代码里存在 - 即使把 `history_max_messages_per_group` 写大,实际仍会被代码上限截断 -### 6.2 `.env` +### 6.3 `config/awakening.toml` + +唤醒模块配置集中在 `config/awakening.toml`: + +- `[awakening.defaults]` + - `extend_duration` + - `fallback_probability` + - `boredom_silence_seconds` + - `boredom_probability` + - `boredom_check_interval` + - `boredom_dnd_start` + - `boredom_dnd_end` + - `interest_topics` + - `relevance_threshold` + - `qa_threshold` +- `[[awakening.group_overrides]]` + - `group_id` + - 任意需要覆盖的默认字段 + +persona TOML 可通过自由扩展字段追加兴趣话题: + +```toml +[awakening] +interest_topics = ["关键词"] +``` + +### 6.4 `.env` 本地开发与容器运行都需要: @@ -376,7 +432,7 @@ Persona 定义已从 `llm.toml` 移出,改为 `config/personas/` 目录下每 - `HOST` - `PORT` -### 6.3 `config/generation.toml` +### 6.5 `config/generation.toml` LLM 相关的多模态输入/产出配置在 `generation.toml` 中维护: @@ -419,6 +475,7 @@ ASR 当前支持 `openai_transcriptions` 协议,即 OpenAI-compatible `POST /a - `/llm memory status` - `/llm memory on` - `/llm memory off` +- `/llm auto_memory status|on|off|reset` - `/llm context_limit ` — 设置本群上下文读取上限(1-20),持久化,不受 clear_context 影响 - `/llm context_limit reset` — 重置为全局默认 - `/llm clear_context` @@ -426,6 +483,10 @@ ASR 当前支持 `openai_transcriptions` 协议,即 OpenAI-compatible `POST /a - `/memories [关键词]` - `/forget <关键词>` - `/forget_all` — 清空本群全部长期记忆 +- `/awakening status` +- `/awakening on ` +- `/awakening off ` +- `/awakening boredom on|off` ### 7.5 联网搜索 @@ -455,7 +516,7 @@ ASR 当前支持 `openai_transcriptions` 协议,即 OpenAI-compatible `POST /a - `data/` 需要持久化 - 镜像构建时需要同时打包 `plugins/` 与 `quickquip/` - API key 通过环境变量注入 -- 使用搜索功能时需提供可访问的 SearXNG 或 Tavily 后端 +- 使用 `/search` 或 `search_web` 时需提供可访问的 SearXNG;Tavily 等外部搜索能力通过 MCP 工具接入 根目录 `.dockerignore` 已经做了收紧,避免把以下内容送进 Docker build 上下文: @@ -469,7 +530,7 @@ ASR 当前支持 `openai_transcriptions` 协议,即 OpenAI-compatible `POST /a ## 9. 现阶段已知边界 -当前模块还不是一个"全自动记忆智能体",而是一个刻意收边的群聊 LLM: +当前模块定位为刻意收边的群聊 LLM,边界如下: - 不自动扫全群消息做长期记忆 - 不自动做复杂摘要归档 diff --git a/docs/index.md b/docs/index.md index 719fa53..54d21ca 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,9 +25,11 @@ QuickQuip 是一个基于 NoneBot2 + OneBot V11 的规则驱动优先 QQ 群聊 | 文件 | 说明 | |------|------| | [admin/deployment.md](admin/deployment.md) | 云端部署指南——服务器选型、Docker Compose 编排、LLBot 登录、贴吧登录态、Web Admin 反代、日常维护与排障 | -| [admin/configuration.md](admin/configuration.md) | 完整配置参考——`.env` 环境变量、`llm.toml`、`generation.toml`、`chat_rules.toml`、`personas/` 所有可配项 | +| [admin/configuration.md](admin/configuration.md) | 完整配置参考——`.env` 环境变量、`llm.toml`、`generation.toml`、`awakening.toml`、`chat_rules.toml`、`games.toml`、`sensitive_words.toml`、`personas/` 所有可配项 | | [admin/tool-discovery.md](admin/tool-discovery.md) | LLM 工具发现配置——大量 MCP 工具接入时的 `tool_search`、`tool_list`、常驻工具和排障建议 | | [admin/game-config.md](admin/game-config.md) | 游戏系统管理——游戏开关、参数配置、数据库文件、故障排查 | +| [admin/sensitive-filter.md](admin/sensitive-filter.md) | 敏感词过滤器——词表配置、接入点、日志与测试方法 | +| [admin/migration-napcat-to-llbot.md](admin/migration-napcat-to-llbot.md) | NapCat 迁移 LLBot——Compose 服务、OneBot 连接方式、回退步骤 | | [admin/web-admin.md](admin/web-admin.md) | Web 管理后台——鉴权结构、Session 管理、反向代理配置、日志/Trace/各标签页功能列表 | ## 开发手册(开发者阅读) diff --git a/docs/user/group-commands.md b/docs/user/group-commands.md index 4d2375f..c048223 100644 --- a/docs/user/group-commands.md +++ b/docs/user/group-commands.md @@ -411,6 +411,10 @@ AI 默认只在下面两种情况回复: /wordcloud month ``` +```text +/wordcloud year +``` + 也支持中文别名: ```text @@ -725,7 +729,27 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) /llm memory off ``` -### 6.6 清空 AI 短期上下文 +### 6.6 开关自动记忆抽取 + +```text +/llm auto_memory status +``` + +```text +/llm auto_memory on +``` + +```text +/llm auto_memory off +``` + +```text +/llm auto_memory reset +``` + +自动记忆抽取只从已触发的 AI 对话中提取稳定事实,`reset` 会让本群跟随全局默认值。 + +### 6.7 清空 AI 短期上下文 ```text /llm clear_context @@ -734,7 +758,7 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) 这会清掉 AI 最近几轮对话上下文。 适合在 AI”串台”或”记错上下文”时用。 -### 6.7 从上下文中删除指定消息 +### 6.8 从上下文中删除指定消息 回复一条消息并发送: @@ -750,7 +774,7 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) 适合在 AI 记住了不该记住的消息时用。 -### 6.8 重载 AI 配置 +### 6.9 重载 AI 配置 ```text /llm reload @@ -758,19 +782,19 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) 重载会同时重置本群的上下文条数覆盖(如有)。 -### 6.9 手动添加记忆 +### 6.10 手动添加记忆 ```text /remember 阿桃喜欢薄荷糖 ``` -### 6.10 删除记忆 +### 6.11 删除记忆 ```text /forget 薄荷糖 ``` -### 6.11 清空本群全部长期记忆 +### 6.12 清空本群全部长期记忆 ```text /forget_all @@ -778,7 +802,7 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) 一次性清除本群所有已存的长期记忆。 -### 6.12 设置上下文读取上限 +### 6.13 设置上下文读取上限 ```text /llm context_limit 5 @@ -793,7 +817,7 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) /llm context_limit reset ``` -### 6.13 群规则开关 +### 6.14 群规则开关 禁用某条旧规则: @@ -807,13 +831,54 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) /enable divine_arrival ``` -### 6.14 重置群统计 +### 6.15 唤醒模块 + +查看本群唤醒状态: + +```text +/awakening status +``` + +可管理的唤醒规则: + +| 规则名 | 说明 | +|------|------| +| `awakening_extend` | 显式触发 AI 后的一小段时间内,继续回应同一用户 | +| `awakening_interest` | 命中配置的兴趣话题时回应 | +| `awakening_relevance` | 判断群友是否在延续 bot 刚才的话题 | +| `awakening_qa` | 判断群友是否提出了需要回答的问题 | +| `awakening_boredom` | 群聊沉寂达到配置时长后主动冒泡 | +| `awakening_fallback` | 按低概率兜底触发 | + +启用或关闭某个规则: + +```text +/awakening on awakening_interest +``` + +```text +/awakening off awakening_interest +``` + +无聊唤醒还需要本群单独 opt-in: + +```text +/awakening boredom on +``` + +```text +/awakening boredom off +``` + +具体阈值和兴趣话题由部署者在 `config/awakening.toml` 中配置。 + +### 6.16 重置群统计 ```text /reset_stats ``` -### 6.15 热重载规则和人格 +### 6.17 热重载规则和人格 重载聊天规则(`chat_rules.toml`): @@ -829,7 +894,7 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) 适合在通过 Web Admin 在线编辑配置文件后立即生效。 -### 6.16 立即同步贴吧缓存 +### 6.18 立即同步贴吧缓存 ```text /tieba refresh @@ -853,7 +918,7 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) 按部署指南完成贴吧登录态导出。 ``` -### 6.17 贴吧实时抓取 +### 6.19 贴吧实时抓取 ```text /tieba_peek 搬石 @@ -981,7 +1046,7 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) - `/tell @某人 内容` — 离线留言 - `/tells` — 查看待收留言 - `/untell` — 撤回留言 -- `/wordcloud [today|week|month]` — 生成词云(管理员) +- `/wordcloud [today|week|month|year]` — 生成词云(管理员) - `/draw 描述` — AI 图片生成 - `/tts 文本` — 文字转语音 - `/music 风格 主题` — AI 写歌 @@ -1018,6 +1083,7 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) - `/llm persona use ` - `/llm memory on` - `/llm memory off` +- `/llm auto_memory status|on|off|reset` - `/llm context_limit ` - `/llm context_limit reset` - `/llm clear_context` @@ -1028,6 +1094,9 @@ QuickQuip 内置 4 款群内游戏,详见 [群内游戏指南](group-games.md) - `/forget_all` - `/disable ` - `/enable ` +- `/awakening status` +- `/awakening on|off ` +- `/awakening boredom on|off` - `/reload_rules` — 热重载聊天规则 - `/reload_personas` — 热重载 LLM 人格 - `/reset_stats` diff --git a/frontend/src/api/awakening.ts b/frontend/src/api/awakening.ts new file mode 100644 index 0000000..281fd41 --- /dev/null +++ b/frontend/src/api/awakening.ts @@ -0,0 +1,30 @@ +import { request } from './index' + +export async function fetchAwakening() { + return request('/api/awakening') +} + +export async function fetchAwakeningGroup(groupId: string) { + return request(`/api/awakening/${encodeURIComponent(groupId)}`) +} + +export async function setAwakeningRule(groupId: string, ruleName: string, enabled: boolean) { + return request(`/api/awakening/${encodeURIComponent(groupId)}/rules/${encodeURIComponent(ruleName)}`, { + method: 'POST', + body: JSON.stringify({ enabled }), + }) +} + +export async function setAwakeningBoredom(groupId: string, enabled: boolean) { + return request(`/api/awakening/${encodeURIComponent(groupId)}/boredom`, { + method: 'POST', + body: JSON.stringify({ enabled }), + }) +} + +export async function updateAwakeningSettings(groupId: string, payload: Record) { + return request(`/api/awakening/${encodeURIComponent(groupId)}/settings`, { + method: 'PUT', + body: JSON.stringify(payload), + }) +} diff --git a/frontend/src/api/groups.ts b/frontend/src/api/groups.ts index d8698e0..083bc98 100644 --- a/frontend/src/api/groups.ts +++ b/frontend/src/api/groups.ts @@ -14,3 +14,14 @@ export async function updateGroup(type: string, gid: string | number, enabled: b body: JSON.stringify({ enabled }), }) } + +export async function runSummaryNow(gid: string | number) { + return request(`/api/groups/summary/${encodeURIComponent(String(gid))}/now`, { method: 'POST' }) +} + +export async function runBriefingNow(gid: string | number, period?: string) { + return request(`/api/groups/briefing/${encodeURIComponent(String(gid))}/now`, { + method: 'POST', + body: JSON.stringify({ period: period || null }), + }) +} diff --git a/frontend/src/api/llmRuntime.ts b/frontend/src/api/llmRuntime.ts new file mode 100644 index 0000000..5d1e7b9 --- /dev/null +++ b/frontend/src/api/llmRuntime.ts @@ -0,0 +1,42 @@ +import { request } from './index' + +export async function fetchLlmHealth(verbose = false, scopeKey = '__web_admin__') { + return request('/api/llm-runtime/health', { + method: 'POST', + body: JSON.stringify({ verbose, scope_key: scopeKey }), + }) +} + +export async function reloadLlmRuntime() { + return request('/api/llm-runtime/reload', { method: 'POST' }) +} + +export async function reloadMcpRuntime() { + return request('/api/llm-runtime/mcp/reload', { method: 'POST' }) +} + +export async function reloadPersonas() { + return request('/api/llm-runtime/personas/reload', { method: 'POST' }) +} + +export async function reloadRules() { + return request('/api/llm-runtime/rules/reload', { method: 'POST' }) +} + +export async function clearLlmContext(scopeKey: string) { + return request('/api/llm-runtime/context/clear', { + method: 'POST', + body: JSON.stringify({ scope_key: scopeKey }), + }) +} + +export async function deleteLlmContextMessage(scopeKey: string, messageId: string) { + return request('/api/llm-runtime/context/delete-message', { + method: 'POST', + body: JSON.stringify({ scope_key: scopeKey, message_id: messageId }), + }) +} + +export async function fetchLlmRuntimeActions(limit = 20) { + return request(`/api/llm-runtime/actions?limit=${encodeURIComponent(String(limit))}`) +} diff --git a/frontend/src/api/tieba.ts b/frontend/src/api/tieba.ts index 3bd4100..41747b7 100644 --- a/frontend/src/api/tieba.ts +++ b/frontend/src/api/tieba.ts @@ -38,3 +38,7 @@ export async function fetchTiebaThreads(forum: string, { keyword, limit, offset export async function fetchTiebaThread(forum: string, tid: number) { return request(`/api/tieba/threads/${encodeURIComponent(forum)}/${encodeURIComponent(tid)}`) } + +export async function peekTiebaThread(forum: string) { + return request(`/api/tieba/peek?forum=${encodeURIComponent(forum)}`) +} diff --git a/frontend/src/components/ui/UiIcon.vue b/frontend/src/components/ui/UiIcon.vue index 49e7d93..5adc42c 100644 --- a/frontend/src/components/ui/UiIcon.vue +++ b/frontend/src/components/ui/UiIcon.vue @@ -12,7 +12,8 @@ import { Check, X, ChevronDown, FolderOpen, Lock, RefreshCw, LayoutDashboard, MessagesSquare, BrainCircuit, ServerCog, Gamepad2, Gauge, Network, Cloud, ArrowRight, ChevronUp, Send, FileCode, ListChecks, - AlertTriangle, Play, Save, ChevronLeft, Download, Sparkles + AlertTriangle, Play, Save, ChevronLeft, Download, Sparkles, + BellRing, Radar, Wrench, ListTree, Eraser, Activity } from 'lucide-vue-next' import { computed } from 'vue' import type { Component } from 'vue' @@ -30,6 +31,7 @@ type IconName = | 'Gamepad2' | 'Gauge' | 'Network' | 'Cloud' | 'ArrowRight' | 'ChevronUp' | 'Send' | 'FileCode' | 'ListChecks' | 'AlertTriangle' | 'Play' | 'Save' | 'ChevronLeft' | 'Download' | 'Sparkles' + | 'BellRing' | 'Radar' | 'Wrench' | 'ListTree' | 'Eraser' | 'Activity' const ICON_MAP: Record = { BarChart3, ToggleLeft, Users, Brain, FileText, @@ -42,7 +44,8 @@ const ICON_MAP: Record = { X, ChevronDown, FolderOpen, Lock, RefreshCw, LayoutDashboard, MessagesSquare, BrainCircuit, ServerCog, Gamepad2, Gauge, Network, Cloud, ArrowRight, ChevronUp, Send, FileCode, ListChecks, - AlertTriangle, Play, Save, ChevronLeft, Download, Sparkles + AlertTriangle, Play, Save, ChevronLeft, Download, Sparkles, + BellRing, Radar, Wrench, ListTree, Eraser, Activity } const props = defineProps<{ diff --git a/frontend/src/config/nav.ts b/frontend/src/config/nav.ts index 3cfdc8b..7f9bef9 100644 --- a/frontend/src/config/nav.ts +++ b/frontend/src/config/nav.ts @@ -9,6 +9,7 @@ import ConversationsView from '../views/ConversationsView.vue' import PersonasView from '../views/PersonasView.vue' import LlmAboutView from '../views/LlmAboutView.vue' import GroupSettingsView from '../views/GroupSettingsView.vue' +import AwakeningView from '../views/AwakeningView.vue' import RateLimitView from '../views/RateLimitView.vue' import TiebaView from '../views/TiebaView.vue' import QuotesView from '../views/QuotesView.vue' @@ -55,6 +56,7 @@ export const NAV_ITEMS: NavItem[] = [ { key: 'rules', path: '/rules', label: '规则', icon: 'ToggleLeft', section: 'ops', component: RulesView }, { key: 'groups', path: '/groups', label: '群组', icon: 'Users', section: 'ops', component: GroupsView }, { key: 'group-settings', path: '/group-settings', label: '群 LLM', icon: 'SlidersHorizontal', section: 'ops', component: GroupSettingsView }, + { key: 'awakening', path: '/awakening', label: '唤醒', icon: 'BellRing', section: 'ops', component: AwakeningView }, { key: 'rate-limit', path: '/rate-limit', label: '限流', icon: 'Gauge', section: 'ops', component: RateLimitView }, { key: 'memory', path: '/memory', label: '记忆', icon: 'Brain', section: 'llm', component: MemoryView }, { key: 'conversations', path: '/conversations', label: '对话', icon: 'MessageCircle', section: 'llm', component: ConversationsView }, diff --git a/frontend/src/views/AwakeningView.vue b/frontend/src/views/AwakeningView.vue new file mode 100644 index 0000000..9a68baf --- /dev/null +++ b/frontend/src/views/AwakeningView.vue @@ -0,0 +1,428 @@ + + + + + diff --git a/frontend/src/views/ConfigView.vue b/frontend/src/views/ConfigView.vue index c5d730c..c088026 100644 --- a/frontend/src/views/ConfigView.vue +++ b/frontend/src/views/ConfigView.vue @@ -1,6 +1,6 @@