diff --git a/README.md b/README.md
index 356effc..2f41e31 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,6 @@ _✨ 支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插
-
@@ -109,7 +108,6 @@ _✨ 支持多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 d3c6605..b2021c2 100755
--- a/nonebot_plugin_llmchat/__init__.py
+++ b/nonebot_plugin_llmchat/__init__.py
@@ -40,13 +40,14 @@ from nonebot_plugin_apscheduler import scheduler
if TYPE_CHECKING:
from openai.types.chat import (
- ChatCompletionContentPartParam,
+ ChatCompletionContentPartImageParam,
+ ChatCompletionContentPartTextParam,
ChatCompletionMessageParam,
)
__plugin_meta__ = PluginMetadata(
name="llmchat",
- description="支持多API预设、MCP协议、联网搜索、视觉模型的AI群聊插件",
+ description="支持多API预设、MCP协议、联网搜索的AI群聊插件",
usage="""@机器人 + 消息 开启对话""",
type="application",
homepage="https://github.com/FuQuan233/nonebot-plugin-llmchat",
@@ -169,12 +170,6 @@ 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)
# 原有@触发条件
@@ -191,7 +186,7 @@ async def is_triggered(event: GroupMessageEvent) -> bool:
# 消息处理器
handler = on_message(
rule=Rule(is_triggered),
- priority=99,
+ priority=10,
block=False,
)
@@ -216,7 +211,7 @@ async def process_images(event: GroupMessageEvent) -> list[str]:
base64_images = []
for segement in event.get_message():
if segement.type == "image":
- image_url = segement.data.get("url") or segement.data.get("file")
+ image_url = segement.data.get("url")
if image_url:
try:
# 处理高版本 httpx 的 [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] 报错
@@ -239,20 +234,6 @@ async def process_images(event: GroupMessageEvent) -> list[str]:
logger.debug(f"共处理 {len(base64_images)} 张图片")
return base64_images
-async def send_split_messages(message_handler, content: str):
- """
- 将消息按分隔符分段并发送
- """
- logger.info(f"准备发送分段消息,分段数:{len(content.split(''))}")
- for segment in content.split(""):
- # 跳过空消息
- if not segment.strip():
- continue
- segment = segment.strip() # 删除前后多余的换行和空格
- await asyncio.sleep(2) # 避免发送过快
- logger.debug(f"发送消息分段 内容:{segment[:50]}...") # 只记录前50个字符避免日志过大
- await message_handler.send(Message(segment))
-
async def process_messages(group_id: int):
state = group_states[group_id]
preset = get_preset(group_id)
@@ -316,19 +297,18 @@ async def process_messages(group_id: int):
if state.past_events.__len__() < 1:
break
- content: list[ChatCompletionContentPartParam] = []
+ # 将消息中的图片转成 base64
+ base64_images = []
+ if preset.support_image:
+ base64_images = await process_images(event)
# 将机器人错过的消息推送给LLM
- past_events_snapshot = list(state.past_events)
- for ev in past_events_snapshot:
- text_content = format_message(ev)
- content.append({"type": "text", "text": text_content})
-
- # 将消息中的图片转成 base64
- if preset.support_image:
- base64_images = await process_images(ev)
- for base64_image in base64_images:
- content.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}})
+ text_content = ",".join([format_message(ev) for ev in state.past_events])
+ content: list[ChatCompletionContentPartTextParam | ChatCompletionContentPartImageParam] = [
+ {"type": "text", "text": text_content}
+ ]
+ for base64_image in base64_images:
+ content.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}})
new_messages: list[ChatCompletionMessageParam] = [
{"role": "user", "content": content}
@@ -370,7 +350,7 @@ async def process_messages(group_id: int):
# 发送LLM调用工具时的回复,一般没有
if message.content:
- await send_split_messages(handler, message.content)
+ await handler.send(Message(message.content))
# 处理每个工具调用
for tool_call in message.tool_calls:
@@ -386,7 +366,7 @@ async def process_messages(group_id: int):
new_messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
- "content": str(result)
+ "content": str(result.content)
})
# 将工具调用的结果交给 LLM
@@ -430,7 +410,20 @@ async def process_messages(group_id: int):
logger.error(f"合并转发消息发送失败:\n{e!s}\n")
assert reply is not None
- await send_split_messages(handler, reply)
+ logger.info(
+ f"准备发送回复消息 群号:{group_id} 消息分段数:{len(reply.split(''))}"
+ )
+ for r in reply.split(""):
+ # 似乎会有空消息的情况导致string index out of range异常
+ if len(r) == 0 or r.isspace():
+ continue
+ # 删除前后多余的换行和空格
+ r = r.strip()
+ await asyncio.sleep(2)
+ logger.debug(
+ f"发送消息分段 内容:{r[:50]}..."
+ ) # 只记录前50个字符避免日志过大
+ await handler.send(Message(r))
except Exception as e:
logger.opt(exception=e).error(f"API请求失败 群号:{group_id}")
@@ -467,7 +460,7 @@ async def handle_preset(event: GroupMessageEvent, args: Message = CommandArg()):
edit_preset_handler = on_command(
"修改设定",
- priority=1,
+ priority=99,
block=True,
permission=(SUPERUSER | GROUP_ADMIN | GROUP_OWNER),
)
@@ -484,7 +477,7 @@ async def handle_edit_preset(event: GroupMessageEvent, args: Message = CommandAr
reset_handler = on_command(
"记忆清除",
- priority=1,
+ priority=99,
block=True,
permission=(SUPERUSER | GROUP_ADMIN | GROUP_OWNER),
)
@@ -501,7 +494,7 @@ async def handle_reset(event: GroupMessageEvent, args: Message = CommandArg()):
set_prob_handler = on_command(
"设置主动回复概率",
- priority=1,
+ priority=99,
block=True,
permission=(SUPERUSER | GROUP_ADMIN | GROUP_OWNER),
)
diff --git a/nonebot_plugin_llmchat/config.py b/nonebot_plugin_llmchat/config.py
index d658875..fa873d5 100755
--- a/nonebot_plugin_llmchat/config.py
+++ b/nonebot_plugin_llmchat/config.py
@@ -44,10 +44,6 @@ 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):
diff --git a/nonebot_plugin_llmchat/mcpclient.py b/nonebot_plugin_llmchat/mcpclient.py
index 55e1b44..7031d34 100644
--- a/nonebot_plugin_llmchat/mcpclient.py
+++ b/nonebot_plugin_llmchat/mcpclient.py
@@ -1,4 +1,3 @@
-import asyncio
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
@@ -65,13 +64,9 @@ class MCPClient:
server_name, real_tool_name = tool_name.split("___")
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=30)
- except asyncio.TimeoutError:
- logger.error(f"调用工具[{real_tool_name}]超时")
- return f"调用工具[{real_tool_name}]超时"
+ response = await session.call_tool(real_tool_name, tool_args)
logger.debug(f"工具[{real_tool_name}]调用完成,响应: {response}")
- return response.content
+ return response
def get_friendly_name(self, tool_name: str):
server_name, real_tool_name = tool_name.split("___")
diff --git a/pyproject.toml b/pyproject.toml
index 7c17df2..b396caa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot-plugin-llmchat"
-version = "0.2.5"
+version = "0.2.2"
description = "Nonebot AI group chat plugin supporting multiple API preset configurations"
license = "GPL"
authors = ["FuQuan i@fuquan.moe"]