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.
Files changed (244) hide show
  1. astrbot/api/__init__.py +10 -11
  2. astrbot/api/event/__init__.py +5 -6
  3. astrbot/api/event/filter/__init__.py +37 -36
  4. astrbot/api/platform/__init__.py +7 -8
  5. astrbot/api/provider/__init__.py +7 -7
  6. astrbot/api/star/__init__.py +3 -4
  7. astrbot/api/util/__init__.py +2 -2
  8. astrbot/cli/__main__.py +5 -5
  9. astrbot/cli/commands/__init__.py +3 -3
  10. astrbot/cli/commands/cmd_conf.py +19 -16
  11. astrbot/cli/commands/cmd_init.py +3 -2
  12. astrbot/cli/commands/cmd_plug.py +8 -10
  13. astrbot/cli/commands/cmd_run.py +5 -6
  14. astrbot/cli/utils/__init__.py +6 -6
  15. astrbot/cli/utils/basic.py +14 -14
  16. astrbot/cli/utils/plugin.py +24 -15
  17. astrbot/cli/utils/version_comparator.py +10 -12
  18. astrbot/core/__init__.py +8 -6
  19. astrbot/core/agent/agent.py +3 -2
  20. astrbot/core/agent/handoff.py +6 -2
  21. astrbot/core/agent/hooks.py +9 -6
  22. astrbot/core/agent/mcp_client.py +50 -15
  23. astrbot/core/agent/message.py +168 -0
  24. astrbot/core/agent/response.py +2 -1
  25. astrbot/core/agent/run_context.py +2 -3
  26. astrbot/core/agent/runners/base.py +10 -13
  27. astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
  28. astrbot/core/agent/tool.py +60 -41
  29. astrbot/core/agent/tool_executor.py +9 -3
  30. astrbot/core/astr_agent_context.py +3 -1
  31. astrbot/core/astrbot_config_mgr.py +29 -9
  32. astrbot/core/config/__init__.py +2 -2
  33. astrbot/core/config/astrbot_config.py +28 -26
  34. astrbot/core/config/default.py +4 -6
  35. astrbot/core/conversation_mgr.py +105 -36
  36. astrbot/core/core_lifecycle.py +68 -54
  37. astrbot/core/db/__init__.py +33 -18
  38. astrbot/core/db/migration/helper.py +12 -10
  39. astrbot/core/db/migration/migra_3_to_4.py +53 -34
  40. astrbot/core/db/migration/migra_45_to_46.py +1 -1
  41. astrbot/core/db/migration/shared_preferences_v3.py +2 -1
  42. astrbot/core/db/migration/sqlite_v3.py +26 -23
  43. astrbot/core/db/po.py +27 -18
  44. astrbot/core/db/sqlite.py +74 -45
  45. astrbot/core/db/vec_db/base.py +10 -14
  46. astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
  47. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
  48. astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
  49. astrbot/core/event_bus.py +8 -6
  50. astrbot/core/file_token_service.py +6 -5
  51. astrbot/core/initial_loader.py +7 -5
  52. astrbot/core/knowledge_base/chunking/__init__.py +1 -3
  53. astrbot/core/knowledge_base/chunking/base.py +1 -0
  54. astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
  55. astrbot/core/knowledge_base/chunking/recursive.py +16 -10
  56. astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
  57. astrbot/core/knowledge_base/kb_helper.py +30 -17
  58. astrbot/core/knowledge_base/kb_mgr.py +6 -7
  59. astrbot/core/knowledge_base/models.py +10 -4
  60. astrbot/core/knowledge_base/parsers/__init__.py +3 -5
  61. astrbot/core/knowledge_base/parsers/base.py +1 -0
  62. astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
  63. astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
  64. astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
  65. astrbot/core/knowledge_base/parsers/util.py +1 -1
  66. astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
  67. astrbot/core/knowledge_base/retrieval/manager.py +17 -14
  68. astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
  69. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
  70. astrbot/core/log.py +21 -13
  71. astrbot/core/message/components.py +123 -217
  72. astrbot/core/message/message_event_result.py +24 -24
  73. astrbot/core/persona_mgr.py +20 -11
  74. astrbot/core/pipeline/__init__.py +7 -7
  75. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  76. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  77. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
  78. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
  79. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  80. astrbot/core/pipeline/context.py +4 -1
  81. astrbot/core/pipeline/context_utils.py +77 -7
  82. astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
  83. astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
  84. astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
  85. astrbot/core/pipeline/process_stage/stage.py +13 -10
  86. astrbot/core/pipeline/process_stage/utils.py +6 -5
  87. astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
  88. astrbot/core/pipeline/respond/stage.py +23 -20
  89. astrbot/core/pipeline/result_decorate/stage.py +31 -23
  90. astrbot/core/pipeline/scheduler.py +12 -8
  91. astrbot/core/pipeline/session_status_check/stage.py +12 -8
  92. astrbot/core/pipeline/stage.py +10 -4
  93. astrbot/core/pipeline/waking_check/stage.py +24 -18
  94. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  95. astrbot/core/platform/__init__.py +6 -6
  96. astrbot/core/platform/astr_message_event.py +76 -110
  97. astrbot/core/platform/astrbot_message.py +11 -13
  98. astrbot/core/platform/manager.py +16 -15
  99. astrbot/core/platform/message_session.py +5 -3
  100. astrbot/core/platform/platform.py +16 -24
  101. astrbot/core/platform/platform_metadata.py +4 -4
  102. astrbot/core/platform/register.py +8 -8
  103. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
  104. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
  105. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
  106. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
  107. astrbot/core/platform/sources/discord/client.py +9 -6
  108. astrbot/core/platform/sources/discord/components.py +18 -14
  109. astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
  110. astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
  111. astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
  112. astrbot/core/platform/sources/lark/lark_event.py +21 -14
  113. astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
  114. astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
  115. astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
  116. astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
  117. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
  118. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  119. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  120. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  121. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
  122. astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
  123. astrbot/core/platform/sources/satori/satori_event.py +34 -25
  124. astrbot/core/platform/sources/slack/client.py +11 -9
  125. astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
  126. astrbot/core/platform/sources/slack/slack_event.py +34 -24
  127. astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
  128. astrbot/core/platform/sources/telegram/tg_event.py +32 -18
  129. astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
  130. astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
  131. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
  132. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
  133. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
  134. astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
  135. astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
  136. astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
  137. astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
  138. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
  139. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
  140. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
  141. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
  142. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
  143. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
  144. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
  145. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
  146. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
  147. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
  148. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
  149. astrbot/core/platform_message_history_mgr.py +5 -3
  150. astrbot/core/provider/__init__.py +2 -3
  151. astrbot/core/provider/entites.py +8 -8
  152. astrbot/core/provider/entities.py +61 -75
  153. astrbot/core/provider/func_tool_manager.py +59 -55
  154. astrbot/core/provider/manager.py +32 -22
  155. astrbot/core/provider/provider.py +72 -46
  156. astrbot/core/provider/register.py +7 -7
  157. astrbot/core/provider/sources/anthropic_source.py +48 -30
  158. astrbot/core/provider/sources/azure_tts_source.py +17 -13
  159. astrbot/core/provider/sources/coze_api_client.py +27 -17
  160. astrbot/core/provider/sources/coze_source.py +104 -87
  161. astrbot/core/provider/sources/dashscope_source.py +18 -11
  162. astrbot/core/provider/sources/dashscope_tts.py +36 -23
  163. astrbot/core/provider/sources/dify_source.py +25 -20
  164. astrbot/core/provider/sources/edge_tts_source.py +21 -17
  165. astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
  166. astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
  167. astrbot/core/provider/sources/gemini_source.py +72 -58
  168. astrbot/core/provider/sources/gemini_tts_source.py +8 -6
  169. astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
  170. astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
  171. astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
  172. astrbot/core/provider/sources/openai_embedding_source.py +6 -8
  173. astrbot/core/provider/sources/openai_source.py +77 -69
  174. astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
  175. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  176. astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
  177. astrbot/core/provider/sources/volcengine_tts.py +38 -31
  178. astrbot/core/provider/sources/whisper_api_source.py +14 -12
  179. astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
  180. astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
  181. astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
  182. astrbot/core/star/__init__.py +16 -11
  183. astrbot/core/star/config.py +10 -15
  184. astrbot/core/star/context.py +97 -75
  185. astrbot/core/star/filter/__init__.py +4 -3
  186. astrbot/core/star/filter/command.py +30 -28
  187. astrbot/core/star/filter/command_group.py +27 -24
  188. astrbot/core/star/filter/custom_filter.py +6 -5
  189. astrbot/core/star/filter/event_message_type.py +4 -2
  190. astrbot/core/star/filter/permission.py +4 -2
  191. astrbot/core/star/filter/platform_adapter_type.py +4 -2
  192. astrbot/core/star/filter/regex.py +4 -2
  193. astrbot/core/star/register/__init__.py +19 -19
  194. astrbot/core/star/register/star.py +6 -2
  195. astrbot/core/star/register/star_handler.py +96 -73
  196. astrbot/core/star/session_llm_manager.py +48 -14
  197. astrbot/core/star/session_plugin_manager.py +29 -15
  198. astrbot/core/star/star.py +1 -2
  199. astrbot/core/star/star_handler.py +13 -8
  200. astrbot/core/star/star_manager.py +151 -59
  201. astrbot/core/star/star_tools.py +44 -37
  202. astrbot/core/star/updator.py +10 -10
  203. astrbot/core/umop_config_router.py +10 -4
  204. astrbot/core/updator.py +13 -5
  205. astrbot/core/utils/astrbot_path.py +3 -5
  206. astrbot/core/utils/dify_api_client.py +33 -15
  207. astrbot/core/utils/io.py +66 -42
  208. astrbot/core/utils/log_pipe.py +1 -1
  209. astrbot/core/utils/metrics.py +7 -7
  210. astrbot/core/utils/path_util.py +15 -16
  211. astrbot/core/utils/pip_installer.py +5 -5
  212. astrbot/core/utils/session_waiter.py +19 -20
  213. astrbot/core/utils/shared_preferences.py +45 -20
  214. astrbot/core/utils/t2i/__init__.py +4 -1
  215. astrbot/core/utils/t2i/network_strategy.py +35 -26
  216. astrbot/core/utils/t2i/renderer.py +11 -5
  217. astrbot/core/utils/t2i/template_manager.py +14 -15
  218. astrbot/core/utils/tencent_record_helper.py +19 -13
  219. astrbot/core/utils/version_comparator.py +10 -13
  220. astrbot/core/zip_updator.py +43 -40
  221. astrbot/dashboard/routes/__init__.py +18 -18
  222. astrbot/dashboard/routes/auth.py +10 -8
  223. astrbot/dashboard/routes/chat.py +30 -21
  224. astrbot/dashboard/routes/config.py +92 -75
  225. astrbot/dashboard/routes/conversation.py +46 -39
  226. astrbot/dashboard/routes/file.py +4 -2
  227. astrbot/dashboard/routes/knowledge_base.py +47 -40
  228. astrbot/dashboard/routes/log.py +9 -4
  229. astrbot/dashboard/routes/persona.py +19 -16
  230. astrbot/dashboard/routes/plugin.py +69 -55
  231. astrbot/dashboard/routes/route.py +3 -1
  232. astrbot/dashboard/routes/session_management.py +130 -116
  233. astrbot/dashboard/routes/stat.py +34 -34
  234. astrbot/dashboard/routes/t2i.py +15 -12
  235. astrbot/dashboard/routes/tools.py +56 -53
  236. astrbot/dashboard/routes/update.py +32 -28
  237. astrbot/dashboard/server.py +30 -26
  238. astrbot/dashboard/utils.py +8 -4
  239. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/METADATA +2 -1
  240. astrbot-4.5.3.dist-info/RECORD +261 -0
  241. astrbot-4.5.1.dist-info/RECORD +0 -260
  242. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,25 +1,27 @@
1
- import random
2
1
  import asyncio
3
2
  import math
3
+ import random
4
+ from collections.abc import AsyncGenerator
5
+
4
6
  import astrbot.core.message.components as Comp
5
- from typing import Union, AsyncGenerator
6
- from ..stage import register_stage, Stage
7
- from ..context import PipelineContext, call_event_hook
8
- from astrbot.core.platform.astr_message_event import AstrMessageEvent
9
- from astrbot.core.message.message_event_result import MessageChain, ResultContentType
10
7
  from astrbot.core import logger
11
8
  from astrbot.core.message.components import BaseMessageComponent, ComponentType
9
+ from astrbot.core.message.message_event_result import MessageChain, ResultContentType
10
+ from astrbot.core.platform.astr_message_event import AstrMessageEvent
12
11
  from astrbot.core.star.star_handler import EventType
13
12
  from astrbot.core.utils.path_util import path_Mapping
14
13
  from astrbot.core.utils.session_lock import session_lock_manager
15
14
 
15
+ from ..context import PipelineContext, call_event_hook
16
+ from ..stage import Stage, register_stage
17
+
16
18
 
17
19
  @register_stage
18
20
  class RespondStage(Stage):
19
21
  # 组件类型到其非空判断函数的映射
20
22
  _component_validators = {
21
23
  Comp.Plain: lambda comp: bool(
22
- comp.text and comp.text.strip()
24
+ comp.text and comp.text.strip(),
23
25
  ), # 纯文本消息需要strip
24
26
  Comp.Face: lambda comp: comp.id is not None, # QQ表情
25
27
  Comp.Record: lambda comp: bool(comp.file), # 语音
@@ -58,7 +60,7 @@ class RespondStage(Stage):
58
60
  "segmented_reply"
59
61
  ]["interval_method"]
60
62
  self.log_base = float(
61
- ctx.astrbot_config["platform_settings"]["segmented_reply"]["log_base"]
63
+ ctx.astrbot_config["platform_settings"]["segmented_reply"]["log_base"],
62
64
  )
63
65
  interval_str: str = ctx.astrbot_config["platform_settings"]["segmented_reply"][
64
66
  "interval"
@@ -86,17 +88,16 @@ class RespondStage(Stage):
86
88
  wc = await self._word_cnt(comp.text)
87
89
  i = math.log(wc + 1, self.log_base)
88
90
  return random.uniform(i, i + 0.5)
89
- else:
90
- return random.uniform(1, 1.75)
91
- else:
92
- # random
93
- return random.uniform(self.interval[0], self.interval[1])
91
+ return random.uniform(1, 1.75)
92
+ # random
93
+ return random.uniform(self.interval[0], self.interval[1])
94
94
 
95
95
  async def _is_empty_message_chain(self, chain: list[BaseMessageComponent]):
96
96
  """检查消息链是否为空
97
97
 
98
98
  Args:
99
99
  chain (list[BaseMessageComponent]): 包含消息对象的列表
100
+
100
101
  """
101
102
  if not chain:
102
103
  return True
@@ -150,8 +151,9 @@ class RespondStage(Stage):
150
151
  return extracted
151
152
 
152
153
  async def process(
153
- self, event: AstrMessageEvent
154
- ) -> Union[None, AsyncGenerator[None, None]]:
154
+ self,
155
+ event: AstrMessageEvent,
156
+ ) -> None | AsyncGenerator[None, None]:
155
157
  result = event.get_result()
156
158
  if result is None:
157
159
  return
@@ -159,7 +161,7 @@ class RespondStage(Stage):
159
161
  return
160
162
 
161
163
  logger.info(
162
- f"Prepare to send - {event.get_sender_name()}/{event.get_sender_id()}: {event._outline_chain(result.chain)}"
164
+ f"Prepare to send - {event.get_sender_name()}/{event.get_sender_id()}: {event._outline_chain(result.chain)}",
163
165
  )
164
166
 
165
167
  if result.result_content_type == ResultContentType.STREAMING_RESULT:
@@ -168,12 +170,13 @@ class RespondStage(Stage):
168
170
  return
169
171
  # 流式结果直接交付平台适配器处理
170
172
  use_fallback = self.config.get("provider_settings", {}).get(
171
- "streaming_segmented", False
173
+ "streaming_segmented",
174
+ False,
172
175
  )
173
176
  logger.info(f"应用流式输出({event.get_platform_id()})")
174
177
  await event.send_streaming(result.async_stream, use_fallback)
175
178
  return
176
- elif len(result.chain) > 0:
179
+ if len(result.chain) > 0:
177
180
  # 检查路径映射
178
181
  if mappings := self.platform_settings.get("path_mapping", []):
179
182
  for idx, component in enumerate(result.chain):
@@ -212,7 +215,7 @@ class RespondStage(Stage):
212
215
  if not result.chain or len(result.chain) == 0:
213
216
  # may fix #2670
214
217
  logger.warning(
215
- f"实际消息链为空, 跳过发送阶段。header_chain: {header_comps}, actual_chain: {result.chain}"
218
+ f"实际消息链为空, 跳过发送阶段。header_chain: {header_comps}, actual_chain: {result.chain}",
216
219
  )
217
220
  return
218
221
  async with session_lock_manager.acquire_lock(event.unified_msg_origin):
@@ -237,7 +240,7 @@ class RespondStage(Stage):
237
240
  ):
238
241
  # may fix #2670
239
242
  logger.warning(
240
- f"消息链全为 Reply 和 At 消息段, 跳过发送阶段。chain: {result.chain}"
243
+ f"消息链全为 Reply 和 At 消息段, 跳过发送阶段。chain: {result.chain}",
241
244
  )
242
245
  return
243
246
  sep_comps = self._extract_comp(
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  import time
3
3
  import traceback
4
- from typing import AsyncGenerator, Union
4
+ from collections.abc import AsyncGenerator
5
5
 
6
6
  from astrbot.core import file_token_service, html_renderer, logger
7
7
  from astrbot.core.message.components import At, File, Image, Node, Plain, Record, Reply
@@ -30,8 +30,7 @@ class ResultDecorateStage(Stage):
30
30
  self.t2i_word_threshold = ctx.astrbot_config["t2i_word_threshold"]
31
31
  try:
32
32
  self.t2i_word_threshold = int(self.t2i_word_threshold)
33
- if self.t2i_word_threshold < 50:
34
- self.t2i_word_threshold = 50
33
+ self.t2i_word_threshold = max(self.t2i_word_threshold, 50)
35
34
  except BaseException:
36
35
  self.t2i_word_threshold = 150
37
36
  self.t2i_strategy = ctx.astrbot_config["t2i_strategy"]
@@ -46,7 +45,7 @@ class ResultDecorateStage(Stage):
46
45
  self.words_count_threshold = int(
47
46
  ctx.astrbot_config["platform_settings"]["segmented_reply"][
48
47
  "words_count_threshold"
49
- ]
48
+ ],
50
49
  )
51
50
  self.enable_segmented_reply = ctx.astrbot_config["platform_settings"][
52
51
  "segmented_reply"
@@ -71,8 +70,9 @@ class ResultDecorateStage(Stage):
71
70
  await self.content_safe_check_stage.initialize(ctx)
72
71
 
73
72
  async def process(
74
- self, event: AstrMessageEvent
75
- ) -> Union[None, AsyncGenerator[None, None]]:
73
+ self,
74
+ event: AstrMessageEvent,
75
+ ) -> None | AsyncGenerator[None, None]:
76
76
  result = event.get_result()
77
77
  if result is None or not result.chain:
78
78
  return
@@ -94,34 +94,36 @@ class ResultDecorateStage(Stage):
94
94
  if isinstance(comp, Plain):
95
95
  text += comp.text
96
96
  async for _ in self.content_safe_check_stage.process(
97
- event, check_text=text
97
+ event,
98
+ check_text=text,
98
99
  ):
99
100
  yield
100
101
 
101
102
  # 发送消息前事件钩子
102
103
  handlers = star_handlers_registry.get_handlers_by_event_type(
103
- EventType.OnDecoratingResultEvent, plugins_name=event.plugins_name
104
+ EventType.OnDecoratingResultEvent,
105
+ plugins_name=event.plugins_name,
104
106
  )
105
107
  for handler in handlers:
106
108
  try:
107
109
  logger.debug(
108
- f"hook(on_decorating_result) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}"
110
+ f"hook(on_decorating_result) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
109
111
  )
110
112
  if is_stream:
111
113
  logger.warning(
112
- "启用流式输出时,依赖发送消息前事件钩子的插件可能无法正常工作"
114
+ "启用流式输出时,依赖发送消息前事件钩子的插件可能无法正常工作",
113
115
  )
114
116
  await handler.handler(event)
115
117
  if event.get_result() is None or not event.get_result().chain:
116
118
  logger.debug(
117
- f"hook(on_decorating_result) -> {star_map[handler.handler_module_path].name} - {handler.handler_name} 将消息结果清空。"
119
+ f"hook(on_decorating_result) -> {star_map[handler.handler_module_path].name} - {handler.handler_name} 将消息结果清空。",
118
120
  )
119
121
  except BaseException:
120
122
  logger.error(traceback.format_exc())
121
123
 
122
124
  if event.is_stopped():
123
125
  logger.info(
124
- f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。"
126
+ f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。",
125
127
  )
126
128
  return
127
129
 
@@ -160,7 +162,9 @@ class ResultDecorateStage(Stage):
160
162
  new_chain.append(comp)
161
163
  continue
162
164
  split_response = re.findall(
163
- self.regex, comp.text, re.DOTALL | re.MULTILINE
165
+ self.regex,
166
+ comp.text,
167
+ re.DOTALL | re.MULTILINE,
164
168
  )
165
169
  if not split_response:
166
170
  new_chain.append(comp)
@@ -177,7 +181,7 @@ class ResultDecorateStage(Stage):
177
181
 
178
182
  # TTS
179
183
  tts_provider = self.ctx.plugin_manager.context.get_using_tts_provider(
180
- event.unified_msg_origin
184
+ event.unified_msg_origin,
181
185
  )
182
186
 
183
187
  if (
@@ -187,7 +191,7 @@ class ResultDecorateStage(Stage):
187
191
  ):
188
192
  if not tts_provider:
189
193
  logger.warning(
190
- f"会话 {event.unified_msg_origin} 未配置文本转语音模型。"
194
+ f"会话 {event.unified_msg_origin} 未配置文本转语音模型。",
191
195
  )
192
196
  else:
193
197
  new_chain = []
@@ -199,7 +203,7 @@ class ResultDecorateStage(Stage):
199
203
  logger.info(f"TTS 结果: {audio_path}")
200
204
  if not audio_path:
201
205
  logger.error(
202
- f"由于 TTS 音频文件未找到,消息段转语音失败: {comp.text}"
206
+ f"由于 TTS 音频文件未找到,消息段转语音失败: {comp.text}",
203
207
  )
204
208
  new_chain.append(comp)
205
209
  continue
@@ -217,7 +221,7 @@ class ResultDecorateStage(Stage):
217
221
  url = None
218
222
  if use_file_service and callback_api_base:
219
223
  token = await file_token_service.register_file(
220
- audio_path
224
+ audio_path,
221
225
  )
222
226
  url = f"{callback_api_base}/api/file/{token}"
223
227
  logger.debug(f"已注册:{url}")
@@ -226,7 +230,7 @@ class ResultDecorateStage(Stage):
226
230
  Record(
227
231
  file=url or audio_path,
228
232
  url=url or audio_path,
229
- )
233
+ ),
230
234
  )
231
235
  if dual_output:
232
236
  new_chain.append(comp)
@@ -242,12 +246,13 @@ class ResultDecorateStage(Stage):
242
246
  elif (
243
247
  result.use_t2i_ is None and self.ctx.astrbot_config["t2i"]
244
248
  ) or result.use_t2i_:
245
- plain_str = ""
249
+ parts = []
246
250
  for comp in result.chain:
247
251
  if isinstance(comp, Plain):
248
- plain_str += "\n\n" + comp.text
252
+ parts.append("\n\n" + comp.text)
249
253
  else:
250
254
  break
255
+ plain_str = "".join(parts)
251
256
  if plain_str and len(plain_str) > self.t2i_word_threshold:
252
257
  render_start = time.time()
253
258
  try:
@@ -262,7 +267,7 @@ class ResultDecorateStage(Stage):
262
267
  return
263
268
  if time.time() - render_start > 3:
264
269
  logger.warning(
265
- "文本转图片耗时超过了 3 秒,如果觉得很慢可以使用 /t2i 关闭文本转图片模式。"
270
+ "文本转图片耗时超过了 3 秒,如果觉得很慢可以使用 /t2i 关闭文本转图片模式。",
266
271
  )
267
272
  if url:
268
273
  if url.startswith("http"):
@@ -286,7 +291,9 @@ class ResultDecorateStage(Stage):
286
291
  word_cnt += len(comp.text)
287
292
  if word_cnt > self.forward_threshold:
288
293
  node = Node(
289
- uin=event.get_self_id(), name="AstrBot", content=[*result.chain]
294
+ uin=event.get_self_id(),
295
+ name="AstrBot",
296
+ content=[*result.chain],
290
297
  )
291
298
  result.chain = [node]
292
299
 
@@ -298,7 +305,8 @@ class ResultDecorateStage(Stage):
298
305
  and event.get_message_type() != MessageType.FRIEND_MESSAGE
299
306
  ):
300
307
  result.chain.insert(
301
- 0, At(qq=event.get_sender_id(), name=event.get_sender_name())
308
+ 0,
309
+ At(qq=event.get_sender_id(), name=event.get_sender_name()),
302
310
  )
303
311
  if len(result.chain) > 1 and isinstance(result.chain[1], Plain):
304
312
  result.chain[1].text = "\n" + result.chain[1].text
@@ -1,9 +1,11 @@
1
+ from collections.abc import AsyncGenerator
2
+
3
+ from astrbot.core import logger
4
+ from astrbot.core.platform import AstrMessageEvent
5
+
1
6
  from . import STAGES_ORDER
2
- from .stage import registered_stages
3
7
  from .context import PipelineContext
4
- from typing import AsyncGenerator
5
- from astrbot.core.platform import AstrMessageEvent
6
- from astrbot.core import logger
8
+ from .stage import registered_stages
7
9
 
8
10
 
9
11
  class PipelineScheduler:
@@ -11,7 +13,7 @@ class PipelineScheduler:
11
13
 
12
14
  def __init__(self, context: PipelineContext):
13
15
  registered_stages.sort(
14
- key=lambda x: STAGES_ORDER.index(x.__name__)
16
+ key=lambda x: STAGES_ORDER.index(x.__name__),
15
17
  ) # 按照顺序排序
16
18
  self.ctx = context # 上下文对象
17
19
  self.stages = [] # 存储阶段实例
@@ -29,12 +31,13 @@ class PipelineScheduler:
29
31
  Args:
30
32
  event (AstrMessageEvent): 事件对象
31
33
  from_stage (int): 从第几个阶段开始执行, 默认从0开始
34
+
32
35
  """
33
36
  for i in range(from_stage, len(self.stages)):
34
37
  stage = self.stages[i] # 获取当前要执行的阶段
35
38
  # logger.debug(f"执行阶段 {stage.__class__.__name__}")
36
39
  coroutine = stage.process(
37
- event
40
+ event,
38
41
  ) # 调用阶段的process方法, 返回协程或者异步生成器
39
42
 
40
43
  if isinstance(coroutine, AsyncGenerator):
@@ -43,7 +46,7 @@ class PipelineScheduler:
43
46
  # 此处是前置处理完成后的暂停点(yield), 下面开始执行后续阶段
44
47
  if event.is_stopped():
45
48
  logger.debug(
46
- f"阶段 {stage.__class__.__name__} 已终止事件传播。"
49
+ f"阶段 {stage.__class__.__name__} 已终止事件传播。",
47
50
  )
48
51
  break
49
52
 
@@ -53,7 +56,7 @@ class PipelineScheduler:
53
56
  # 此处是后续所有阶段处理完毕后返回的点, 执行后置处理
54
57
  if event.is_stopped():
55
58
  logger.debug(
56
- f"阶段 {stage.__class__.__name__} 已终止事件传播。"
59
+ f"阶段 {stage.__class__.__name__} 已终止事件传播。",
57
60
  )
58
61
  break
59
62
  else:
@@ -70,6 +73,7 @@ class PipelineScheduler:
70
73
 
71
74
  Args:
72
75
  event (AstrMessageEvent): 事件对象
76
+
73
77
  """
74
78
  await self._process_stages(event)
75
79
 
@@ -1,9 +1,11 @@
1
- from ..stage import Stage, register_stage
2
- from ..context import PipelineContext
3
- from typing import AsyncGenerator, Union
1
+ from collections.abc import AsyncGenerator
2
+
3
+ from astrbot.core import logger
4
4
  from astrbot.core.platform.astr_message_event import AstrMessageEvent
5
5
  from astrbot.core.star.session_llm_manager import SessionServiceManager
6
- from astrbot.core import logger
6
+
7
+ from ..context import PipelineContext
8
+ from ..stage import Stage, register_stage
7
9
 
8
10
 
9
11
  @register_stage
@@ -15,19 +17,21 @@ class SessionStatusCheckStage(Stage):
15
17
  self.conv_mgr = ctx.plugin_manager.context.conversation_manager
16
18
 
17
19
  async def process(
18
- self, event: AstrMessageEvent
19
- ) -> Union[None, AsyncGenerator[None, None]]:
20
+ self,
21
+ event: AstrMessageEvent,
22
+ ) -> None | AsyncGenerator[None, None]:
20
23
  # 检查会话是否整体启用
21
24
  if not SessionServiceManager.is_session_enabled(event.unified_msg_origin):
22
25
  logger.debug(f"会话 {event.unified_msg_origin} 已被关闭,已终止事件传播。")
23
26
 
24
27
  # workaround for #2309
25
28
  conv_id = await self.conv_mgr.get_curr_conversation_id(
26
- event.unified_msg_origin
29
+ event.unified_msg_origin,
27
30
  )
28
31
  if not conv_id:
29
32
  await self.conv_mgr.new_conversation(
30
- event.unified_msg_origin, platform_id=event.get_platform_id()
33
+ event.unified_msg_origin,
34
+ platform_id=event.get_platform_id(),
31
35
  )
32
36
 
33
37
  event.stop_event()
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
+
2
3
  import abc
3
- from typing import List, AsyncGenerator, Union, Type
4
+ from collections.abc import AsyncGenerator
5
+
4
6
  from astrbot.core.platform.astr_message_event import AstrMessageEvent
7
+
5
8
  from .context import PipelineContext
6
9
 
7
- registered_stages: List[Type[Stage]] = [] # 维护了所有已注册的 Stage 实现类类型
10
+ registered_stages: list[type[Stage]] = [] # 维护了所有已注册的 Stage 实现类类型
8
11
 
9
12
 
10
13
  def register_stage(cls):
@@ -22,18 +25,21 @@ class Stage(abc.ABC):
22
25
 
23
26
  Args:
24
27
  ctx (PipelineContext): 消息管道上下文对象, 包括配置和插件管理器
28
+
25
29
  """
26
30
  raise NotImplementedError
27
31
 
28
32
  @abc.abstractmethod
29
33
  async def process(
30
- self, event: AstrMessageEvent
31
- ) -> Union[None, AsyncGenerator[None, None]]:
34
+ self,
35
+ event: AstrMessageEvent,
36
+ ) -> None | AsyncGenerator[None, None]:
32
37
  """处理事件
33
38
 
34
39
  Args:
35
40
  event (AstrMessageEvent): 事件对象,包含事件的相关信息
36
41
  Returns:
37
42
  Union[None, AsyncGenerator[None, None]]: 处理结果,可能是 None 或者异步生成器, 如果为 None 则表示不需要继续处理, 如果为异步生成器则表示需要继续处理(进入下一个阶段)
43
+
38
44
  """
39
45
  raise NotImplementedError
@@ -1,11 +1,11 @@
1
- from typing import AsyncGenerator, Union
1
+ from collections.abc import AsyncGenerator
2
2
 
3
3
  from astrbot import logger
4
4
  from astrbot.core.message.components import At, AtAll, Reply
5
5
  from astrbot.core.message.message_event_result import MessageChain, MessageEventResult
6
6
  from astrbot.core.platform.astr_message_event import AstrMessageEvent
7
- from astrbot.core.star.filter.permission import PermissionTypeFilter
8
7
  from astrbot.core.star.filter.command_group import CommandGroupFilter
8
+ from astrbot.core.star.filter.permission import PermissionTypeFilter
9
9
  from astrbot.core.star.session_plugin_manager import SessionPluginManager
10
10
  from astrbot.core.star.star import star_map
11
11
  from astrbot.core.star.star_handler import EventType, star_handlers_registry
@@ -30,10 +30,12 @@ class WakingCheckStage(Stage):
30
30
 
31
31
  Args:
32
32
  ctx (PipelineContext): 消息管道上下文对象, 包括配置和插件管理器
33
+
33
34
  """
34
35
  self.ctx = ctx
35
36
  self.no_permission_reply = self.ctx.astrbot_config["platform_settings"].get(
36
- "no_permission_reply", True
37
+ "no_permission_reply",
38
+ True,
37
39
  )
38
40
  # 私聊是否需要 wake_prefix 才能唤醒机器人
39
41
  self.friend_message_needs_wake_prefix = self.ctx.astrbot_config[
@@ -41,15 +43,18 @@ class WakingCheckStage(Stage):
41
43
  ].get("friend_message_needs_wake_prefix", False)
42
44
  # 是否忽略机器人自己发送的消息
43
45
  self.ignore_bot_self_message = self.ctx.astrbot_config["platform_settings"].get(
44
- "ignore_bot_self_message", False
46
+ "ignore_bot_self_message",
47
+ False,
45
48
  )
46
49
  self.ignore_at_all = self.ctx.astrbot_config["platform_settings"].get(
47
- "ignore_at_all", False
50
+ "ignore_at_all",
51
+ False,
48
52
  )
49
53
 
50
54
  async def process(
51
- self, event: AstrMessageEvent
52
- ) -> Union[None, AsyncGenerator[None, None]]:
55
+ self,
56
+ event: AstrMessageEvent,
57
+ ) -> None | AsyncGenerator[None, None]:
53
58
  if (
54
59
  self.ignore_bot_self_message
55
60
  and event.get_self_id() == event.get_sender_id()
@@ -123,7 +128,8 @@ class WakingCheckStage(Stage):
123
128
  logger.debug(f"enabled_plugins_name: {enabled_plugins_name}")
124
129
 
125
130
  for handler in star_handlers_registry.get_handlers_by_event_type(
126
- EventType.AdapterMessageEvent, plugins_name=event.plugins_name
131
+ EventType.AdapterMessageEvent,
132
+ plugins_name=event.plugins_name,
127
133
  ):
128
134
  # filter 需满足 AND 逻辑关系
129
135
  passed = True
@@ -138,15 +144,14 @@ class WakingCheckStage(Stage):
138
144
  if not filter.filter(event, self.ctx.astrbot_config):
139
145
  permission_not_pass = True
140
146
  permission_filter_raise_error = filter.raise_error
141
- else:
142
- if not filter.filter(event, self.ctx.astrbot_config):
143
- passed = False
144
- break
147
+ elif not filter.filter(event, self.ctx.astrbot_config):
148
+ passed = False
149
+ break
145
150
  except Exception as e:
146
151
  await event.send(
147
152
  MessageEventResult().message(
148
- f"插件 {star_map[handler.handler_module_path].name}: {e}"
149
- )
153
+ f"插件 {star_map[handler.handler_module_path].name}: {e}",
154
+ ),
150
155
  )
151
156
  event.stop_event()
152
157
  passed = False
@@ -159,11 +164,11 @@ class WakingCheckStage(Stage):
159
164
  if self.no_permission_reply:
160
165
  await event.send(
161
166
  MessageChain().message(
162
- f"您(ID: {event.get_sender_id()})的权限不足以使用此指令。通过 /sid 获取 ID 并请管理员添加。"
163
- )
167
+ f"您(ID: {event.get_sender_id()})的权限不足以使用此指令。通过 /sid 获取 ID 并请管理员添加。",
168
+ ),
164
169
  )
165
170
  logger.info(
166
- f"触发 {star_map[handler.handler_module_path].name} 时, 用户(ID={event.get_sender_id()}) 权限不足。"
171
+ f"触发 {star_map[handler.handler_module_path].name} 时, 用户(ID={event.get_sender_id()}) 权限不足。",
167
172
  )
168
173
  event.stop_event()
169
174
  return
@@ -185,7 +190,8 @@ class WakingCheckStage(Stage):
185
190
 
186
191
  # 根据会话配置过滤插件处理器
187
192
  activated_handlers = SessionPluginManager.filter_handlers_by_session(
188
- event, activated_handlers
193
+ event,
194
+ activated_handlers,
189
195
  )
190
196
 
191
197
  event.set_extra("activated_handlers", activated_handlers)
@@ -1,9 +1,11 @@
1
- from ..stage import Stage, register_stage
2
- from ..context import PipelineContext
3
- from typing import AsyncGenerator, Union
1
+ from collections.abc import AsyncGenerator
2
+
3
+ from astrbot.core import logger
4
4
  from astrbot.core.platform.astr_message_event import AstrMessageEvent
5
5
  from astrbot.core.platform.message_type import MessageType
6
- from astrbot.core import logger
6
+
7
+ from ..context import PipelineContext
8
+ from ..stage import Stage, register_stage
7
9
 
8
10
 
9
11
  @register_stage
@@ -27,8 +29,9 @@ class WhitelistCheckStage(Stage):
27
29
  self.wl_log = ctx.astrbot_config["platform_settings"]["id_whitelist_log"]
28
30
 
29
31
  async def process(
30
- self, event: AstrMessageEvent
31
- ) -> Union[None, AsyncGenerator[None, None]]:
32
+ self,
33
+ event: AstrMessageEvent,
34
+ ) -> None | AsyncGenerator[None, None]:
32
35
  if not self.enable_whitelist_check:
33
36
  # 白名单检查未启用
34
37
  return
@@ -60,6 +63,6 @@ class WhitelistCheckStage(Stage):
60
63
  ):
61
64
  if self.wl_log:
62
65
  logger.info(
63
- f"会话 ID {event.unified_msg_origin} 不在会话白名单中,已终止事件传播。请在配置文件中添加该会话 ID 到白名单。"
66
+ f"会话 ID {event.unified_msg_origin} 不在会话白名单中,已终止事件传播。请在配置文件中添加该会话 ID 到白名单。",
64
67
  )
65
68
  event.stop_event()
@@ -1,14 +1,14 @@
1
- from .platform import Platform
2
1
  from .astr_message_event import AstrMessageEvent
2
+ from .astrbot_message import AstrBotMessage, Group, MessageMember, MessageType
3
+ from .platform import Platform
3
4
  from .platform_metadata import PlatformMetadata
4
- from .astrbot_message import AstrBotMessage, MessageMember, MessageType, Group
5
5
 
6
6
  __all__ = [
7
- "Platform",
8
- "AstrMessageEvent",
9
- "PlatformMetadata",
10
7
  "AstrBotMessage",
8
+ "AstrMessageEvent",
9
+ "Group",
11
10
  "MessageMember",
12
11
  "MessageType",
13
- "Group",
12
+ "Platform",
13
+ "PlatformMetadata",
14
14
  ]