nonebot-plugin-aigf-master 0.1.2__tar.gz → 0.1.4__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 (29) hide show
  1. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/PKG-INFO +9 -7
  2. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/README.md +8 -6
  3. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/llm_client.py +3 -0
  4. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/processor.py +4 -1
  5. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/prompt_builder.py +25 -4
  6. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/response_parser.py +50 -1
  7. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master.egg-info/PKG-INFO +9 -7
  8. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/pyproject.toml +1 -1
  9. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/LICENSE +0 -0
  10. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/__init__.py +0 -0
  11. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/api_hooks.py +0 -0
  12. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/command_learner.py +0 -0
  13. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/config.py +0 -0
  14. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/context_bus.py +0 -0
  15. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/image_handler.py +0 -0
  16. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/meme_store.py +0 -0
  17. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/memory_store.py +0 -0
  18. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/models.py +0 -0
  19. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/peer_client.py +0 -0
  20. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/plugin_discovery.py +0 -0
  21. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/plugin_invoker.py +0 -0
  22. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/preset_store.py +0 -0
  23. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/search_client.py +0 -0
  24. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master/vlm_client.py +0 -0
  25. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master.egg-info/SOURCES.txt +0 -0
  26. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master.egg-info/dependency_links.txt +0 -0
  27. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master.egg-info/requires.txt +0 -0
  28. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/nonebot_plugin_aigf_master.egg-info/top_level.txt +0 -0
  29. {nonebot_plugin_aigf_master-0.1.2 → nonebot_plugin_aigf_master-0.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-aigf-master
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: 群聊特化LLM聊天机器人(增强版),具有记忆、表情包、跨插件调用和跨bot通信能力
5
5
  License-Expression: MIT
6
6
  Requires-Python: <4.0,>=3.10
@@ -329,12 +329,14 @@ LLM 可以:
329
329
 
330
330
  ### 搜索 API
331
331
 
332
- | API | 说明 | 额外依赖 |
333
- |-----|------|---------|
334
- | `tavily`(默认) | 专为 AI 设计,返回格式友好 | `tavily-python` |
335
- | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 |
336
- | `bing` | 微软必应搜索 | 无 |
337
- | `openwebsearch` | 本地 Node.js 搜索服务,聚合多引擎(bing/baidu/duckduckgo 等) | 需先启动 daemon:`cd open-webSearch && npm install && npm run serve`,配置 `AIGFM_OPENWEBSEARCH_URL` |
332
+ | API | 说明 | 额外依赖 | 获取 Key / 部署 |
333
+ |-----|------|---------|----------------|
334
+ | `tavily`(默认) | 专为 AI 设计,返回格式友好 | `tavily-python` | https://app.tavily.com |
335
+ | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 | https://open.bochaai.com |
336
+ | `bing` | 微软必应搜索(Azure Bing Search API v7) | 无 | https://portal.azure.com |
337
+ | `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 |
338
+
339
+ > **Tavily** 需额外安装依赖:`pip install tavily-python`(或安装本插件时带上 extra:`pip install "nonebot-plugin-aigf-master[tavily]"`)
338
340
 
339
341
  ## 🧠 记忆系统
340
342
 
@@ -309,12 +309,14 @@ LLM 可以:
309
309
 
310
310
  ### 搜索 API
311
311
 
312
- | API | 说明 | 额外依赖 |
313
- |-----|------|---------|
314
- | `tavily`(默认) | 专为 AI 设计,返回格式友好 | `tavily-python` |
315
- | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 |
316
- | `bing` | 微软必应搜索 | 无 |
317
- | `openwebsearch` | 本地 Node.js 搜索服务,聚合多引擎(bing/baidu/duckduckgo 等) | 需先启动 daemon:`cd open-webSearch && npm install && npm run serve`,配置 `AIGFM_OPENWEBSEARCH_URL` |
312
+ | API | 说明 | 额外依赖 | 获取 Key / 部署 |
313
+ |-----|------|---------|----------------|
314
+ | `tavily`(默认) | 专为 AI 设计,返回格式友好 | `tavily-python` | https://app.tavily.com |
315
+ | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 | https://open.bochaai.com |
316
+ | `bing` | 微软必应搜索(Azure Bing Search API v7) | 无 | https://portal.azure.com |
317
+ | `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 |
318
+
319
+ > **Tavily** 需额外安装依赖:`pip install tavily-python`(或安装本插件时带上 extra:`pip install "nonebot-plugin-aigf-master[tavily]"`)
318
320
 
319
321
  ## 🧠 记忆系统
320
322
 
@@ -52,6 +52,7 @@ class LLMClient:
52
52
  async def chat_with_tools(
53
53
  self, prompt: str, model: str,
54
54
  tools: list[dict], tool_handler,
55
+ json_mode: bool = False,
55
56
  ) -> tuple[str | None, bool]:
56
57
  """支持 function calling 的响应生成"""
57
58
  messages = [{"role": "user", "content": prompt}]
@@ -81,6 +82,8 @@ class LLMClient:
81
82
  messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result})
82
83
 
83
84
  kwargs: dict = {"messages": messages, "model": model, "temperature": 0.5, "timeout": 300}
85
+ if json_mode:
86
+ kwargs["response_format"] = {"type": "json_object"}
84
87
  response = await self._client.chat.completions.create(**kwargs)
85
88
  content = response.choices[0].message.content
86
89
  return (_strip_think_tags(content) if content else None), used_tool
@@ -373,7 +373,10 @@ class MessageProcessor:
373
373
  return result
374
374
  return f"未知工具: {name}"
375
375
 
376
- return await self.llm.chat_with_tools(prompt, self.config.aigfm_llm_model, tools, handler)
376
+ return await self.llm.chat_with_tools(
377
+ prompt, self.config.aigfm_llm_model, tools, handler,
378
+ json_mode=self.config.aigfm_llm_json_mode,
379
+ )
377
380
 
378
381
  async def _save_memes(self, save_meme: list, cached_stickers: list[dict]):
379
382
  current_ids = {s["id"] for s in cached_stickers}
@@ -108,13 +108,29 @@ def build_prompt(
108
108
  sticker_section = """
109
109
  ## 当前消息中出现的图片(可收藏)
110
110
  以下图片已通过视觉输入提供。请结合上下文判断是否值得收藏。
111
+ - 标记为"可能是表情包"的图片已附带情感和内容描述,是较可能的收藏候选
112
+ - 仅标记为"图片"的通常为普通图片,一般不需要收藏
113
+ 请结合上下文进一步判断:
114
+ - 如果是表情包(能表达一定的情感,适合在群聊中反复使用),且你的表情包库内没有类似的图片,且你觉得值得保存的,可以收藏。
115
+ - 如果只是群友分享的照片、截图、梗图、普通图片,或者表情包库内已有类似图片,则不要收藏。
116
+ - 若群友连续发了多张图片,通常是在分享普通图片
117
+ - 若群友只发了一张图片,而前后均没有与这张图片相关的内容,或是在图片前后仅有对该图片的介绍或评论,通常是在分享普通图片
118
+ - 若群友发的图片内容与之前的内容有关联,或这张图片是在其它群友发言之后发出的,且**包含较为明显的情感**(开心、愤怒、疑惑等),则可能是表情包
111
119
  如果值得收藏,在 memory.save_meme 中填入 id、简短描述和关键词。
112
120
  可用 id:""" + ", ".join(sticker_ids)
113
121
  else:
114
122
  sticker_section = """
115
123
  ## 当前消息中出现的图片(可收藏)
116
124
  消息中已标注初步判断结果。请结合上下文进一步判断是否值得收藏。
117
- 如果值得收藏,在 memory.save_meme 中填入 id 和简短描述。
125
+ - 标记为"可能是表情包"的图片已附带情感和内容描述,是较可能的收藏候选
126
+ - 仅标记为"图片"的通常为普通图片,一般不需要收藏
127
+ 请结合上下文进一步判断:
128
+ - 如果是表情包(能表达一定的情感,适合在群聊中反复使用),且你的表情包库内没有类似的图片,且你觉得值得保存的,可以收藏。
129
+ - 如果只是群友分享的照片、截图、梗图、普通图片,或者表情包库内已有类似图片,则不要收藏。
130
+ - 若群友连续发了多张图片,通常是在分享普通图片
131
+ - 若群友只发了一张图片,而前后均没有与这张图片相关的内容,或是在图片前后仅有对该图片的介绍或评论,通常是在分享普通图片
132
+ - 若群友发的图片内容与之前的内容有关联,或这张图片是在其它群友发言之后发出的,且**包含较为明显的情感**(开心、愤怒、疑惑等),则可能是表情包
133
+ 如果值得收藏,在 memory.save_meme 中填入 id 和你写的简短描述。
118
134
  可用 id:""" + ", ".join(sticker_ids)
119
135
 
120
136
  # 群友列表
@@ -129,7 +145,7 @@ def build_prompt(
129
145
  当用户要求使用以下**本机**功能,且聊天记录中**还没有**该命令的调用记录时,调用 invoke_plugin 工具来执行命令,不要自己编造回复:
130
146
  {cmd_list}
131
147
 
132
- 调用示例:用户说"今日小猪",且你此前未调用过,应调用 invoke_plugin(command="今日小猪")
148
+ 调用示例:用户说"帮我查一下今日小猪",且你此前未调用过,应调用 invoke_plugin(command="今日小猪")
133
149
 
134
150
  ## 调用身份
135
151
  调用 invoke_plugin 或 invoke_peer_plugin 时,可通过 user_id 参数指定以哪位群友的身份调用(从上方"相关群友信息/已知群友昵称"中选择 QQ 号)。
@@ -140,7 +156,7 @@ def build_prompt(
140
156
  调用 invoke_plugin / invoke_peer_plugin 后,插件的响应(文本或图片描述)会以 `[插件名] 内容` 或 `[bot名] 内容` 的形式作为**新消息**出现在后续聊天记录中。
141
157
  - 调用后,本次回复必须为空(`"reply": []`),**不要说话**,等插件响应出现
142
158
  - 当聊天记录中出现 `[插件名] 内容` 或 `[bot名] 内容` 后,再根据插件响应决定是否回复、回复什么,**不要编造**插件的内容
143
- - **不要重复调用**:如果聊天记录中已经出现 `[小助手] 已调用命令「xxx」`,说明该命令已调用过,**绝不要再调用**,直接根据已有内容判断是否回复
159
+ - **不要重复调用**:如果聊天记录中已经出现 `[你的名字] 已调用命令「xxx」`,说明该命令已调用过,**绝不要再调用**,直接根据已有内容判断是否回复
144
160
  - 如果插件响应一直未出现,再视情况告知用户插件未响应
145
161
 
146
162
  ## 重要:已知命令的处理
@@ -271,10 +287,15 @@ fields: info(一般信息), aliases(称呼), nickname(QQ昵称), past_nicknames(
271
287
  "friends": {{"123456": {{"add": [], "modify": [], "delete": [], "add_alias": [], "remove_alias": []}}}},
272
288
  "save_meme": [{{ "id": "cache_id", "description": "描述", "keywords": ["关键词"] }}],
273
289
  "culture": {{"add": [{{"term": "", "meaning": "", "context": ""}}], "modify": [], "delete": []}}
274
- }}
290
+ }},
291
+ "command_learning": [{{"name": "命令名", "parameters": "可选参数说明(无则留空)", "usage": "命令 [参数]", "hook_type": "current或peer", "source": "current填插件名/peer填bot名", "examples": ["用户发送的原文"]}}],
292
+ "command_edit": [{{"name": "命令名", "parameters": "更新后参数说明", "usage": "更新后用法", "examples": ["更新后示例"]}}],
293
+ "command_delete": ["命令名"]
275
294
  }}
276
295
  ```
277
296
 
297
+ > `command_learning` / `command_edit` / `command_delete` 是**可选**顶层字段:只在识别到新命令、需修正或删除已有命令时才输出,无相关操作时**整个字段省略**(`reply`、`memory` 各子项为空数组时也要保留外层结构)。
298
+
278
299
  ## 回复决策
279
300
  - 有人 @ 你 → 通常回复
280
301
  - 有人回复了你之前的消息 → 回复
@@ -6,12 +6,61 @@ import re
6
6
  from .models import MemoryOps, ReplySegment
7
7
 
8
8
 
9
+ def _extract_json_object(text: str) -> str | None:
10
+ """扫描每个 '{' 起点做花括号平衡(正确处理字符串内的 {} 与 \\" 转义),
11
+ 返回第一个能成功解析为 JSON 的对象子串;找不到返回 None。"""
12
+ n = len(text)
13
+ i = 0
14
+ while i < n:
15
+ if text[i] != "{":
16
+ i += 1
17
+ continue
18
+ depth = 0
19
+ in_str = False
20
+ esc = False
21
+ j = i
22
+ while j < n:
23
+ ch = text[j]
24
+ if in_str:
25
+ if esc:
26
+ esc = False
27
+ elif ch == "\\":
28
+ esc = True
29
+ elif ch == '"':
30
+ in_str = False
31
+ else:
32
+ if ch == '"':
33
+ in_str = True
34
+ elif ch == "{":
35
+ depth += 1
36
+ elif ch == "}":
37
+ depth -= 1
38
+ if depth == 0:
39
+ break
40
+ j += 1
41
+ if j < n:
42
+ candidate = text[i:j + 1]
43
+ try:
44
+ json.loads(candidate)
45
+ return candidate
46
+ except json.JSONDecodeError:
47
+ pass
48
+ i += 1
49
+ return None
50
+
51
+
9
52
  def parse_llm_response(raw: str) -> dict | None:
10
- """解析 LLM 返回的 JSON,去除 think 标签和代码块标记"""
53
+ """解析 LLM 返回的 JSON,去除 think 标签和代码块标记;容忍前导/尾随文字"""
11
54
  cleaned = re.sub(r"^```json\s*|\s*```$", "", raw.strip())
12
55
  try:
13
56
  return json.loads(cleaned)
14
57
  except json.JSONDecodeError:
58
+ candidate = _extract_json_object(cleaned)
59
+ if candidate:
60
+ try:
61
+ return json.loads(candidate)
62
+ except json.JSONDecodeError:
63
+ return None
15
64
  return None
16
65
 
17
66
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-aigf-master
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: 群聊特化LLM聊天机器人(增强版),具有记忆、表情包、跨插件调用和跨bot通信能力
5
5
  License-Expression: MIT
6
6
  Requires-Python: <4.0,>=3.10
@@ -329,12 +329,14 @@ LLM 可以:
329
329
 
330
330
  ### 搜索 API
331
331
 
332
- | API | 说明 | 额外依赖 |
333
- |-----|------|---------|
334
- | `tavily`(默认) | 专为 AI 设计,返回格式友好 | `tavily-python` |
335
- | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 |
336
- | `bing` | 微软必应搜索 | 无 |
337
- | `openwebsearch` | 本地 Node.js 搜索服务,聚合多引擎(bing/baidu/duckduckgo 等) | 需先启动 daemon:`cd open-webSearch && npm install && npm run serve`,配置 `AIGFM_OPENWEBSEARCH_URL` |
332
+ | API | 说明 | 额外依赖 | 获取 Key / 部署 |
333
+ |-----|------|---------|----------------|
334
+ | `tavily`(默认) | 专为 AI 设计,返回格式友好 | `tavily-python` | https://app.tavily.com |
335
+ | `bocha` | 国产 AI 搜索 API,中文搜索效果好 | 无 | https://open.bochaai.com |
336
+ | `bing` | 微软必应搜索(Azure Bing Search API v7) | 无 | https://portal.azure.com |
337
+ | `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 |
338
+
339
+ > **Tavily** 需额外安装依赖:`pip install tavily-python`(或安装本插件时带上 extra:`pip install "nonebot-plugin-aigf-master[tavily]"`)
338
340
 
339
341
  ## 🧠 记忆系统
340
342
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nonebot-plugin-aigf-master"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "群聊特化LLM聊天机器人(增强版),具有记忆、表情包、跨插件调用和跨bot通信能力"
9
9
  readme = "README.md"
10
10
  license = "MIT"