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
@@ -3,7 +3,7 @@ import base64
3
3
  import logging
4
4
  import os
5
5
  import uuid
6
- from typing import Optional, Tuple
6
+
7
7
  import aiohttp
8
8
  import dashscope
9
9
  from dashscope.audio.tts_v2 import AudioFormat, SpeechSynthesizer
@@ -15,14 +15,17 @@ except (
15
15
  ): # pragma: no cover - older dashscope versions without Qwen TTS support
16
16
  MultiModalConversation = None
17
17
 
18
+ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
19
+
18
20
  from ..entities import ProviderType
19
21
  from ..provider import TTSProvider
20
22
  from ..register import register_provider_adapter
21
- from astrbot.core.utils.astrbot_path import get_astrbot_data_path
22
23
 
23
24
 
24
25
  @register_provider_adapter(
25
- "dashscope_tts", "Dashscope TTS API", provider_type=ProviderType.TEXT_TO_SPEECH
26
+ "dashscope_tts",
27
+ "Dashscope TTS API",
28
+ provider_type=ProviderType.TEXT_TO_SPEECH,
26
29
  )
27
30
  class ProviderDashscopeTTSAPI(TTSProvider):
28
31
  def __init__(
@@ -33,7 +36,7 @@ class ProviderDashscopeTTSAPI(TTSProvider):
33
36
  super().__init__(provider_config, provider_settings)
34
37
  self.chosen_api_key: str = provider_config.get("api_key", "")
35
38
  self.voice: str = provider_config.get("dashscope_tts_voice", "loongstella")
36
- self.set_model(provider_config.get("model", None))
39
+ self.set_model(provider_config.get("model"))
37
40
  self.timeout_ms = float(provider_config.get("timeout", 20)) * 1000
38
41
  dashscope.api_key = self.chosen_api_key
39
42
 
@@ -52,7 +55,7 @@ class ProviderDashscopeTTSAPI(TTSProvider):
52
55
 
53
56
  if not audio_bytes:
54
57
  raise RuntimeError(
55
- "Audio synthesis failed, returned empty content. The model may not be supported or the service is unavailable."
58
+ "Audio synthesis failed, returned empty content. The model may not be supported or the service is unavailable.",
56
59
  )
57
60
 
58
61
  path = os.path.join(temp_dir, f"dashscope_tts_{uuid.uuid4()}{ext}")
@@ -63,7 +66,7 @@ class ProviderDashscopeTTSAPI(TTSProvider):
63
66
  def _call_qwen_tts(self, model: str, text: str):
64
67
  if MultiModalConversation is None:
65
68
  raise RuntimeError(
66
- "dashscope SDK missing MultiModalConversation. Please upgrade the dashscope package to use Qwen TTS models."
69
+ "dashscope SDK missing MultiModalConversation. Please upgrade the dashscope package to use Qwen TTS models.",
67
70
  )
68
71
 
69
72
  kwargs = {
@@ -74,24 +77,26 @@ class ProviderDashscopeTTSAPI(TTSProvider):
74
77
  }
75
78
  if not self.voice:
76
79
  logging.warning(
77
- "No voice specified for Qwen TTS model, using default 'Cherry'."
80
+ "No voice specified for Qwen TTS model, using default 'Cherry'.",
78
81
  )
79
82
  return MultiModalConversation.call(**kwargs)
80
83
 
81
84
  async def _synthesize_with_qwen_tts(
82
- self, model: str, text: str
83
- ) -> Tuple[Optional[bytes], str]:
85
+ self,
86
+ model: str,
87
+ text: str,
88
+ ) -> tuple[bytes | None, str]:
84
89
  loop = asyncio.get_event_loop()
85
90
  response = await loop.run_in_executor(None, self._call_qwen_tts, model, text)
86
91
  audio_bytes = await self._extract_audio_from_response(response)
87
92
  if not audio_bytes:
88
93
  raise RuntimeError(
89
- f"Audio synthesis failed for model '{model}'. {response}"
94
+ f"Audio synthesis failed for model '{model}'. {response}",
90
95
  )
91
96
  ext = ".wav"
92
97
  return audio_bytes, ext
93
98
 
94
- async def _extract_audio_from_response(self, response) -> Optional[bytes]:
99
+ async def _extract_audio_from_response(self, response) -> bytes | None:
95
100
  output = getattr(response, "output", None)
96
101
  audio_obj = getattr(output, "audio", None) if output is not None else None
97
102
  if not audio_obj:
@@ -102,7 +107,7 @@ class ProviderDashscopeTTSAPI(TTSProvider):
102
107
  try:
103
108
  return base64.b64decode(data_b64)
104
109
  except (ValueError, TypeError):
105
- logging.error("Failed to decode base64 audio data.")
110
+ logging.exception("Failed to decode base64 audio data.")
106
111
  return None
107
112
 
108
113
  url = getattr(audio_obj, "url", None)
@@ -110,23 +115,28 @@ class ProviderDashscopeTTSAPI(TTSProvider):
110
115
  return await self._download_audio_from_url(url)
111
116
  return None
112
117
 
113
- async def _download_audio_from_url(self, url: str) -> Optional[bytes]:
118
+ async def _download_audio_from_url(self, url: str) -> bytes | None:
114
119
  if not url:
115
120
  return None
116
121
  timeout = max(self.timeout_ms / 1000, 1) if self.timeout_ms else 20
117
122
  try:
118
- async with aiohttp.ClientSession() as session:
119
- async with session.get(
120
- url, timeout=aiohttp.ClientTimeout(total=timeout)
121
- ) as response:
122
- return await response.read()
123
+ async with (
124
+ aiohttp.ClientSession() as session,
125
+ session.get(
126
+ url,
127
+ timeout=aiohttp.ClientTimeout(total=timeout),
128
+ ) as response,
129
+ ):
130
+ return await response.read()
123
131
  except (aiohttp.ClientError, asyncio.TimeoutError, OSError) as e:
124
- logging.error(f"Failed to download audio from URL {url}: {e}")
132
+ logging.exception(f"Failed to download audio from URL {url}: {e}")
125
133
  return None
126
134
 
127
135
  async def _synthesize_with_cosyvoice(
128
- self, model: str, text: str
129
- ) -> Tuple[Optional[bytes], str]:
136
+ self,
137
+ model: str,
138
+ text: str,
139
+ ) -> tuple[bytes | None, str]:
130
140
  synthesizer = SpeechSynthesizer(
131
141
  model=model,
132
142
  voice=self.voice,
@@ -134,13 +144,16 @@ class ProviderDashscopeTTSAPI(TTSProvider):
134
144
  )
135
145
  loop = asyncio.get_event_loop()
136
146
  audio_bytes = await loop.run_in_executor(
137
- None, synthesizer.call, text, self.timeout_ms
147
+ None,
148
+ synthesizer.call,
149
+ text,
150
+ self.timeout_ms,
138
151
  )
139
152
  if not audio_bytes:
140
153
  resp = synthesizer.get_response()
141
154
  if resp and isinstance(resp, dict):
142
155
  raise RuntimeError(
143
- f"Audio synthesis failed for model '{model}'. {resp}".strip()
156
+ f"Audio synthesis failed for model '{model}'. {resp}".strip(),
144
157
  )
145
158
  return audio_bytes, ".wav"
146
159
 
@@ -1,13 +1,15 @@
1
- import astrbot.core.message.components as Comp
2
1
  import os
3
- from .. import Provider
4
- from ..entities import LLMResponse
5
- from ..register import register_provider_adapter
6
- from astrbot.core.utils.dify_api_client import DifyAPIClient
7
- from astrbot.core.utils.io import download_image_by_url, download_file
2
+
3
+ import astrbot.core.message.components as Comp
8
4
  from astrbot.core import logger, sp
9
5
  from astrbot.core.message.message_event_result import MessageChain
10
6
  from astrbot.core.utils.astrbot_path import get_astrbot_data_path
7
+ from astrbot.core.utils.dify_api_client import DifyAPIClient
8
+ from astrbot.core.utils.io import download_file, download_image_by_url
9
+
10
+ from .. import Provider
11
+ from ..entities import LLMResponse
12
+ from ..register import register_provider_adapter
11
13
 
12
14
 
13
15
  @register_provider_adapter("dify", "Dify APP 适配器。")
@@ -32,10 +34,12 @@ class ProviderDify(Provider):
32
34
  raise Exception("Dify API 类型不能为空。")
33
35
  self.model_name = "dify"
34
36
  self.workflow_output_key = provider_config.get(
35
- "dify_workflow_output_key", "astrbot_wf_output"
37
+ "dify_workflow_output_key",
38
+ "astrbot_wf_output",
36
39
  )
37
40
  self.dify_query_input_key = provider_config.get(
38
- "dify_query_input_key", "astrbot_text_query"
41
+ "dify_query_input_key",
42
+ "astrbot_text_query",
39
43
  )
40
44
  if not self.dify_query_input_key:
41
45
  self.dify_query_input_key = "astrbot_text_query"
@@ -76,12 +80,13 @@ class ProviderDify(Provider):
76
80
  else image_url
77
81
  )
78
82
  file_response = await self.api_client.file_upload(
79
- image_path, user=session_id
83
+ image_path,
84
+ user=session_id,
80
85
  )
81
86
  logger.debug(f"Dify 上传图片响应:{file_response}")
82
87
  if "id" not in file_response:
83
88
  logger.warning(
84
- f"上传图片后得到未知的 Dify 响应:{file_response},图片将忽略。"
89
+ f"上传图片后得到未知的 Dify 响应:{file_response},图片将忽略。",
85
90
  )
86
91
  continue
87
92
  files_payload.append(
@@ -89,7 +94,7 @@ class ProviderDify(Provider):
89
94
  "type": "image",
90
95
  "transfer_method": "local_file",
91
96
  "upload_file_id": file_response["id"],
92
- }
97
+ },
93
98
  )
94
99
 
95
100
  # 获得会话变量
@@ -132,7 +137,7 @@ class ProviderDify(Provider):
132
137
  elif chunk["event"] == "error":
133
138
  logger.error(f"Dify 出现错误:{chunk}")
134
139
  raise Exception(
135
- f"Dify 出现错误 status: {chunk['status']} message: {chunk['message']}"
140
+ f"Dify 出现错误 status: {chunk['status']} message: {chunk['message']}",
136
141
  )
137
142
 
138
143
  case "workflow":
@@ -149,37 +154,37 @@ class ProviderDify(Provider):
149
154
  match chunk["event"]:
150
155
  case "workflow_started":
151
156
  logger.info(
152
- f"Dify 工作流(ID: {chunk['workflow_run_id']})开始运行。"
157
+ f"Dify 工作流(ID: {chunk['workflow_run_id']})开始运行。",
153
158
  )
154
159
  case "node_finished":
155
160
  logger.debug(
156
- f"Dify 工作流节点(ID: {chunk['data']['node_id']} Title: {chunk['data'].get('title', '')})运行结束。"
161
+ f"Dify 工作流节点(ID: {chunk['data']['node_id']} Title: {chunk['data'].get('title', '')})运行结束。",
157
162
  )
158
163
  case "workflow_finished":
159
164
  logger.info(
160
- f"Dify 工作流(ID: {chunk['workflow_run_id']})运行结束"
165
+ f"Dify 工作流(ID: {chunk['workflow_run_id']})运行结束",
161
166
  )
162
167
  logger.debug(f"Dify 工作流结果:{chunk}")
163
168
  if chunk["data"]["error"]:
164
169
  logger.error(
165
- f"Dify 工作流出现错误:{chunk['data']['error']}"
170
+ f"Dify 工作流出现错误:{chunk['data']['error']}",
166
171
  )
167
172
  raise Exception(
168
- f"Dify 工作流出现错误:{chunk['data']['error']}"
173
+ f"Dify 工作流出现错误:{chunk['data']['error']}",
169
174
  )
170
175
  if (
171
176
  self.workflow_output_key
172
177
  not in chunk["data"]["outputs"]
173
178
  ):
174
179
  raise Exception(
175
- f"Dify 工作流的输出不包含指定的键名:{self.workflow_output_key}"
180
+ f"Dify 工作流的输出不包含指定的键名:{self.workflow_output_key}",
176
181
  )
177
182
  result = chunk
178
183
  case _:
179
184
  raise Exception(f"未知的 Dify API 类型:{self.api_type}")
180
185
  except Exception as e:
181
- logger.error(f"Dify 请求失败:{str(e)}")
182
- return LLMResponse(role="err", completion_text=f"Dify 请求失败:{str(e)}")
186
+ logger.error(f"Dify 请求失败:{e!s}")
187
+ return LLMResponse(role="err", completion_text=f"Dify 请求失败:{e!s}")
183
188
 
184
189
  if not result:
185
190
  logger.warning("Dify 请求结果为空,请查看 Debug 日志。")
@@ -1,14 +1,17 @@
1
- import uuid
1
+ import asyncio
2
2
  import os
3
- import edge_tts
4
3
  import subprocess
5
- import asyncio
6
- from ..provider import TTSProvider
7
- from ..entities import ProviderType
8
- from ..register import register_provider_adapter
4
+ import uuid
5
+
6
+ import edge_tts
7
+
9
8
  from astrbot.core import logger
10
9
  from astrbot.core.utils.astrbot_path import get_astrbot_data_path
11
10
 
11
+ from ..entities import ProviderType
12
+ from ..provider import TTSProvider
13
+ from ..register import register_provider_adapter
14
+
12
15
  """
13
16
  edge_tts 方式,能够免费、快速生成语音,使用需要先安装edge-tts库
14
17
  ```
@@ -19,7 +22,9 @@ Windows 如果提示找不到指定文件,以管理员身份运行命令行窗
19
22
 
20
23
 
21
24
  @register_provider_adapter(
22
- "edge_tts", "Microsoft Edge TTS", provider_type=ProviderType.TEXT_TO_SPEECH
25
+ "edge_tts",
26
+ "Microsoft Edge TTS",
27
+ provider_type=ProviderType.TEXT_TO_SPEECH,
23
28
  )
24
29
  class ProviderEdgeTTS(TTSProvider):
25
30
  def __init__(
@@ -31,9 +36,9 @@ class ProviderEdgeTTS(TTSProvider):
31
36
 
32
37
  # 设置默认语音,如果没有指定则使用中文小萱
33
38
  self.voice = provider_config.get("edge-tts-voice", "zh-CN-XiaoxiaoNeural")
34
- self.rate = provider_config.get("rate", None)
35
- self.volume = provider_config.get("volume", None)
36
- self.pitch = provider_config.get("pitch", None)
39
+ self.rate = provider_config.get("rate")
40
+ self.volume = provider_config.get("volume")
41
+ self.pitch = provider_config.get("pitch")
37
42
  self.timeout = provider_config.get("timeout", 30)
38
43
 
39
44
  self.proxy = os.getenv("https_proxy", None)
@@ -97,26 +102,25 @@ class ProviderEdgeTTS(TTSProvider):
97
102
  os.remove(mp3_path)
98
103
  if os.path.exists(wav_path) and os.path.getsize(wav_path) > 0:
99
104
  return wav_path
100
- else:
101
- logger.error("生成的WAV文件不存在或为空")
102
- raise RuntimeError("生成的WAV文件不存在或为空")
105
+ logger.error("生成的WAV文件不存在或为空")
106
+ raise RuntimeError("生成的WAV文件不存在或为空")
103
107
 
104
108
  except subprocess.CalledProcessError as e:
105
109
  logger.error(
106
- f"FFmpeg 转换失败: {e.stderr.decode() if e.stderr else str(e)}"
110
+ f"FFmpeg 转换失败: {e.stderr.decode() if e.stderr else str(e)}",
107
111
  )
108
112
  try:
109
113
  if os.path.exists(mp3_path):
110
114
  os.remove(mp3_path)
111
115
  except Exception:
112
116
  pass
113
- raise RuntimeError(f"FFmpeg 转换失败: {str(e)}")
117
+ raise RuntimeError(f"FFmpeg 转换失败: {e!s}")
114
118
 
115
119
  except Exception as e:
116
- logger.error(f"音频生成失败: {str(e)}")
120
+ logger.error(f"音频生成失败: {e!s}")
117
121
  try:
118
122
  if os.path.exists(mp3_path):
119
123
  os.remove(mp3_path)
120
124
  except Exception:
121
125
  pass
122
- raise RuntimeError(f"音频生成失败: {str(e)}")
126
+ raise RuntimeError(f"音频生成失败: {e!s}")
@@ -1,14 +1,17 @@
1
1
  import os
2
- import uuid
3
2
  import re
3
+ import uuid
4
+ from typing import Annotated, Literal
5
+
4
6
  import ormsgpack
5
- from pydantic import BaseModel, conint
6
7
  from httpx import AsyncClient
7
- from typing import Annotated, Literal
8
- from ..provider import TTSProvider
8
+ from pydantic import BaseModel, conint
9
+
10
+ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
11
+
9
12
  from ..entities import ProviderType
13
+ from ..provider import TTSProvider
10
14
  from ..register import register_provider_adapter
11
- from astrbot.core.utils.astrbot_path import get_astrbot_data_path
12
15
 
13
16
 
14
17
  class ServeReferenceAudio(BaseModel):
@@ -35,7 +38,9 @@ class ServeTTSRequest(BaseModel):
35
38
 
36
39
 
37
40
  @register_provider_adapter(
38
- "fishaudio_tts_api", "FishAudio TTS API", provider_type=ProviderType.TEXT_TO_SPEECH
41
+ "fishaudio_tts_api",
42
+ "FishAudio TTS API",
43
+ provider_type=ProviderType.TEXT_TO_SPEECH,
39
44
  )
40
45
  class ProviderFishAudioTTSAPI(TTSProvider):
41
46
  def __init__(
@@ -48,16 +53,16 @@ class ProviderFishAudioTTSAPI(TTSProvider):
48
53
  self.reference_id: str = provider_config.get("fishaudio-tts-reference-id", "")
49
54
  self.character: str = provider_config.get("fishaudio-tts-character", "可莉")
50
55
  self.api_base: str = provider_config.get(
51
- "api_base", "https://api.fish-audio.cn/v1"
56
+ "api_base",
57
+ "https://api.fish-audio.cn/v1",
52
58
  )
53
59
  self.headers = {
54
60
  "Authorization": f"Bearer {self.chosen_api_key}",
55
61
  }
56
- self.set_model(provider_config.get("model", None))
62
+ self.set_model(provider_config.get("model"))
57
63
 
58
64
  async def _get_reference_id_by_character(self, character: str) -> str:
59
- """
60
- 获取角色的reference_id
65
+ """获取角色的reference_id
61
66
 
62
67
  Args:
63
68
  character: 角色名称
@@ -67,13 +72,16 @@ class ProviderFishAudioTTSAPI(TTSProvider):
67
72
 
68
73
  exception:
69
74
  APIException: 获取语音角色列表为空
75
+
70
76
  """
71
77
  sort_options = ["score", "task_count", "created_at"]
72
78
  async with AsyncClient(base_url=self.api_base.replace("/v1", "")) as client:
73
79
  for sort_by in sort_options:
74
80
  params = {"title": character, "sort_by": sort_by}
75
81
  response = await client.get(
76
- "/model", params=params, headers=self.headers
82
+ "/model",
83
+ params=params,
84
+ headers=self.headers,
77
85
  )
78
86
  resp_data = response.json()
79
87
  if resp_data["total"] == 0:
@@ -84,14 +92,14 @@ class ProviderFishAudioTTSAPI(TTSProvider):
84
92
  return None
85
93
 
86
94
  def _validate_reference_id(self, reference_id: str) -> bool:
87
- """
88
- 验证reference_id格式是否有效
95
+ """验证reference_id格式是否有效
89
96
 
90
97
  Args:
91
98
  reference_id: 参考模型ID
92
99
 
93
100
  Returns:
94
101
  bool: ID是否有效
102
+
95
103
  """
96
104
  if not reference_id or not reference_id.strip():
97
105
  return False
@@ -109,7 +117,7 @@ class ProviderFishAudioTTSAPI(TTSProvider):
109
117
  raise ValueError(
110
118
  f"无效的FishAudio参考模型ID: '{self.reference_id}'. "
111
119
  f"请确保ID是32位十六进制字符串(例如: 626bb6d3f3364c9cbc3aa6a67300a664)。"
112
- f"您可以从 https://fish.audio/zh-CN/discovery 获取有效的模型ID。"
120
+ f"您可以从 https://fish.audio/zh-CN/discovery 获取有效的模型ID。",
113
121
  )
114
122
  reference_id = self.reference_id.strip()
115
123
  else:
@@ -1,9 +1,10 @@
1
1
  from google import genai
2
2
  from google.genai import types
3
3
  from google.genai.errors import APIError
4
+
5
+ from ..entities import ProviderType
4
6
  from ..provider import EmbeddingProvider
5
7
  from ..register import register_provider_adapter
6
- from ..entities import ProviderType
7
8
 
8
9
 
9
10
  @register_provider_adapter(
@@ -18,40 +19,38 @@ class GeminiEmbeddingProvider(EmbeddingProvider):
18
19
  self.provider_settings = provider_settings
19
20
 
20
21
  api_key: str = provider_config.get("embedding_api_key")
21
- api_base: str = provider_config.get("embedding_api_base", None)
22
+ api_base: str = provider_config.get("embedding_api_base")
22
23
  timeout: int = int(provider_config.get("timeout", 20))
23
24
 
24
25
  http_options = types.HttpOptions(timeout=timeout * 1000)
25
26
  if api_base:
26
- if api_base.endswith("/"):
27
- api_base = api_base[:-1]
27
+ api_base = api_base.removesuffix("/")
28
28
  http_options.base_url = api_base
29
29
 
30
30
  self.client = genai.Client(api_key=api_key, http_options=http_options).aio
31
31
 
32
32
  self.model = provider_config.get(
33
- "embedding_model", "gemini-embedding-exp-03-07"
33
+ "embedding_model",
34
+ "gemini-embedding-exp-03-07",
34
35
  )
35
36
 
36
37
  async def get_embedding(self, text: str) -> list[float]:
37
- """
38
- 获取文本的嵌入
39
- """
38
+ """获取文本的嵌入"""
40
39
  try:
41
40
  result = await self.client.models.embed_content(
42
- model=self.model, contents=text
41
+ model=self.model,
42
+ contents=text,
43
43
  )
44
44
  return result.embeddings[0].values
45
45
  except APIError as e:
46
46
  raise Exception(f"Gemini Embedding API请求失败: {e.message}")
47
47
 
48
48
  async def get_embeddings(self, texts: list[str]) -> list[list[float]]:
49
- """
50
- 批量获取文本的嵌入
51
- """
49
+ """批量获取文本的嵌入"""
52
50
  try:
53
51
  result = await self.client.models.embed_content(
54
- model=self.model, contents=texts
52
+ model=self.model,
53
+ contents=texts,
55
54
  )
56
55
  return [embedding.values for embedding in result.embeddings]
57
56
  except APIError as e: