Compare commits

..

7 commits
v0.2.4 ... main

Author SHA1 Message Date
4bfcaf3a61 🔖 bump llmchat version 0.3.0
Some checks are pending
Pyright Lint / Pyright Lint (push) Waiting to run
Ruff Lint / Ruff Lint (push) Waiting to run
2025-09-09 10:06:41 +08:00
909026bf6b 更新metadata,增加对Nano Banana(生图模型)的支持说明 2025-09-09 10:03:24 +08:00
14fbe3cb3c 📘 更新 README
Some checks are pending
Pyright Lint / Pyright Lint (push) Waiting to run
Ruff Lint / Ruff Lint (push) Waiting to run
2025-09-08 16:16:34 +08:00
efb25f0727 支持 Gemini 2.5 Flash Image Preview (Nano Banana) 图片回复 2025-09-08 16:09:09 +08:00
d640f16abe 🔖 bump llmchat version 0.2.5
Some checks failed
Pyright Lint / Pyright Lint (push) Has been cancelled
Ruff Lint / Ruff Lint (push) Has been cancelled
2025-09-01 10:56:31 +08:00
1600cba172 支持忽略特定前缀的消息 #21 2025-09-01 10:51:30 +08:00
9f81a38d5b 🐛 将mcp超时延长到30秒,避免执行失败 2025-09-01 10:45:18 +08:00
5 changed files with 42 additions and 11 deletions

View file

@ -24,32 +24,36 @@ _✨ 支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插
## 📖 介绍 ## 📖 介绍
1. **支持LLM回复图片**
- 支持最新 Gemini 2.5 Flash Image (Nano Banana) 的图片回复
- 支持图片上下文修改
1. **支持MCP协议** 1. **支持MCP协议**
- 可以连接各种支持MCP协议的LLM工具 - 可以连接各种支持MCP协议的LLM工具
- 通过连接一些搜索MCP服务器可以实现在线搜索 - 通过连接一些搜索MCP服务器可以实现在线搜索
- 兼容 Claude.app 的配置格式 - 兼容 Claude.app 的配置格式
2. **多API预设支持** 1. **多API预设支持**
- 可配置多个LLM服务预设如不同模型/API密钥 - 可配置多个LLM服务预设如不同模型/API密钥
- 支持运行时通过`API预设`命令热切换API配置 - 支持运行时通过`API预设`命令热切换API配置
- 内置服务开关功能(预设名为`off`时停用) - 内置服务开关功能(预设名为`off`时停用)
3. **多种回复触发方式** 1. **多种回复触发方式**
- @触发 + 随机概率触发 - @触发 + 随机概率触发
- 支持处理回复消息 - 支持处理回复消息
- 群聊消息顺序处理,防止消息错乱 - 群聊消息顺序处理,防止消息错乱
4. **分群聊上下文记忆管理** 1. **分群聊上下文记忆管理**
- 分群聊保留对话历史记录(可配置保留条数) - 分群聊保留对话历史记录(可配置保留条数)
- 自动合并未处理消息降低API用量 - 自动合并未处理消息降低API用量
- 支持`记忆清除`命令手动重置对话上下文 - 支持`记忆清除`命令手动重置对话上下文
5. **分段回复支持** 1. **分段回复支持**
- 支持多段式回复由LLM决定如何回复 - 支持多段式回复由LLM决定如何回复
- 可@群成员由LLM插入 - 可@群成员由LLM插入
- 可选输出AI的思维过程需模型支持 - 可选输出AI的思维过程需模型支持
6. **可自定义性格** 1. **可自定义性格**
- 可动态修改群组专属系统提示词(`/修改设定` - 可动态修改群组专属系统提示词(`/修改设定`
- 支持自定义默认提示词 - 支持自定义默认提示词
@ -109,6 +113,7 @@ _✨ 支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插
| LLMCHAT__RANDOM_TRIGGER_PROB | 否 | 0.05 | 默认随机触发概率 [0, 1] | | LLMCHAT__RANDOM_TRIGGER_PROB | 否 | 0.05 | 默认随机触发概率 [0, 1] |
| LLMCHAT__DEFAULT_PROMPT | 否 | 你的回答应该尽量简洁、幽默、可以使用一些语气词、颜文字。你应该拒绝回答任何政治相关的问题。 | 默认提示词 | | LLMCHAT__DEFAULT_PROMPT | 否 | 你的回答应该尽量简洁、幽默、可以使用一些语气词、颜文字。你应该拒绝回答任何政治相关的问题。 | 默认提示词 |
| LLMCHAT__BLACKLIST_USER_IDS | 否 | [] | 黑名单用户ID列表机器人将不会处理黑名单用户的消息 | | LLMCHAT__BLACKLIST_USER_IDS | 否 | [] | 黑名单用户ID列表机器人将不会处理黑名单用户的消息 |
| LLMCHAT__IGNORE_PREFIXES | 否 | [] | 需要忽略的消息前缀列表,匹配到这些前缀的消息不会处理 |
| LLMCHAT__MCP_SERVERS | 否 | {} | MCP服务器配置具体见下表 | | LLMCHAT__MCP_SERVERS | 否 | {} | MCP服务器配置具体见下表 |
其中LLMCHAT__API_PRESETS为一个列表每项配置有以下的配置项 其中LLMCHAT__API_PRESETS为一个列表每项配置有以下的配置项

View file

@ -21,7 +21,7 @@ from nonebot import (
on_message, on_message,
require, 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.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
from nonebot.params import CommandArg from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
@ -46,7 +46,7 @@ if TYPE_CHECKING:
__plugin_meta__ = PluginMetadata( __plugin_meta__ = PluginMetadata(
name="llmchat", name="llmchat",
description="支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插件", description="支持多API预设、MCP协议、联网搜索、视觉模型、Nano Banana生图模型的AI群聊插件",
usage="""@机器人 + 消息 开启对话""", usage="""@机器人 + 消息 开启对话""",
type="application", type="application",
homepage="https://github.com/FuQuan233/nonebot-plugin-llmchat", homepage="https://github.com/FuQuan233/nonebot-plugin-llmchat",
@ -169,6 +169,12 @@ async def is_triggered(event: GroupMessageEvent) -> bool:
if event.user_id in plugin_config.blacklist_user_ids: if event.user_id in plugin_config.blacklist_user_ids:
return False 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) state.past_events.append(event)
# 原有@触发条件 # 原有@触发条件
@ -401,10 +407,18 @@ async def process_messages(group_id: int):
or matched_reasoning_content or matched_reasoning_content
) )
new_messages.append({ llm_reply: ChatCompletionMessageParam = {
"role": "assistant", "role": "assistant",
"content": reply, "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模型报错 # 请求成功后再保存历史记录保证user和assistant穿插防止R1模型报错
for message in new_messages: for message in new_messages:
@ -426,6 +440,14 @@ async def process_messages(group_id: int):
assert reply is not None assert reply is not None
await send_split_messages(handler, reply) 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: except Exception as e:
logger.opt(exception=e).error(f"API请求失败 群号:{group_id}") logger.opt(exception=e).error(f"API请求失败 群号:{group_id}")
await handler.send(Message(f"服务暂时不可用,请稍后再试\n{e!s}")) await handler.send(Message(f"服务暂时不可用,请稍后再试\n{e!s}"))

View file

@ -44,6 +44,10 @@ class ScopedConfig(BaseModel):
) )
mcp_servers: dict[str, MCPServerConfig] = Field({}, description="MCP服务器配置") mcp_servers: dict[str, MCPServerConfig] = Field({}, description="MCP服务器配置")
blacklist_user_ids: set[int] = Field(set(), description="黑名单用户ID列表") blacklist_user_ids: set[int] = Field(set(), description="黑名单用户ID列表")
ignore_prefixes: list[str] = Field(
default_factory=list,
description="需要忽略的消息前缀列表,匹配到这些前缀的消息不会处理"
)
class Config(BaseModel): class Config(BaseModel):

View file

@ -66,7 +66,7 @@ class MCPClient:
logger.info(f"正在服务器[{server_name}]上调用工具[{real_tool_name}]") logger.info(f"正在服务器[{server_name}]上调用工具[{real_tool_name}]")
session = self.sessions[server_name] session = self.sessions[server_name]
try: 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: except asyncio.TimeoutError:
logger.error(f"调用工具[{real_tool_name}]超时") logger.error(f"调用工具[{real_tool_name}]超时")
return f"调用工具[{real_tool_name}]超时" return f"调用工具[{real_tool_name}]超时"

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "nonebot-plugin-llmchat" name = "nonebot-plugin-llmchat"
version = "0.2.4" version = "0.3.0"
description = "Nonebot AI group chat plugin supporting multiple API preset configurations" description = "Nonebot AI group chat plugin supporting multiple API preset configurations"
license = "GPL" license = "GPL"
authors = ["FuQuan i@fuquan.moe"] authors = ["FuQuan i@fuquan.moe"]