nonebot-plugin-aigf-master 2.0.0b2__tar.gz → 2.0.0b4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/PKG-INFO +24 -13
  2. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/README.md +23 -12
  3. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/__init__.py +7 -1
  4. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/config.py +3 -2
  5. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/memory_store.py +48 -1
  6. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/processor.py +34 -3
  7. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/search_client.py +58 -1
  8. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master.egg-info/PKG-INFO +24 -13
  9. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/pyproject.toml +1 -1
  10. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/LICENSE +0 -0
  11. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/anime_recognizer.py +0 -0
  12. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/animetrace_client.py +0 -0
  13. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/api_hooks.py +0 -0
  14. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/cache_cleanup.py +0 -0
  15. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/command_learner.py +0 -0
  16. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/context_bus.py +0 -0
  17. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/image_gen_client.py +0 -0
  18. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/image_handler.py +0 -0
  19. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/llm_client.py +0 -0
  20. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/meme_store.py +0 -0
  21. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/models.py +0 -0
  22. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/notice.py +0 -0
  23. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/peer_client.py +0 -0
  24. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/plugin_discovery.py +0 -0
  25. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/plugin_invoker.py +0 -0
  26. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/preset_store.py +0 -0
  27. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/prompt_builder.py +0 -0
  28. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/response_parser.py +0 -0
  29. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/session.py +0 -0
  30. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/unimsg.py +0 -0
  31. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/vlm_client.py +0 -0
  32. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master.egg-info/SOURCES.txt +0 -0
  33. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master.egg-info/dependency_links.txt +0 -0
  34. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master.egg-info/requires.txt +0 -0
  35. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master.egg-info/top_level.txt +0 -0
  36. {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-aigf-master
3
- Version: 2.0.0b2
3
+ Version: 2.0.0b4
4
4
  Summary: 群聊特化LLM聊天机器人(增强版),具有记忆、表情包、跨插件调用、跨bot通信与跨适配器支持
5
5
  License-Expression: MIT
6
6
  Requires-Python: <4.0,>=3.10
@@ -55,7 +55,7 @@ Dynamic: license-file
55
55
  - 🧠 **LLM 命令学习**:通过观察群聊自动学习命令用法,支持学习、编辑、删除
56
56
  - 🌐 **跨 bot 通信**:通过 HTTP 获取其它 bot(同一 QQ 号下)的插件输出,并可远程调用其命令
57
57
  - 🖼️ **表情包功能**:AI 自主决定发表情包;自动从群聊中收藏
58
- - 🔍 **联网搜索**:支持 Tavily / Bocha / Bing,自动搜索不懂的梗和网络用语
58
+ - 🔍 **联网搜索**:支持 Tavily / Bocha / Bing / DeepSeek / 本地 open-websearch,自动搜索不懂的梗和网络用语
59
59
  - 🧠 **四层记忆系统**:短期记忆、长期记忆、群友信息、文化记忆,LLM 自主管理
60
60
  - 🎨 **AI 生图**:LLM可调用生图模型生成图片发到群里,尺寸由 LLM 决定(程序限最大尺寸),支持参考图改绘(回复图片消息或从聊天记录选图)
61
61
 
@@ -80,9 +80,9 @@ Dynamic: license-file
80
80
  >
81
81
  > 从 2.0 起插件还依赖 `nonebot-plugin-alconna` 与 `nonebot-plugin-uninfo`(跨适配器收发与会话/成员信息),用下面的方式安装时会自动装上。
82
82
 
83
- ### 安装 beta 版本(2.0.0b2)
83
+ ### 安装 beta 版本(2.0.0b4)
84
84
 
85
- `2.0.0b2` 是**预发布(pre-release)版本**,因此:
85
+ `2.0.0b4` 是**预发布(pre-release)版本**,因此:
86
86
 
87
87
  - `pip install nonebot-plugin-aigf-master` 装的仍是稳定版 **1.6.x**
88
88
  - `nb plugin install nonebot-plugin-aigf-master` 同样只装稳定版,而且 nb-cli **不支持指定版本**,所以 beta 只能用 pip 装
@@ -90,7 +90,7 @@ Dynamic: license-file
90
90
  要装 beta,**必须显式写出版本号**:
91
91
 
92
92
  ```bash
93
- pip install nonebot-plugin-aigf-master==2.0.0b2
93
+ pip install nonebot-plugin-aigf-master==2.0.0b4
94
94
  ```
95
95
 
96
96
  在 `pyproject.toml` 中添加:
@@ -115,8 +115,8 @@ plugins = ["nonebot-plugin-aigf-master"]
115
115
  AIGFM_LLM_API_KEY="sk-xxxxxxxxxxxx" # LLM API Key
116
116
  AIGFM_LLM_BASE_URL="https://api.deepseek.com" # LLM API 地址
117
117
  AIGFM_LLM_MODEL="deepseek-v4-flash" # LLM 模型名称
118
- AIGFM_ENABLED_GROUPS=[123456, 789012] # 启用的群/频道会话(可写 `适配器:会话id`,只写数字按 OneBot V11 识别)
119
- AIGFM_ENABLED_PRIVATE=[] # 启用的私聊会话(同上写法;留空则私聊不响应)
118
+ AIGFM_ENABLED_GROUPS=["onebot11:123456789", "console:console-chat"] # 启用的群/频道会话(`适配器:会话id`;也可只写数字,按 OneBot V11 识别)
119
+ AIGFM_ENABLED_PRIVATE=["onebot11:20001"] # 启用的私聊会话(同上写法;留空则私聊不响应)
120
120
 
121
121
  # --- 图片理解(VLM) ---
122
122
  AIGFM_IMAGE_MODE="vlm" # 图片模式: vlm / llm(默认 vlm)
@@ -158,10 +158,11 @@ AIGFM_ENERGY_BASELINE=0.7 # 社交能量基线(默认 0.7,范
158
158
 
159
159
  # --- 联网搜索 ---
160
160
  AIGFM_SEARCH_ENABLED=false # 是否启用联网搜索(默认 false)
161
- AIGFM_SEARCH_API=tavily # 搜索API: tavily / bocha / bing / openwebsearch(默认 tavily)
162
- AIGFM_SEARCH_API_KEY="xxx" # 搜索 API Key(api=tavily/bocha/bing 时必填)
161
+ AIGFM_SEARCH_API=tavily # 搜索API: tavily / bocha / bing / openwebsearch / deepseek(默认 tavily)
162
+ AIGFM_SEARCH_API_KEY="xxx" # 搜索 API Key(api=tavily/bocha/bing 时必填;deepseek 留空则沿用 LLM Key)
163
163
  AIGFM_SEARCH_MAX_RESULTS=3 # 最大搜索结果数(默认 3)
164
164
  AIGFM_OPENWEBSEARCH_URL="" # open-websearch 本地服务地址(api=openwebsearch 时必填,如 http://127.0.0.1:3210)
165
+ AIGFM_SEARCH_BASE_URL="" # 搜索 API 根地址(api=deepseek 时用,留空则用官方 https://api.deepseek.com)
165
166
 
166
167
  # --- 代理 ---
167
168
  AIGFM_PROXY_ENABLED=false # 是否启用代理(默认 false)
@@ -234,7 +235,7 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
234
235
  - 纯 @ 消息或检测到同一用户连续发送时,以及调用的插件返回图片时,等待时间会用 `AIGFM_INCOMPLETE_TIMEOUT`(默认 40 秒)**替代**默认的 30 秒(非叠加)
235
236
  - **有图片正在解析时整批推迟触发**:等到 VLM 描述填好后随**同一批**发给 LLM,不会把没有描述的空消息发出去;"图片进入解析"这一特例最长等待 `AIGFM_BATCH_TIMEOUT + AIGFM_INCOMPLETE_TIMEOUT`(默认 70 秒)后仍会放行,此时该图片以 `[图片解析中]` 进入上下文
236
237
  - 消息合并窗口内的同一用户连续消息会被合并为一条
237
- - 每次处理时,LLM 收到最近 **20 条**(可配置)历史聊天记录 + 四层记忆 + 预设 + 表情包列表 + 其它插件响应;本批消息只出现在「新消息」段,同一条插件响应也只注入一次(不会在历史与新消息里重复)
238
+ - 每次处理时,LLM 收到最近 **20 条**(可配置)历史聊天记录 + 四层记忆 + 预设 + 表情包列表 + 其它插件响应;本批消息只出现在「新消息」段,同一条插件响应也只注入一次(不会在历史与新消息里重复)。历史聊天记录会落盘(`memory/<群号>/recent.json`),**机器人重启后仍能读回**
238
239
  - LLM 一次调用同时完成:回复决策 + 记忆管理 + 表情包选择 + 命令学习
239
240
 
240
241
  ## 🧩 多适配器支持
@@ -243,9 +244,9 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
243
244
 
244
245
  ### 会话与启用清单
245
246
 
246
- - 会话以 **`适配器:会话id`** 为键,例如 `onebot11:617770183`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
247
+ - 会话以 **`适配器:会话id`** 为键,例如 `onebot11:123456789`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
247
248
  - `AIGFM_ENABLED_GROUPS` 管群/频道会话、`AIGFM_ENABLED_PRIVATE` 管私聊会话;条目可写带前缀的完整键,也可以只写 id(**按 OneBot V11 识别**,旧配置无需改动)
248
- - 落盘目录名不能含 `:`,因此磁盘上写作 `onebot11_617770183`;`memory/` 下旧版裸数字目录会在启动时**一次性自动迁移**(目标已存在则跳过,幂等)
249
+ - 落盘目录名不能含 `:`,因此磁盘上写作 `onebot11_123456789`;`memory/` 下旧版裸数字目录会在启动时**一次性自动迁移**(目标已存在则跳过,幂等)
249
250
  - 非启用会话不发请求、不入缓冲:插件只会为启用的会话解析成员信息与图片
250
251
 
251
252
  ### 功能 × 适配器支持表(2026-09-25 实测)
@@ -274,7 +275,11 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
274
275
  | YunHu | ✅ | ✅ | ✅ | 未测 | ⚠️ | ✅ | ✅ | ✅ | ✅ |
275
276
  | bilibili Live | ✅ | ✅ | ✅ | ❌ 无好友私聊类型 | ⚠️ | ✅ | ✅ | ⚠️ | ✅ |
276
277
 
277
- 图例:**✅ 已实测通过** | **⚠️ 已实现但未验证通过**(离线构造不出该适配器的事件,或需要真实 API / 真机)| **❌ 该适配器没有这类事件,或库层无法把该通知定位到群/频道会话(通知会被跳过)** | **未测** 本次未构造该场景。
278
+ **图例:**
279
+ - **✅ 已实测通过**
280
+ - **⚠️ 已实现但未验证通过**(离线构造不出该适配器的事件,或需要真实 API / 真机)
281
+ - **❌ 该适配器没有这类事件,或库层无法把该通知定位到群/频道会话(通知会被跳过)**
282
+ - **未测** 本次未构造该场景。
278
283
 
279
284
  **已知特例**
280
285
 
@@ -559,6 +564,7 @@ LLM 可以:
559
564
  | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 | https://open.bochaai.com |
560
565
  | `bing` | 微软必应搜索(Azure Bing Search API v7) | 无 | https://portal.azure.com |
561
566
  | `openwebsearch` | 本地 Node.js 搜索服务,聚合多引擎(bing/baidu/duckduckgo 等) | 需先启动 daemon:`cd open-webSearch && npm install && npm run serve`,配置 `AIGFM_OPENWEBSEARCH_URL` | 无需 Key,项目:https://github.com/Aas-ee/open-webSearch |
567
+ | `deepseek` | DeepSeek 自带联网搜索,摘要写得很适合喂给模型 | 无(`AIGFM_SEARCH_API_KEY` 留空则沿用 LLM Key) | https://platform.deepseek.com |
562
568
 
563
569
  > **Tavily** 需额外安装依赖:`pip install tavily-python`(或安装本插件时带上 extra:`pip install "nonebot-plugin-aigf-master[tavily]"`)
564
570
 
@@ -590,6 +596,10 @@ LLM 可以:
590
596
 
591
597
  存储在 `<插件数据目录>/memory/<群号>/culture.json`,记录梗、网络用语、流行语。LLM 主动学习和存储,根据聊天内容自动匹配。
592
598
 
599
+ ### 最近聊天记录
600
+
601
+ 上面四层记忆之外的**滚动窗口**:最近 50 条消息(含机器人自己的回复与插件响应)会落盘在 `<插件数据目录>/memory/<群号>/recent.json`,**机器人重启后会读回**,因此刚重启的会话也能接上之前的上下文(注入 prompt 的条数由 `AIGFM_RECENT_MESSAGES` 控制,默认 20)。`/reset` 会连这份记录一起清空。
602
+
593
603
  ## 🖼️ 表情包功能
594
604
 
595
605
  ### 工作原理
@@ -766,6 +776,7 @@ LLM 支持以下回复类型:
766
776
  short_term.json # 短期记忆
767
777
  long_term.json # 长期记忆
768
778
  culture.json # 文化记忆
779
+ recent.json # 最近的聊天记录(重启后仍可读)
769
780
  friends/
770
781
  {user_id}.json # 群友信息
771
782
  memes/
@@ -33,7 +33,7 @@
33
33
  - 🧠 **LLM 命令学习**:通过观察群聊自动学习命令用法,支持学习、编辑、删除
34
34
  - 🌐 **跨 bot 通信**:通过 HTTP 获取其它 bot(同一 QQ 号下)的插件输出,并可远程调用其命令
35
35
  - 🖼️ **表情包功能**:AI 自主决定发表情包;自动从群聊中收藏
36
- - 🔍 **联网搜索**:支持 Tavily / Bocha / Bing,自动搜索不懂的梗和网络用语
36
+ - 🔍 **联网搜索**:支持 Tavily / Bocha / Bing / DeepSeek / 本地 open-websearch,自动搜索不懂的梗和网络用语
37
37
  - 🧠 **四层记忆系统**:短期记忆、长期记忆、群友信息、文化记忆,LLM 自主管理
38
38
  - 🎨 **AI 生图**:LLM可调用生图模型生成图片发到群里,尺寸由 LLM 决定(程序限最大尺寸),支持参考图改绘(回复图片消息或从聊天记录选图)
39
39
 
@@ -58,9 +58,9 @@
58
58
  >
59
59
  > 从 2.0 起插件还依赖 `nonebot-plugin-alconna` 与 `nonebot-plugin-uninfo`(跨适配器收发与会话/成员信息),用下面的方式安装时会自动装上。
60
60
 
61
- ### 安装 beta 版本(2.0.0b2)
61
+ ### 安装 beta 版本(2.0.0b4)
62
62
 
63
- `2.0.0b2` 是**预发布(pre-release)版本**,因此:
63
+ `2.0.0b4` 是**预发布(pre-release)版本**,因此:
64
64
 
65
65
  - `pip install nonebot-plugin-aigf-master` 装的仍是稳定版 **1.6.x**
66
66
  - `nb plugin install nonebot-plugin-aigf-master` 同样只装稳定版,而且 nb-cli **不支持指定版本**,所以 beta 只能用 pip 装
@@ -68,7 +68,7 @@
68
68
  要装 beta,**必须显式写出版本号**:
69
69
 
70
70
  ```bash
71
- pip install nonebot-plugin-aigf-master==2.0.0b2
71
+ pip install nonebot-plugin-aigf-master==2.0.0b4
72
72
  ```
73
73
 
74
74
  在 `pyproject.toml` 中添加:
@@ -93,8 +93,8 @@ plugins = ["nonebot-plugin-aigf-master"]
93
93
  AIGFM_LLM_API_KEY="sk-xxxxxxxxxxxx" # LLM API Key
94
94
  AIGFM_LLM_BASE_URL="https://api.deepseek.com" # LLM API 地址
95
95
  AIGFM_LLM_MODEL="deepseek-v4-flash" # LLM 模型名称
96
- AIGFM_ENABLED_GROUPS=[123456, 789012] # 启用的群/频道会话(可写 `适配器:会话id`,只写数字按 OneBot V11 识别)
97
- AIGFM_ENABLED_PRIVATE=[] # 启用的私聊会话(同上写法;留空则私聊不响应)
96
+ AIGFM_ENABLED_GROUPS=["onebot11:123456789", "console:console-chat"] # 启用的群/频道会话(`适配器:会话id`;也可只写数字,按 OneBot V11 识别)
97
+ AIGFM_ENABLED_PRIVATE=["onebot11:20001"] # 启用的私聊会话(同上写法;留空则私聊不响应)
98
98
 
99
99
  # --- 图片理解(VLM) ---
100
100
  AIGFM_IMAGE_MODE="vlm" # 图片模式: vlm / llm(默认 vlm)
@@ -136,10 +136,11 @@ AIGFM_ENERGY_BASELINE=0.7 # 社交能量基线(默认 0.7,范
136
136
 
137
137
  # --- 联网搜索 ---
138
138
  AIGFM_SEARCH_ENABLED=false # 是否启用联网搜索(默认 false)
139
- AIGFM_SEARCH_API=tavily # 搜索API: tavily / bocha / bing / openwebsearch(默认 tavily)
140
- AIGFM_SEARCH_API_KEY="xxx" # 搜索 API Key(api=tavily/bocha/bing 时必填)
139
+ AIGFM_SEARCH_API=tavily # 搜索API: tavily / bocha / bing / openwebsearch / deepseek(默认 tavily)
140
+ AIGFM_SEARCH_API_KEY="xxx" # 搜索 API Key(api=tavily/bocha/bing 时必填;deepseek 留空则沿用 LLM Key)
141
141
  AIGFM_SEARCH_MAX_RESULTS=3 # 最大搜索结果数(默认 3)
142
142
  AIGFM_OPENWEBSEARCH_URL="" # open-websearch 本地服务地址(api=openwebsearch 时必填,如 http://127.0.0.1:3210)
143
+ AIGFM_SEARCH_BASE_URL="" # 搜索 API 根地址(api=deepseek 时用,留空则用官方 https://api.deepseek.com)
143
144
 
144
145
  # --- 代理 ---
145
146
  AIGFM_PROXY_ENABLED=false # 是否启用代理(默认 false)
@@ -212,7 +213,7 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
212
213
  - 纯 @ 消息或检测到同一用户连续发送时,以及调用的插件返回图片时,等待时间会用 `AIGFM_INCOMPLETE_TIMEOUT`(默认 40 秒)**替代**默认的 30 秒(非叠加)
213
214
  - **有图片正在解析时整批推迟触发**:等到 VLM 描述填好后随**同一批**发给 LLM,不会把没有描述的空消息发出去;"图片进入解析"这一特例最长等待 `AIGFM_BATCH_TIMEOUT + AIGFM_INCOMPLETE_TIMEOUT`(默认 70 秒)后仍会放行,此时该图片以 `[图片解析中]` 进入上下文
214
215
  - 消息合并窗口内的同一用户连续消息会被合并为一条
215
- - 每次处理时,LLM 收到最近 **20 条**(可配置)历史聊天记录 + 四层记忆 + 预设 + 表情包列表 + 其它插件响应;本批消息只出现在「新消息」段,同一条插件响应也只注入一次(不会在历史与新消息里重复)
216
+ - 每次处理时,LLM 收到最近 **20 条**(可配置)历史聊天记录 + 四层记忆 + 预设 + 表情包列表 + 其它插件响应;本批消息只出现在「新消息」段,同一条插件响应也只注入一次(不会在历史与新消息里重复)。历史聊天记录会落盘(`memory/<群号>/recent.json`),**机器人重启后仍能读回**
216
217
  - LLM 一次调用同时完成:回复决策 + 记忆管理 + 表情包选择 + 命令学习
217
218
 
218
219
  ## 🧩 多适配器支持
@@ -221,9 +222,9 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
221
222
 
222
223
  ### 会话与启用清单
223
224
 
224
- - 会话以 **`适配器:会话id`** 为键,例如 `onebot11:617770183`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
225
+ - 会话以 **`适配器:会话id`** 为键,例如 `onebot11:123456789`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
225
226
  - `AIGFM_ENABLED_GROUPS` 管群/频道会话、`AIGFM_ENABLED_PRIVATE` 管私聊会话;条目可写带前缀的完整键,也可以只写 id(**按 OneBot V11 识别**,旧配置无需改动)
226
- - 落盘目录名不能含 `:`,因此磁盘上写作 `onebot11_617770183`;`memory/` 下旧版裸数字目录会在启动时**一次性自动迁移**(目标已存在则跳过,幂等)
227
+ - 落盘目录名不能含 `:`,因此磁盘上写作 `onebot11_123456789`;`memory/` 下旧版裸数字目录会在启动时**一次性自动迁移**(目标已存在则跳过,幂等)
227
228
  - 非启用会话不发请求、不入缓冲:插件只会为启用的会话解析成员信息与图片
228
229
 
229
230
  ### 功能 × 适配器支持表(2026-09-25 实测)
@@ -252,7 +253,11 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
252
253
  | YunHu | ✅ | ✅ | ✅ | 未测 | ⚠️ | ✅ | ✅ | ✅ | ✅ |
253
254
  | bilibili Live | ✅ | ✅ | ✅ | ❌ 无好友私聊类型 | ⚠️ | ✅ | ✅ | ⚠️ | ✅ |
254
255
 
255
- 图例:**✅ 已实测通过** | **⚠️ 已实现但未验证通过**(离线构造不出该适配器的事件,或需要真实 API / 真机)| **❌ 该适配器没有这类事件,或库层无法把该通知定位到群/频道会话(通知会被跳过)** | **未测** 本次未构造该场景。
256
+ **图例:**
257
+ - **✅ 已实测通过**
258
+ - **⚠️ 已实现但未验证通过**(离线构造不出该适配器的事件,或需要真实 API / 真机)
259
+ - **❌ 该适配器没有这类事件,或库层无法把该通知定位到群/频道会话(通知会被跳过)**
260
+ - **未测** 本次未构造该场景。
256
261
 
257
262
  **已知特例**
258
263
 
@@ -537,6 +542,7 @@ LLM 可以:
537
542
  | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 | https://open.bochaai.com |
538
543
  | `bing` | 微软必应搜索(Azure Bing Search API v7) | 无 | https://portal.azure.com |
539
544
  | `openwebsearch` | 本地 Node.js 搜索服务,聚合多引擎(bing/baidu/duckduckgo 等) | 需先启动 daemon:`cd open-webSearch && npm install && npm run serve`,配置 `AIGFM_OPENWEBSEARCH_URL` | 无需 Key,项目:https://github.com/Aas-ee/open-webSearch |
545
+ | `deepseek` | DeepSeek 自带联网搜索,摘要写得很适合喂给模型 | 无(`AIGFM_SEARCH_API_KEY` 留空则沿用 LLM Key) | https://platform.deepseek.com |
540
546
 
541
547
  > **Tavily** 需额外安装依赖:`pip install tavily-python`(或安装本插件时带上 extra:`pip install "nonebot-plugin-aigf-master[tavily]"`)
542
548
 
@@ -568,6 +574,10 @@ LLM 可以:
568
574
 
569
575
  存储在 `<插件数据目录>/memory/<群号>/culture.json`,记录梗、网络用语、流行语。LLM 主动学习和存储,根据聊天内容自动匹配。
570
576
 
577
+ ### 最近聊天记录
578
+
579
+ 上面四层记忆之外的**滚动窗口**:最近 50 条消息(含机器人自己的回复与插件响应)会落盘在 `<插件数据目录>/memory/<群号>/recent.json`,**机器人重启后会读回**,因此刚重启的会话也能接上之前的上下文(注入 prompt 的条数由 `AIGFM_RECENT_MESSAGES` 控制,默认 20)。`/reset` 会连这份记录一起清空。
580
+
571
581
  ## 🖼️ 表情包功能
572
582
 
573
583
  ### 工作原理
@@ -744,6 +754,7 @@ LLM 支持以下回复类型:
744
754
  short_term.json # 短期记忆
745
755
  long_term.json # 长期记忆
746
756
  culture.json # 文化记忆
757
+ recent.json # 最近的聊天记录(重启后仍可读)
747
758
  friends/
748
759
  {user_id}.json # 群友信息
749
760
  memes/
@@ -89,8 +89,12 @@ _image_gen = ImageGenClient(
89
89
  _proxy,
90
90
  ) if plugin_config.aigfm_image_gen_enabled else None
91
91
  _search = create_search_client(
92
- plugin_config.aigfm_search_api, plugin_config.aigfm_search_api_key,
92
+ plugin_config.aigfm_search_api,
93
+ # deepseek 的 Key 一般和 LLM 是同一个,没单独配就沿用 LLM Key
94
+ plugin_config.aigfm_search_api_key
95
+ or (plugin_config.aigfm_llm_api_key if plugin_config.aigfm_search_api == "deepseek" else ""),
93
96
  plugin_config.aigfm_openwebsearch_url,
97
+ plugin_config.aigfm_search_base_url,
94
98
  ) if plugin_config.aigfm_search_enabled else None
95
99
  _peer_client = PeerClient(plugin_config.aigfm_peer_bots) if plugin_config.aigfm_peer_bots else None
96
100
 
@@ -507,6 +511,8 @@ async def _(bot: Bot, event: Event):
507
511
  processor.bot_name = "小助手"
508
512
  processor.bot_role = "一个友好的群聊助手"
509
513
  processor.recent_messages.clear()
514
+ # 落盘的聊天记录一起清,否则重置后重启又会把旧历史读回来
515
+ await processor.memory.clear_recent()
510
516
  # 插件响应另有一份存在 ContextBus 里,不清会跨 reset 残留进后续 prompt
511
517
  processor.context_bus.clear(session.key)
512
518
  # 图片缓存索引随聊天记录一起清(历史 id 已不在 prompt 中,留着也无可收藏的上下文)
@@ -71,10 +71,11 @@ class PluginConfig(BaseModel):
71
71
 
72
72
  # 搜索
73
73
  aigfm_search_enabled: bool = Field(False, description="是否启用联网搜索")
74
- aigfm_search_api: Literal["tavily", "bocha", "bing", "openwebsearch"] = Field("tavily", description="搜索 API")
75
- aigfm_search_api_key: str = Field("", description="搜索 API Key(tavily/bocha/bing 必填)")
74
+ aigfm_search_api: Literal["tavily", "bocha", "bing", "openwebsearch", "deepseek"] = Field("tavily", description="搜索 API")
75
+ aigfm_search_api_key: str = Field("", description="搜索 API Key(tavily/bocha/bing 必填;deepseek 留空则用 LLM Key)")
76
76
  aigfm_search_max_results: int = Field(3, description="最大搜索结果数")
77
77
  aigfm_openwebsearch_url: str = Field("", description="open-websearch 本地服务地址(如 http://127.0.0.1:3210)")
78
+ aigfm_search_base_url: str = Field("", description="搜索 API 根地址(deepseek 用,留空则用官方 https://api.deepseek.com)")
78
79
 
79
80
  # 代理
80
81
  aigfm_proxy_enabled: bool = Field(False, description="是否启用代理")
@@ -10,7 +10,7 @@ from pathlib import Path
10
10
  import anyio
11
11
  from nonebot import logger
12
12
 
13
- from .models import MemoryOps
13
+ from .models import ChatMessage, MemoryOps
14
14
 
15
15
 
16
16
  class MemoryStore:
@@ -59,6 +59,53 @@ class MemoryStore:
59
59
  async with await anyio.open_file(path, "w", encoding="utf-8") as f:
60
60
  await f.write(json.dumps({"facts": facts}, ensure_ascii=False, indent=2))
61
61
 
62
+ # ========== 最近的聊天记录(重启后仍可读) ==========
63
+
64
+ async def load_recent(self) -> list[ChatMessage]:
65
+ """读回落盘的聊天记录;文件缺失/损坏时返回空列表"""
66
+ path = self._group_dir / "recent.json"
67
+ if not path.exists():
68
+ return []
69
+ try:
70
+ async with await anyio.open_file(path, encoding="utf-8") as f:
71
+ raw = json.loads(await f.read()).get("messages", [])
72
+ except Exception as e:
73
+ logger.error(f"加载聊天记录失败: {e}")
74
+ return []
75
+ messages = []
76
+ for item in raw if isinstance(raw, list) else []:
77
+ try:
78
+ messages.append(ChatMessage(
79
+ time=datetime.fromisoformat(item["time"]),
80
+ user_name=item.get("user_name", ""),
81
+ content=item.get("content", ""),
82
+ user_id=str(item.get("user_id", "")),
83
+ is_at_only=bool(item.get("is_at_only", False)),
84
+ ))
85
+ except Exception:
86
+ continue # 单条坏数据丢弃,不影响其余记录
87
+ return messages
88
+
89
+ async def save_recent(self, messages: list[ChatMessage]):
90
+ path = self._group_dir / "recent.json"
91
+ data = {"messages": [
92
+ {
93
+ "time": m.time.isoformat(), "user_name": m.user_name, "content": m.content,
94
+ "user_id": m.user_id, "is_at_only": m.is_at_only,
95
+ }
96
+ for m in messages
97
+ ]}
98
+ async with await anyio.open_file(path, "w", encoding="utf-8") as f:
99
+ await f.write(json.dumps(data, ensure_ascii=False, indent=2))
100
+
101
+ async def clear_recent(self):
102
+ path = self._group_dir / "recent.json"
103
+ try:
104
+ if path.exists():
105
+ path.unlink()
106
+ except Exception as e:
107
+ logger.error(f"清除聊天记录失败: {e}")
108
+
62
109
  # ========== 群友信息 ==========
63
110
 
64
111
  async def load_friend(self, user_id: str) -> dict | None:
@@ -146,6 +146,9 @@ def _parts_key(parts: list[dict] | None) -> str:
146
146
  # 调用台账注入 prompt 时最多展示的条数(比对的台账条数由 aigfm_invoke_dedup_records 控制)
147
147
  _LEDGER_DISPLAY = 10
148
148
 
149
+ # 「最近的聊天记录」在内存与落盘文件中保留的条数上限(注入 prompt 的条数另由 aigfm_recent_messages 控制)
150
+ _RECENT_KEEP = 50
151
+
149
152
 
150
153
  class MessageProcessor:
151
154
  """消息处理编排器,协调所有组件"""
@@ -186,6 +189,8 @@ class MessageProcessor:
186
189
  self.bot_role = "一个友好的群聊助手"
187
190
  self.current_preset = ""
188
191
  self.recent_messages: list[ChatMessage] = []
192
+ # 「最近的聊天记录」在首次处理时从 memory/{会话}/recent.json 读回(重启后不丢历史)
193
+ self._recent_loaded = False
189
194
  # 代为执行过的命令台账:既注入 prompt 让模型「看得见自己刚调过什么」,
190
195
  # 也作为「最近调用过相同命令」去重的比对依据(见 _find_recent_same)
191
196
  self._invoke_log: list[_Invocation] = []
@@ -325,10 +330,22 @@ class MessageProcessor:
325
330
  for msg in messages:
326
331
  logger.debug(f"[处理] 消息: [{msg.user_name}] {msg.content[:80]}")
327
332
 
328
- # 更新最近消息
333
+ # 更新最近消息(首次处理时先读回上次落盘的记录,这样重启后 LLM 仍能看到历史)
334
+ if not self._recent_loaded:
335
+ self._recent_loaded = True
336
+ try:
337
+ restored = await self.memory.load_recent()
338
+ except Exception as e:
339
+ logger.error(f"[记忆] 加载聊天记录失败: {e}")
340
+ restored = []
341
+ if restored:
342
+ self.recent_messages = restored + self.recent_messages
343
+ logger.info(f"[记忆] {self.session_key} 恢复聊天记录 {len(restored)} 条")
329
344
  self.recent_messages.extend(messages)
330
- if len(self.recent_messages) > 50:
331
- self.recent_messages = self.recent_messages[-50:]
345
+ if len(self.recent_messages) > _RECENT_KEEP:
346
+ self.recent_messages = self.recent_messages[-_RECENT_KEEP:]
347
+ # 先落盘一次:后面任何提前 return(没回复/解析失败)都不影响历史的保存
348
+ await self._persist_recent()
332
349
 
333
350
  # 加载记忆
334
351
  short_term = await self.memory.load_short_term()
@@ -512,6 +529,7 @@ class MessageProcessor:
512
529
 
513
530
  # 更新最近消息
514
531
  self._record_bot_reply(reply_segments)
532
+ await self._persist_recent()
515
533
 
516
534
  # 消耗社交能量
517
535
  text_length = sum(len(s.content) for s in reply_segments if s.type == "text")
@@ -783,6 +801,19 @@ class MessageProcessor:
783
801
  # 索引常驻后 LLM 会引用历史 id,被上限淘汰或不存在时在此暴露,避免静默失败无从排查
784
802
  logger.warning(f"[表情包收藏] 跳过: id={cache_id} 不在缓存索引中(已淘汰或不存在)")
785
803
 
804
+ async def _persist_recent(self):
805
+ """把「最近的聊天记录」落盘,让重启后的会话仍带着上下文(条数与内存里一致)
806
+
807
+ 落盘前后都按 `_RECENT_KEEP` 截断:批次内还会追加机器人的回复/自述等,
808
+ 不在这里截一次的话内存与文件都会慢慢超过上限。
809
+ """
810
+ if len(self.recent_messages) > _RECENT_KEEP:
811
+ self.recent_messages = self.recent_messages[-_RECENT_KEEP:]
812
+ try:
813
+ await self.memory.save_recent(self.recent_messages)
814
+ except Exception as e:
815
+ logger.error(f"[记忆] 保存聊天记录失败: {e}")
816
+
786
817
  def _record_bot_reply(self, segments: list[ReplySegment]):
787
818
  for seg in segments:
788
819
  if seg.type == "text":
@@ -112,7 +112,62 @@ class OpenWebSearchClient(SearchClient):
112
112
  return []
113
113
 
114
114
 
115
- def create_search_client(api: str, api_key: str, base_url: str = "") -> SearchClient | None:
115
+ class DeepSeekSearchClient(SearchClient):
116
+ """DeepSeek 联网搜索(POST {base}/v1/tools/search)
117
+
118
+ 文档:https://api-docs.deepseek.com/zh-cn/api/search
119
+ - 按**成功请求次数**计费(不按 token、不按条数),`results` 为空数组不算成功、不计费
120
+ - `summary` 是给模型看的长摘要,`snippet` 是短摘要;`content` 只有传 `need_content`
121
+ 才会带网页正文(默认不带,省掉额外抓取)
122
+ - QPS 100;401 = Key 无效,402 = 余额不足
123
+ """
124
+
125
+ def __init__(self, api_key: str, base_url: str = ""):
126
+ self._api_key = api_key
127
+ self._base_url = (base_url or "https://api.deepseek.com").rstrip("/")
128
+
129
+ async def search(self, query: str, max_results: int = 3) -> list[dict]:
130
+ try:
131
+ headers = {"Authorization": f"Bearer {self._api_key}", "Content-Type": "application/json"}
132
+ # count 取值范围 1-50,越界会被判 400
133
+ payload = {"query": query, "count": max(1, min(int(max_results or 1), 50))}
134
+ async with httpx.AsyncClient(timeout=15.0) as client:
135
+ resp = await client.post(f"{self._base_url}/v1/tools/search", headers=headers, json=payload)
136
+ if resp.status_code != 200:
137
+ self._log_failure(resp)
138
+ return []
139
+ data = resp.json()
140
+ results = []
141
+ for r in data.get("results", []):
142
+ content = (r.get("summary") or r.get("snippet") or "").strip()
143
+ results.append({"title": r.get("title", ""), "content": content, "url": r.get("url", "")})
144
+ return results
145
+ except Exception as e:
146
+ logger.error(f"[搜索] DeepSeek 失败: {e}")
147
+ return []
148
+
149
+ def _log_failure(self, resp):
150
+ """DeepSeek 的错误体是 OpenAI 兼容格式,挑出人能看懂的说明"""
151
+ try:
152
+ err = (resp.json() or {}).get("error", {}) or {}
153
+ msg = err.get("message") or ""
154
+ except Exception:
155
+ msg = ""
156
+ hint = {
157
+ 400: "请求参数不合法",
158
+ 401: "API Key 无效",
159
+ 402: "余额不足",
160
+ 429: "触发 QPS 限制",
161
+ 500: "服务端错误",
162
+ 504: "下游超时",
163
+ }.get(resp.status_code, "")
164
+ logger.error(f"[搜索] DeepSeek 失败: HTTP {resp.status_code}"
165
+ + (f"({hint})" if hint else "")
166
+ + (f" {msg}" if msg else ""))
167
+
168
+
169
+ def create_search_client(api: str, api_key: str, base_url: str = "",
170
+ api_base_url: str = "") -> SearchClient | None:
116
171
  api = api.lower()
117
172
  if api == "tavily":
118
173
  return TavilySearchClient(api_key) if api_key else None
@@ -122,4 +177,6 @@ def create_search_client(api: str, api_key: str, base_url: str = "") -> SearchCl
122
177
  return BingSearchClient(api_key) if api_key else None
123
178
  elif api == "openwebsearch":
124
179
  return OpenWebSearchClient(base_url) if base_url else None
180
+ elif api == "deepseek":
181
+ return DeepSeekSearchClient(api_key, api_base_url) if api_key else None
125
182
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-aigf-master
3
- Version: 2.0.0b2
3
+ Version: 2.0.0b4
4
4
  Summary: 群聊特化LLM聊天机器人(增强版),具有记忆、表情包、跨插件调用、跨bot通信与跨适配器支持
5
5
  License-Expression: MIT
6
6
  Requires-Python: <4.0,>=3.10
@@ -55,7 +55,7 @@ Dynamic: license-file
55
55
  - 🧠 **LLM 命令学习**:通过观察群聊自动学习命令用法,支持学习、编辑、删除
56
56
  - 🌐 **跨 bot 通信**:通过 HTTP 获取其它 bot(同一 QQ 号下)的插件输出,并可远程调用其命令
57
57
  - 🖼️ **表情包功能**:AI 自主决定发表情包;自动从群聊中收藏
58
- - 🔍 **联网搜索**:支持 Tavily / Bocha / Bing,自动搜索不懂的梗和网络用语
58
+ - 🔍 **联网搜索**:支持 Tavily / Bocha / Bing / DeepSeek / 本地 open-websearch,自动搜索不懂的梗和网络用语
59
59
  - 🧠 **四层记忆系统**:短期记忆、长期记忆、群友信息、文化记忆,LLM 自主管理
60
60
  - 🎨 **AI 生图**:LLM可调用生图模型生成图片发到群里,尺寸由 LLM 决定(程序限最大尺寸),支持参考图改绘(回复图片消息或从聊天记录选图)
61
61
 
@@ -80,9 +80,9 @@ Dynamic: license-file
80
80
  >
81
81
  > 从 2.0 起插件还依赖 `nonebot-plugin-alconna` 与 `nonebot-plugin-uninfo`(跨适配器收发与会话/成员信息),用下面的方式安装时会自动装上。
82
82
 
83
- ### 安装 beta 版本(2.0.0b2)
83
+ ### 安装 beta 版本(2.0.0b4)
84
84
 
85
- `2.0.0b2` 是**预发布(pre-release)版本**,因此:
85
+ `2.0.0b4` 是**预发布(pre-release)版本**,因此:
86
86
 
87
87
  - `pip install nonebot-plugin-aigf-master` 装的仍是稳定版 **1.6.x**
88
88
  - `nb plugin install nonebot-plugin-aigf-master` 同样只装稳定版,而且 nb-cli **不支持指定版本**,所以 beta 只能用 pip 装
@@ -90,7 +90,7 @@ Dynamic: license-file
90
90
  要装 beta,**必须显式写出版本号**:
91
91
 
92
92
  ```bash
93
- pip install nonebot-plugin-aigf-master==2.0.0b2
93
+ pip install nonebot-plugin-aigf-master==2.0.0b4
94
94
  ```
95
95
 
96
96
  在 `pyproject.toml` 中添加:
@@ -115,8 +115,8 @@ plugins = ["nonebot-plugin-aigf-master"]
115
115
  AIGFM_LLM_API_KEY="sk-xxxxxxxxxxxx" # LLM API Key
116
116
  AIGFM_LLM_BASE_URL="https://api.deepseek.com" # LLM API 地址
117
117
  AIGFM_LLM_MODEL="deepseek-v4-flash" # LLM 模型名称
118
- AIGFM_ENABLED_GROUPS=[123456, 789012] # 启用的群/频道会话(可写 `适配器:会话id`,只写数字按 OneBot V11 识别)
119
- AIGFM_ENABLED_PRIVATE=[] # 启用的私聊会话(同上写法;留空则私聊不响应)
118
+ AIGFM_ENABLED_GROUPS=["onebot11:123456789", "console:console-chat"] # 启用的群/频道会话(`适配器:会话id`;也可只写数字,按 OneBot V11 识别)
119
+ AIGFM_ENABLED_PRIVATE=["onebot11:20001"] # 启用的私聊会话(同上写法;留空则私聊不响应)
120
120
 
121
121
  # --- 图片理解(VLM) ---
122
122
  AIGFM_IMAGE_MODE="vlm" # 图片模式: vlm / llm(默认 vlm)
@@ -158,10 +158,11 @@ AIGFM_ENERGY_BASELINE=0.7 # 社交能量基线(默认 0.7,范
158
158
 
159
159
  # --- 联网搜索 ---
160
160
  AIGFM_SEARCH_ENABLED=false # 是否启用联网搜索(默认 false)
161
- AIGFM_SEARCH_API=tavily # 搜索API: tavily / bocha / bing / openwebsearch(默认 tavily)
162
- AIGFM_SEARCH_API_KEY="xxx" # 搜索 API Key(api=tavily/bocha/bing 时必填)
161
+ AIGFM_SEARCH_API=tavily # 搜索API: tavily / bocha / bing / openwebsearch / deepseek(默认 tavily)
162
+ AIGFM_SEARCH_API_KEY="xxx" # 搜索 API Key(api=tavily/bocha/bing 时必填;deepseek 留空则沿用 LLM Key)
163
163
  AIGFM_SEARCH_MAX_RESULTS=3 # 最大搜索结果数(默认 3)
164
164
  AIGFM_OPENWEBSEARCH_URL="" # open-websearch 本地服务地址(api=openwebsearch 时必填,如 http://127.0.0.1:3210)
165
+ AIGFM_SEARCH_BASE_URL="" # 搜索 API 根地址(api=deepseek 时用,留空则用官方 https://api.deepseek.com)
165
166
 
166
167
  # --- 代理 ---
167
168
  AIGFM_PROXY_ENABLED=false # 是否启用代理(默认 false)
@@ -234,7 +235,7 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
234
235
  - 纯 @ 消息或检测到同一用户连续发送时,以及调用的插件返回图片时,等待时间会用 `AIGFM_INCOMPLETE_TIMEOUT`(默认 40 秒)**替代**默认的 30 秒(非叠加)
235
236
  - **有图片正在解析时整批推迟触发**:等到 VLM 描述填好后随**同一批**发给 LLM,不会把没有描述的空消息发出去;"图片进入解析"这一特例最长等待 `AIGFM_BATCH_TIMEOUT + AIGFM_INCOMPLETE_TIMEOUT`(默认 70 秒)后仍会放行,此时该图片以 `[图片解析中]` 进入上下文
236
237
  - 消息合并窗口内的同一用户连续消息会被合并为一条
237
- - 每次处理时,LLM 收到最近 **20 条**(可配置)历史聊天记录 + 四层记忆 + 预设 + 表情包列表 + 其它插件响应;本批消息只出现在「新消息」段,同一条插件响应也只注入一次(不会在历史与新消息里重复)
238
+ - 每次处理时,LLM 收到最近 **20 条**(可配置)历史聊天记录 + 四层记忆 + 预设 + 表情包列表 + 其它插件响应;本批消息只出现在「新消息」段,同一条插件响应也只注入一次(不会在历史与新消息里重复)。历史聊天记录会落盘(`memory/<群号>/recent.json`),**机器人重启后仍能读回**
238
239
  - LLM 一次调用同时完成:回复决策 + 记忆管理 + 表情包选择 + 命令学习
239
240
 
240
241
  ## 🧩 多适配器支持
@@ -243,9 +244,9 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
243
244
 
244
245
  ### 会话与启用清单
245
246
 
246
- - 会话以 **`适配器:会话id`** 为键,例如 `onebot11:617770183`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
247
+ - 会话以 **`适配器:会话id`** 为键,例如 `onebot11:123456789`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
247
248
  - `AIGFM_ENABLED_GROUPS` 管群/频道会话、`AIGFM_ENABLED_PRIVATE` 管私聊会话;条目可写带前缀的完整键,也可以只写 id(**按 OneBot V11 识别**,旧配置无需改动)
248
- - 落盘目录名不能含 `:`,因此磁盘上写作 `onebot11_617770183`;`memory/` 下旧版裸数字目录会在启动时**一次性自动迁移**(目标已存在则跳过,幂等)
249
+ - 落盘目录名不能含 `:`,因此磁盘上写作 `onebot11_123456789`;`memory/` 下旧版裸数字目录会在启动时**一次性自动迁移**(目标已存在则跳过,幂等)
249
250
  - 非启用会话不发请求、不入缓冲:插件只会为启用的会话解析成员信息与图片
250
251
 
251
252
  ### 功能 × 适配器支持表(2026-09-25 实测)
@@ -274,7 +275,11 @@ AIGFM_PEER_CAPTURE_PLUGINS=[] # 要捕获输出的插件名列表,为
274
275
  | YunHu | ✅ | ✅ | ✅ | 未测 | ⚠️ | ✅ | ✅ | ✅ | ✅ |
275
276
  | bilibili Live | ✅ | ✅ | ✅ | ❌ 无好友私聊类型 | ⚠️ | ✅ | ✅ | ⚠️ | ✅ |
276
277
 
277
- 图例:**✅ 已实测通过** | **⚠️ 已实现但未验证通过**(离线构造不出该适配器的事件,或需要真实 API / 真机)| **❌ 该适配器没有这类事件,或库层无法把该通知定位到群/频道会话(通知会被跳过)** | **未测** 本次未构造该场景。
278
+ **图例:**
279
+ - **✅ 已实测通过**
280
+ - **⚠️ 已实现但未验证通过**(离线构造不出该适配器的事件,或需要真实 API / 真机)
281
+ - **❌ 该适配器没有这类事件,或库层无法把该通知定位到群/频道会话(通知会被跳过)**
282
+ - **未测** 本次未构造该场景。
278
283
 
279
284
  **已知特例**
280
285
 
@@ -559,6 +564,7 @@ LLM 可以:
559
564
  | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 | https://open.bochaai.com |
560
565
  | `bing` | 微软必应搜索(Azure Bing Search API v7) | 无 | https://portal.azure.com |
561
566
  | `openwebsearch` | 本地 Node.js 搜索服务,聚合多引擎(bing/baidu/duckduckgo 等) | 需先启动 daemon:`cd open-webSearch && npm install && npm run serve`,配置 `AIGFM_OPENWEBSEARCH_URL` | 无需 Key,项目:https://github.com/Aas-ee/open-webSearch |
567
+ | `deepseek` | DeepSeek 自带联网搜索,摘要写得很适合喂给模型 | 无(`AIGFM_SEARCH_API_KEY` 留空则沿用 LLM Key) | https://platform.deepseek.com |
562
568
 
563
569
  > **Tavily** 需额外安装依赖:`pip install tavily-python`(或安装本插件时带上 extra:`pip install "nonebot-plugin-aigf-master[tavily]"`)
564
570
 
@@ -590,6 +596,10 @@ LLM 可以:
590
596
 
591
597
  存储在 `<插件数据目录>/memory/<群号>/culture.json`,记录梗、网络用语、流行语。LLM 主动学习和存储,根据聊天内容自动匹配。
592
598
 
599
+ ### 最近聊天记录
600
+
601
+ 上面四层记忆之外的**滚动窗口**:最近 50 条消息(含机器人自己的回复与插件响应)会落盘在 `<插件数据目录>/memory/<群号>/recent.json`,**机器人重启后会读回**,因此刚重启的会话也能接上之前的上下文(注入 prompt 的条数由 `AIGFM_RECENT_MESSAGES` 控制,默认 20)。`/reset` 会连这份记录一起清空。
602
+
593
603
  ## 🖼️ 表情包功能
594
604
 
595
605
  ### 工作原理
@@ -766,6 +776,7 @@ LLM 支持以下回复类型:
766
776
  short_term.json # 短期记忆
767
777
  long_term.json # 长期记忆
768
778
  culture.json # 文化记忆
779
+ recent.json # 最近的聊天记录(重启后仍可读)
769
780
  friends/
770
781
  {user_id}.json # 群友信息
771
782
  memes/
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nonebot-plugin-aigf-master"
7
- version = "2.0.0b2"
7
+ version = "2.0.0b4"
8
8
  description = "群聊特化LLM聊天机器人(增强版),具有记忆、表情包、跨插件调用、跨bot通信与跨适配器支持"
9
9
  readme = "README.md"
10
10
  license = "MIT"