From b74b5933b85b96813aa9c2bfb7d285348ae18d34 Mon Sep 17 00:00:00 2001 From: left666 <2868322078@qq.com> Date: Thu, 27 Mar 2025 10:30:19 +0800 Subject: [PATCH 01/46] =?UTF-8?q?feat(core):=20=E5=9C=A8=20MessageChain=20?= =?UTF-8?q?=E7=B1=BB=E4=B8=AD=E6=B7=BB=E5=8A=A0=20at=20=E5=92=8C=20at=5Fal?= =?UTF-8?q?l=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 at 方法,用于添加 At 消息到消息链中 - 新增 at_all 方法,用于添加 AtAll 消息到消息链中 --- astrbot/core/message/message_event_result.py | 28 ++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/astrbot/core/message/message_event_result.py b/astrbot/core/message/message_event_result.py index 4cc7fb842..9ecb09b56 100644 --- a/astrbot/core/message/message_event_result.py +++ b/astrbot/core/message/message_event_result.py @@ -1,8 +1,8 @@ import enum -from typing import List, Optional +from typing import List, Optional, Union from dataclasses import dataclass, field -from astrbot.core.message.components import BaseMessageComponent, Plain, Image +from astrbot.core.message.components import BaseMessageComponent, Plain, Image, At, AtAll from typing_extensions import deprecated @@ -31,6 +31,30 @@ class MessageChain: self.chain.append(Plain(message)) return self + def at(self, name: str, qq: Union[str, int]): + """添加一条 At 消息到消息链 `chain` 中。 + + Example: + + CommandResult().at("张三", "12345678910") + # 输出 @张三 + + """ + self.chain.append(At(name=name, qq=qq)) + return self + + def at_all(self): + """添加一条 AtAll 消息到消息链 `chain` 中。 + + Example: + + CommandResult().at_all() + # 输出 @所有人 + + """ + self.chain.append(AtAll()) + return self + @deprecated("请使用 message 方法代替。") def error(self, message: str): """添加一条错误消息到消息链 `chain` 中 From 33f86f3bde3e8022a19dd8556c5be3c7bf2207b8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 02:56:55 +0000 Subject: [PATCH 02/46] :balloon: auto fixes by pre-commit hooks --- astrbot/core/message/message_event_result.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/astrbot/core/message/message_event_result.py b/astrbot/core/message/message_event_result.py index 9ecb09b56..dcbd73d0f 100644 --- a/astrbot/core/message/message_event_result.py +++ b/astrbot/core/message/message_event_result.py @@ -2,7 +2,13 @@ import enum from typing import List, Optional, Union from dataclasses import dataclass, field -from astrbot.core.message.components import BaseMessageComponent, Plain, Image, At, AtAll +from astrbot.core.message.components import ( + BaseMessageComponent, + Plain, + Image, + At, + AtAll, +) from typing_extensions import deprecated From ad37ff5048819b987ec95ec35e43386c8fa4d99b Mon Sep 17 00:00:00 2001 From: Zhenyi Wang Date: Thu, 27 Mar 2025 11:17:52 +0800 Subject: [PATCH 03/46] =?UTF-8?q?feat:=20gewechat=20client=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=8E=B7=E5=8F=96=E9=80=9A=E8=AE=AF=E5=BD=95=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/sources/gewechat/client.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index abab1790b..157145add 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -735,3 +735,22 @@ class SimpleGewechatClient: json_blob = await resp.json() logger.debug(f"获取群信息结果: {json_blob}") return json_blob + + async def get_contacts_list(self): + """ + 获取通讯录列表 + 见 https://apifox.com/apidoc/shared/69ba62ca-cb7d-437e-85e4-6f3d3df271b1/api-196794504 + """ + payload = { + "appId": self.appid + } + + async with aiohttp.ClientSession() as session: + async with session.post( + f"{self.base_url}/contacts/fetchContactsList", + headers=self.headers, + json=payload, + ) as resp: + json_blob = await resp.json() + logger.debug(f"获取通讯录列表结果: {json_blob}") + return json_blob From 333c2d92995c6d8c728a133beecc053ac6522ed3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 03:21:42 +0000 Subject: [PATCH 04/46] :balloon: auto fixes by pre-commit hooks --- astrbot/core/platform/sources/gewechat/client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 157145add..ccecc0c71 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -741,9 +741,7 @@ class SimpleGewechatClient: 获取通讯录列表 见 https://apifox.com/apidoc/shared/69ba62ca-cb7d-437e-85e4-6f3d3df271b1/api-196794504 """ - payload = { - "appId": self.appid - } + payload = {"appId": self.appid} async with aiohttp.ClientSession() as session: async with session.post( From fd0c0f897523517377f0c624ec5e463aaa46a8ef Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Fri, 28 Mar 2025 23:45:19 +0800 Subject: [PATCH 05/46] =?UTF-8?q?=F0=9F=90=9B=20fix:=20newgroup=20?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E5=90=8D=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/astrbot/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index bb246b559..5c4297fe9 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -754,7 +754,7 @@ UID: {user_id} 此 ID 可用于设置管理员。 ) else: message.set_result( - MessageEventResult().message("请输入群聊 ID。/newgroup 群聊ID。") + MessageEventResult().message("请输入群聊 ID。/groupnew 群聊ID。") ) @filter.command("switch") From 84a3e0a30b0f6b768e3e23c0114d8a226f891ef1 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 29 Mar 2025 16:36:02 +0800 Subject: [PATCH 06/46] =?UTF-8?q?=F0=9F=8E=88=20feat:=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20Dockerfile=EF=BC=8C=E6=B7=BB=E5=8A=A0=20Node.js=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BE=9D=E8=B5=96=E5=AE=89?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 8 +++++--- Dockerfile_with_node | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Dockerfile_with_node diff --git a/Dockerfile b/Dockerfile index 0b46d5d48..1b2314970 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,12 +9,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3-dev \ libffi-dev \ libssl-dev \ + ca-certificates \ + bash \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -RUN python -m pip install -r requirements.txt --no-cache-dir - -RUN python -m pip install socksio wechatpy cryptography --no-cache-dir +RUN python -m pip install uv +RUN uv pip install -r requirements.txt --no-cache-dir --system +RUN uv pip install socksio uv pyffmpeg --no-cache-dir --system EXPOSE 6185 EXPOSE 6186 diff --git a/Dockerfile_with_node b/Dockerfile_with_node new file mode 100644 index 000000000..3bd37468a --- /dev/null +++ b/Dockerfile_with_node @@ -0,0 +1,35 @@ +FROM python:3.10-slim + +WORKDIR /AstrBot + +COPY . /AstrBot/ + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + build-essential \ + python3-dev \ + libffi-dev \ + libssl-dev \ + curl \ + unzip \ + ca-certificates \ + bash \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Installation of Node.js +ENV NVM_DIR="/root/.nvm" +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash && \ + . "$NVM_DIR/nvm.sh" && \ + nvm install 22 && \ + nvm use 22 +RUN /bin/bash -c ". \"$NVM_DIR/nvm.sh\" && node -v && npm -v" + +RUN python -m pip install uv +RUN uv pip install -r requirements.txt --no-cache-dir --system +RUN uv pip install socksio uv pyffmpeg --no-cache-dir --system + +EXPOSE 6185 +EXPOSE 6186 + +CMD ["python", "main.py"] From faef98b08930ecc213692af0538c85e016f6e9d2 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Sat, 29 Mar 2025 17:07:12 +0800 Subject: [PATCH 07/46] =?UTF-8?q?=E5=9C=A8lifecycle=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E8=B5=84=E6=BA=90=E6=B8=85=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/core_lifecycle.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py index e52d94674..b162faf3b 100644 --- a/astrbot/core/core_lifecycle.py +++ b/astrbot/core/core_lifecycle.py @@ -133,6 +133,16 @@ class AstrBotCoreLifecycle: for task in self.curr_tasks: task.cancel() + for plugin in self.plugin_manager.context.get_all_stars(): + logger.info(f"正在终止插件 {plugin.name} ...") + try: + await self.plugin_manager._terminate_plugin(plugin) + except Exception as e: + logger.warning(traceback.format_exc()) + logger.warning( + f"插件 {plugin.name} 未被正常终止 {e!s}, 可能会导致资源泄露等问题。" + ) + await self.provider_manager.terminate() await self.platform_manager.terminate() self.dashboard_shutdown_event.set() From c681aae8ee0424e9a2477b3d523cb2eb5fb58f0c Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Sat, 29 Mar 2025 17:25:38 +0800 Subject: [PATCH 08/46] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/star/star_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 67796decc..13d1768ef 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -558,7 +558,7 @@ class PluginManager: async def _terminate_plugin(self, star_metadata: StarMetadata): """终止插件,调用插件的 terminate() 和 __del__() 方法""" - logging.info(f"正在终止插件 {star_metadata.name} ...") + logger.info(f"正在终止插件 {star_metadata.name} ...") if not star_metadata.activated: # 说明之前已经被禁用了 From 298c77740d36266b9f01f4aa46dbad8bc81ab49e Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 29 Mar 2025 17:26:57 +0800 Subject: [PATCH 09/46] =?UTF-8?q?=E2=9C=A8feat:=20docker=20=E9=95=9C?= =?UTF-8?q?=E5=83=8F=E6=8F=90=E4=BE=9B=E5=86=85=E7=BD=AE=20ffmpeg=20#979?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1b2314970..2bc60833b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN python -m pip install uv RUN uv pip install -r requirements.txt --no-cache-dir --system -RUN uv pip install socksio uv pyffmpeg --no-cache-dir --system +RUN uv pip install socksio uv pyffmpeg pilk --no-cache-dir --system + +# 释出 ffmpeg +RUN python -c "from pyffmpeg import FFmpeg; ff = FFmpeg();" + +# add /root/.pyffmpeg/bin/ffmpeg to PATH, inorder to use ffmpeg +RUN echo 'export PATH=$PATH:/root/.pyffmpeg/bin' >> ~/.bashrc EXPOSE 6185 EXPOSE 6186 From 0e8d52b59193c3e136670d7942b2d2991bc7d00d Mon Sep 17 00:00:00 2001 From: Futureppo Date: Wed, 26 Mar 2025 16:40:25 +0800 Subject: [PATCH 10/46] =?UTF-8?q?:ballon:=20feat:=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=AD=A3=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E6=8E=89=20/model=20=E5=8F=AF=E8=83=BD=E6=9A=B4=E9=9C=B2?= =?UTF-8?q?=E7=9A=84=20api=5Fkey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed: 更新正则表达式 :balloon: auto fixes by pre-commit hooks Update main.py Update main.py chore: bugfixes --- packages/astrbot/main.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index b0f51d98f..1f5f23cd8 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -2,6 +2,7 @@ import aiohttp import datetime import builtins import traceback +import re import astrbot.api.star as star import astrbot.api.event.filter as filter from astrbot.api.event import AstrMessageEvent, MessageEventResult @@ -13,6 +14,7 @@ from astrbot.core.provider.sources.dify_source import ProviderDify from astrbot.core.utils.io import download_dashboard, get_dashboard_version from astrbot.core.star.star_handler import star_handlers_registry, StarHandlerMetadata from astrbot.core.star.star import star_map +from astrbot.core.star.star_manager import PluginManager from astrbot.core.star.filter.command import CommandFilter from astrbot.core.star.filter.command_group import CommandGroupFilter from astrbot.core.star.filter.permission import PermissionTypeFilter @@ -196,7 +198,29 @@ class Main(star.Star): return await self.context._star_manager.turn_on_plugin(oper2) event.set_result(MessageEventResult().message(f"插件 {oper2} 已启用。")) + elif oper1 == "get": + if not oper2: + raise Exception("请输入插件地址。") + if not event.is_admin(): + raise Exception( + "改指令限制仅管理员使用,且无法通过 /alter_cmd 更改。" + ) + if not oper2.startswith("http"): + oper2 = f"https://github.com/{oper2}" + logger.info(f"准备从 {oper2} 获取插件。") + + if self.context._star_manager: + star_mgr: PluginManager = self.context._star_manager + try: + await star_mgr.install_plugin(oper2) + event.set_result(MessageEventResult().message("获取插件成功。")) + except Exception as e: + logger.error(f"获取插件失败: {e}") + event.set_result( + MessageEventResult().message(f"获取插件失败: {e}") + ) + return else: # 获取插件帮助 plugin = self.context.get_registered_star(oper1) @@ -497,15 +521,20 @@ UID: {user_id} 此 ID 可用于设置管理员。 MessageEventResult().message("未找到任何 LLM 提供商。请先配置。") ) return + # 定义正则表达式匹配 API 密钥 + api_key_pattern = re.compile(r"key=[^&'\" ]+") if idx_or_name is None: models = [] try: models = await self.context.get_using_provider().get_models() except BaseException as e: + err_msg = api_key_pattern.sub( + "key=***", str(e) + ) message.set_result( MessageEventResult() - .message("获取模型列表失败: " + str(e)) + .message("获取模型列表失败: " + err_msg) .use_t2i(False) ) return From a38b00be6bbeaed947fc02ddded511984d367617 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 26 Mar 2025 13:41:18 +0800 Subject: [PATCH 11/46] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=8F=AF=E8=83=BD=E5=BD=A2=E6=88=90=20SQL=20?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=E7=9A=84=E9=A3=8E=E9=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/db/sqlite.py | 33 +++++++++---------- astrbot/core/db/sqlite_init.sql | 8 +++-- .../process_stage/method/llm_request.py | 2 +- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index 98e6af04e..65d68526e 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -128,24 +128,23 @@ class SQLiteDatabase(BaseDatabase): except sqlite3.ProgrammingError: c = self._get_conn(self.db_path).cursor() - where_clause = "" - if session_id or provider_type: - where_clause += " WHERE " - has = False - if session_id: - where_clause += f"session_id = '{session_id}'" - has = True - if provider_type: - if has: - where_clause += " AND " - where_clause += f"provider_type = '{provider_type}'" + conditions = [] + params = [] + + if session_id: + conditions.append("session_id = ?") + params.append(session_id) + + if provider_type: + conditions.append("provider_type = ?") + params.append(provider_type) + + sql = "SELECT * FROM llm_history" + if conditions: + sql += " WHERE " + " AND ".join(conditions) + + c.execute(sql, params) - c.execute( - """ - SELECT * FROM llm_history - """ - + where_clause - ) res = c.fetchall() histories = [] for row in res: diff --git a/astrbot/core/db/sqlite_init.sql b/astrbot/core/db/sqlite_init.sql index 900f4f2c0..a1ebc54b5 100644 --- a/astrbot/core/db/sqlite_init.sql +++ b/astrbot/core/db/sqlite_init.sql @@ -38,11 +38,13 @@ CREATE TABLE IF NOT EXISTS atri_vision( ); CREATE TABLE IF NOT EXISTS webchat_conversation( - user_id TEXT, - cid TEXT, + user_id TEXT, -- 会话 id + cid TEXT, -- 对话 id history TEXT, created_at INTEGER, updated_at INTEGER, title TEXT, persona_id TEXT -); \ No newline at end of file +); + +PRAGMA encoding = 'UTF-8'; \ No newline at end of file diff --git a/astrbot/core/pipeline/process_stage/method/llm_request.py b/astrbot/core/pipeline/process_stage/method/llm_request.py index 872875e8f..8d606d9e5 100644 --- a/astrbot/core/pipeline/process_stage/method/llm_request.py +++ b/astrbot/core/pipeline/process_stage/method/llm_request.py @@ -128,7 +128,7 @@ class LLMRequestSubStage(Stage): # max context length if ( - self.max_context_length != -1 # -1 为不限制 + self.max_context_length != -1 # -1 为不限制 and len(req.contexts) // 2 > self.max_context_length ): logger.debug("上下文长度超过限制,将截断。") From 89d3fd5fab001b07570a655f75d30b55701f6859 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 26 Mar 2025 13:50:11 +0800 Subject: [PATCH 12/46] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20WebUI=20=E5=AF=B9=E8=AF=9D=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E5=8E=86=E5=8F=B2=E6=A3=80=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/db/sqlite.py | 1 + 1 file changed, 1 insertion(+) diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index 65d68526e..3a4f6c1b8 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -491,6 +491,7 @@ class SQLiteDatabase(BaseDatabase): # 搜索关键词 if search_query: + search_query = search_query.encode("unicode_escape").decode("utf-8") where_clauses.append( "(title LIKE ? OR user_id LIKE ? OR cid LIKE ? OR history LIKE ?)" ) From e0f029e2cb8de64f1522ef1afe1dfdc5a017f2d3 Mon Sep 17 00:00:00 2001 From: Gao Jinzhe <2968474907@qq.com> Date: Sun, 23 Mar 2025 14:35:44 +0800 Subject: [PATCH 13/46] Add files via upload --- packages/session_controller/main.py | 44 +++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/session_controller/main.py b/packages/session_controller/main.py index 89a98dca0..f73d87be5 100644 --- a/packages/session_controller/main.py +++ b/packages/session_controller/main.py @@ -1,5 +1,6 @@ import astrbot.api.message_components as Comp import copy +import json from astrbot.api import logger from astrbot.api.event import AstrMessageEvent, filter from astrbot.api.star import Context, Star, register @@ -54,8 +55,36 @@ class Waiter(Star): isinstance(messages[0], Comp.Plain) and messages[0].text.strip() in self.wake_prefix ): - yield event.plain_result("想要问什么呢?😄") - + try: + # 尝试使用 LLM 生成更生动的回复 + func_tools_mgr = self.context.get_llm_tool_manager() + + # 获取用户当前的对话信息 + curr_cid = await self.context.conversation_manager.get_curr_conversation_id(event.unified_msg_origin) + conversation = None + context = [] + + if curr_cid: + conversation = await self.context.conversation_manager.get_conversation(event.unified_msg_origin, curr_cid) + context = json.loads(conversation.history) if conversation.history else [] + else: + # 创建新对话 + curr_cid = await self.context.conversation_manager.new_conversation(event.unified_msg_origin) + + # 使用 LLM 生成回复 + yield event.request_llm( + prompt="用户只是@我或唤醒我,请友好地询问用户想要聊些什么或者需要什么帮助,回复要符合人设,不要太过机械化。", + func_tool_manager=func_tools_mgr, + session_id=curr_cid, + contexts=context, + system_prompt="", + conversation=conversation + ) + except Exception as e: + logger.error(f"LLM response failed: {str(e)}") + # LLM 回复失败,使用原始预设回复 + yield event.plain_result("想要问什么呢?😄") + @session_waiter(60) async def empty_mention_waiter( controller: SessionController, event: AstrMessageEvent @@ -74,7 +103,16 @@ class Waiter(Star): try: await empty_mention_waiter(event) except TimeoutError as _: - yield event.plain_result("如果需要帮助,请再次 @ 我哦~") + try: + # 超时时也尝试使用 LLM 生成回复 + yield event.request_llm( + prompt="用户在提问后超时未回复,请生成一个温馨友好的提醒,告诉用户如果需要帮助可以再次提问,回答要符合人设。", + func_tool_manager=self.context.get_llm_tool_manager(), + system_prompt="" + ) + except Exception: + # LLM 回复失败,使用原始预设回复 + yield event.plain_result("如果需要帮助,请再次 @ 我哦~") except Exception as e: yield event.plain_result("发生错误,请联系管理员: " + str(e)) finally: From 3f0b6435d9c041fed36261b513c34d7634a1cd96 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 23 Mar 2025 06:39:12 +0000 Subject: [PATCH 14/46] :balloon: auto fixes by pre-commit hooks --- packages/session_controller/main.py | 30 +++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/session_controller/main.py b/packages/session_controller/main.py index f73d87be5..6bf3f71fb 100644 --- a/packages/session_controller/main.py +++ b/packages/session_controller/main.py @@ -58,19 +58,29 @@ class Waiter(Star): try: # 尝试使用 LLM 生成更生动的回复 func_tools_mgr = self.context.get_llm_tool_manager() - + # 获取用户当前的对话信息 - curr_cid = await self.context.conversation_manager.get_curr_conversation_id(event.unified_msg_origin) + curr_cid = await self.context.conversation_manager.get_curr_conversation_id( + event.unified_msg_origin + ) conversation = None context = [] - + if curr_cid: - conversation = await self.context.conversation_manager.get_conversation(event.unified_msg_origin, curr_cid) - context = json.loads(conversation.history) if conversation.history else [] + conversation = await self.context.conversation_manager.get_conversation( + event.unified_msg_origin, curr_cid + ) + context = ( + json.loads(conversation.history) + if conversation.history + else [] + ) else: # 创建新对话 - curr_cid = await self.context.conversation_manager.new_conversation(event.unified_msg_origin) - + curr_cid = await self.context.conversation_manager.new_conversation( + event.unified_msg_origin + ) + # 使用 LLM 生成回复 yield event.request_llm( prompt="用户只是@我或唤醒我,请友好地询问用户想要聊些什么或者需要什么帮助,回复要符合人设,不要太过机械化。", @@ -78,13 +88,13 @@ class Waiter(Star): session_id=curr_cid, contexts=context, system_prompt="", - conversation=conversation + conversation=conversation, ) except Exception as e: logger.error(f"LLM response failed: {str(e)}") # LLM 回复失败,使用原始预设回复 yield event.plain_result("想要问什么呢?😄") - + @session_waiter(60) async def empty_mention_waiter( controller: SessionController, event: AstrMessageEvent @@ -108,7 +118,7 @@ class Waiter(Star): yield event.request_llm( prompt="用户在提问后超时未回复,请生成一个温馨友好的提醒,告诉用户如果需要帮助可以再次提问,回答要符合人设。", func_tool_manager=self.context.get_llm_tool_manager(), - system_prompt="" + system_prompt="", ) except Exception: # LLM 回复失败,使用原始预设回复 From a604b4943c813a6219d2796811d9126c84ae4560 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 26 Mar 2025 14:13:57 +0800 Subject: [PATCH 15/46] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=B0=E7=89=88=E6=9C=AC=E6=97=B6=E7=9A=84=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/zip_updator.py | 2 +- .../full/vertical-header/VerticalHeader.vue | 51 ++++++++++++++++--- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/astrbot/core/zip_updator.py b/astrbot/core/zip_updator.py index 4622b47cd..23ff3ba76 100644 --- a/astrbot/core/zip_updator.py +++ b/astrbot/core/zip_updator.py @@ -23,7 +23,7 @@ class ReleaseInfo: self.body = body def __str__(self) -> str: - return f"新版本: {self.version}, 发布于: {self.published_at}, 详细内容: {self.body}" + return f"\n{self.body}\n\n版本: {self.version} | 发布于: {self.published_at}" class RepoZipUpdator: diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index 3d08c9cd6..014191945 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -5,6 +5,7 @@ import axios from 'axios'; import { md5 } from 'js-md5'; import { useAuthStore } from '@/stores/auth'; import { useCommonStore } from '@/stores/common'; +import { marked } from 'marked'; const customizer = useCustomizerStore(); let dialog = ref(false); @@ -15,6 +16,7 @@ let newPassword = ref(''); let newUsername = ref(''); let status = ref(''); let updateStatus = ref('') +let releaseMessage = ref(''); let hasNewVersion = ref(false); let botCurrVersion = ref(''); let dashboardHasNewVersion = ref(false); @@ -81,7 +83,13 @@ function checkUpdate() { axios.get('/api/update/check') .then((res) => { hasNewVersion.value = res.data.data.has_new_version; - updateStatus.value = res.data.message; + + if (res.data.data.has_new_version) { + releaseMessage.value = res.data.message; + updateStatus.value = '有新版本!'; + } else { + updateStatus.value = res.data.message; + } botCurrVersion.value = res.data.data.version; dashboardCurrentVersion.value = res.data.data.dashboard_version; dashboardHasNewVersion.value = res.data.data.dashboard_has_new_version; @@ -226,15 +234,23 @@ if (localStorage.getItem('change_pwd_hint') != null && localStorage.getItem('cha - +

{{ botCurrVersion }}

{{ updateStatus }}
+
+ +
+
- 💡 TIP: 跳到旧版本或者切换到某个版本不会重新下载管理面板文件,这可能会造成部分数据显示错误。您可在 此处 - 找到对应的面板文件 dist.zip,解压后替换 data/dist 文件夹即可。当然,前端源代码在 dashboard 目录下,你也可以自己使用 npm install 和 npm build 构建。 + 💡 TIP: 跳到旧版本或者切换到某个版本不会重新下载管理面板文件,这可能会造成部分数据显示错误。您可在 此处 + 找到对应的面板文件 dist.zip,解压后替换 data/dist 文件夹即可。当然,前端源代码在 dashboard 目录下,你也可以自己使用 npm install 和 npm build + 构建。
@@ -269,7 +285,7 @@ if (localStorage.getItem('change_pwd_hint') != null && localStorage.getItem('cha - +
@@ -319,7 +335,8 @@ if (localStorage.getItem('change_pwd_hint') != null && localStorage.getItem('cha

- + 下载并更新 @@ -379,4 +396,24 @@ if (localStorage.getItem('change_pwd_hint') != null && localStorage.getItem('cha - \ No newline at end of file + + + \ No newline at end of file From a156b1af14c2cf082889d2d6f9a3ceb5b2a3fdfc Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 26 Mar 2025 14:33:45 +0800 Subject: [PATCH 16/46] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E6=8C=87=E4=BB=A4=E4=B8=8B=E8=BD=BD=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=20/plugin=20get?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/star/star_manager.py | 5 ++++- astrbot/dashboard/routes/conversation.py | 12 ------------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index c49027268..8a2bed689 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -332,7 +332,10 @@ class PluginManager: ) # 绑定 llm_tool handler for func_tool in llm_tools.func_list: - if func_tool.handler.__module__ == metadata.module_path: + if ( + func_tool.handler + and func_tool.handler.__module__ == metadata.module_path + ): func_tool.handler_module_path = metadata.module_path func_tool.handler = functools.partial( func_tool.handler, metadata.star_cls diff --git a/astrbot/dashboard/routes/conversation.py b/astrbot/dashboard/routes/conversation.py index a3c2db793..aa8b0af36 100644 --- a/astrbot/dashboard/routes/conversation.py +++ b/astrbot/dashboard/routes/conversation.py @@ -53,13 +53,6 @@ class ConversationRoute(Route): exclude_platforms.split(",") if exclude_platforms else [] ) - logger.info( - f"获取对话列表: page={page}, page_size={page_size}, " - f"platforms={platform_list}, message_types={message_type_list}, " - f"search={search_query}, exclude_ids={exclude_id_list}, " - f"exclude_platforms={exclude_platform_list}" - ) - # 限制页面大小,防止请求过大数据 if page < 1: page = 1 @@ -79,7 +72,6 @@ class ConversationRoute(Route): exclude_ids=exclude_id_list, exclude_platforms=exclude_platform_list, ) - logger.info(f"获取到 {len(conversations)} 条对话,总数: {total_count}") except Exception as e: logger.error(f"数据库查询出错: {str(e)}\n{traceback.format_exc()}") return Response().error(f"数据库查询出错: {str(e)}").__dict__ @@ -98,10 +90,6 @@ class ConversationRoute(Route): "total_pages": total_pages, }, } - - logger.info( - f"返回对话列表成功: {json.dumps(result, ensure_ascii=False)[:200]}..." - ) return Response().ok(result).__dict__ except Exception as e: From fb19d4d45bb9161a7af066dc2a0e4fc511da76f1 Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 26 Mar 2025 15:06:33 +0800 Subject: [PATCH 17/46] =?UTF-8?q?fix:=20install=5Fplugin=5Ffrom=5Ffile=20?= =?UTF-8?q?=E6=96=B9=E6=B3=95load=E4=BC=A0=E5=8F=82=E6=95=B0=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=96=87=E4=BB=B6=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/star/star_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 8a2bed689..67796decc 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -606,4 +606,4 @@ class PluginManager: except BaseException as e: logger.warning(f"删除插件压缩包失败: {str(e)}") # await self.reload() - await self.load(desti_dir) + await self.load(specified_dir_name=dir_name) From 67a0172b289bb6d099b0bca02bb45e06f740a720 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 26 Mar 2025 15:30:06 +0800 Subject: [PATCH 18/46] =?UTF-8?q?=F0=9F=93=A6=20release:=20v3.5.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 2 +- changelogs/v3.5.0.md | 59 ++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + uv.lock | 11 +++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 changelogs/v3.5.0.md diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 310d7ee28..c45c8d813 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -2,7 +2,7 @@ 如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。 """ -VERSION = "3.4.39" +VERSION = "3.5.0" DB_PATH = "data/data_v3.db" # 默认配置 diff --git a/changelogs/v3.5.0.md b/changelogs/v3.5.0.md new file mode 100644 index 000000000..e0b68bd4e --- /dev/null +++ b/changelogs/v3.5.0.md @@ -0,0 +1,59 @@ +# What's Changed + +> 📢 AstrBot 上架宝塔面板 Docker 应用商店了! +> 📢 在升级前,请完整阅读本次更新日志。 + +## ✨ 新增的功能 + +1. ‼️ 新增支持接入 MCP 服务器 @Soulter @AraragiEro +1. ‼️ 新增支持本地渲染 Markdown,并支持自定义字体,详见 -> [#957](https://github.com/Soulter/AstrBot/issues/957#issuecomment-2749981802) +2. 新增支持在 WebUI 管理所有与大模型的对话 +3. 适配完整的 function-calling 流程。[#804](https://github.com/Soulter/AstrBot/issues/804) [#566](https://github.com/Soulter/AstrBot/issues/566) +4. 新增支持消息平台热重载,不再需要重启 AstrBot +5. 新增支持阿里云百炼应用的 RAG 应用 [#878](https://github.com/Soulter/AstrBot/issues/878) +6. 新增 `/plugin get` OP 指令下载插件。如 `/plugin get Raven95676/astrbot_plugin_wordle` +7. 新增 `/newgroup` OP 指令,支持私聊 bot 给指定群聊创建新的对话。by @LunarMeal +8. Gewechat 下支持 `添加好友`, `接收/发送视频`, `获取群信息`, `接收/发送表情包` by @Moyuyanli @Soulter @XuYingJie-cmd @NiceAir +9. Telegram 下支持接收和处理表情包(Sticker) @Raven95676 + + +## 🎈 功能性优化 + +0. 更加美观的 WebUI 设计,降低疲劳程度。 +1. 微信下,忽略 `微信团队` 的消息 [#859](https://github.com/Soulter/AstrBot/issues/859) +2. 完善 Dify 的图片输入功能 [#893](https://github.com/Soulter/AstrBot/issues/893) +3. 消息平台和配置提供商配置页中,自动更新旧的配置项 +4. 优化钉钉在配置错误之后堵塞整个线程的问题 [#885](https://github.com/Soulter/AstrBot/issues/885) +5. WebUI 删除插件时提供二次确认避免误删 @zhx8702 +6. WebUI 优化新版本时的信息显示 +7. 发送消息失败时的报错回显优化 +8. 改善所有消息平台的优雅退出逻辑 +9. 空 @ 时调用 LLM 获得更加富有人格的回复 by @advent259141 + +## 🐛 修复的 Bug + +1. 修复图片没有被存储到聊天上下文历史记录 +2. 修复 Telegram 下无法识别图片描述(Caption) [#910](https://github.com/Soulter/AstrBot/issues/910) +3. 修复 Telegram Topic 群组下引用消息来源错误的问题 [#908](https://github.com/Soulter/AstrBot/issues/908) +4. 修复 Telegram 下 `/start` 指令的一些问题 [#751](https://github.com/Soulter/AstrBot/issues/751) +5. WebUI 插件市场卡片显示风格的过滤问题。[#927](https://github.com/Soulter/AstrBot/issues/927) +6. 统一 SSL 证书验证逻辑,修复 `SSLCertVerificationError` 的问题。by @IGCrystal [#950](https://github.com/Soulter/AstrBot/issues/950) +7. 修复可能形成 SQL 注入的风险 +8. 修复本地上传插件时无法重载插件的问题 [#995](https://github.com/Soulter/AstrBot/issues/995) by @zhx8702 + +## 🧩 新增的插件 + +1. astrbot_plugin_majsoul-master - 雀魂多功能插件 - by @kterna +2. astrbot_plugin_server - 可视化服务器状态卡片,/status 或 /状态查询 查看 - by @yanfd @Meguminlove +3. astrbot_plugin_Getcwm - 刺猬猫小说数据获取与画图插件 - by @Li-shi-ling +4. astrbot_plugin_anti_withdrawal - 防撤回插件,目前只支持微信私聊群聊的文本消息,将撤回的消息记录并发送给设定的人 - by @NiceAir +5. astrbot_plugin_hello77 - 游戏梗自动回复插件 - by @ttq7 +6. astrbot_plugin_push_lite - Webhook 轻量级推送插件 - @Raven95676 +7. astrbot_plugin_pokecheck - 检测“戳”关键词的插件 - @huanyan434 +8. astrbot_plugin_MultiAI_PollPad - 轮询调用配置的大语言模型输出多个结果。同时将 AI 结果拷贝至在线文本编辑器 - by @Ynkcc +9. astrbot_plugin_box - / - by @Zhalslar +10. astrbot_plugin_Translation - 通过调用百度翻译 API 实现翻译文本 - by @zengweis +11. astrbot_plugin_wordle_2 - Wordle 游戏插件 - by @Raven95676 @whzcc +12. astrbot_plugin_mai_sgin - 舞萌出勤与退勤签到插件 - by @Rinyin +13. astrbot_plugin_Lolicon - Lolicon API 随机动漫图片插件 - by @ttq7 +14. astrbot_plugin_aiocensor - 综合内容安全+群管插件 - by @Raven95676 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 30195047c..20f61c95b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "colorlog>=6.9.0", "cryptography>=44.0.2", "dashscope>=1.22.2", + "defusedxml>=0.7.1", "dingtalk-stream>=0.22.1", "docstring-parser>=0.16", "googlesearch-python>=1.3.0", diff --git a/uv.lock b/uv.lock index 57880f340..835b323b3 100644 --- a/uv.lock +++ b/uv.lock @@ -206,6 +206,7 @@ dependencies = [ { name = "colorlog" }, { name = "cryptography" }, { name = "dashscope" }, + { name = "defusedxml" }, { name = "dingtalk-stream" }, { name = "docstring-parser" }, { name = "googlesearch-python" }, @@ -240,6 +241,7 @@ requires-dist = [ { name = "colorlog", specifier = ">=6.9.0" }, { name = "cryptography", specifier = ">=44.0.2" }, { name = "dashscope", specifier = ">=1.22.2" }, + { name = "defusedxml", specifier = ">=0.7.1" }, { name = "dingtalk-stream", specifier = ">=0.22.1" }, { name = "docstring-parser", specifier = ">=0.16" }, { name = "googlesearch-python", specifier = ">=1.3.0" }, @@ -537,6 +539,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/06/5dc2f9e391faf8f980c11e8dea5b06e0fc8d08aeca6298e12e6bc5a8ca4b/dashscope-1.22.2-py3-none-any.whl", hash = "sha256:781952cc9ce38cd2080a9d9f1cfabda55013dd2c996d3eca49863c8532db26b9", size = 1273150 }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + [[package]] name = "dingtalk-stream" version = "0.22.1" From bbf85f8a1251ed38e6531c6e903236b0c4fd64ef Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 26 Mar 2025 15:43:56 +0800 Subject: [PATCH 19/46] =?UTF-8?q?=F0=9F=90=9B=20fix:=20remove=20error=20lo?= =?UTF-8?q?gging=20for=20empty=20result=20and=20refresh=20extensions=20aft?= =?UTF-8?q?er=20upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/zip_updator.py | 1 - dashboard/src/views/ExtensionMarketplace.vue | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/astrbot/core/zip_updator.py b/astrbot/core/zip_updator.py index 23ff3ba76..7a27c1940 100644 --- a/astrbot/core/zip_updator.py +++ b/astrbot/core/zip_updator.py @@ -56,7 +56,6 @@ class RepoZipUpdator: raise Exception(f"请求失败,状态码: {response.status}") result = await response.json() if not result: - logger.error("返回空的结果") return [] # if latest: # ret = self.github_api_release_parser([result[0]]) diff --git a/dashboard/src/views/ExtensionMarketplace.vue b/dashboard/src/views/ExtensionMarketplace.vue index 959439375..3dfaea0d7 100644 --- a/dashboard/src/views/ExtensionMarketplace.vue +++ b/dashboard/src/views/ExtensionMarketplace.vue @@ -357,6 +357,7 @@ export default { this.upload_file = ""; this.onLoadingDialogResult(1, res.data.message); this.dialog = false; + this.getExtensions(); // this.$refs.wfr.check(); }).catch((err) => { this.loading_ = false; @@ -380,6 +381,7 @@ export default { this.extension_url = ""; this.onLoadingDialogResult(1, res.data.message); this.dialog = false; + this.getExtensions(); // this.$refs.wfr.check(); }).catch((err) => { this.loading_ = false; From 32b0cc1865a7ee9172c0b413c0dd5d28fea9612b Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:16:03 +0800 Subject: [PATCH 20/46] Update README.md --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 509db0b63..8f7dff593 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,12 @@ AstrBot 是一个松耦合、异步、支持多消息平台部署、具有易用 ## ✨ 主要功能 1. **大语言模型对话**。支持各种大语言模型,包括 OpenAI API、Google Gemini、Llama、Deepseek、ChatGLM 等,支持接入本地部署的大模型,通过 Ollama、LLMTuner。具有多轮对话、人格情境、多模态能力,支持图片理解、语音转文字(Whisper)。 -2. **多消息平台接入**。支持接入 QQ(OneBot)、QQ 频道、微信(Gewechat)、飞书、Telegram。后续将支持钉钉、Discord、WhatsApp、小爱音响。支持速率限制、白名单、关键词过滤、百度内容审核。 -3. **Agent**。原生支持部分 Agent 能力,如代码执行器、自然语言待办、网页搜索。对接 [Dify 平台](https://astrbot.app/others/dify.html),便捷接入 Dify 智能助手、知识库和 Dify 工作流。 -4. **插件扩展**。深度优化的插件机制,支持[开发插件](https://astrbot.app/dev/plugin.html)扩展功能,极简开发。已支持安装多个插件。 -5. **可视化管理面板**。支持可视化修改配置、插件管理、日志查看等功能,降低配置难度。集成 WebChat,可在面板上与大模型对话。 -6. **高稳定性、高模块化**。基于事件总线和流水线的架构设计,高度模块化,低耦合。 +2. **支持 MCP**。AstrBot 现已支持接入 MCP 服务器。 +3. **多消息平台接入**。支持接入 QQ(OneBot)、QQ 频道、微信(Gewechat)、飞书、Telegram。后续将支持钉钉、Discord、WhatsApp、小爱音响。支持速率限制、白名单、关键词过滤、百度内容审核。 +4. **Agent**。原生支持部分 Agent 能力,如代码执行器、自然语言待办、网页搜索。对接 [Dify 平台](https://astrbot.app/others/dify.html),便捷接入 Dify 智能助手、知识库和 Dify 工作流。 +5. **插件扩展**。深度优化的插件机制,支持[开发插件](https://astrbot.app/dev/plugin.html)扩展功能,极简开发。已支持安装多个插件。 +6. **可视化管理面板**。支持可视化修改配置、插件管理、日志查看等功能,降低配置难度。集成 WebChat,可在面板上与大模型对话。 +7. **高稳定性、高模块化**。基于事件总线和流水线的架构设计,高度模块化,低耦合。 > [!TIP] > 管理面板在线体验 Demo: [https://demo.astrbot.app/](https://demo.astrbot.app/) From a221de9a2b31ab63ca7f637301132979bd052372 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 26 Mar 2025 17:56:51 +0800 Subject: [PATCH 21/46] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20LLM=20=E5=93=8D=E5=BA=94=E5=90=8E=E4=BA=8B=E4=BB=B6=E9=92=A9?= =?UTF-8?q?=E5=AD=90=E6=97=A0=E6=B3=95=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../process_stage/method/llm_request.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/astrbot/core/pipeline/process_stage/method/llm_request.py b/astrbot/core/pipeline/process_stage/method/llm_request.py index 8d606d9e5..7d41f7ecc 100644 --- a/astrbot/core/pipeline/process_stage/method/llm_request.py +++ b/astrbot/core/pipeline/process_stage/method/llm_request.py @@ -140,6 +140,26 @@ class LLMRequestSubStage(Stage): need_loop = False logger.debug(f"提供商请求 Payload: {req}") llm_response = await provider.text_chat(**req.__dict__) # 请求 LLM + + # 执行 LLM 响应后的事件钩子。 + handlers = star_handlers_registry.get_handlers_by_event_type( + EventType.OnLLMResponseEvent + ) + for handler in handlers: + try: + logger.debug( + f"hook(on_llm_response) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}" + ) + await handler.handler(event, llm_response) + except BaseException: + logger.error(traceback.format_exc()) + + if event.is_stopped(): + logger.info( + f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。" + ) + return + async for result in self._handle_llm_response(event, req, llm_response): if isinstance(result, ProviderRequest): # 有函数工具调用并且返回了结果,我们需要再次请求 LLM @@ -148,25 +168,6 @@ class LLMRequestSubStage(Stage): else: yield - # 执行 LLM 响应后的事件钩子。 - handlers = star_handlers_registry.get_handlers_by_event_type( - EventType.OnLLMResponseEvent - ) - for handler in handlers: - try: - logger.debug( - f"hook(on_llm_response) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}" - ) - await handler.handler(event, llm_response) - except BaseException: - logger.error(traceback.format_exc()) - - if event.is_stopped(): - logger.info( - f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。" - ) - return - asyncio.create_task( Metric.upload( llm_tick=1, From 3e325debcc9fc758ab3749a6290b543cc16fbde7 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Wed, 26 Mar 2025 20:51:53 +0800 Subject: [PATCH 22/46] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f7dff593..8d1f17ebc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- -![6e1279651f16d7fdf4727558b72bbaf1](https://github.com/user-attachments/assets/ead4c551-fc3c-48f7-a6f7-afbfdb820512) + +![yjtp](https://github.com/user-attachments/assets/dcc74009-c57e-4b66-9ae3-0a81fc001255)

From 005e9eae7c15ee2c766b5da67f64e50d537e7388 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Thu, 27 Mar 2025 10:04:40 +0800 Subject: [PATCH 23/46] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=B6=E6=B2=A1=E6=9C=89=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=8A=A0=E9=80=9F=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/star/updator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrbot/core/star/updator.py b/astrbot/core/star/updator.py index 5f98af9c1..d439e98cc 100644 --- a/astrbot/core/star/updator.py +++ b/astrbot/core/star/updator.py @@ -41,7 +41,7 @@ class PluginUpdator(RepoZipUpdator): plugin_path = os.path.join(self.plugin_store_path, plugin.root_dir_name) logger.info(f"正在更新插件,路径: {plugin_path},仓库地址: {repo_url}") - await self.download_from_repo_url(plugin_path, repo_url) + await self.download_from_repo_url(plugin_path, repo_url, proxy=proxy) try: remove_dir(plugin_path) From 42ff9a4d3433bdd3dbfb6056f19b50a92634fe84 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:39:18 +0800 Subject: [PATCH 24/46] Update README.md --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8d1f17ebc..98ae9be72 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,18 @@ AstrBot 是一个松耦合、异步、支持多消息平台部署、具有易用 +## ✨ 近期更新 + +1. AstrBot 现已支持接入 [MCP](https://modelcontextprotocol.io/) 服务器! + ## ✨ 主要功能 1. **大语言模型对话**。支持各种大语言模型,包括 OpenAI API、Google Gemini、Llama、Deepseek、ChatGLM 等,支持接入本地部署的大模型,通过 Ollama、LLMTuner。具有多轮对话、人格情境、多模态能力,支持图片理解、语音转文字(Whisper)。 -2. **支持 MCP**。AstrBot 现已支持接入 MCP 服务器。 -3. **多消息平台接入**。支持接入 QQ(OneBot)、QQ 频道、微信(Gewechat)、飞书、Telegram。后续将支持钉钉、Discord、WhatsApp、小爱音响。支持速率限制、白名单、关键词过滤、百度内容审核。 -4. **Agent**。原生支持部分 Agent 能力,如代码执行器、自然语言待办、网页搜索。对接 [Dify 平台](https://astrbot.app/others/dify.html),便捷接入 Dify 智能助手、知识库和 Dify 工作流。 -5. **插件扩展**。深度优化的插件机制,支持[开发插件](https://astrbot.app/dev/plugin.html)扩展功能,极简开发。已支持安装多个插件。 -6. **可视化管理面板**。支持可视化修改配置、插件管理、日志查看等功能,降低配置难度。集成 WebChat,可在面板上与大模型对话。 -7. **高稳定性、高模块化**。基于事件总线和流水线的架构设计,高度模块化,低耦合。 +2. **多消息平台接入**。支持接入 QQ(OneBot)、QQ 频道、微信(Gewechat)、飞书、Telegram。后续将支持钉钉、Discord、WhatsApp、小爱音响。支持速率限制、白名单、关键词过滤、百度内容审核。 +3. **Agent**。原生支持部分 Agent 能力,如代码执行器、自然语言待办、网页搜索。对接 [Dify 平台](https://astrbot.app/others/dify.html),便捷接入 Dify 智能助手、知识库和 Dify 工作流。 +4. **插件扩展**。深度优化的插件机制,支持[开发插件](https://astrbot.app/dev/plugin.html)扩展功能,极简开发。已支持安装多个插件。 +5. **可视化管理面板**。支持可视化修改配置、插件管理、日志查看等功能,降低配置难度。集成 WebChat,可在面板上与大模型对话。 +6. **高稳定性、高模块化**。基于事件总线和流水线的架构设计,高度模块化,低耦合。 > [!TIP] > 管理面板在线体验 Demo: [https://demo.astrbot.app/](https://demo.astrbot.app/) From fb85a7bb354a26ac6b675619fe394057c2cb5e25 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Thu, 27 Mar 2025 15:54:23 +0800 Subject: [PATCH 25/46] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20demo=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/__init__.py | 1 + astrbot/dashboard/routes/auth.py | 9 +++++- astrbot/dashboard/routes/plugin.py | 50 ++++++++++++++++++++++++++++++ astrbot/dashboard/routes/stat.py | 8 +++++ astrbot/dashboard/routes/update.py | 8 +++++ 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/astrbot/core/__init__.py b/astrbot/core/__init__.py index 9749dee24..cfedf92e5 100644 --- a/astrbot/core/__init__.py +++ b/astrbot/core/__init__.py @@ -24,3 +24,4 @@ pip_installer = PipInstaller(astrbot_config.get("pip_install_arg", "")) web_chat_queue = asyncio.Queue(maxsize=32) web_chat_back_queue = asyncio.Queue(maxsize=32) WEBUI_SK = "Advanced_System_for_Text_Response_and_Bot_Operations_Tool" +DEMO_MODE = os.getenv("DEMO_MODE", False) diff --git a/astrbot/dashboard/routes/auth.py b/astrbot/dashboard/routes/auth.py index 34ba8fd3f..896bea4e7 100644 --- a/astrbot/dashboard/routes/auth.py +++ b/astrbot/dashboard/routes/auth.py @@ -2,7 +2,7 @@ import jwt import datetime from .route import Route, Response, RouteContext from quart import request -from astrbot.core import WEBUI_SK +from astrbot.core import WEBUI_SK, DEMO_MODE from astrbot import logger @@ -40,6 +40,13 @@ class AuthRoute(Route): return Response().error("用户名或密码错误").__dict__ async def edit_account(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + password = self.config["dashboard"]["password"] post_data = await request.json diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py index 32dc99176..3ffa1c557 100644 --- a/astrbot/dashboard/routes/plugin.py +++ b/astrbot/dashboard/routes/plugin.py @@ -15,6 +15,7 @@ from astrbot.core.star.filter.command_group import CommandGroupFilter from astrbot.core.star.filter.permission import PermissionTypeFilter from astrbot.core.star.filter.regex import RegexFilter from astrbot.core.star.star_handler import EventType +from astrbot.core import DEMO_MODE class PluginRoute(Route): @@ -50,6 +51,13 @@ class PluginRoute(Route): } async def reload_plugins(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + data = await request.json plugin_name = data.get("name", None) try: @@ -187,6 +195,13 @@ class PluginRoute(Route): return handlers async def install_plugin(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + post_data = await request.json repo_url = post_data["url"] @@ -205,6 +220,13 @@ class PluginRoute(Route): return Response().error(str(e)).__dict__ async def install_plugin_upload(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + try: file = await request.files file = file["file"] @@ -220,6 +242,13 @@ class PluginRoute(Route): return Response().error(str(e)).__dict__ async def uninstall_plugin(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + post_data = await request.json plugin_name = post_data["name"] try: @@ -232,6 +261,13 @@ class PluginRoute(Route): return Response().error(str(e)).__dict__ async def update_plugin(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + post_data = await request.json plugin_name = post_data["name"] proxy: str = post_data.get("proxy", None) @@ -247,6 +283,13 @@ class PluginRoute(Route): return Response().error(str(e)).__dict__ async def off_plugin(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + post_data = await request.json plugin_name = post_data["name"] try: @@ -258,6 +301,13 @@ class PluginRoute(Route): return Response().error(str(e)).__dict__ async def on_plugin(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + post_data = await request.json plugin_name = post_data["name"] try: diff --git a/astrbot/dashboard/routes/stat.py b/astrbot/dashboard/routes/stat.py index e73c09455..337e544af 100644 --- a/astrbot/dashboard/routes/stat.py +++ b/astrbot/dashboard/routes/stat.py @@ -8,6 +8,7 @@ from quart import request from astrbot.core.core_lifecycle import AstrBotCoreLifecycle from astrbot.core.db import BaseDatabase from astrbot.core.config import VERSION +from astrbot.core import DEMO_MODE class StatRoute(Route): @@ -29,6 +30,13 @@ class StatRoute(Route): self.core_lifecycle = core_lifecycle async def restart_core(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + await self.core_lifecycle.restart() return Response().ok().__dict__ diff --git a/astrbot/dashboard/routes/update.py b/astrbot/dashboard/routes/update.py index e9ada18f5..bd0782088 100644 --- a/astrbot/dashboard/routes/update.py +++ b/astrbot/dashboard/routes/update.py @@ -6,6 +6,7 @@ from astrbot.core.updator import AstrBotUpdator from astrbot.core import logger, pip_installer from astrbot.core.utils.io import download_dashboard, get_dashboard_version from astrbot.core.config.default import VERSION +from astrbot.core import DEMO_MODE class UpdateRoute(Route): @@ -126,6 +127,13 @@ class UpdateRoute(Route): return Response().error(e.__str__()).__dict__ async def install_pip_package(self): + if DEMO_MODE: + return ( + Response() + .error("You are not permitted to do this operation in demo mode") + .__dict__ + ) + data = await request.json package = data.get("package", "") if not package: From c7484d0cc9b62df5170f417907786616494afed4 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Fri, 28 Mar 2025 13:23:29 +0800 Subject: [PATCH 26/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98ae9be72..23247b2b9 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ AstrBot 是一个松耦合、异步、支持多消息平台部署、具有易用 > [!TIP] > 管理面板在线体验 Demo: [https://demo.astrbot.app/](https://demo.astrbot.app/) > -> 用户名: `astrbot`, 密码: `astrbot`。未配置 LLM,无法在聊天页使用大模型。(不要再修改 demo 的登录密码了 😭) +> 用户名: `astrbot`, 密码: `astrbot`。未配置 LLM,无法在聊天页使用大模型。 ## ✨ 使用方式 From c33c07e4af7395335cb429fbfce1de1818a67bdc Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Fri, 28 Mar 2025 13:24:41 +0800 Subject: [PATCH 27/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23247b2b9..a5dd762ae 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ _✨ 易上手的多平台 LLM 聊天机器人及开发框架 ✨_ Docker pull Static Badge [![wakatime](https://wakatime.com/badge/user/915e5316-99c6-4563-a483-ef186cf000c9/project/018e705a-a1a7-409a-a849-3013485e6c8e.svg?style=for-the-badge&color=76bad9)](https://wakatime.com/badge/user/915e5316-99c6-4563-a483-ef186cf000c9/project/018e705a-a1a7-409a-a849-3013485e6c8e) -![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fstats&query=v&label=7%E6%97%A5%E6%B4%BB%E8%B7%83%E9%87%8F&cacheSeconds=60&style=for-the-badge&color=3b618e) +![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fstats&query=v&label=7%E6%97%A5%E6%B4%BB%E8%B7%83%E9%87%8F&cacheSeconds=3600&style=for-the-badge&color=3b618e) English日本語 | From 93a1699a3525f7e157804a3d240b388c00da0d17 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Fri, 28 Mar 2025 18:20:33 +0800 Subject: [PATCH 28/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5dd762ae..7493d557d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ _✨ 易上手的多平台 LLM 聊天机器人及开发框架 ✨_ Docker pull Static Badge [![wakatime](https://wakatime.com/badge/user/915e5316-99c6-4563-a483-ef186cf000c9/project/018e705a-a1a7-409a-a849-3013485e6c8e.svg?style=for-the-badge&color=76bad9)](https://wakatime.com/badge/user/915e5316-99c6-4563-a483-ef186cf000c9/project/018e705a-a1a7-409a-a849-3013485e6c8e) -![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fstats&query=v&label=7%E6%97%A5%E6%B4%BB%E8%B7%83%E9%87%8F&cacheSeconds=3600&style=for-the-badge&color=3b618e) +![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fstats&query=v&label=7%E6%97%A5%E6%B4%BB%E8%B7%83%E9%87%8F&cacheSeconds=10800&style=for-the-badge&color=3b618e) English日本語 | From 20e41a7f737a1f05bfbbe91594011f418c590ad9 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Fri, 28 Mar 2025 23:45:19 +0800 Subject: [PATCH 29/46] =?UTF-8?q?=F0=9F=90=9B=20fix:=20newgroup=20?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E5=90=8D=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/astrbot/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index 1f5f23cd8..30d6d99ce 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -760,7 +760,7 @@ UID: {user_id} 此 ID 可用于设置管理员。 ) else: message.set_result( - MessageEventResult().message("请输入群聊 ID。/newgroup 群聊ID。") + MessageEventResult().message("请输入群聊 ID。/groupnew 群聊ID。") ) @filter.command("switch") From 8a0665b2225f2555ca91c0042a3a1e22216c6cf9 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 29 Mar 2025 16:36:02 +0800 Subject: [PATCH 30/46] =?UTF-8?q?=F0=9F=8E=88=20feat:=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20Dockerfile=EF=BC=8C=E6=B7=BB=E5=8A=A0=20Node.js=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BE=9D=E8=B5=96=E5=AE=89?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 8 +++++--- Dockerfile_with_node | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Dockerfile_with_node diff --git a/Dockerfile b/Dockerfile index 0b46d5d48..1b2314970 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,12 +9,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3-dev \ libffi-dev \ libssl-dev \ + ca-certificates \ + bash \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -RUN python -m pip install -r requirements.txt --no-cache-dir - -RUN python -m pip install socksio wechatpy cryptography --no-cache-dir +RUN python -m pip install uv +RUN uv pip install -r requirements.txt --no-cache-dir --system +RUN uv pip install socksio uv pyffmpeg --no-cache-dir --system EXPOSE 6185 EXPOSE 6186 diff --git a/Dockerfile_with_node b/Dockerfile_with_node new file mode 100644 index 000000000..3bd37468a --- /dev/null +++ b/Dockerfile_with_node @@ -0,0 +1,35 @@ +FROM python:3.10-slim + +WORKDIR /AstrBot + +COPY . /AstrBot/ + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + build-essential \ + python3-dev \ + libffi-dev \ + libssl-dev \ + curl \ + unzip \ + ca-certificates \ + bash \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Installation of Node.js +ENV NVM_DIR="/root/.nvm" +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash && \ + . "$NVM_DIR/nvm.sh" && \ + nvm install 22 && \ + nvm use 22 +RUN /bin/bash -c ". \"$NVM_DIR/nvm.sh\" && node -v && npm -v" + +RUN python -m pip install uv +RUN uv pip install -r requirements.txt --no-cache-dir --system +RUN uv pip install socksio uv pyffmpeg --no-cache-dir --system + +EXPOSE 6185 +EXPOSE 6186 + +CMD ["python", "main.py"] From 93f525e3fea6e4b0cafee85fe76332cd6cfb2025 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 29 Mar 2025 17:48:22 +0800 Subject: [PATCH 31/46] =?UTF-8?q?=F0=9F=8E=88=20perf:=20edge=20tts=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BD=BF=E7=94=A8=E4=BB=A3=E7=90=86=EF=BC=9B?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=BA=86=E4=B8=80=E4=BA=9B=E4=B8=8D=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/provider/sources/edge_tts_source.py | 70 +++++++++++-------- .../sources/sensevoice_selfhosted_source.py | 8 --- .../provider/sources/whisper_api_source.py | 8 --- .../sources/whisper_selfhosted_source.py | 8 --- 4 files changed, 42 insertions(+), 52 deletions(-) diff --git a/astrbot/core/provider/sources/edge_tts_source.py b/astrbot/core/provider/sources/edge_tts_source.py index c7887d3ea..b6b758e29 100644 --- a/astrbot/core/provider/sources/edge_tts_source.py +++ b/astrbot/core/provider/sources/edge_tts_source.py @@ -35,6 +35,8 @@ class ProviderEdgeTTS(TTSProvider): self.pitch = provider_config.get("pitch", None) self.timeout = provider_config.get("timeout", 30) + self.proxy = os.getenv("https_proxy", None) + self.set_model("edge_tts") async def get_audio(self, text: str) -> str: @@ -42,7 +44,7 @@ class ProviderEdgeTTS(TTSProvider): mp3_path = f"data/temp/edge_tts_temp_{uuid.uuid4()}.mp3" wav_path = f"data/temp/edge_tts_{uuid.uuid4()}.wav" - # 构建Edge TTS参数 + # 构建 Edge TTS 参数 kwargs = {"text": text, "voice": self.voice} if self.rate: kwargs["rate"] = self.rate @@ -52,35 +54,47 @@ class ProviderEdgeTTS(TTSProvider): kwargs["pitch"] = self.pitch try: - communicate = edge_tts.Communicate(**kwargs) + communicate = edge_tts.Communicate(proxy=self.proxy, **kwargs) await communicate.save(mp3_path) - # 使用ffmpeg将MP3转换为标准WAV格式 - _ = await asyncio.create_subprocess_exec( - "ffmpeg", - "-y", # 覆盖输出文件 - "-i", - mp3_path, # 输入文件 - "-acodec", - "pcm_s16le", # 16位PCM编码 - "-ar", - "24000", # 采样率24kHz (适合微信语音) - "-ac", - "1", # 单声道 - "-af", - "apad=pad_dur=2", # 确保输出时长准确 - "-fflags", - "+genpts", # 强制生成时间戳 - "-hide_banner", # 隐藏版本信息 - wav_path, # 输出文件 - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - # 等待进程完成并获取输出 - stdout, stderr = await _.communicate() - logger.info(f"[EdgeTTS] FFmpeg 标准输出: {stdout.decode().strip()}") - logger.debug(f"FFmpeg错误输出: {stderr.decode().strip()}") - logger.info(f"[EdgeTTS] 返回值(0代表成功): {_.returncode}") + try: + from pyffmpeg import FFmpeg + + ff = FFmpeg() + ff.convert(input=mp3_path, output=wav_path) + except Exception as e: + logger.debug( + f"pyffmpeg 转换失败: {e}, 尝试使用 ffmpeg 命令行进行转换" + ) + # use ffmpeg command line + + # 使用ffmpeg将MP3转换为标准WAV格式 + p = await asyncio.create_subprocess_exec( + "ffmpeg", + "-y", # 覆盖输出文件 + "-i", + mp3_path, # 输入文件 + "-acodec", + "pcm_s16le", # 16位PCM编码 + "-ar", + "24000", # 采样率24kHz (适合微信语音) + "-ac", + "1", # 单声道 + "-af", + "apad=pad_dur=2", # 确保输出时长准确 + "-fflags", + "+genpts", # 强制生成时间戳 + "-hide_banner", # 隐藏版本信息 + wav_path, # 输出文件 + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + # 等待进程完成并获取输出 + stdout, stderr = await p.communicate() + logger.info(f"[EdgeTTS] FFmpeg 标准输出: {stdout.decode().strip()}") + logger.debug(f"FFmpeg错误输出: {stderr.decode().strip()}") + logger.info(f"[EdgeTTS] 返回值(0代表成功): {p.returncode}") + os.remove(mp3_path) if os.path.exists(wav_path) and os.path.getsize(wav_path) > 0: return wav_path diff --git a/astrbot/core/provider/sources/sensevoice_selfhosted_source.py b/astrbot/core/provider/sources/sensevoice_selfhosted_source.py index 4842b0e04..84087ecf6 100644 --- a/astrbot/core/provider/sources/sensevoice_selfhosted_source.py +++ b/astrbot/core/provider/sources/sensevoice_selfhosted_source.py @@ -48,14 +48,6 @@ class ProviderSenseVoiceSTTSelfHost(STTProvider): timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") return os.path.join("data", "temp", f"{timestamp}") - async def _convert_audio(self, path: str) -> str: - from pyffmpeg import FFmpeg - - filename = await self.get_timestamped_path() + ".mp3" - ff = FFmpeg() - output_path = ff.convert(path, os.path.join('data","temp', filename)) - return output_path - async def _is_silk_file(self, file_path): silk_header = b"SILK" with open(file_path, "rb") as f: diff --git a/astrbot/core/provider/sources/whisper_api_source.py b/astrbot/core/provider/sources/whisper_api_source.py index ce474f4ef..e38a81de9 100644 --- a/astrbot/core/provider/sources/whisper_api_source.py +++ b/astrbot/core/provider/sources/whisper_api_source.py @@ -31,14 +31,6 @@ class ProviderOpenAIWhisperAPI(STTProvider): self.set_model(provider_config.get("model", None)) - async def _convert_audio(self, path: str) -> str: - from pyffmpeg import FFmpeg - - filename = str(uuid.uuid4()) + ".mp3" - ff = FFmpeg() - output_path = ff.convert(path, os.path.join("data/temp", filename)) - return output_path - async def _is_silk_file(self, file_path): silk_header = b"SILK" with open(file_path, "rb") as f: diff --git a/astrbot/core/provider/sources/whisper_selfhosted_source.py b/astrbot/core/provider/sources/whisper_selfhosted_source.py index 1bbc2a1dc..cfd1267d0 100644 --- a/astrbot/core/provider/sources/whisper_selfhosted_source.py +++ b/astrbot/core/provider/sources/whisper_selfhosted_source.py @@ -33,14 +33,6 @@ class ProviderOpenAIWhisperSelfHost(STTProvider): ) logger.info("Whisper 模型加载完成。") - async def _convert_audio(self, path: str) -> str: - from pyffmpeg import FFmpeg - - filename = str(uuid.uuid4()) + ".mp3" - ff = FFmpeg() - output_path = ff.convert(path, os.path.join("data/temp", filename)) - return output_path - async def _is_silk_file(self, file_path): silk_header = b"SILK" with open(file_path, "rb") as f: From bd92aac28052e3172698b19496275202abb520a2 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 29 Mar 2025 18:31:07 +0800 Subject: [PATCH 32/46] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=94=AF=E6=8C=81=20?= =?UTF-8?q?/llm=20=E6=8C=87=E4=BB=A4=E5=BF=AB=E6=8D=B7=E5=90=AF=E5=81=9C?= =?UTF-8?q?=20LLM=20=E5=8A=9F=E8=83=BD=20#296?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/astrbot/main.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index 30d6d99ce..fc5ee3f32 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -88,6 +88,7 @@ class Main(star.Star): /alter_cmd: 设置指令权限(op) [大模型] +/llm: 开启/关闭 LLM /provider: 大模型提供商 /model: 模型列表 /ls: 对话列表 @@ -96,7 +97,7 @@ class Main(star.Star): /switch 序号: 切换对话 /rename 新名字: 重命名当前对话 /del: 删除当前会话对话(op) -/reset: 重置 LLM 会话(op) +/reset: 重置 LLM 会话 /history: 当前对话的对话记录 /persona: 人格情景(op) /tool ls: 函数工具 @@ -106,6 +107,20 @@ class Main(star.Star): event.set_result(MessageEventResult().message(msg).use_t2i(False)) + @filter.command("llm") + async def llm(self, event: AstrMessageEvent): + """开启/关闭 LLM""" + cfg = self.context.get_config() + enable = cfg["provider_settings"]["enable"] + if enable: + cfg["provider_settings"]["enable"] = False + status = "关闭" + else: + cfg["provider_settings"]["enable"] = True + status = "开启" + cfg.save_config() + yield event.plain_result(f"{status} LLM 聊天功能。") + @filter.command_group("tool") def tool(self): pass @@ -529,9 +544,7 @@ UID: {user_id} 此 ID 可用于设置管理员。 try: models = await self.context.get_using_provider().get_models() except BaseException as e: - err_msg = api_key_pattern.sub( - "key=***", str(e) - ) + err_msg = api_key_pattern.sub("key=***", str(e)) message.set_result( MessageEventResult() .message("获取模型列表失败: " + err_msg) From b6c3089510ef1fb0fe6a8d400ee7cb397cf2866c Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 29 Mar 2025 19:09:35 +0800 Subject: [PATCH 33/46] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=A9=BA=20at=20=E5=9B=9E=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/session_controller/main.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/session_controller/main.py b/packages/session_controller/main.py index 6bf3f71fb..95016df1b 100644 --- a/packages/session_controller/main.py +++ b/packages/session_controller/main.py @@ -83,7 +83,7 @@ class Waiter(Star): # 使用 LLM 生成回复 yield event.request_llm( - prompt="用户只是@我或唤醒我,请友好地询问用户想要聊些什么或者需要什么帮助,回复要符合人设,不要太过机械化。", + prompt="用户只是@我或唤醒我,请友好地询问用户想要聊些什么或者需要什么帮助,回复要符合人设,不要太过机械化。仅输出要回复内容。", func_tool_manager=func_tools_mgr, session_id=curr_cid, contexts=context, @@ -113,16 +113,7 @@ class Waiter(Star): try: await empty_mention_waiter(event) except TimeoutError as _: - try: - # 超时时也尝试使用 LLM 生成回复 - yield event.request_llm( - prompt="用户在提问后超时未回复,请生成一个温馨友好的提醒,告诉用户如果需要帮助可以再次提问,回答要符合人设。", - func_tool_manager=self.context.get_llm_tool_manager(), - system_prompt="", - ) - except Exception: - # LLM 回复失败,使用原始预设回复 - yield event.plain_result("如果需要帮助,请再次 @ 我哦~") + pass except Exception as e: yield event.plain_result("发生错误,请联系管理员: " + str(e)) finally: From 3d59ab8108057ef33b8aa4e4d078f6bf29528d47 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 29 Mar 2025 19:17:56 +0800 Subject: [PATCH 34/46] fix: conversation and tool use page refresh 404 --- astrbot/dashboard/routes/static_file.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/astrbot/dashboard/routes/static_file.py b/astrbot/dashboard/routes/static_file.py index 7a673fd15..5d4c05c6b 100644 --- a/astrbot/dashboard/routes/static_file.py +++ b/astrbot/dashboard/routes/static_file.py @@ -20,6 +20,8 @@ class StaticFileRoute(Route): "/providers", "/about", "/extension-marketplace", + "/conversation", + "/tool-use" ] for i in index_: self.app.add_url_rule(i, view_func=self.index) From b70b3b158e3eefcdc17fcd950cf84dd262f87e3c Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 29 Mar 2025 20:51:27 +0800 Subject: [PATCH 35/46] =?UTF-8?q?=E2=9C=A8feat:=20=E6=94=AF=E6=8C=81=20gem?= =?UTF-8?q?ini-2.0-flash-exp-image-generation=20=E5=AF=B9=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=A8=A1=E6=80=81=E7=9A=84=E8=BE=93=E5=85=A5=20#1017?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 8 +- .../core/provider/sources/gemini_source.py | 94 +++++++++++-------- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index c45c8d813..76404cfa4 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -519,8 +519,9 @@ CONFIG_METADATA_2 = { "api_base": "https://generativelanguage.googleapis.com/", "timeout": 120, "model_config": { - "model": "gemini-1.5-flash", + "model": "gemini-2.0-flash-exp", }, + "gm_resp_image_modal": False, }, "DeepSeek": { "id": "deepseek_default", @@ -672,6 +673,11 @@ CONFIG_METADATA_2 = { }, }, "items": { + "gm_resp_image_modal": { + "description": "启用图片模态", + "type": "bool", + "hint": "启用后,将支持返回图片内容。需要模型支持,否则会报错。具体支持模型请查看 Google Gemini 官方网站。温馨提示,如果您需要生成图片,请关闭 `启用群员识别` 配置获得更好的效果。", + }, "rag_options": { "description": "RAG 选项", "type": "object", diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index 90a584235..e9d1b50a5 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -2,6 +2,8 @@ import base64 import aiohttp import json import random +import astrbot.core.message.components as Comp +from astrbot.core.message.message_event_result import MessageChain from astrbot.core.utils.io import download_image_by_url from astrbot.core.db import BaseDatabase from astrbot.api.provider import Provider, Personality @@ -39,6 +41,7 @@ class SimpleGoogleGenAIClient: model: str = "gemini-1.5-flash", system_instruction: str = "", tools: dict = None, + modalities: List[str] = ["Text"], ): payload = {} if system_instruction: @@ -46,6 +49,9 @@ class SimpleGoogleGenAIClient: if tools: payload["tools"] = [tools] payload["contents"] = contents + payload["generationConfig"] = { + "responseModalities": modalities, + } logger.debug(f"payload: {payload}") request_url = ( f"{self.api_base}/v1beta/models/{model}:generateContent?key={self.api_key}" @@ -185,22 +191,53 @@ class ProviderGoogleGenAI(Provider): logger.debug(f"google_genai_conversation: {google_genai_conversation}") - result = await self.client.generate_content( - contents=google_genai_conversation, - model=self.get_model(), - system_instruction=system_instruction, - tools=tool, - ) - logger.debug(f"result: {result}") + modalites = ["Text"] + if self.provider_config.get("gm_resp_image_modal", False): + modalites.append("Image") - if "candidates" not in result: - raise Exception("Gemini 返回异常结果: " + str(result)) + loop = True + while loop: + loop = False + result = await self.client.generate_content( + contents=google_genai_conversation, + model=self.get_model(), + system_instruction=system_instruction, + tools=tool, + modalities=modalites, + ) + logger.debug(f"result: {result}") + + # Developer instruction is not enabled for models/gemini-2.0-flash-exp + if "Developer instruction is not enabled" in str(result): + logger.warning( + f"{self.get_model()} 不支持 system prompt, 已自动去除, 将会影响人格设置。" + ) + system_instruction = "" + loop = True + + elif "Function calling is not enabled" in str(result): + logger.warning( + f"{self.get_model()} 不支持函数调用,已自动去除,不影响使用。" + ) + tool = None + loop = True + + elif "Multi-modal output is not supported" in str(result): + logger.warning( + f"{self.get_model()} 不支持多模态输出,降级为文本模态重新请求。" + ) + modalites = ["Text"] + loop = True + + elif "candidates" not in result: + raise Exception("Gemini 返回异常结果: " + str(result)) candidates = result["candidates"][0]["content"]["parts"] llm_response = LLMResponse("assistant") + chain = [] for candidate in candidates: if "text" in candidate: - llm_response.completion_text += candidate["text"] + chain.append(Comp.Plain(candidate["text"])) elif "functionCall" in candidate: llm_response.role = "tool" llm_response.tools_call_args.append(candidate["functionCall"]["args"]) @@ -208,8 +245,12 @@ class ProviderGoogleGenAI(Provider): llm_response.tools_call_ids.append( candidate["functionCall"]["name"] ) # 没有 tool id + elif "inlineData" in candidate: + mime_type: str = candidate["inlineData"]["mimeType"] + if mime_type.startswith("image/"): + chain.append(Comp.Image.fromBase64(candidate["inlineData"]["data"])) - llm_response.completion_text = llm_response.completion_text.strip() + llm_response.result_chain = MessageChain(chain=chain) return llm_response async def text_chat( @@ -253,34 +294,7 @@ class ProviderGoogleGenAI(Provider): llm_response = await self._query(payloads, func_tool) break except Exception as e: - if "maximum context length" in str(e): - retry_cnt = 20 - while retry_cnt > 0: - logger.warning( - f"请求失败:{e}。上下文长度超过限制。尝试弹出最早的记录然后重试。当前记录条数: {len(context_query)}" - ) - try: - await self.pop_record(context_query) - llm_response = await self._query(payloads, func_tool) - break - except Exception as e: - if "maximum context length" in str(e): - retry_cnt -= 1 - else: - raise e - if retry_cnt == 0: - llm_response = LLMResponse( - "err", "err: 请尝试 /reset 重置会话" - ) - elif "Function calling is not enabled" in str(e): - logger.info( - f"{self.get_model()} 不支持函数工具调用,已自动去除,不影响使用。" - ) - if "tools" in payloads: - del payloads["tools"] - llm_response = await self._query(payloads, None) - break - elif "429" in str(e) or "API key not valid" in str(e): + if "429" in str(e) or "API key not valid" in str(e): keys.remove(chosen_key) if len(keys) > 0: chosen_key = random.choice(keys) @@ -292,7 +306,7 @@ class ProviderGoogleGenAI(Provider): logger.error( f"检测到 Key 异常({str(e)}),且已没有可用的 Key。 当前 Key: {chosen_key[:12]}..." ) - raise Exception("API 资源已耗尽,且没有可用的 Key 重试...") + raise Exception("达到了 Gemini 速率限制, 请稍后再试...") else: logger.error( f"发生了错误(gemini_source)。Provider 配置如下: {self.provider_config}" From c6c4a3228320c4fff423e8423d02aea25b369251 Mon Sep 17 00:00:00 2001 From: Gao Jinzhe <2968474907@qq.com> Date: Sat, 29 Mar 2025 22:37:18 +0800 Subject: [PATCH 36/46] Add files via upload --- packages/session_controller/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/session_controller/main.py b/packages/session_controller/main.py index 95016df1b..6c00bc81d 100644 --- a/packages/session_controller/main.py +++ b/packages/session_controller/main.py @@ -83,10 +83,10 @@ class Waiter(Star): # 使用 LLM 生成回复 yield event.request_llm( - prompt="用户只是@我或唤醒我,请友好地询问用户想要聊些什么或者需要什么帮助,回复要符合人设,不要太过机械化。仅输出要回复内容。", + prompt="注意,你正在社交媒体上中与用户进行聊天,用户只是通过@来唤醒你,但并未在这条消息中输入内容,他可能会在接下来一条发送他想发送的内容。请你友好地询问用户想要聊些什么或者需要什么帮助,回复要符合人设,不要太过机械化。注意,你仅需要输出要回复用户的内容,不要输出其他任何东西", func_tool_manager=func_tools_mgr, session_id=curr_cid, - contexts=context, + contexts=[], system_prompt="", conversation=conversation, ) From b63b606a4e06986b07f1ae5190be9b66f9837bdc Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 30 Mar 2025 10:39:07 +0800 Subject: [PATCH 37/46] =?UTF-8?q?docs:=20=E6=8E=A8=E8=8D=90=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20uv=20=E8=BF=9B=E8=A1=8C=E6=89=8B=E5=8A=A8=E9=83=A8?= =?UTF-8?q?=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7493d557d..6a7a22086 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,15 @@ AstrBot 是一个松耦合、异步、支持多消息平台部署、具有易用 #### 手动部署 -请参阅官方文档 [通过源码部署 AstrBot](https://astrbot.app/deploy/astrbot/cli.html) 。 +推荐使用 `uv`。 + +```bash +git clone https://github.com/AstrBotDevs/AstrBot && cd AstrBot +pip install uv +uv run main.py +``` + +或者请参阅官方文档 [通过源码部署 AstrBot](https://astrbot.app/deploy/astrbot/cli.html) 。 #### Replit 部署 From 1a92edf8bea02ce18821c5e1fbd947f84bc3b205 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Sun, 30 Mar 2025 14:38:40 +0800 Subject: [PATCH 38/46] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=97=A0?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=83=85=E5=86=B5=E4=B8=8B=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=BA=BA=E6=A0=BC=E7=9A=84=E5=8F=8D=E9=A6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/astrbot/main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index fc5ee3f32..3887fc929 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -1018,6 +1018,13 @@ UID: {user_id} 此 ID 可用于设置管理员。 message.set_result(MessageEventResult().message("取消人格成功。")) else: ps = "".join(l[1:]).strip() + if not cid: + message.set_result( + MessageEventResult().message( + "当前没有对话,请先开始对话或使用 /new 创建一个对话。" + ) + ) + return if persona := next( builtins.filter( lambda persona: persona["name"] == ps, From fe76c41ed865961ce16cc3d16bc349a9da8e3c43 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Sun, 30 Mar 2025 15:18:48 +0800 Subject: [PATCH 39/46] =?UTF-8?q?perf:=20=E8=8B=A5=E7=A6=81=E7=94=A8?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E5=95=86=EF=BC=8C=E8=87=AA=E5=8A=A8=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=88=B0=E5=8F=A6=E4=B8=80=E4=B8=AA=E5=8F=AF=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E6=8F=90=E4=BE=9B=E5=95=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/manager.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index ef9040445..71647a5ac 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -306,10 +306,42 @@ class ProviderManager: if len(self.provider_insts) == 0: self.curr_provider_inst = None + elif ( + self.curr_provider_inst is None + and len(self.provider_insts) > 0 + and self.provider_enabled + ): + self.curr_provider_inst = self.provider_insts[0] + self.selected_provider_id = self.curr_provider_inst.meta().id + logger.info( + f"自动选择 {self.curr_provider_inst.meta().id} 作为当前提供商适配器。" + ) + if len(self.stt_provider_insts) == 0: self.curr_stt_provider_inst = None + elif ( + self.curr_stt_provider_inst is None + and len(self.stt_provider_insts) > 0 + and self.stt_enabled + ): + self.curr_stt_provider_inst = self.stt_provider_insts[0] + self.selected_stt_provider_id = self.curr_stt_provider_inst.meta().id + logger.info( + f"自动选择 {self.curr_stt_provider_inst.meta().id} 作为当前语音转文本提供商适配器。" + ) + if len(self.tts_provider_insts) == 0: self.curr_tts_provider_inst = None + elif ( + self.curr_tts_provider_inst is None + and len(self.tts_provider_insts) > 0 + and self.tts_enabled + ): + self.curr_tts_provider_inst = self.tts_provider_insts[0] + self.selected_tts_provider_id = self.curr_tts_provider_inst.meta().id + logger.info( + f"自动选择 {self.curr_tts_provider_inst.meta().id} 作为当前文本转语音提供商适配器。" + ) def get_insts(self): return self.provider_insts From 62e73299b1451865314364425e19570b91ee7266 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 30 Mar 2025 21:33:41 +0800 Subject: [PATCH 40/46] =?UTF-8?q?=F0=9F=90=9B=20fix:=20forcely=20write=20s?= =?UTF-8?q?hared=20preference=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note: this is a fast fix for recent feedbacks, we'll improve its performance. --- astrbot/core/utils/shared_preferences.py | 1 + 1 file changed, 1 insertion(+) diff --git a/astrbot/core/utils/shared_preferences.py b/astrbot/core/utils/shared_preferences.py index b469c9d71..bf88ba8db 100644 --- a/astrbot/core/utils/shared_preferences.py +++ b/astrbot/core/utils/shared_preferences.py @@ -16,6 +16,7 @@ class SharedPreferences: def _save_preferences(self): with open(self.path, "w") as f: json.dump(self._data, f, indent=4) + f.flush() def get(self, key, default=None): return self._data.get(key, default) From 7288348857e39f7bd211d38a10ae5162ca8f4bdb Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 30 Mar 2025 22:00:45 +0800 Subject: [PATCH 41/46] =?UTF-8?q?=F0=9F=8E=88=20perf:=20OpenAI=20sources?= =?UTF-8?q?=20supports=20api=20key=20load=20balance(random)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/provider/sources/openai_source.py | 124 +++++++++--------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 628b1849d..d452f09f4 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -2,6 +2,7 @@ import base64 import json import os import inspect +import random from openai import AsyncOpenAI, AsyncAzureOpenAI from openai.types.chat.chat_completion import ChatCompletion @@ -176,77 +177,78 @@ class ProviderOpenAIOfficial(Provider): payloads = {"messages": context_query, **model_config} llm_response = None - try: - llm_response = await self._query(payloads, func_tool) - except UnprocessableEntityError as e: - logger.warning(f"不可处理的实体错误:{e},尝试删除图片。") - # 尝试删除所有 image - new_contexts = await self._remove_image_from_context(context_query) - payloads["messages"] = new_contexts - context_query = new_contexts - llm_response = await self._query(payloads, func_tool) - except Exception as e: - if "maximum context length" in str(e): - # 重试 10 次 - retry_cnt = 20 - while retry_cnt > 0: - logger.warning( - f"上下文长度超过限制。尝试弹出最早的记录然后重试。当前记录条数: {len(context_query)}" - ) - try: - await self.pop_record(context_query) - llm_response = await self._query(payloads, func_tool) - break - except Exception as e: - if "maximum context length" in str(e): - retry_cnt -= 1 - else: - raise e - if retry_cnt == 0: - llm_response = LLMResponse( - "err", "err: 请尝试 /reset 清除会话记录。" - ) - elif "The model is not a VLM" in str(e): # siliconcloud + + retry = 10 + keys = self.api_keys.copy() + chosen_key = random.choice(keys) + + e = None + retry_cnt = 0 + for retry_cnt in range(retry): + try: + self.client.api_key = chosen_key + llm_response = await self._query(payloads, func_tool) + break + except UnprocessableEntityError as e: + logger.warning(f"不可处理的实体错误:{e},尝试删除图片。") # 尝试删除所有 image new_contexts = await self._remove_image_from_context(context_query) payloads["messages"] = new_contexts - llm_response = await self._query(payloads, func_tool) - - # openai, ollama, gemini openai, siliconcloud 的错误提示与 code 不统一,只能通过字符串匹配 - elif ( - "does not support Function Calling" in str(e) - or "does not support tools" in str(e) - or "Function call is not supported" in str(e) - or "Function calling is not enabled" in str(e) - or "Tool calling is not supported" in str(e) - or "No endpoints found that support tool use" in str(e) - or "model does not support function calling" in str(e) - or ("tool" in str(e) and "support" in str(e).lower()) - or ("function" in str(e) and "support" in str(e).lower()) - ): - logger.info( - f"{self.get_model()} 不支持函数工具调用,已自动去除,不影响使用。" - ) - if "tools" in payloads: - del payloads["tools"] - llm_response = await self._query(payloads, None) - else: - logger.error(f"发生了错误。Provider 配置如下: {self.provider_config}") - - if "tool" in str(e).lower() and "support" in str(e).lower(): + context_query = new_contexts + except Exception as e: + if "429" in str(e): + logger.warning( + f"API 调用过于频繁,尝试使用其他 Key 重试。当前 Key: {chosen_key[:12]}" + ) + keys.remove(chosen_key) + if len(keys) > 0: + chosen_key = random.choice(keys) + continue + else: + raise e + elif "maximum context length" in str(e): + logger.warning( + f"上下文长度超过限制。尝试弹出最早的记录然后重试。当前记录条数: {len(context_query)}" + ) + await self.pop_record(context_query) + elif "The model is not a VLM" in str(e): # siliconcloud + # 尝试删除所有 image + new_contexts = await self._remove_image_from_context(context_query) + payloads["messages"] = new_contexts + elif ( + "Function calling is not enabled" in str(e) + or ("tool" in str(e) and "support" in str(e).lower()) + or ("function" in str(e) and "support" in str(e).lower()) + ): + # openai, ollama, gemini openai, siliconcloud 的错误提示与 code 不统一,只能通过字符串匹配 + logger.info( + f"{self.get_model()} 不支持函数工具调用,已自动去除,不影响使用。" + ) + if "tools" in payloads: + del payloads["tools"] + func_tool = None + else: logger.error( - "疑似该模型不支持函数调用工具调用。请输入 /tool off_all" + f"发生了错误。Provider 配置如下: {self.provider_config}" ) - if "Connection error." in str(e): - proxy = os.environ.get("http_proxy", None) - if proxy: + if "tool" in str(e).lower() and "support" in str(e).lower(): logger.error( - f"可能为代理原因,请检查代理是否正常。当前代理: {proxy}" + "疑似该模型不支持函数调用工具调用。请输入 /tool off_all" ) - raise e + if "Connection error." in str(e): + proxy = os.environ.get("http_proxy", None) + if proxy: + logger.error( + f"可能为代理原因,请检查代理是否正常。当前代理: {proxy}" + ) + raise e + + if retry_cnt == retry - 1: + logger.error(f"API 调用失败,重试 {retry} 次仍然失败。") + raise e return llm_response async def _remove_image_from_context(self, contexts: List): From 76895fe86d48d0e156276b20ec5acedc7d3d8bd1 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 30 Mar 2025 22:12:34 +0800 Subject: [PATCH 42/46] chore: improve variable names --- astrbot/core/provider/sources/openai_source.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index d452f09f4..68d0bd6c0 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -178,13 +178,13 @@ class ProviderOpenAIOfficial(Provider): llm_response = None - retry = 10 - keys = self.api_keys.copy() - chosen_key = random.choice(keys) + max_retries = 10 + available_api_keys = self.api_keys.copy() + chosen_key = random.choice(available_api_keys) e = None retry_cnt = 0 - for retry_cnt in range(retry): + for retry_cnt in range(max_retries): try: self.client.api_key = chosen_key llm_response = await self._query(payloads, func_tool) @@ -200,9 +200,9 @@ class ProviderOpenAIOfficial(Provider): logger.warning( f"API 调用过于频繁,尝试使用其他 Key 重试。当前 Key: {chosen_key[:12]}" ) - keys.remove(chosen_key) - if len(keys) > 0: - chosen_key = random.choice(keys) + available_api_keys.remove(chosen_key) + if len(available_api_keys) > 0: + chosen_key = random.choice(available_api_keys) continue else: raise e @@ -246,8 +246,8 @@ class ProviderOpenAIOfficial(Provider): raise e - if retry_cnt == retry - 1: - logger.error(f"API 调用失败,重试 {retry} 次仍然失败。") + if retry_cnt == max_retries - 1: + logger.error(f"API 调用失败,重试 {max_retries} 次仍然失败。") raise e return llm_response From 627d2a4701c13144a03eba9e4ff37ae107b501c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=B8=A695676?= Date: Sun, 30 Mar 2025 22:33:21 +0800 Subject: [PATCH 43/46] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E9=97=B4=E9=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/openai_source.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 68d0bd6c0..32860826d 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -200,6 +200,9 @@ class ProviderOpenAIOfficial(Provider): logger.warning( f"API 调用过于频繁,尝试使用其他 Key 重试。当前 Key: {chosen_key[:12]}" ) + # 最后一次不等待 + if retry_cnt < max_retries - 1: + await asyncio.sleep(1) available_api_keys.remove(chosen_key) if len(available_api_keys) > 0: chosen_key = random.choice(available_api_keys) From 2eb9e5dde347b89b42dbd308bde66822b560fc58 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 30 Mar 2025 22:51:34 +0800 Subject: [PATCH 44/46] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=E9=87=8D?= =?UTF-8?q?=E8=AF=95=E7=AD=89=E5=BE=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/gemini_source.py | 2 ++ astrbot/core/provider/sources/openai_source.py | 1 + 2 files changed, 3 insertions(+) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index e9d1b50a5..3233b3453 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -2,6 +2,7 @@ import base64 import aiohttp import json import random +import asyncio import astrbot.core.message.components as Comp from astrbot.core.message.message_event_result import MessageChain from astrbot.core.utils.io import download_image_by_url @@ -301,6 +302,7 @@ class ProviderGoogleGenAI(Provider): logger.info( f"检测到 Key 异常({str(e)}),正在尝试更换 API Key 重试... 当前 Key: {chosen_key[:12]}..." ) + await asyncio.sleep(1) continue else: logger.error( diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 32860826d..766835719 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -3,6 +3,7 @@ import json import os import inspect import random +import asyncio from openai import AsyncOpenAI, AsyncAzureOpenAI from openai.types.chat.chat_completion import ChatCompletion From fff1f23a8305aa9cf770057778d23515f0d8bcaa Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Mon, 31 Mar 2025 00:57:23 +0800 Subject: [PATCH 45/46] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6a7a22086..80bae6c8b 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,9 @@ AstrBot 是一个松耦合、异步、支持多消息平台部署、具有易用 ## ✨ 主要功能 +> [!NOTE] +> 🪧 我们正基于前沿科研成果,设计并实现适用于角色扮演和情感陪伴的长短期记忆模型及情绪控制模型,旨在提升对话的真实性与情感表达能力。敬请期待 `v3.6.0` 版本! + 1. **大语言模型对话**。支持各种大语言模型,包括 OpenAI API、Google Gemini、Llama、Deepseek、ChatGLM 等,支持接入本地部署的大模型,通过 Ollama、LLMTuner。具有多轮对话、人格情境、多模态能力,支持图片理解、语音转文字(Whisper)。 2. **多消息平台接入**。支持接入 QQ(OneBot)、QQ 频道、微信(Gewechat)、飞书、Telegram。后续将支持钉钉、Discord、WhatsApp、小爱音响。支持速率限制、白名单、关键词过滤、百度内容审核。 3. **Agent**。原生支持部分 Agent 能力,如代码执行器、自然语言待办、网页搜索。对接 [Dify 平台](https://astrbot.app/others/dify.html),便捷接入 Dify 智能助手、知识库和 Dify 工作流。 From 02f21e07d37e69e2ad10dbfa051e596a3da85987 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Mon, 31 Mar 2025 10:59:32 +0800 Subject: [PATCH 46/46] =?UTF-8?q?=F0=9F=93=A6=20release:=20v3.5.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 2 +- changelogs/v3.5.1.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 changelogs/v3.5.1.md diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 76404cfa4..879193f43 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -2,7 +2,7 @@ 如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。 """ -VERSION = "3.5.0" +VERSION = "3.5.1" DB_PATH = "data/data_v3.db" # 默认配置 diff --git a/changelogs/v3.5.1.md b/changelogs/v3.5.1.md new file mode 100644 index 000000000..80ef10b42 --- /dev/null +++ b/changelogs/v3.5.1.md @@ -0,0 +1,30 @@ +# What's Changed + +> 📢 在升级前,请完整阅读本次更新日志。 + +## ✨ 新增的功能 + +1. 适配 `gemini-2.0-flash-exp-image-generation` 对图片模态的输入 [#1017](https://github.com/Soulter/AstrBot/issues/1017) +2. 在 MessageChain 类中添加 at 和 at_all 方法,用于快速添加 At 消息 @left666 +3. Gewechat Client 增加获取通讯录列表接口 +4. 支持 /llm 指令快捷启停 LLM 功能 [#296](https://github.com/Soulter/AstrBot/issues/296) + +## 🎈 功能性优化 + +1. Edge TTS 支持使用代理 +2. 在 Lifecycle 新增插件资源清理逻辑 @Raven95676 +3. Docker 镜像提供内置 FFmpeg [#979](https://github.com/Soulter/AstrBot/issues/979) +4. 优化无对话情况下设置人格的反馈 @Raven95676 +5. 若禁用提供商,自动切换到另一个可用的提供商 @Raven95676 +6. openai_source 同步支持随机请求均衡,同时优化 LLM 请求逻辑的异常处理 +7. 保存 shared_preferences 时强制刷新文件缓冲区 +8. 优化空 At 回复 @advent259141 + +## 🐛 修复的 Bug + +1. 插件更新时没有正确应用加速地址 +2. newgroup 指令名显示错误 + +## 🧩 新增的插件 + +待补充