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.
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/PKG-INFO +24 -13
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/README.md +23 -12
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/__init__.py +7 -1
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/config.py +3 -2
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/memory_store.py +48 -1
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/processor.py +34 -3
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/search_client.py +58 -1
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master.egg-info/PKG-INFO +24 -13
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/pyproject.toml +1 -1
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/LICENSE +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/anime_recognizer.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/animetrace_client.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/api_hooks.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/cache_cleanup.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/command_learner.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/context_bus.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/image_gen_client.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/image_handler.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/llm_client.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/meme_store.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/models.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/notice.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/peer_client.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/plugin_discovery.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/plugin_invoker.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/preset_store.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/prompt_builder.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/response_parser.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/session.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/unimsg.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master/vlm_client.py +0 -0
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master.egg-info/SOURCES.txt +0 -0
- {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
- {nonebot_plugin_aigf_master-2.0.0b2 → nonebot_plugin_aigf_master-2.0.0b4}/nonebot_plugin_aigf_master.egg-info/requires.txt +0 -0
- {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
- {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.
|
|
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.
|
|
83
|
+
### 安装 beta 版本(2.0.0b4)
|
|
84
84
|
|
|
85
|
-
`2.0.
|
|
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.
|
|
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=[
|
|
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:
|
|
247
|
+
- 会话以 **`适配器:会话id`** 为键,例如 `onebot11:123456789`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
|
|
247
248
|
- `AIGFM_ENABLED_GROUPS` 管群/频道会话、`AIGFM_ENABLED_PRIVATE` 管私聊会话;条目可写带前缀的完整键,也可以只写 id(**按 OneBot V11 识别**,旧配置无需改动)
|
|
248
|
-
- 落盘目录名不能含 `:`,因此磁盘上写作 `
|
|
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
|
-
|
|
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.
|
|
61
|
+
### 安装 beta 版本(2.0.0b4)
|
|
62
62
|
|
|
63
|
-
`2.0.
|
|
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.
|
|
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=[
|
|
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:
|
|
225
|
+
- 会话以 **`适配器:会话id`** 为键,例如 `onebot11:123456789`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
|
|
225
226
|
- `AIGFM_ENABLED_GROUPS` 管群/频道会话、`AIGFM_ENABLED_PRIVATE` 管私聊会话;条目可写带前缀的完整键,也可以只写 id(**按 OneBot V11 识别**,旧配置无需改动)
|
|
226
|
-
- 落盘目录名不能含 `:`,因此磁盘上写作 `
|
|
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
|
-
|
|
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,
|
|
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) >
|
|
331
|
-
self.recent_messages = self.recent_messages[-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
83
|
+
### 安装 beta 版本(2.0.0b4)
|
|
84
84
|
|
|
85
|
-
`2.0.
|
|
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.
|
|
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=[
|
|
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:
|
|
247
|
+
- 会话以 **`适配器:会话id`** 为键,例如 `onebot11:123456789`(群)、`console:console-chat`(频道)、`onebot11:20001`(私聊)
|
|
247
248
|
- `AIGFM_ENABLED_GROUPS` 管群/频道会话、`AIGFM_ENABLED_PRIVATE` 管私聊会话;条目可写带前缀的完整键,也可以只写 id(**按 OneBot V11 识别**,旧配置无需改动)
|
|
248
|
-
- 落盘目录名不能含 `:`,因此磁盘上写作 `
|
|
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
|
-
|
|
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/
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|