AstrBot 4.5.1__py3-none-any.whl → 4.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrbot/api/__init__.py +10 -11
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -36
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +7 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__main__.py +5 -5
- astrbot/cli/commands/__init__.py +3 -3
- astrbot/cli/commands/cmd_conf.py +19 -16
- astrbot/cli/commands/cmd_init.py +3 -2
- astrbot/cli/commands/cmd_plug.py +8 -10
- astrbot/cli/commands/cmd_run.py +5 -6
- astrbot/cli/utils/__init__.py +6 -6
- astrbot/cli/utils/basic.py +14 -14
- astrbot/cli/utils/plugin.py +24 -15
- astrbot/cli/utils/version_comparator.py +10 -12
- astrbot/core/__init__.py +8 -6
- astrbot/core/agent/agent.py +3 -2
- astrbot/core/agent/handoff.py +6 -2
- astrbot/core/agent/hooks.py +9 -6
- astrbot/core/agent/mcp_client.py +50 -15
- astrbot/core/agent/message.py +168 -0
- astrbot/core/agent/response.py +2 -1
- astrbot/core/agent/run_context.py +2 -3
- astrbot/core/agent/runners/base.py +10 -13
- astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
- astrbot/core/agent/tool.py +60 -41
- astrbot/core/agent/tool_executor.py +9 -3
- astrbot/core/astr_agent_context.py +3 -1
- astrbot/core/astrbot_config_mgr.py +29 -9
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +28 -26
- astrbot/core/config/default.py +4 -6
- astrbot/core/conversation_mgr.py +105 -36
- astrbot/core/core_lifecycle.py +68 -54
- astrbot/core/db/__init__.py +33 -18
- astrbot/core/db/migration/helper.py +12 -10
- astrbot/core/db/migration/migra_3_to_4.py +53 -34
- astrbot/core/db/migration/migra_45_to_46.py +1 -1
- astrbot/core/db/migration/shared_preferences_v3.py +2 -1
- astrbot/core/db/migration/sqlite_v3.py +26 -23
- astrbot/core/db/po.py +27 -18
- astrbot/core/db/sqlite.py +74 -45
- astrbot/core/db/vec_db/base.py +10 -14
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
- astrbot/core/event_bus.py +8 -6
- astrbot/core/file_token_service.py +6 -5
- astrbot/core/initial_loader.py +7 -5
- astrbot/core/knowledge_base/chunking/__init__.py +1 -3
- astrbot/core/knowledge_base/chunking/base.py +1 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
- astrbot/core/knowledge_base/chunking/recursive.py +16 -10
- astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
- astrbot/core/knowledge_base/kb_helper.py +30 -17
- astrbot/core/knowledge_base/kb_mgr.py +6 -7
- astrbot/core/knowledge_base/models.py +10 -4
- astrbot/core/knowledge_base/parsers/__init__.py +3 -5
- astrbot/core/knowledge_base/parsers/base.py +1 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
- astrbot/core/knowledge_base/parsers/util.py +1 -1
- astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
- astrbot/core/knowledge_base/retrieval/manager.py +17 -14
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
- astrbot/core/log.py +21 -13
- astrbot/core/message/components.py +123 -217
- astrbot/core/message/message_event_result.py +24 -24
- astrbot/core/persona_mgr.py +20 -11
- astrbot/core/pipeline/__init__.py +7 -7
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +4 -1
- astrbot/core/pipeline/context_utils.py +77 -7
- astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
- astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
- astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
- astrbot/core/pipeline/process_stage/stage.py +13 -10
- astrbot/core/pipeline/process_stage/utils.py +6 -5
- astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
- astrbot/core/pipeline/respond/stage.py +23 -20
- astrbot/core/pipeline/result_decorate/stage.py +31 -23
- astrbot/core/pipeline/scheduler.py +12 -8
- astrbot/core/pipeline/session_status_check/stage.py +12 -8
- astrbot/core/pipeline/stage.py +10 -4
- astrbot/core/pipeline/waking_check/stage.py +24 -18
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +76 -110
- astrbot/core/platform/astrbot_message.py +11 -13
- astrbot/core/platform/manager.py +16 -15
- astrbot/core/platform/message_session.py +5 -3
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +4 -4
- astrbot/core/platform/register.py +8 -8
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
- astrbot/core/platform/sources/discord/client.py +9 -6
- astrbot/core/platform/sources/discord/components.py +18 -14
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
- astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
- astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
- astrbot/core/platform/sources/lark/lark_event.py +21 -14
- astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
- astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
- astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
- astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
- astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
- astrbot/core/platform/sources/satori/satori_event.py +34 -25
- astrbot/core/platform/sources/slack/client.py +11 -9
- astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
- astrbot/core/platform/sources/slack/slack_event.py +34 -24
- astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
- astrbot/core/platform/sources/telegram/tg_event.py +32 -18
- astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
- astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
- astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
- astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
- astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
- astrbot/core/platform_message_history_mgr.py +5 -3
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +61 -75
- astrbot/core/provider/func_tool_manager.py +59 -55
- astrbot/core/provider/manager.py +32 -22
- astrbot/core/provider/provider.py +72 -46
- astrbot/core/provider/register.py +7 -7
- astrbot/core/provider/sources/anthropic_source.py +48 -30
- astrbot/core/provider/sources/azure_tts_source.py +17 -13
- astrbot/core/provider/sources/coze_api_client.py +27 -17
- astrbot/core/provider/sources/coze_source.py +104 -87
- astrbot/core/provider/sources/dashscope_source.py +18 -11
- astrbot/core/provider/sources/dashscope_tts.py +36 -23
- astrbot/core/provider/sources/dify_source.py +25 -20
- astrbot/core/provider/sources/edge_tts_source.py +21 -17
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
- astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
- astrbot/core/provider/sources/gemini_source.py +72 -58
- astrbot/core/provider/sources/gemini_tts_source.py +8 -6
- astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
- astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
- astrbot/core/provider/sources/openai_embedding_source.py +6 -8
- astrbot/core/provider/sources/openai_source.py +77 -69
- astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
- astrbot/core/provider/sources/volcengine_tts.py +38 -31
- astrbot/core/provider/sources/whisper_api_source.py +14 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
- astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
- astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
- astrbot/core/star/__init__.py +16 -11
- astrbot/core/star/config.py +10 -15
- astrbot/core/star/context.py +97 -75
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +30 -28
- astrbot/core/star/filter/command_group.py +27 -24
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +4 -2
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -19
- astrbot/core/star/register/star.py +6 -2
- astrbot/core/star/register/star_handler.py +96 -73
- astrbot/core/star/session_llm_manager.py +48 -14
- astrbot/core/star/session_plugin_manager.py +29 -15
- astrbot/core/star/star.py +1 -2
- astrbot/core/star/star_handler.py +13 -8
- astrbot/core/star/star_manager.py +151 -59
- astrbot/core/star/star_tools.py +44 -37
- astrbot/core/star/updator.py +10 -10
- astrbot/core/umop_config_router.py +10 -4
- astrbot/core/updator.py +13 -5
- astrbot/core/utils/astrbot_path.py +3 -5
- astrbot/core/utils/dify_api_client.py +33 -15
- astrbot/core/utils/io.py +66 -42
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +7 -7
- astrbot/core/utils/path_util.py +15 -16
- astrbot/core/utils/pip_installer.py +5 -5
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +45 -20
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/network_strategy.py +35 -26
- astrbot/core/utils/t2i/renderer.py +11 -5
- astrbot/core/utils/t2i/template_manager.py +14 -15
- astrbot/core/utils/tencent_record_helper.py +19 -13
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +43 -40
- astrbot/dashboard/routes/__init__.py +18 -18
- astrbot/dashboard/routes/auth.py +10 -8
- astrbot/dashboard/routes/chat.py +30 -21
- astrbot/dashboard/routes/config.py +92 -75
- astrbot/dashboard/routes/conversation.py +46 -39
- astrbot/dashboard/routes/file.py +4 -2
- astrbot/dashboard/routes/knowledge_base.py +47 -40
- astrbot/dashboard/routes/log.py +9 -4
- astrbot/dashboard/routes/persona.py +19 -16
- astrbot/dashboard/routes/plugin.py +69 -55
- astrbot/dashboard/routes/route.py +3 -1
- astrbot/dashboard/routes/session_management.py +130 -116
- astrbot/dashboard/routes/stat.py +34 -34
- astrbot/dashboard/routes/t2i.py +15 -12
- astrbot/dashboard/routes/tools.py +56 -53
- astrbot/dashboard/routes/update.py +32 -28
- astrbot/dashboard/server.py +30 -26
- astrbot/dashboard/utils.py +8 -4
- {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/METADATA +2 -1
- astrbot-4.5.3.dist-info/RECORD +261 -0
- astrbot-4.5.1.dist-info/RECORD +0 -260
- {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/WHEEL +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/entry_points.txt +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,28 +1,28 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import sys
|
|
2
3
|
import uuid
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
import quart
|
|
6
|
+
from requests import Response
|
|
7
|
+
from wechatpy import WeChatClient, parse_message
|
|
8
|
+
from wechatpy.crypto import WeChatCrypto
|
|
9
|
+
from wechatpy.exceptions import InvalidSignatureException
|
|
10
|
+
from wechatpy.messages import BaseMessage, ImageMessage, TextMessage, VoiceMessage
|
|
11
|
+
from wechatpy.utils import check_signature
|
|
5
12
|
|
|
13
|
+
from astrbot.api.event import MessageChain
|
|
14
|
+
from astrbot.api.message_components import Image, Plain, Record
|
|
6
15
|
from astrbot.api.platform import (
|
|
7
|
-
Platform,
|
|
8
16
|
AstrBotMessage,
|
|
9
17
|
MessageMember,
|
|
10
|
-
PlatformMetadata,
|
|
11
18
|
MessageType,
|
|
19
|
+
Platform,
|
|
20
|
+
PlatformMetadata,
|
|
21
|
+
register_platform_adapter,
|
|
12
22
|
)
|
|
13
|
-
from astrbot.api.event import MessageChain
|
|
14
|
-
from astrbot.api.message_components import Plain, Image, Record
|
|
15
|
-
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
16
|
-
from astrbot.api.platform import register_platform_adapter
|
|
17
23
|
from astrbot.core import logger
|
|
18
|
-
from
|
|
24
|
+
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
19
25
|
|
|
20
|
-
from wechatpy.utils import check_signature
|
|
21
|
-
from wechatpy.crypto import WeChatCrypto
|
|
22
|
-
from wechatpy import WeChatClient
|
|
23
|
-
from wechatpy.messages import TextMessage, ImageMessage, VoiceMessage, BaseMessage
|
|
24
|
-
from wechatpy.exceptions import InvalidSignatureException
|
|
25
|
-
from wechatpy import parse_message
|
|
26
26
|
from .weixin_offacc_event import WeixinOfficialAccountPlatformEvent
|
|
27
27
|
|
|
28
28
|
if sys.version_info >= (3, 12):
|
|
@@ -40,10 +40,14 @@ class WecomServer:
|
|
|
40
40
|
self.encoding_aes_key = config.get("encoding_aes_key")
|
|
41
41
|
self.appid = config.get("appid")
|
|
42
42
|
self.server.add_url_rule(
|
|
43
|
-
"/callback/command",
|
|
43
|
+
"/callback/command",
|
|
44
|
+
view_func=self.verify,
|
|
45
|
+
methods=["GET"],
|
|
44
46
|
)
|
|
45
47
|
self.server.add_url_rule(
|
|
46
|
-
"/callback/command",
|
|
48
|
+
"/callback/command",
|
|
49
|
+
view_func=self.callback_command,
|
|
50
|
+
methods=["POST"],
|
|
47
51
|
)
|
|
48
52
|
self.crypto = WeChatCrypto(self.token, self.encoding_aes_key, self.appid)
|
|
49
53
|
|
|
@@ -97,7 +101,7 @@ class WecomServer:
|
|
|
97
101
|
|
|
98
102
|
async def start_polling(self):
|
|
99
103
|
logger.info(
|
|
100
|
-
f"将在 {self.callback_server_host}:{self.port} 端口启动 微信公众平台 适配器。"
|
|
104
|
+
f"将在 {self.callback_server_host}:{self.port} 端口启动 微信公众平台 适配器。",
|
|
101
105
|
)
|
|
102
106
|
await self.server.run_task(
|
|
103
107
|
host=self.callback_server_host,
|
|
@@ -112,22 +116,25 @@ class WecomServer:
|
|
|
112
116
|
@register_platform_adapter("weixin_official_account", "微信公众平台 适配器")
|
|
113
117
|
class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
114
118
|
def __init__(
|
|
115
|
-
self,
|
|
119
|
+
self,
|
|
120
|
+
platform_config: dict,
|
|
121
|
+
platform_settings: dict,
|
|
122
|
+
event_queue: asyncio.Queue,
|
|
116
123
|
) -> None:
|
|
117
124
|
super().__init__(event_queue)
|
|
118
125
|
self.config = platform_config
|
|
119
126
|
self.settingss = platform_settings
|
|
120
127
|
self.client_self_id = uuid.uuid4().hex[:8]
|
|
121
128
|
self.api_base_url = platform_config.get(
|
|
122
|
-
"api_base_url",
|
|
129
|
+
"api_base_url",
|
|
130
|
+
"https://api.weixin.qq.com/cgi-bin/",
|
|
123
131
|
)
|
|
124
132
|
self.active_send_mode = self.config.get("active_send_mode", False)
|
|
125
133
|
|
|
126
134
|
if not self.api_base_url:
|
|
127
135
|
self.api_base_url = "https://api.weixin.qq.com/cgi-bin/"
|
|
128
136
|
|
|
129
|
-
|
|
130
|
-
self.api_base_url = self.api_base_url[:-1]
|
|
137
|
+
self.api_base_url = self.api_base_url.removesuffix("/")
|
|
131
138
|
if not self.api_base_url.endswith("/cgi-bin"):
|
|
132
139
|
self.api_base_url += "/cgi-bin"
|
|
133
140
|
|
|
@@ -161,7 +168,8 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
161
168
|
await self.convert_message(msg, future)
|
|
162
169
|
# I love shield so much!
|
|
163
170
|
result = await asyncio.wait_for(
|
|
164
|
-
asyncio.shield(future),
|
|
171
|
+
asyncio.shield(future),
|
|
172
|
+
60,
|
|
165
173
|
) # wait for 60s
|
|
166
174
|
logger.debug(f"Got future result: {result}")
|
|
167
175
|
self.wexin_event_workers.pop(msg.id, None)
|
|
@@ -175,7 +183,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
175
183
|
|
|
176
184
|
@override
|
|
177
185
|
async def send_by_session(
|
|
178
|
-
self,
|
|
186
|
+
self,
|
|
187
|
+
session: MessageSesion,
|
|
188
|
+
message_chain: MessageChain,
|
|
179
189
|
):
|
|
180
190
|
await super().send_by_session(session, message_chain)
|
|
181
191
|
|
|
@@ -192,7 +202,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
192
202
|
await self.server.start_polling()
|
|
193
203
|
|
|
194
204
|
async def convert_message(
|
|
195
|
-
self,
|
|
205
|
+
self,
|
|
206
|
+
msg,
|
|
207
|
+
future: asyncio.Future = None,
|
|
196
208
|
) -> AstrBotMessage | None:
|
|
197
209
|
abm = AstrBotMessage()
|
|
198
210
|
if isinstance(msg, TextMessage):
|
|
@@ -224,7 +236,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
224
236
|
assert isinstance(msg, VoiceMessage)
|
|
225
237
|
|
|
226
238
|
resp: Response = await asyncio.get_event_loop().run_in_executor(
|
|
227
|
-
None,
|
|
239
|
+
None,
|
|
240
|
+
self.client.media.download,
|
|
241
|
+
msg.media_id,
|
|
228
242
|
)
|
|
229
243
|
path = f"data/temp/wecom_{msg.media_id}.amr"
|
|
230
244
|
with open(path, "wb") as f:
|
|
@@ -238,7 +252,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
238
252
|
audio.export(path_wav, format="wav")
|
|
239
253
|
except Exception as e:
|
|
240
254
|
logger.error(
|
|
241
|
-
f"转换音频失败: {e}。如果没有安装 pydub 和 ffmpeg 请先安装。"
|
|
255
|
+
f"转换音频失败: {e}。如果没有安装 pydub 和 ffmpeg 请先安装。",
|
|
242
256
|
)
|
|
243
257
|
path_wav = path
|
|
244
258
|
return
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import uuid
|
|
2
1
|
import asyncio
|
|
3
|
-
|
|
4
|
-
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
|
|
5
|
-
from astrbot.api.message_components import Plain, Image, Record
|
|
6
|
-
from wechatpy import WeChatClient
|
|
7
|
-
from wechatpy.replies import TextReply, ImageReply, VoiceReply
|
|
2
|
+
import uuid
|
|
8
3
|
|
|
4
|
+
from wechatpy import WeChatClient
|
|
5
|
+
from wechatpy.replies import ImageReply, TextReply, VoiceReply
|
|
9
6
|
|
|
10
7
|
from astrbot.api import logger
|
|
8
|
+
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
9
|
+
from astrbot.api.message_components import Image, Plain, Record
|
|
10
|
+
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
13
|
import pydub
|
|
14
14
|
except Exception:
|
|
15
15
|
logger.warning(
|
|
16
|
-
"检测到 pydub 库未安装,微信公众平台将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。"
|
|
16
|
+
"检测到 pydub 库未安装,微信公众平台将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。",
|
|
17
17
|
)
|
|
18
|
-
pass
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
@@ -32,7 +31,9 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
32
31
|
|
|
33
32
|
@staticmethod
|
|
34
33
|
async def send_with_client(
|
|
35
|
-
client: WeChatClient,
|
|
34
|
+
client: WeChatClient,
|
|
35
|
+
message: MessageChain,
|
|
36
|
+
user_name: str,
|
|
36
37
|
):
|
|
37
38
|
pass
|
|
38
39
|
|
|
@@ -43,44 +44,44 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
43
44
|
plain (str): 要分割的长文本
|
|
44
45
|
Returns:
|
|
45
46
|
list[str]: 分割后的文本列表
|
|
47
|
+
|
|
46
48
|
"""
|
|
47
49
|
if len(plain) <= 2048:
|
|
48
50
|
return [plain]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
result = []
|
|
52
|
+
start = 0
|
|
53
|
+
while start < len(plain):
|
|
54
|
+
# 剩下的字符串长度<2048时结束
|
|
55
|
+
if start + 2048 >= len(plain):
|
|
56
|
+
result.append(plain[start:])
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
# 向前搜索分割标点符号
|
|
60
|
+
end = min(start + 2048, len(plain))
|
|
61
|
+
cut_position = end
|
|
62
|
+
for i in range(end, start, -1):
|
|
63
|
+
if i < len(plain) and plain[i - 1] in [
|
|
64
|
+
"。",
|
|
65
|
+
"!",
|
|
66
|
+
"?",
|
|
67
|
+
".",
|
|
68
|
+
"!",
|
|
69
|
+
"?",
|
|
70
|
+
"\n",
|
|
71
|
+
";",
|
|
72
|
+
";",
|
|
73
|
+
]:
|
|
74
|
+
cut_position = i
|
|
56
75
|
break
|
|
57
76
|
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
# 没找到合适的位置分割, 直接切分
|
|
78
|
+
if cut_position == end and end < len(plain):
|
|
60
79
|
cut_position = end
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
".",
|
|
67
|
-
"!",
|
|
68
|
-
"?",
|
|
69
|
-
"\n",
|
|
70
|
-
";",
|
|
71
|
-
";",
|
|
72
|
-
]:
|
|
73
|
-
cut_position = i
|
|
74
|
-
break
|
|
75
|
-
|
|
76
|
-
# 没找到合适的位置分割, 直接切分
|
|
77
|
-
if cut_position == end and end < len(plain):
|
|
78
|
-
cut_position = end
|
|
79
|
-
|
|
80
|
-
result.append(plain[start:cut_position])
|
|
81
|
-
start = cut_position
|
|
82
|
-
|
|
83
|
-
return result
|
|
80
|
+
|
|
81
|
+
result.append(plain[start:cut_position])
|
|
82
|
+
start = cut_position
|
|
83
|
+
|
|
84
|
+
return result
|
|
84
85
|
|
|
85
86
|
async def send(self, message: MessageChain):
|
|
86
87
|
message_obj = self.message_obj
|
|
@@ -111,7 +112,7 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
111
112
|
except Exception as e:
|
|
112
113
|
logger.error(f"微信公众平台上传图片失败: {e}")
|
|
113
114
|
await self.send(
|
|
114
|
-
MessageChain().message(f"微信公众平台上传图片失败: {e}")
|
|
115
|
+
MessageChain().message(f"微信公众平台上传图片失败: {e}"),
|
|
115
116
|
)
|
|
116
117
|
return
|
|
117
118
|
logger.debug(f"微信公众平台上传图片返回: {response}")
|
|
@@ -136,7 +137,8 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
136
137
|
# 转成amr
|
|
137
138
|
record_path_amr = f"data/temp/{uuid.uuid4()}.amr"
|
|
138
139
|
pydub.AudioSegment.from_wav(record_path).export(
|
|
139
|
-
record_path_amr,
|
|
140
|
+
record_path_amr,
|
|
141
|
+
format="amr",
|
|
140
142
|
)
|
|
141
143
|
|
|
142
144
|
with open(record_path_amr, "rb") as f:
|
|
@@ -145,7 +147,7 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
145
147
|
except Exception as e:
|
|
146
148
|
logger.error(f"微信公众平台上传语音失败: {e}")
|
|
147
149
|
await self.send(
|
|
148
|
-
MessageChain().message(f"微信公众平台上传语音失败: {e}")
|
|
150
|
+
MessageChain().message(f"微信公众平台上传语音失败: {e}"),
|
|
149
151
|
)
|
|
150
152
|
return
|
|
151
153
|
logger.info(f"微信公众平台上传语音返回: {response}")
|
|
@@ -178,7 +180,7 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
178
180
|
else:
|
|
179
181
|
buffer.chain.extend(chain.chain)
|
|
180
182
|
if not buffer:
|
|
181
|
-
return
|
|
183
|
+
return None
|
|
182
184
|
buffer.squash_plain()
|
|
183
185
|
await self.send(buffer)
|
|
184
186
|
return await super().send_streaming(generator, use_fallback)
|
|
@@ -11,8 +11,8 @@ class PlatformMessageHistoryManager:
|
|
|
11
11
|
platform_id: str,
|
|
12
12
|
user_id: str,
|
|
13
13
|
content: list[dict], # TODO: parse from message chain
|
|
14
|
-
sender_id: str = None,
|
|
15
|
-
sender_name: str = None,
|
|
14
|
+
sender_id: str | None = None,
|
|
15
|
+
sender_name: str | None = None,
|
|
16
16
|
):
|
|
17
17
|
"""Insert a new platform message history record."""
|
|
18
18
|
await self.db.insert_platform_message_history(
|
|
@@ -43,5 +43,7 @@ class PlatformMessageHistoryManager:
|
|
|
43
43
|
async def delete(self, platform_id: str, user_id: str, offset_sec: int = 86400):
|
|
44
44
|
"""Delete platform message history records older than the specified offset."""
|
|
45
45
|
await self.db.delete_platform_message_offset(
|
|
46
|
-
platform_id=platform_id,
|
|
46
|
+
platform_id=platform_id,
|
|
47
|
+
user_id=user_id,
|
|
48
|
+
offset_sec=offset_sec,
|
|
47
49
|
)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
from .provider import Provider, Personality, STTProvider
|
|
2
|
-
|
|
3
1
|
from .entities import ProviderMetaData
|
|
2
|
+
from .provider import Personality, Provider, STTProvider
|
|
4
3
|
|
|
5
|
-
__all__ = ["
|
|
4
|
+
__all__ = ["Personality", "Provider", "ProviderMetaData", "STTProvider"]
|
astrbot/core/provider/entites.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
from astrbot.core.provider.entities import (
|
|
2
|
+
AssistantMessageSegment,
|
|
3
|
+
LLMResponse,
|
|
4
|
+
ProviderMetaData,
|
|
2
5
|
ProviderRequest,
|
|
3
6
|
ProviderType,
|
|
4
|
-
ProviderMetaData,
|
|
5
|
-
ToolCallsResult,
|
|
6
|
-
AssistantMessageSegment,
|
|
7
7
|
ToolCallMessageSegment,
|
|
8
|
-
|
|
8
|
+
ToolCallsResult,
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
|
+
"AssistantMessageSegment",
|
|
13
|
+
"LLMResponse",
|
|
14
|
+
"ProviderMetaData",
|
|
12
15
|
"ProviderRequest",
|
|
13
16
|
"ProviderType",
|
|
14
|
-
"ProviderMetaData",
|
|
15
|
-
"ToolCallsResult",
|
|
16
|
-
"AssistantMessageSegment",
|
|
17
17
|
"ToolCallMessageSegment",
|
|
18
|
-
"
|
|
18
|
+
"ToolCallsResult",
|
|
19
19
|
]
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import enum
|
|
2
1
|
import base64
|
|
2
|
+
import enum
|
|
3
3
|
import json
|
|
4
|
-
from astrbot.core.utils.io import download_image_by_url
|
|
5
|
-
from astrbot import logger
|
|
6
4
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import
|
|
8
|
-
|
|
9
|
-
from
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from anthropic.types import Message as AnthropicMessage
|
|
10
8
|
from google.genai.types import GenerateContentResponse
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
from openai.types.chat.chat_completion import ChatCompletion
|
|
10
|
+
|
|
11
|
+
import astrbot.core.message.components as Comp
|
|
12
|
+
from astrbot import logger
|
|
13
|
+
from astrbot.core.agent.message import (
|
|
14
|
+
AssistantMessageSegment,
|
|
15
|
+
ToolCall,
|
|
16
|
+
ToolCallMessageSegment,
|
|
14
17
|
)
|
|
18
|
+
from astrbot.core.agent.tool import ToolSet
|
|
15
19
|
from astrbot.core.db.po import Conversation
|
|
16
20
|
from astrbot.core.message.message_event_result import MessageChain
|
|
17
|
-
|
|
21
|
+
from astrbot.core.utils.io import download_image_by_url
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
class ProviderType(enum.Enum):
|
|
@@ -30,9 +34,9 @@ class ProviderMetaData:
|
|
|
30
34
|
type: str
|
|
31
35
|
"""提供商适配器名称,如 openai, ollama"""
|
|
32
36
|
desc: str = ""
|
|
33
|
-
"""
|
|
37
|
+
"""提供商适配器描述"""
|
|
34
38
|
provider_type: ProviderType = ProviderType.CHAT_COMPLETION
|
|
35
|
-
cls_type:
|
|
39
|
+
cls_type: Any = None
|
|
36
40
|
|
|
37
41
|
default_config_tmpl: dict | None = None
|
|
38
42
|
"""平台的默认配置模板"""
|
|
@@ -40,57 +44,19 @@ class ProviderMetaData:
|
|
|
40
44
|
"""显示在 WebUI 配置页中的提供商名称,如空则是 type"""
|
|
41
45
|
|
|
42
46
|
|
|
43
|
-
@dataclass
|
|
44
|
-
class ToolCallMessageSegment:
|
|
45
|
-
"""OpenAI 格式的上下文中 role 为 tool 的消息段。参考: https://platform.openai.com/docs/guides/function-calling"""
|
|
46
|
-
|
|
47
|
-
tool_call_id: str
|
|
48
|
-
content: str
|
|
49
|
-
role: str = "tool"
|
|
50
|
-
|
|
51
|
-
def to_dict(self):
|
|
52
|
-
return {
|
|
53
|
-
"tool_call_id": self.tool_call_id,
|
|
54
|
-
"content": self.content,
|
|
55
|
-
"role": self.role,
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@dataclass
|
|
60
|
-
class AssistantMessageSegment:
|
|
61
|
-
"""OpenAI 格式的上下文中 role 为 assistant 的消息段。参考: https://platform.openai.com/docs/guides/function-calling"""
|
|
62
|
-
|
|
63
|
-
content: str | None = None
|
|
64
|
-
tool_calls: List[ChatCompletionMessageToolCall | Dict] = field(default_factory=list)
|
|
65
|
-
role: str = "assistant"
|
|
66
|
-
|
|
67
|
-
def to_dict(self):
|
|
68
|
-
ret: dict[str, str | list[dict]] = {
|
|
69
|
-
"role": self.role,
|
|
70
|
-
}
|
|
71
|
-
if self.content:
|
|
72
|
-
ret["content"] = self.content
|
|
73
|
-
if self.tool_calls:
|
|
74
|
-
tool_calls_dict = [
|
|
75
|
-
tc if isinstance(tc, dict) else tc.to_dict() for tc in self.tool_calls
|
|
76
|
-
]
|
|
77
|
-
ret["tool_calls"] = tool_calls_dict
|
|
78
|
-
return ret
|
|
79
|
-
|
|
80
|
-
|
|
81
47
|
@dataclass
|
|
82
48
|
class ToolCallsResult:
|
|
83
49
|
"""工具调用结果"""
|
|
84
50
|
|
|
85
51
|
tool_calls_info: AssistantMessageSegment
|
|
86
52
|
"""函数调用的信息"""
|
|
87
|
-
tool_calls_result:
|
|
53
|
+
tool_calls_result: list[ToolCallMessageSegment]
|
|
88
54
|
"""函数调用的结果"""
|
|
89
55
|
|
|
90
|
-
def to_openai_messages(self) ->
|
|
56
|
+
def to_openai_messages(self) -> list[dict]:
|
|
91
57
|
ret = [
|
|
92
|
-
self.tool_calls_info.
|
|
93
|
-
*[item.
|
|
58
|
+
self.tool_calls_info.model_dump(),
|
|
59
|
+
*[item.model_dump() for item in self.tool_calls_result],
|
|
94
60
|
]
|
|
95
61
|
return ret
|
|
96
62
|
|
|
@@ -106,16 +72,16 @@ class ProviderRequest:
|
|
|
106
72
|
func_tool: ToolSet | None = None
|
|
107
73
|
"""可用的函数工具"""
|
|
108
74
|
contexts: list[dict] = field(default_factory=list)
|
|
109
|
-
"""
|
|
75
|
+
"""
|
|
76
|
+
OpenAI 格式上下文列表。
|
|
110
77
|
参考 https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages
|
|
111
78
|
"""
|
|
112
79
|
system_prompt: str = ""
|
|
113
80
|
"""系统提示词"""
|
|
114
81
|
conversation: Conversation | None = None
|
|
115
|
-
|
|
82
|
+
"""关联的对话对象"""
|
|
116
83
|
tool_calls_result: list[ToolCallsResult] | ToolCallsResult | None = None
|
|
117
84
|
"""附加的上次请求后工具调用的结果。参考: https://platform.openai.com/docs/guides/function-calling#handling-function-calls"""
|
|
118
|
-
|
|
119
85
|
model: str | None = None
|
|
120
86
|
"""模型名称,为 None 时使用提供商的默认模型"""
|
|
121
87
|
|
|
@@ -175,13 +141,13 @@ class ProviderRequest:
|
|
|
175
141
|
|
|
176
142
|
return result_parts
|
|
177
143
|
|
|
178
|
-
async def assemble_context(self) ->
|
|
144
|
+
async def assemble_context(self) -> dict:
|
|
179
145
|
"""将请求(prompt 和 image_urls)包装成 OpenAI 的消息格式。"""
|
|
180
146
|
if self.image_urls:
|
|
181
147
|
user_content = {
|
|
182
148
|
"role": "user",
|
|
183
149
|
"content": [
|
|
184
|
-
{"type": "text", "text": self.prompt if self.prompt else "[图片]"}
|
|
150
|
+
{"type": "text", "text": self.prompt if self.prompt else "[图片]"},
|
|
185
151
|
],
|
|
186
152
|
}
|
|
187
153
|
for image_url in self.image_urls:
|
|
@@ -197,11 +163,10 @@ class ProviderRequest:
|
|
|
197
163
|
logger.warning(f"图片 {image_url} 得到的结果为空,将忽略。")
|
|
198
164
|
continue
|
|
199
165
|
user_content["content"].append(
|
|
200
|
-
{"type": "image_url", "image_url": {"url": image_data}}
|
|
166
|
+
{"type": "image_url", "image_url": {"url": image_data}},
|
|
201
167
|
)
|
|
202
168
|
return user_content
|
|
203
|
-
|
|
204
|
-
return {"role": "user", "content": self.prompt}
|
|
169
|
+
return {"role": "user", "content": self.prompt}
|
|
205
170
|
|
|
206
171
|
async def _encode_image_bs64(self, image_url: str) -> str:
|
|
207
172
|
"""将图片转换为 base64"""
|
|
@@ -219,15 +184,17 @@ class LLMResponse:
|
|
|
219
184
|
"""角色, assistant, tool, err"""
|
|
220
185
|
result_chain: MessageChain | None = None
|
|
221
186
|
"""返回的消息链"""
|
|
222
|
-
tools_call_args:
|
|
187
|
+
tools_call_args: list[dict[str, Any]] = field(default_factory=list)
|
|
223
188
|
"""工具调用参数"""
|
|
224
|
-
tools_call_name:
|
|
189
|
+
tools_call_name: list[str] = field(default_factory=list)
|
|
225
190
|
"""工具调用名称"""
|
|
226
|
-
tools_call_ids:
|
|
191
|
+
tools_call_ids: list[str] = field(default_factory=list)
|
|
227
192
|
"""工具调用 ID"""
|
|
228
193
|
|
|
229
|
-
raw_completion:
|
|
230
|
-
|
|
194
|
+
raw_completion: (
|
|
195
|
+
ChatCompletion | GenerateContentResponse | AnthropicMessage | None
|
|
196
|
+
) = None
|
|
197
|
+
_new_record: dict[str, Any] | None = None
|
|
231
198
|
|
|
232
199
|
_completion_text: str = ""
|
|
233
200
|
|
|
@@ -239,11 +206,14 @@ class LLMResponse:
|
|
|
239
206
|
role: str,
|
|
240
207
|
completion_text: str = "",
|
|
241
208
|
result_chain: MessageChain | None = None,
|
|
242
|
-
tools_call_args:
|
|
243
|
-
tools_call_name:
|
|
244
|
-
tools_call_ids:
|
|
245
|
-
raw_completion: ChatCompletion
|
|
246
|
-
|
|
209
|
+
tools_call_args: list[dict[str, Any]] | None = None,
|
|
210
|
+
tools_call_name: list[str] | None = None,
|
|
211
|
+
tools_call_ids: list[str] | None = None,
|
|
212
|
+
raw_completion: ChatCompletion
|
|
213
|
+
| GenerateContentResponse
|
|
214
|
+
| AnthropicMessage
|
|
215
|
+
| None = None,
|
|
216
|
+
_new_record: dict[str, Any] | None = None,
|
|
247
217
|
is_chunk: bool = False,
|
|
248
218
|
):
|
|
249
219
|
"""初始化 LLMResponse
|
|
@@ -255,6 +225,7 @@ class LLMResponse:
|
|
|
255
225
|
tools_call_args (List[Dict[str, any]], optional): 工具调用参数. Defaults to None.
|
|
256
226
|
tools_call_name (List[str], optional): 工具调用名称. Defaults to None.
|
|
257
227
|
raw_completion (ChatCompletion, optional): 原始响应, OpenAI 格式. Defaults to None.
|
|
228
|
+
|
|
258
229
|
"""
|
|
259
230
|
if tools_call_args is None:
|
|
260
231
|
tools_call_args = []
|
|
@@ -291,8 +262,8 @@ class LLMResponse:
|
|
|
291
262
|
else:
|
|
292
263
|
self._completion_text = value
|
|
293
264
|
|
|
294
|
-
def to_openai_tool_calls(self) ->
|
|
295
|
-
"""
|
|
265
|
+
def to_openai_tool_calls(self) -> list[dict]:
|
|
266
|
+
"""Convert to OpenAI tool calls format. Deprecated, use to_openai_to_calls_model instead."""
|
|
296
267
|
ret = []
|
|
297
268
|
for idx, tool_call_arg in enumerate(self.tools_call_args):
|
|
298
269
|
ret.append(
|
|
@@ -303,7 +274,22 @@ class LLMResponse:
|
|
|
303
274
|
"arguments": json.dumps(tool_call_arg),
|
|
304
275
|
},
|
|
305
276
|
"type": "function",
|
|
306
|
-
}
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
return ret
|
|
280
|
+
|
|
281
|
+
def to_openai_to_calls_model(self) -> list[ToolCall]:
|
|
282
|
+
"""The same as to_openai_tool_calls but return pydantic model."""
|
|
283
|
+
ret = []
|
|
284
|
+
for idx, tool_call_arg in enumerate(self.tools_call_args):
|
|
285
|
+
ret.append(
|
|
286
|
+
ToolCall(
|
|
287
|
+
id=self.tools_call_ids[idx],
|
|
288
|
+
function=ToolCall.FunctionBody(
|
|
289
|
+
name=self.tools_call_name[idx],
|
|
290
|
+
arguments=json.dumps(tool_call_arg),
|
|
291
|
+
),
|
|
292
|
+
),
|
|
307
293
|
)
|
|
308
294
|
return ret
|
|
309
295
|
|