AstrBot 4.5.1__py3-none-any.whl → 4.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrbot/api/__init__.py +10 -11
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -36
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +7 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__main__.py +5 -5
- astrbot/cli/commands/__init__.py +3 -3
- astrbot/cli/commands/cmd_conf.py +19 -16
- astrbot/cli/commands/cmd_init.py +3 -2
- astrbot/cli/commands/cmd_plug.py +8 -10
- astrbot/cli/commands/cmd_run.py +5 -6
- astrbot/cli/utils/__init__.py +6 -6
- astrbot/cli/utils/basic.py +14 -14
- astrbot/cli/utils/plugin.py +24 -15
- astrbot/cli/utils/version_comparator.py +10 -12
- astrbot/core/__init__.py +8 -6
- astrbot/core/agent/agent.py +3 -2
- astrbot/core/agent/handoff.py +6 -2
- astrbot/core/agent/hooks.py +9 -6
- astrbot/core/agent/mcp_client.py +50 -15
- astrbot/core/agent/message.py +168 -0
- astrbot/core/agent/response.py +2 -1
- astrbot/core/agent/run_context.py +2 -3
- astrbot/core/agent/runners/base.py +10 -13
- astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
- astrbot/core/agent/tool.py +60 -41
- astrbot/core/agent/tool_executor.py +9 -3
- astrbot/core/astr_agent_context.py +3 -1
- astrbot/core/astrbot_config_mgr.py +29 -9
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +28 -26
- astrbot/core/config/default.py +4 -6
- astrbot/core/conversation_mgr.py +105 -36
- astrbot/core/core_lifecycle.py +68 -54
- astrbot/core/db/__init__.py +33 -18
- astrbot/core/db/migration/helper.py +12 -10
- astrbot/core/db/migration/migra_3_to_4.py +53 -34
- astrbot/core/db/migration/migra_45_to_46.py +1 -1
- astrbot/core/db/migration/shared_preferences_v3.py +2 -1
- astrbot/core/db/migration/sqlite_v3.py +26 -23
- astrbot/core/db/po.py +27 -18
- astrbot/core/db/sqlite.py +74 -45
- astrbot/core/db/vec_db/base.py +10 -14
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
- astrbot/core/event_bus.py +8 -6
- astrbot/core/file_token_service.py +6 -5
- astrbot/core/initial_loader.py +7 -5
- astrbot/core/knowledge_base/chunking/__init__.py +1 -3
- astrbot/core/knowledge_base/chunking/base.py +1 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
- astrbot/core/knowledge_base/chunking/recursive.py +16 -10
- astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
- astrbot/core/knowledge_base/kb_helper.py +30 -17
- astrbot/core/knowledge_base/kb_mgr.py +6 -7
- astrbot/core/knowledge_base/models.py +10 -4
- astrbot/core/knowledge_base/parsers/__init__.py +3 -5
- astrbot/core/knowledge_base/parsers/base.py +1 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
- astrbot/core/knowledge_base/parsers/util.py +1 -1
- astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
- astrbot/core/knowledge_base/retrieval/manager.py +17 -14
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
- astrbot/core/log.py +21 -13
- astrbot/core/message/components.py +123 -217
- astrbot/core/message/message_event_result.py +24 -24
- astrbot/core/persona_mgr.py +20 -11
- astrbot/core/pipeline/__init__.py +7 -7
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +4 -1
- astrbot/core/pipeline/context_utils.py +77 -7
- astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
- astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
- astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
- astrbot/core/pipeline/process_stage/stage.py +13 -10
- astrbot/core/pipeline/process_stage/utils.py +6 -5
- astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
- astrbot/core/pipeline/respond/stage.py +23 -20
- astrbot/core/pipeline/result_decorate/stage.py +31 -23
- astrbot/core/pipeline/scheduler.py +12 -8
- astrbot/core/pipeline/session_status_check/stage.py +12 -8
- astrbot/core/pipeline/stage.py +10 -4
- astrbot/core/pipeline/waking_check/stage.py +24 -18
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +76 -110
- astrbot/core/platform/astrbot_message.py +11 -13
- astrbot/core/platform/manager.py +16 -15
- astrbot/core/platform/message_session.py +5 -3
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +4 -4
- astrbot/core/platform/register.py +8 -8
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
- astrbot/core/platform/sources/discord/client.py +9 -6
- astrbot/core/platform/sources/discord/components.py +18 -14
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
- astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
- astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
- astrbot/core/platform/sources/lark/lark_event.py +21 -14
- astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
- astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
- astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
- astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
- astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
- astrbot/core/platform/sources/satori/satori_event.py +34 -25
- astrbot/core/platform/sources/slack/client.py +11 -9
- astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
- astrbot/core/platform/sources/slack/slack_event.py +34 -24
- astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
- astrbot/core/platform/sources/telegram/tg_event.py +32 -18
- astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
- astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
- astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
- astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
- astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
- astrbot/core/platform_message_history_mgr.py +5 -3
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +61 -75
- astrbot/core/provider/func_tool_manager.py +59 -55
- astrbot/core/provider/manager.py +32 -22
- astrbot/core/provider/provider.py +72 -46
- astrbot/core/provider/register.py +7 -7
- astrbot/core/provider/sources/anthropic_source.py +48 -30
- astrbot/core/provider/sources/azure_tts_source.py +17 -13
- astrbot/core/provider/sources/coze_api_client.py +27 -17
- astrbot/core/provider/sources/coze_source.py +104 -87
- astrbot/core/provider/sources/dashscope_source.py +18 -11
- astrbot/core/provider/sources/dashscope_tts.py +36 -23
- astrbot/core/provider/sources/dify_source.py +25 -20
- astrbot/core/provider/sources/edge_tts_source.py +21 -17
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
- astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
- astrbot/core/provider/sources/gemini_source.py +72 -58
- astrbot/core/provider/sources/gemini_tts_source.py +8 -6
- astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
- astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
- astrbot/core/provider/sources/openai_embedding_source.py +6 -8
- astrbot/core/provider/sources/openai_source.py +77 -69
- astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
- astrbot/core/provider/sources/volcengine_tts.py +38 -31
- astrbot/core/provider/sources/whisper_api_source.py +14 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
- astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
- astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
- astrbot/core/star/__init__.py +16 -11
- astrbot/core/star/config.py +10 -15
- astrbot/core/star/context.py +97 -75
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +30 -28
- astrbot/core/star/filter/command_group.py +27 -24
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +4 -2
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -19
- astrbot/core/star/register/star.py +6 -2
- astrbot/core/star/register/star_handler.py +96 -73
- astrbot/core/star/session_llm_manager.py +48 -14
- astrbot/core/star/session_plugin_manager.py +29 -15
- astrbot/core/star/star.py +1 -2
- astrbot/core/star/star_handler.py +13 -8
- astrbot/core/star/star_manager.py +151 -59
- astrbot/core/star/star_tools.py +44 -37
- astrbot/core/star/updator.py +10 -10
- astrbot/core/umop_config_router.py +10 -4
- astrbot/core/updator.py +13 -5
- astrbot/core/utils/astrbot_path.py +3 -5
- astrbot/core/utils/dify_api_client.py +33 -15
- astrbot/core/utils/io.py +66 -42
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +7 -7
- astrbot/core/utils/path_util.py +15 -16
- astrbot/core/utils/pip_installer.py +5 -5
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +45 -20
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/network_strategy.py +35 -26
- astrbot/core/utils/t2i/renderer.py +11 -5
- astrbot/core/utils/t2i/template_manager.py +14 -15
- astrbot/core/utils/tencent_record_helper.py +19 -13
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +43 -40
- astrbot/dashboard/routes/__init__.py +18 -18
- astrbot/dashboard/routes/auth.py +10 -8
- astrbot/dashboard/routes/chat.py +30 -21
- astrbot/dashboard/routes/config.py +92 -75
- astrbot/dashboard/routes/conversation.py +46 -39
- astrbot/dashboard/routes/file.py +4 -2
- astrbot/dashboard/routes/knowledge_base.py +47 -40
- astrbot/dashboard/routes/log.py +9 -4
- astrbot/dashboard/routes/persona.py +19 -16
- astrbot/dashboard/routes/plugin.py +69 -55
- astrbot/dashboard/routes/route.py +3 -1
- astrbot/dashboard/routes/session_management.py +130 -116
- astrbot/dashboard/routes/stat.py +34 -34
- astrbot/dashboard/routes/t2i.py +15 -12
- astrbot/dashboard/routes/tools.py +56 -53
- astrbot/dashboard/routes/update.py +32 -28
- astrbot/dashboard/server.py +30 -26
- astrbot/dashboard/utils.py +8 -4
- {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/METADATA +2 -1
- astrbot-4.5.3.dist-info/RECORD +261 -0
- astrbot-4.5.1.dist-info/RECORD +0 -260
- {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/WHEEL +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/entry_points.txt +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,7 +3,7 @@ import base64
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import uuid
|
|
6
|
-
|
|
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",
|
|
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"
|
|
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,
|
|
83
|
-
|
|
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) ->
|
|
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.
|
|
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) ->
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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.
|
|
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,
|
|
129
|
-
|
|
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,
|
|
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
|
-
|
|
4
|
-
|
|
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",
|
|
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",
|
|
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,
|
|
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 请求失败:{
|
|
182
|
-
return LLMResponse(role="err", completion_text=f"Dify 请求失败:{
|
|
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
|
|
1
|
+
import asyncio
|
|
2
2
|
import os
|
|
3
|
-
import edge_tts
|
|
4
3
|
import subprocess
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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",
|
|
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"
|
|
35
|
-
self.volume = provider_config.get("volume"
|
|
36
|
-
self.pitch = provider_config.get("pitch"
|
|
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
|
-
|
|
101
|
-
|
|
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 转换失败: {
|
|
117
|
+
raise RuntimeError(f"FFmpeg 转换失败: {e!s}")
|
|
114
118
|
|
|
115
119
|
except Exception as e:
|
|
116
|
-
logger.error(f"音频生成失败: {
|
|
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"音频生成失败: {
|
|
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
|
|
8
|
-
|
|
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",
|
|
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",
|
|
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"
|
|
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",
|
|
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"
|
|
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
|
-
|
|
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",
|
|
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,
|
|
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,
|
|
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:
|