From 19ff0026c07bbd68bac2464484e9169925074460 Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Fri, 16 May 2025 21:43:08 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8Ddeque=20?= =?UTF-8?q?mutated=20during=20iteration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nonebot_plugin_llmchat/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nonebot_plugin_llmchat/__init__.py b/nonebot_plugin_llmchat/__init__.py index 2fb78fb..c9dda84 100755 --- a/nonebot_plugin_llmchat/__init__.py +++ b/nonebot_plugin_llmchat/__init__.py @@ -313,8 +313,10 @@ async def process_messages(group_id: int): content: list[ChatCompletionContentPartParam] = [] # 将机器人错过的消息推送给LLM - for ev in state.past_events: - content.append({"type": "text", "text": format_message(ev)}) + past_events_snapshot = list(state.past_events) + for ev in past_events_snapshot: + text_content = ",".join([format_message(ev) for ev in past_events_snapshot]) + content.append({"type": "text", "text": text_content}) # 将消息中的图片转成 base64 if preset.support_image: From 89baec6abcc842af4f153f1d2a824db8c921a5aa Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Mon, 19 May 2025 14:17:25 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=93=98=20=E6=9B=B4=E6=96=B0=20READM?= =?UTF-8?q?E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2f41e31..ceef771 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ _✨ 支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插 pypi python +Ask DeepWiki From 5014d3014bb38d36df7851d091a74111b9ee837c Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Wed, 20 Aug 2025 11:40:54 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8Dmcp?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=8D=A1=E4=BD=8F=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E5=8D=A1=E6=AD=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nonebot_plugin_llmchat/__init__.py | 2 +- nonebot_plugin_llmchat/mcpclient.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/nonebot_plugin_llmchat/__init__.py b/nonebot_plugin_llmchat/__init__.py index c9dda84..d0d5857 100755 --- a/nonebot_plugin_llmchat/__init__.py +++ b/nonebot_plugin_llmchat/__init__.py @@ -380,7 +380,7 @@ async def process_messages(group_id: int): new_messages.append({ "role": "tool", "tool_call_id": tool_call.id, - "content": str(result.content) + "content": str(result) }) # 将工具调用的结果交给 LLM diff --git a/nonebot_plugin_llmchat/mcpclient.py b/nonebot_plugin_llmchat/mcpclient.py index 7031d34..9ed9fe0 100644 --- a/nonebot_plugin_llmchat/mcpclient.py +++ b/nonebot_plugin_llmchat/mcpclient.py @@ -1,4 +1,5 @@ from contextlib import AsyncExitStack +import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.sse import sse_client @@ -64,9 +65,15 @@ class MCPClient: server_name, real_tool_name = tool_name.split("___") logger.info(f"正在服务器[{server_name}]上调用工具[{real_tool_name}]") session = self.sessions[server_name] - response = await session.call_tool(real_tool_name, tool_args) + try: + response = await asyncio.wait_for( + session.call_tool(real_tool_name, tool_args), timeout=10 + ) + except asyncio.TimeoutError: + logger.error(f"调用工具[{real_tool_name}]超时") + return f"调用工具[{real_tool_name}]超时" logger.debug(f"工具[{real_tool_name}]调用完成,响应: {response}") - return response + return response.content def get_friendly_name(self, tool_name: str): server_name, real_tool_name = tool_name.split("___") From ea635fd147a381bb6cc814b5e8708b5e5837122d Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Wed, 20 Aug 2025 12:38:39 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E5=8F=91=E9=80=81=E6=B6=88=E6=81=AF=E7=BB=99?= =?UTF-8?q?llm=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nonebot_plugin_llmchat/__init__.py | 2 +- nonebot_plugin_llmchat/mcpclient.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/nonebot_plugin_llmchat/__init__.py b/nonebot_plugin_llmchat/__init__.py index d0d5857..0d0feae 100755 --- a/nonebot_plugin_llmchat/__init__.py +++ b/nonebot_plugin_llmchat/__init__.py @@ -315,7 +315,7 @@ async def process_messages(group_id: int): # 将机器人错过的消息推送给LLM past_events_snapshot = list(state.past_events) for ev in past_events_snapshot: - text_content = ",".join([format_message(ev) for ev in past_events_snapshot]) + text_content = format_message(ev) content.append({"type": "text", "text": text_content}) # 将消息中的图片转成 base64 diff --git a/nonebot_plugin_llmchat/mcpclient.py b/nonebot_plugin_llmchat/mcpclient.py index 9ed9fe0..a6c6ff3 100644 --- a/nonebot_plugin_llmchat/mcpclient.py +++ b/nonebot_plugin_llmchat/mcpclient.py @@ -1,5 +1,5 @@ -from contextlib import AsyncExitStack import asyncio +from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.sse import sse_client @@ -66,9 +66,7 @@ class MCPClient: logger.info(f"正在服务器[{server_name}]上调用工具[{real_tool_name}]") session = self.sessions[server_name] try: - response = await asyncio.wait_for( - session.call_tool(real_tool_name, tool_args), timeout=10 - ) + response = await asyncio.wait_for(session.call_tool(real_tool_name, tool_args), timeout=10) except asyncio.TimeoutError: logger.error(f"调用工具[{real_tool_name}]超时") return f"调用工具[{real_tool_name}]超时" From 53d57beba3f4e30df393cd052acfebb789376175 Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Wed, 20 Aug 2025 12:48:13 +0800 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=94=96=20bump=20llmchat=20version?= =?UTF-8?q?=200.2.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 02c7bcf..61e73fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nonebot-plugin-llmchat" -version = "0.2.3" +version = "0.2.4" description = "Nonebot AI group chat plugin supporting multiple API preset configurations" license = "GPL" authors = ["FuQuan i@fuquan.moe"] From 9f81a38d5b27d5f8693d918a916dc400b87f4a2a Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Mon, 1 Sep 2025 10:45:18 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=90=9B=20=E5=B0=86mcp=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E5=BB=B6=E9=95=BF=E5=88=B030=E7=A7=92=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=89=A7=E8=A1=8C=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nonebot_plugin_llmchat/mcpclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nonebot_plugin_llmchat/mcpclient.py b/nonebot_plugin_llmchat/mcpclient.py index a6c6ff3..55e1b44 100644 --- a/nonebot_plugin_llmchat/mcpclient.py +++ b/nonebot_plugin_llmchat/mcpclient.py @@ -66,7 +66,7 @@ class MCPClient: logger.info(f"正在服务器[{server_name}]上调用工具[{real_tool_name}]") session = self.sessions[server_name] try: - response = await asyncio.wait_for(session.call_tool(real_tool_name, tool_args), timeout=10) + response = await asyncio.wait_for(session.call_tool(real_tool_name, tool_args), timeout=30) except asyncio.TimeoutError: logger.error(f"调用工具[{real_tool_name}]超时") return f"调用工具[{real_tool_name}]超时" From 1600cba1720f186c7b2098dd1b6014e5425bc560 Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Mon, 1 Sep 2025 10:51:30 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=E2=9C=A8=20=E6=94=AF=E6=8C=81=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E7=89=B9=E5=AE=9A=E5=89=8D=E7=BC=80=E7=9A=84=E6=B6=88?= =?UTF-8?q?=E6=81=AF=20#21?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + nonebot_plugin_llmchat/__init__.py | 6 ++++++ nonebot_plugin_llmchat/config.py | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/README.md b/README.md index ceef771..356effc 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ _✨ 支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插 | LLMCHAT__RANDOM_TRIGGER_PROB | 否 | 0.05 | 默认随机触发概率 [0, 1] | | LLMCHAT__DEFAULT_PROMPT | 否 | 你的回答应该尽量简洁、幽默、可以使用一些语气词、颜文字。你应该拒绝回答任何政治相关的问题。 | 默认提示词 | | LLMCHAT__BLACKLIST_USER_IDS | 否 | [] | 黑名单用户ID列表,机器人将不会处理黑名单用户的消息 | +| LLMCHAT__IGNORE_PREFIXES | 否 | [] | 需要忽略的消息前缀列表,匹配到这些前缀的消息不会处理 | | LLMCHAT__MCP_SERVERS | 否 | {} | MCP服务器配置,具体见下表 | 其中LLMCHAT__API_PRESETS为一个列表,每项配置有以下的配置项 diff --git a/nonebot_plugin_llmchat/__init__.py b/nonebot_plugin_llmchat/__init__.py index 0d0feae..d3c6605 100755 --- a/nonebot_plugin_llmchat/__init__.py +++ b/nonebot_plugin_llmchat/__init__.py @@ -169,6 +169,12 @@ async def is_triggered(event: GroupMessageEvent) -> bool: if event.user_id in plugin_config.blacklist_user_ids: return False + # 忽略特定前缀的消息 + msg_text = event.get_plaintext().strip() + for prefix in plugin_config.ignore_prefixes: + if msg_text.startswith(prefix): + return False + state.past_events.append(event) # 原有@触发条件 diff --git a/nonebot_plugin_llmchat/config.py b/nonebot_plugin_llmchat/config.py index fa873d5..d658875 100755 --- a/nonebot_plugin_llmchat/config.py +++ b/nonebot_plugin_llmchat/config.py @@ -44,6 +44,10 @@ class ScopedConfig(BaseModel): ) mcp_servers: dict[str, MCPServerConfig] = Field({}, description="MCP服务器配置") blacklist_user_ids: set[int] = Field(set(), description="黑名单用户ID列表") + ignore_prefixes: list[str] = Field( + default_factory=list, + description="需要忽略的消息前缀列表,匹配到这些前缀的消息不会处理" + ) class Config(BaseModel): From d640f16abee05ee82eac8046273c0653d77e0b49 Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Mon, 1 Sep 2025 10:56:31 +0800 Subject: [PATCH 08/12] =?UTF-8?q?=F0=9F=94=96=20bump=20llmchat=20version?= =?UTF-8?q?=200.2.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 61e73fa..7c17df2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nonebot-plugin-llmchat" -version = "0.2.4" +version = "0.2.5" description = "Nonebot AI group chat plugin supporting multiple API preset configurations" license = "GPL" authors = ["FuQuan i@fuquan.moe"] From efb25f0727387f7ab87f98ddcb6363f8d06e3467 Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Mon, 8 Sep 2025 16:09:09 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=E2=9C=A8=20=E6=94=AF=E6=8C=81=20Gemini?= =?UTF-8?q?=202.5=20Flash=20Image=20Preview=20(Nano=20Banana)=20=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=9B=9E=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nonebot_plugin_llmchat/__init__.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/nonebot_plugin_llmchat/__init__.py b/nonebot_plugin_llmchat/__init__.py index d3c6605..6aab0bc 100755 --- a/nonebot_plugin_llmchat/__init__.py +++ b/nonebot_plugin_llmchat/__init__.py @@ -21,7 +21,7 @@ from nonebot import ( on_message, require, ) -from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message +from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageSegment from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER from nonebot.params import CommandArg from nonebot.permission import SUPERUSER @@ -407,10 +407,18 @@ async def process_messages(group_id: int): or matched_reasoning_content ) - new_messages.append({ + llm_reply: ChatCompletionMessageParam = { "role": "assistant", "content": reply, - }) + } + + reply_images = getattr(response.choices[0].message, "images", None) + + if reply_images: + # openai的sdk里的assistant消息暂时没有images字段,需要单独处理 + llm_reply["images"] = reply_images # pyright: ignore[reportGeneralTypeIssues] + + new_messages.append(llm_reply) # 请求成功后再保存历史记录,保证user和assistant穿插,防止R1模型报错 for message in new_messages: @@ -432,6 +440,14 @@ async def process_messages(group_id: int): assert reply is not None await send_split_messages(handler, reply) + if reply_images: + logger.debug(f"API响应 图片数:{len(reply_images)}") + for i, image in enumerate(reply_images, start=1): + logger.debug(f"正在发送第{i}张图片") + image_base64 = image["image_url"]["url"].removeprefix("data:image/png;base64,") + image_msg = MessageSegment.image(base64.b64decode(image_base64)) + await handler.send(image_msg) + except Exception as e: logger.opt(exception=e).error(f"API请求失败 群号:{group_id}") await handler.send(Message(f"服务暂时不可用,请稍后再试\n{e!s}")) From 14fbe3cb3caceb19e87d843218a2567a816718e7 Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Mon, 8 Sep 2025 16:16:34 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=93=98=20=E6=9B=B4=E6=96=B0=20READM?= =?UTF-8?q?E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 356effc..198d121 100644 --- a/README.md +++ b/README.md @@ -24,32 +24,36 @@ _✨ 支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插 ## 📖 介绍 +1. **支持LLM回复图片** + - 支持最新 Gemini 2.5 Flash Image (Nano Banana) 的图片回复 + - 支持图片上下文修改 + 1. **支持MCP协议** - 可以连接各种支持MCP协议的LLM工具 - 通过连接一些搜索MCP服务器可以实现在线搜索 - 兼容 Claude.app 的配置格式 -2. **多API预设支持** +1. **多API预设支持** - 可配置多个LLM服务预设(如不同模型/API密钥) - 支持运行时通过`API预设`命令热切换API配置 - 内置服务开关功能(预设名为`off`时停用) -3. **多种回复触发方式** +1. **多种回复触发方式** - @触发 + 随机概率触发 - 支持处理回复消息 - 群聊消息顺序处理,防止消息错乱 -4. **分群聊上下文记忆管理** +1. **分群聊上下文记忆管理** - 分群聊保留对话历史记录(可配置保留条数) - 自动合并未处理消息,降低API用量 - 支持`记忆清除`命令手动重置对话上下文 -5. **分段回复支持** +1. **分段回复支持** - 支持多段式回复(由LLM决定如何回复) - 可@群成员(由LLM插入) - 可选输出AI的思维过程(需模型支持) -6. **可自定义性格** +1. **可自定义性格** - 可动态修改群组专属系统提示词(`/修改设定`) - 支持自定义默认提示词 From 909026bf6b97c845e10ca6e99c77855f603bfb38 Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Tue, 9 Sep 2025 10:03:24 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=E2=9C=A8=20=E6=9B=B4=E6=96=B0metadata?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=AF=B9Nano=20Banana=EF=BC=88?= =?UTF-8?q?=E7=94=9F=E5=9B=BE=E6=A8=A1=E5=9E=8B=EF=BC=89=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nonebot_plugin_llmchat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nonebot_plugin_llmchat/__init__.py b/nonebot_plugin_llmchat/__init__.py index 6aab0bc..e89e485 100755 --- a/nonebot_plugin_llmchat/__init__.py +++ b/nonebot_plugin_llmchat/__init__.py @@ -46,7 +46,7 @@ if TYPE_CHECKING: __plugin_meta__ = PluginMetadata( name="llmchat", - description="支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插件", + description="支持多API预设、MCP协议、联网搜索、视觉模型、Nano Banana(生图模型)的AI群聊插件", usage="""@机器人 + 消息 开启对话""", type="application", homepage="https://github.com/FuQuan233/nonebot-plugin-llmchat", From 4bfcaf3a61541101ec60d15c79998b9852e410fe Mon Sep 17 00:00:00 2001 From: FuQuan233 Date: Tue, 9 Sep 2025 10:06:41 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=94=96=20bump=20llmchat=20version?= =?UTF-8?q?=200.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7c17df2..bb49e2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nonebot-plugin-llmchat" -version = "0.2.5" +version = "0.3.0" description = "Nonebot AI group chat plugin supporting multiple API preset configurations" license = "GPL" authors = ["FuQuan i@fuquan.moe"]