AstrBot 4.5.1__py3-none-any.whl → 4.5.2__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 +47 -52
  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.2.dist-info}/METADATA +2 -1
  240. astrbot-4.5.2.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.2.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,16 +1,20 @@
1
- import uuid
1
+ import asyncio
2
2
  import json
3
3
  import os
4
- import asyncio
4
+ import uuid
5
5
  from contextlib import asynccontextmanager
6
- from .route import Route, Response, RouteContext
7
- from astrbot.core.platform.sources.webchat.webchat_queue_mgr import webchat_queue_mgr
8
- from quart import request, Response as QuartResponse, g, make_response
9
- from astrbot.core.db import BaseDatabase
6
+
7
+ from quart import Response as QuartResponse
8
+ from quart import g, make_response, request
9
+
10
10
  from astrbot.core import logger
11
11
  from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
12
- from astrbot.core.utils.astrbot_path import get_astrbot_data_path
12
+ from astrbot.core.db import BaseDatabase
13
13
  from astrbot.core.platform.astr_message_event import MessageSession
14
+ from astrbot.core.platform.sources.webchat.webchat_queue_mgr import webchat_queue_mgr
15
+ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
16
+
17
+ from .route import Response, Route, RouteContext
14
18
 
15
19
 
16
20
  @asynccontextmanager
@@ -70,10 +74,9 @@ class ChatRoute(Route):
70
74
 
71
75
  if filename_ext == ".wav":
72
76
  return QuartResponse(f.read(), mimetype="audio/wav")
73
- elif filename_ext[1:] in self.supported_imgs:
77
+ if filename_ext[1:] in self.supported_imgs:
74
78
  return QuartResponse(f.read(), mimetype="image/jpeg")
75
- else:
76
- return QuartResponse(f.read())
79
+ return QuartResponse(f.read())
77
80
 
78
81
  except (FileNotFoundError, OSError):
79
82
  return Response().error("File access error").__dict__
@@ -96,7 +99,7 @@ class ChatRoute(Route):
96
99
  return Response().error("Missing key: file").__dict__
97
100
 
98
101
  file = post_data["file"]
99
- filename = f"{str(uuid.uuid4())}"
102
+ filename = f"{uuid.uuid4()!s}"
100
103
  # 通过文件格式判断文件类型
101
104
  if file.content_type.startswith("audio"):
102
105
  filename += ".wav"
@@ -131,10 +134,10 @@ class ChatRoute(Route):
131
134
  if not conversation_id:
132
135
  return Response().error("conversation_id is empty").__dict__
133
136
 
134
- # append user message
137
+ # 追加用户消息
135
138
  webchat_conv_id = await self._get_webchat_conv_id_from_conv_id(conversation_id)
136
139
 
137
- # Get conversation-specific queues
140
+ # 获取会话特定的队列
138
141
  back_queue = webchat_queue_mgr.get_or_create_back_queue(webchat_conv_id)
139
142
 
140
143
  new_his = {"type": "user", "message": message}
@@ -179,7 +182,7 @@ class ChatRoute(Route):
179
182
  except Exception as e:
180
183
  if not client_disconnected:
181
184
  logger.debug(
182
- f"[WebChat] 用户 {username} 断开聊天长连接。 {e}"
185
+ f"[WebChat] 用户 {username} 断开聊天长连接。 {e}",
183
186
  )
184
187
  client_disconnected = True
185
188
 
@@ -197,7 +200,7 @@ class ChatRoute(Route):
197
200
  or not streaming
198
201
  or type == "break"
199
202
  ):
200
- # append bot message
203
+ # 追加机器人消息
201
204
  new_his = {"type": "bot", "message": result_text}
202
205
  await self.platform_history_mgr.insert(
203
206
  platform_id="webchat",
@@ -209,7 +212,7 @@ class ChatRoute(Route):
209
212
  except BaseException as e:
210
213
  logger.exception(f"WebChat stream unexpected error: {e}", exc_info=True)
211
214
 
212
- # Put message to conversation-specific queue
215
+ # 将消息放入会话特定的队列
213
216
  chat_queue = webchat_queue_mgr.get_or_create_queue(webchat_conv_id)
214
217
  await chat_queue.put(
215
218
  (
@@ -222,7 +225,7 @@ class ChatRoute(Route):
222
225
  "selected_provider": selected_provider,
223
226
  "selected_model": selected_model,
224
227
  },
225
- )
228
+ ),
226
229
  )
227
230
 
228
231
  response = await make_response(
@@ -243,7 +246,8 @@ class ChatRoute(Route):
243
246
  NOTE: 关于这里为什么要单独做一个 WebChat 的 Conversation ID 出来,这个是为了向前兼容。
244
247
  """
245
248
  conversation = await self.conv_mgr.get_conversation(
246
- unified_msg_origin="webchat", conversation_id=conversation_id
249
+ unified_msg_origin="webchat",
250
+ conversation_id=conversation_id,
247
251
  )
248
252
  if not conversation:
249
253
  raise ValueError(f"Conversation with ID {conversation_id} not found.")
@@ -267,7 +271,9 @@ class ChatRoute(Route):
267
271
  conversation_id=conversation_id,
268
272
  )
269
273
  await self.platform_history_mgr.delete(
270
- platform_id="webchat", user_id=webchat_conv_id, offset_sec=99999999
274
+ platform_id="webchat",
275
+ user_id=webchat_conv_id,
276
+ offset_sec=99999999,
271
277
  )
272
278
  return Response().ok().__dict__
273
279
 
@@ -314,7 +320,10 @@ class ChatRoute(Route):
314
320
 
315
321
  # Get platform message history
316
322
  history_ls = await self.platform_history_mgr.get(
317
- platform_id="webchat", user_id=webchat_conv_id, page=1, page_size=1000
323
+ platform_id="webchat",
324
+ user_id=webchat_conv_id,
325
+ page=1,
326
+ page_size=1000,
318
327
  )
319
328
 
320
329
  history_res = [history.model_dump() for history in history_ls]
@@ -325,7 +334,7 @@ class ChatRoute(Route):
325
334
  data={
326
335
  "history": history_res,
327
336
  "is_running": self.running_convs.get(webchat_conv_id, False),
328
- }
337
+ },
329
338
  )
330
339
  .__dict__
331
340
  )
@@ -1,26 +1,29 @@
1
- import traceback
2
- import os
1
+ import asyncio
3
2
  import inspect
4
- from .route import Route, Response, RouteContext
5
- from astrbot.core.provider.entities import ProviderType
3
+ import os
4
+ import traceback
5
+
6
6
  from quart import request
7
+
8
+ from astrbot.core import file_token_service, logger
9
+ from astrbot.core.config.astrbot_config import AstrBotConfig
7
10
  from astrbot.core.config.default import (
8
- DEFAULT_CONFIG,
9
11
  CONFIG_METADATA_2,
10
- DEFAULT_VALUE_MAP,
11
12
  CONFIG_METADATA_3,
12
13
  CONFIG_METADATA_3_SYSTEM,
14
+ DEFAULT_CONFIG,
15
+ DEFAULT_VALUE_MAP,
13
16
  )
14
- from astrbot.core.utils.astrbot_path import get_astrbot_path
15
- from astrbot.core.config.astrbot_config import AstrBotConfig
16
17
  from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
17
- from astrbot.core.platform.register import platform_registry, platform_cls_map
18
- from astrbot.core.provider.register import provider_registry
19
- from astrbot.core.star.star import star_registry
20
- from astrbot.core import logger, file_token_service
18
+ from astrbot.core.platform.register import platform_cls_map, platform_registry
21
19
  from astrbot.core.provider import Provider
20
+ from astrbot.core.provider.entities import ProviderType
22
21
  from astrbot.core.provider.provider import RerankProvider
23
- import asyncio
22
+ from astrbot.core.provider.register import provider_registry
23
+ from astrbot.core.star.star import star_registry
24
+ from astrbot.core.utils.astrbot_path import get_astrbot_path
25
+
26
+ from .route import Response, Route, RouteContext
24
27
 
25
28
 
26
29
  def try_cast(value: str, type_: str):
@@ -33,9 +36,7 @@ def try_cast(value: str, type_: str):
33
36
  type_ == "float"
34
37
  and isinstance(value, str)
35
38
  and value.replace(".", "", 1).isdigit()
36
- ):
37
- return float(value)
38
- elif type_ == "float" and isinstance(value, int):
39
+ ) or (type_ == "float" and isinstance(value, int)):
39
40
  return float(value)
40
41
  elif type_ == "float":
41
42
  try:
@@ -61,7 +62,7 @@ def validate_config(data, schema: dict, is_core: bool) -> tuple[list[str], dict]
61
62
  continue
62
63
  if meta["type"] == "list" and not isinstance(value, list):
63
64
  errors.append(
64
- f"错误的类型 {path}{key}: 期望是 list, 得到了 {type(value).__name__}"
65
+ f"错误的类型 {path}{key}: 期望是 list, 得到了 {type(value).__name__}",
65
66
  )
66
67
  elif (
67
68
  meta["type"] == "list"
@@ -80,31 +81,31 @@ def validate_config(data, schema: dict, is_core: bool) -> tuple[list[str], dict]
80
81
  casted = try_cast(value, "int")
81
82
  if casted is None:
82
83
  errors.append(
83
- f"错误的类型 {path}{key}: 期望是 int, 得到了 {type(value).__name__}"
84
+ f"错误的类型 {path}{key}: 期望是 int, 得到了 {type(value).__name__}",
84
85
  )
85
86
  data[key] = casted
86
87
  elif meta["type"] == "float" and not isinstance(value, float):
87
88
  casted = try_cast(value, "float")
88
89
  if casted is None:
89
90
  errors.append(
90
- f"错误的类型 {path}{key}: 期望是 float, 得到了 {type(value).__name__}"
91
+ f"错误的类型 {path}{key}: 期望是 float, 得到了 {type(value).__name__}",
91
92
  )
92
93
  data[key] = casted
93
94
  elif meta["type"] == "bool" and not isinstance(value, bool):
94
95
  errors.append(
95
- f"错误的类型 {path}{key}: 期望是 bool, 得到了 {type(value).__name__}"
96
+ f"错误的类型 {path}{key}: 期望是 bool, 得到了 {type(value).__name__}",
96
97
  )
97
98
  elif meta["type"] in ["string", "text"] and not isinstance(value, str):
98
99
  errors.append(
99
- f"错误的类型 {path}{key}: 期望是 string, 得到了 {type(value).__name__}"
100
+ f"错误的类型 {path}{key}: 期望是 string, 得到了 {type(value).__name__}",
100
101
  )
101
102
  elif meta["type"] == "list" and not isinstance(value, list):
102
103
  errors.append(
103
- f"错误的类型 {path}{key}: 期望是 list, 得到了 {type(value).__name__}"
104
+ f"错误的类型 {path}{key}: 期望是 list, 得到了 {type(value).__name__}",
104
105
  )
105
106
  elif meta["type"] == "object" and not isinstance(value, dict):
106
107
  errors.append(
107
- f"错误的类型 {path}{key}: 期望是 dict, 得到了 {type(value).__name__}"
108
+ f"错误的类型 {path}{key}: 期望是 dict, 得到了 {type(value).__name__}",
108
109
  )
109
110
 
110
111
  if is_core:
@@ -127,7 +128,9 @@ def save_config(post_config: dict, config: AstrBotConfig, is_core: bool = False)
127
128
  try:
128
129
  if is_core:
129
130
  errors, post_config = validate_config(
130
- post_config, CONFIG_METADATA_2, is_core
131
+ post_config,
132
+ CONFIG_METADATA_2,
133
+ is_core,
131
134
  )
132
135
  else:
133
136
  errors, post_config = validate_config(post_config, config.schema, is_core)
@@ -143,7 +146,9 @@ def save_config(post_config: dict, config: AstrBotConfig, is_core: bool = False)
143
146
 
144
147
  class ConfigRoute(Route):
145
148
  def __init__(
146
- self, context: RouteContext, core_lifecycle: AstrBotCoreLifecycle
149
+ self,
150
+ context: RouteContext,
151
+ core_lifecycle: AstrBotCoreLifecycle,
147
152
  ) -> None:
148
153
  super().__init__(context)
149
154
  self.core_lifecycle = core_lifecycle
@@ -199,7 +204,7 @@ class ConfigRoute(Route):
199
204
  return Response().ok(message="更新成功").__dict__
200
205
  except Exception as e:
201
206
  logger.error(traceback.format_exc())
202
- return Response().error(f"更新路由表失败: {str(e)}").__dict__
207
+ return Response().error(f"更新路由表失败: {e!s}").__dict__
203
208
 
204
209
  async def update_ucr(self):
205
210
  """更新 UMOP 配置路由表"""
@@ -218,7 +223,7 @@ class ConfigRoute(Route):
218
223
  return Response().ok(message="更新成功").__dict__
219
224
  except Exception as e:
220
225
  logger.error(traceback.format_exc())
221
- return Response().error(f"更新路由表失败: {str(e)}").__dict__
226
+ return Response().error(f"更新路由表失败: {e!s}").__dict__
222
227
 
223
228
  async def delete_ucr(self):
224
229
  """删除 UMOP 配置路由表中的一项"""
@@ -238,7 +243,7 @@ class ConfigRoute(Route):
238
243
  return Response().ok(message="删除成功").__dict__
239
244
  except Exception as e:
240
245
  logger.error(traceback.format_exc())
241
- return Response().error(f"删除路由表项失败: {str(e)}").__dict__
246
+ return Response().error(f"删除路由表项失败: {e!s}").__dict__
242
247
 
243
248
  async def get_default_config(self):
244
249
  """获取默认配置文件"""
@@ -305,13 +310,12 @@ class ConfigRoute(Route):
305
310
  success = self.acm.delete_conf(conf_id)
306
311
  if success:
307
312
  return Response().ok(message="删除成功").__dict__
308
- else:
309
- return Response().error("删除失败").__dict__
313
+ return Response().error("删除失败").__dict__
310
314
  except ValueError as e:
311
315
  return Response().error(str(e)).__dict__
312
316
  except Exception as e:
313
317
  logger.error(traceback.format_exc())
314
- return Response().error(f"删除配置文件失败: {str(e)}").__dict__
318
+ return Response().error(f"删除配置文件失败: {e!s}").__dict__
315
319
 
316
320
  async def update_abconf(self):
317
321
  """更新指定 AstrBot 配置文件信息"""
@@ -329,13 +333,12 @@ class ConfigRoute(Route):
329
333
  success = self.acm.update_conf_info(conf_id, name=name)
330
334
  if success:
331
335
  return Response().ok(message="更新成功").__dict__
332
- else:
333
- return Response().error("更新失败").__dict__
336
+ return Response().error("更新失败").__dict__
334
337
  except ValueError as e:
335
338
  return Response().error(str(e)).__dict__
336
339
  except Exception as e:
337
340
  logger.error(traceback.format_exc())
338
- return Response().error(f"更新配置文件失败: {str(e)}").__dict__
341
+ return Response().error(f"更新配置文件失败: {e!s}").__dict__
339
342
 
340
343
  async def _test_single_provider(self, provider):
341
344
  """辅助函数:测试单个 provider 的可用性"""
@@ -352,17 +355,18 @@ class ConfigRoute(Route):
352
355
  "error": None,
353
356
  }
354
357
  logger.debug(
355
- f"Attempting to check provider: {status_info['name']} (ID: {status_info['id']}, Type: {status_info['type']}, Model: {status_info['model']})"
358
+ f"Attempting to check provider: {status_info['name']} (ID: {status_info['id']}, Type: {status_info['type']}, Model: {status_info['model']})",
356
359
  )
357
360
 
358
361
  if provider_capability_type == ProviderType.CHAT_COMPLETION:
359
362
  try:
360
363
  logger.debug(f"Sending 'Ping' to provider: {status_info['name']}")
361
364
  response = await asyncio.wait_for(
362
- provider.text_chat(prompt="REPLY `PONG` ONLY"), timeout=45.0
365
+ provider.text_chat(prompt="REPLY `PONG` ONLY"),
366
+ timeout=45.0,
363
367
  )
364
368
  logger.debug(
365
- f"Received response from {status_info['name']}: {response}"
369
+ f"Received response from {status_info['name']}: {response}",
366
370
  )
367
371
  if response is not None:
368
372
  status_info["status"] = "available"
@@ -386,14 +390,14 @@ class ConfigRoute(Route):
386
390
  except Exception as _:
387
391
  pass
388
392
  logger.info(
389
- f"Provider {status_info['name']} (ID: {status_info['id']}) is available. Response snippet: '{response_text_snippet}'"
393
+ f"Provider {status_info['name']} (ID: {status_info['id']}) is available. Response snippet: '{response_text_snippet}'",
390
394
  )
391
395
  else:
392
396
  status_info["error"] = (
393
397
  "Test call returned None, but expected an LLMResponse object."
394
398
  )
395
399
  logger.warning(
396
- f"Provider {status_info['name']} (ID: {status_info['id']}) test call returned None."
400
+ f"Provider {status_info['name']} (ID: {status_info['id']}) test call returned None.",
397
401
  )
398
402
 
399
403
  except asyncio.TimeoutError:
@@ -401,16 +405,16 @@ class ConfigRoute(Route):
401
405
  "Connection timed out after 45 seconds during test call."
402
406
  )
403
407
  logger.warning(
404
- f"Provider {status_info['name']} (ID: {status_info['id']}) timed out."
408
+ f"Provider {status_info['name']} (ID: {status_info['id']}) timed out.",
405
409
  )
406
410
  except Exception as e:
407
411
  error_message = str(e)
408
412
  status_info["error"] = error_message
409
413
  logger.warning(
410
- f"Provider {status_info['name']} (ID: {status_info['id']}) is unavailable. Error: {error_message}"
414
+ f"Provider {status_info['name']} (ID: {status_info['id']}) is unavailable. Error: {error_message}",
411
415
  )
412
416
  logger.debug(
413
- f"Traceback for {status_info['name']}:\n{traceback.format_exc()}"
417
+ f"Traceback for {status_info['name']}:\n{traceback.format_exc()}",
414
418
  )
415
419
 
416
420
  elif provider_capability_type == ProviderType.EMBEDDING:
@@ -432,7 +436,7 @@ class ConfigRoute(Route):
432
436
  exc_info=True,
433
437
  )
434
438
  status_info["status"] = "unavailable"
435
- status_info["error"] = f"Embedding test failed: {str(e)}"
439
+ status_info["error"] = f"Embedding test failed: {e!s}"
436
440
 
437
441
  elif provider_capability_type == ProviderType.TEXT_TO_SPEECH:
438
442
  try:
@@ -447,17 +451,20 @@ class ConfigRoute(Route):
447
451
  )
448
452
  except Exception as e:
449
453
  logger.error(
450
- f"Error testing TTS provider {provider_name}: {e}", exc_info=True
454
+ f"Error testing TTS provider {provider_name}: {e}",
455
+ exc_info=True,
451
456
  )
452
457
  status_info["status"] = "unavailable"
453
- status_info["error"] = f"TTS test failed: {str(e)}"
458
+ status_info["error"] = f"TTS test failed: {e!s}"
454
459
  elif provider_capability_type == ProviderType.SPEECH_TO_TEXT:
455
460
  try:
456
461
  logger.debug(
457
- f"Sending health check audio to provider: {status_info['name']}"
462
+ f"Sending health check audio to provider: {status_info['name']}",
458
463
  )
459
464
  sample_audio_path = os.path.join(
460
- get_astrbot_path(), "samples", "stt_health_check.wav"
465
+ get_astrbot_path(),
466
+ "samples",
467
+ "stt_health_check.wav",
461
468
  )
462
469
  if not os.path.exists(sample_audio_path):
463
470
  status_info["status"] = "unavailable"
@@ -465,7 +472,7 @@ class ConfigRoute(Route):
465
472
  "STT test failed: sample audio file not found."
466
473
  )
467
474
  logger.warning(
468
- f"STT test for {status_info['name']} failed: sample audio file not found at {sample_audio_path}"
475
+ f"STT test for {status_info['name']} failed: sample audio file not found at {sample_audio_path}",
469
476
  )
470
477
  else:
471
478
  text_result = await provider.get_text(sample_audio_path)
@@ -477,7 +484,7 @@ class ConfigRoute(Route):
477
484
  else text_result
478
485
  )
479
486
  logger.info(
480
- f"Provider {status_info['name']} (ID: {status_info['id']}) is available. Response snippet: '{snippet}'"
487
+ f"Provider {status_info['name']} (ID: {status_info['id']}) is available. Response snippet: '{snippet}'",
481
488
  )
482
489
  else:
483
490
  status_info["status"] = "unavailable"
@@ -485,14 +492,15 @@ class ConfigRoute(Route):
485
492
  f"STT test failed: unexpected result type {type(text_result)}"
486
493
  )
487
494
  logger.warning(
488
- f"STT test for {status_info['name']} failed: unexpected result type {type(text_result)}"
495
+ f"STT test for {status_info['name']} failed: unexpected result type {type(text_result)}",
489
496
  )
490
497
  except Exception as e:
491
498
  logger.error(
492
- f"Error testing STT provider {provider_name}: {e}", exc_info=True
499
+ f"Error testing STT provider {provider_name}: {e}",
500
+ exc_info=True,
493
501
  )
494
502
  status_info["status"] = "unavailable"
495
- status_info["error"] = f"STT test failed: {str(e)}"
503
+ status_info["error"] = f"STT test failed: {e!s}"
496
504
  elif provider_capability_type == ProviderType.RERANK:
497
505
  try:
498
506
  assert isinstance(provider, RerankProvider)
@@ -504,11 +512,11 @@ class ConfigRoute(Route):
504
512
  exc_info=True,
505
513
  )
506
514
  status_info["status"] = "unavailable"
507
- status_info["error"] = f"Rerank test failed: {str(e)}"
515
+ status_info["error"] = f"Rerank test failed: {e!s}"
508
516
 
509
517
  else:
510
518
  logger.debug(
511
- f"Provider {provider_name} is not a Chat Completion or Embedding provider. Marking as available without test. Meta: {meta}"
519
+ f"Provider {provider_name} is not a Chat Completion or Embedding provider. Marking as available without test. Meta: {meta}",
512
520
  )
513
521
  status_info["status"] = "available"
514
522
  status_info["error"] = (
@@ -518,7 +526,10 @@ class ConfigRoute(Route):
518
526
  return status_info
519
527
 
520
528
  def _error_response(
521
- self, message: str, status_code: int = 500, log_fn=logger.error
529
+ self,
530
+ message: str,
531
+ status_code: int = 500,
532
+ log_fn=logger.error,
522
533
  ):
523
534
  log_fn(message)
524
535
  # 记录更详细的traceback信息,但只在是严重错误时
@@ -531,7 +542,9 @@ class ConfigRoute(Route):
531
542
  provider_id = request.args.get("id")
532
543
  if not provider_id:
533
544
  return self._error_response(
534
- "Missing provider_id parameter", 400, logger.warning
545
+ "Missing provider_id parameter",
546
+ 400,
547
+ logger.warning,
535
548
  )
536
549
 
537
550
  logger.info(f"API call: /config/provider/check_one id={provider_id}")
@@ -541,7 +554,7 @@ class ConfigRoute(Route):
541
554
 
542
555
  if not target:
543
556
  logger.warning(
544
- f"Provider with id '{provider_id}' not found in provider_manager."
557
+ f"Provider with id '{provider_id}' not found in provider_manager.",
545
558
  )
546
559
  return (
547
560
  Response()
@@ -554,7 +567,8 @@ class ConfigRoute(Route):
554
567
 
555
568
  except Exception as e:
556
569
  return self._error_response(
557
- f"Critical error checking provider {provider_id}: {e}", 500
570
+ f"Critical error checking provider {provider_id}: {e}",
571
+ 500,
558
572
  )
559
573
 
560
574
  async def get_configs(self):
@@ -646,13 +660,13 @@ class ConfigRoute(Route):
646
660
  dim = len(vec)
647
661
 
648
662
  logger.info(
649
- f"检测到 {provider_config.get('id', 'unknown')} 的嵌入向量维度为 {dim}"
663
+ f"检测到 {provider_config.get('id', 'unknown')} 的嵌入向量维度为 {dim}",
650
664
  )
651
665
 
652
666
  return Response().ok({"embedding_dimensions": dim}).__dict__
653
667
  except Exception as e:
654
668
  logger.error(traceback.format_exc())
655
- return Response().error(f"获取嵌入维度失败: {str(e)}").__dict__
669
+ return Response().error(f"获取嵌入维度失败: {e!s}").__dict__
656
670
 
657
671
  async def get_platform_list(self):
658
672
  """获取所有平台的列表"""
@@ -693,7 +707,7 @@ class ConfigRoute(Route):
693
707
  try:
694
708
  save_config(self.config, self.config, is_core=True)
695
709
  await self.core_lifecycle.platform_manager.load_platform(
696
- new_platform_config
710
+ new_platform_config,
697
711
  )
698
712
  except Exception as e:
699
713
  return Response().error(str(e)).__dict__
@@ -705,7 +719,7 @@ class ConfigRoute(Route):
705
719
  try:
706
720
  save_config(self.config, self.config, is_core=True)
707
721
  await self.core_lifecycle.provider_manager.load_provider(
708
- new_provider_config
722
+ new_provider_config,
709
723
  )
710
724
  except Exception as e:
711
725
  return Response().error(str(e)).__dict__
@@ -802,9 +816,9 @@ class ConfigRoute(Route):
802
816
  if cache_key in self._logo_token_cache:
803
817
  cached_token = self._logo_token_cache[cache_key]
804
818
  # 确保platform_default_tmpl[platform.name]存在且为字典
805
- if platform.name not in platform_default_tmpl:
806
- platform_default_tmpl[platform.name] = {}
807
- elif not isinstance(platform_default_tmpl[platform.name], dict):
819
+ if platform.name not in platform_default_tmpl or not isinstance(
820
+ platform_default_tmpl[platform.name], dict
821
+ ):
808
822
  platform_default_tmpl[platform.name] = {}
809
823
  platform_default_tmpl[platform.name]["logo_token"] = cached_token
810
824
  logger.debug(f"Using cached logo token for platform {platform.name}")
@@ -826,13 +840,14 @@ class ConfigRoute(Route):
826
840
  # 检查文件是否存在并注册令牌
827
841
  if os.path.exists(logo_file_path):
828
842
  logo_token = await file_token_service.register_file(
829
- logo_file_path, timeout=3600
843
+ logo_file_path,
844
+ timeout=3600,
830
845
  )
831
846
 
832
847
  # 确保platform_default_tmpl[platform.name]存在且为字典
833
- if platform.name not in platform_default_tmpl:
834
- platform_default_tmpl[platform.name] = {}
835
- elif not isinstance(platform_default_tmpl[platform.name], dict):
848
+ if platform.name not in platform_default_tmpl or not isinstance(
849
+ platform_default_tmpl[platform.name], dict
850
+ ):
836
851
  platform_default_tmpl[platform.name] = {}
837
852
 
838
853
  platform_default_tmpl[platform.name]["logo_token"] = logo_token
@@ -843,18 +858,18 @@ class ConfigRoute(Route):
843
858
  logger.debug(f"Logo token registered for platform {platform.name}")
844
859
  else:
845
860
  logger.warning(
846
- f"Platform {platform.name} logo file not found: {logo_file_path}"
861
+ f"Platform {platform.name} logo file not found: {logo_file_path}",
847
862
  )
848
863
 
849
864
  except (ImportError, AttributeError) as e:
850
865
  logger.warning(
851
- f"Failed to import required modules for platform {platform.name}: {e}"
866
+ f"Failed to import required modules for platform {platform.name}: {e}",
852
867
  )
853
868
  except OSError as e:
854
869
  logger.warning(f"File system error for platform {platform.name} logo: {e}")
855
870
  except Exception as e:
856
871
  logger.warning(
857
- f"Unexpected error registering logo for platform {platform.name}: {e}"
872
+ f"Unexpected error registering logo for platform {platform.name}: {e}",
858
873
  )
859
874
 
860
875
  async def _get_astrbot_config(self):
@@ -873,7 +888,7 @@ class ConfigRoute(Route):
873
888
  # 收集logo注册任务
874
889
  if platform.logo_path:
875
890
  logo_registration_tasks.append(
876
- self._register_platform_logo(platform, platform_default_tmpl)
891
+ self._register_platform_logo(platform, platform_default_tmpl),
877
892
  )
878
893
 
879
894
  # 并行执行logo注册
@@ -905,13 +920,15 @@ class ConfigRoute(Route):
905
920
  "description": f"{plugin_name} 配置",
906
921
  "type": "object",
907
922
  "items": plugin_md.config.schema, # 初始化时通过 __setattr__ 存入了 schema
908
- }
923
+ },
909
924
  }
910
925
  break
911
926
 
912
927
  return ret
913
928
 
914
- async def _save_astrbot_configs(self, post_configs: dict, conf_id: str = None):
929
+ async def _save_astrbot_configs(
930
+ self, post_configs: dict, conf_id: str | None = None
931
+ ):
915
932
  try:
916
933
  if conf_id not in self.acm.confs:
917
934
  raise ValueError(f"配置文件 {conf_id} 不存在")