AstrBot 4.5.0__py3-none-any.whl → 4.5.1__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/core/config/default.py +41 -1
- astrbot/core/db/migration/helper.py +6 -3
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +5 -2
- astrbot/core/provider/manager.py +8 -0
- astrbot/core/provider/sources/openai_source.py +25 -0
- astrbot/core/provider/sources/xinference_rerank_source.py +108 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +187 -0
- astrbot/core/star/context.py +16 -13
- {astrbot-4.5.0.dist-info → astrbot-4.5.1.dist-info}/METADATA +3 -2
- {astrbot-4.5.0.dist-info → astrbot-4.5.1.dist-info}/RECORD +13 -11
- {astrbot-4.5.0.dist-info → astrbot-4.5.1.dist-info}/WHEEL +0 -0
- {astrbot-4.5.0.dist-info → astrbot-4.5.1.dist-info}/entry_points.txt +0 -0
- {astrbot-4.5.0.dist-info → astrbot-4.5.1.dist-info}/licenses/LICENSE +0 -0
astrbot/core/config/default.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
|
|
7
7
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
8
8
|
|
|
9
|
-
VERSION = "4.5.
|
|
9
|
+
VERSION = "4.5.1"
|
|
10
10
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
|
11
11
|
|
|
12
12
|
# 默认配置
|
|
@@ -324,6 +324,10 @@ CONFIG_METADATA_2 = {
|
|
|
324
324
|
# "type": "string",
|
|
325
325
|
# "options": ["fullscreen", "embedded"],
|
|
326
326
|
# },
|
|
327
|
+
"is_sandbox": {
|
|
328
|
+
"description": "沙箱模式",
|
|
329
|
+
"type": "bool",
|
|
330
|
+
},
|
|
327
331
|
"satori_api_base_url": {
|
|
328
332
|
"description": "Satori API 终结点",
|
|
329
333
|
"type": "string",
|
|
@@ -767,6 +771,7 @@ CONFIG_METADATA_2 = {
|
|
|
767
771
|
"timeout": 120,
|
|
768
772
|
"model_config": {"model": "grok-2-latest", "temperature": 0.4},
|
|
769
773
|
"custom_extra_body": {},
|
|
774
|
+
"xai_native_search": False,
|
|
770
775
|
"modalities": ["text", "image", "tool_use"],
|
|
771
776
|
},
|
|
772
777
|
"Anthropic": {
|
|
@@ -1258,8 +1263,38 @@ CONFIG_METADATA_2 = {
|
|
|
1258
1263
|
"rerank_model": "BAAI/bge-reranker-base",
|
|
1259
1264
|
"timeout": 20,
|
|
1260
1265
|
},
|
|
1266
|
+
"Xinference Rerank": {
|
|
1267
|
+
"id": "xinference_rerank",
|
|
1268
|
+
"type": "xinference_rerank",
|
|
1269
|
+
"provider": "xinference",
|
|
1270
|
+
"provider_type": "rerank",
|
|
1271
|
+
"enable": True,
|
|
1272
|
+
"rerank_api_key": "",
|
|
1273
|
+
"rerank_api_base": "http://127.0.0.1:9997",
|
|
1274
|
+
"rerank_model": "BAAI/bge-reranker-base",
|
|
1275
|
+
"timeout": 20,
|
|
1276
|
+
"launch_model_if_not_running": False,
|
|
1277
|
+
},
|
|
1278
|
+
"Xinference STT": {
|
|
1279
|
+
"id": "xinference_stt",
|
|
1280
|
+
"type": "xinference_stt",
|
|
1281
|
+
"provider": "xinference",
|
|
1282
|
+
"provider_type": "speech_to_text",
|
|
1283
|
+
"enable": False,
|
|
1284
|
+
"api_key": "",
|
|
1285
|
+
"api_base": "http://127.0.0.1:9997",
|
|
1286
|
+
"model": "whisper-large-v3",
|
|
1287
|
+
"timeout": 180,
|
|
1288
|
+
"launch_model_if_not_running": False,
|
|
1289
|
+
},
|
|
1261
1290
|
},
|
|
1262
1291
|
"items": {
|
|
1292
|
+
"xai_native_search": {
|
|
1293
|
+
"description": "启用原生搜索功能",
|
|
1294
|
+
"type": "bool",
|
|
1295
|
+
"hint": "启用后,将通过 xAI 的 Chat Completions 原生 Live Search 进行联网检索(按需计费)。仅对 xAI 提供商生效。",
|
|
1296
|
+
"condition": {"provider": "xai"},
|
|
1297
|
+
},
|
|
1263
1298
|
"rerank_api_base": {
|
|
1264
1299
|
"description": "重排序模型 API Base URL",
|
|
1265
1300
|
"type": "string",
|
|
@@ -1274,6 +1309,11 @@ CONFIG_METADATA_2 = {
|
|
|
1274
1309
|
"description": "重排序模型名称",
|
|
1275
1310
|
"type": "string",
|
|
1276
1311
|
},
|
|
1312
|
+
"launch_model_if_not_running": {
|
|
1313
|
+
"description": "模型未运行时自动启动",
|
|
1314
|
+
"type": "bool",
|
|
1315
|
+
"hint": "如果模型当前未在 Xinference 服务中运行,是否尝试自动启动它。在生产环境中建议关闭。",
|
|
1316
|
+
},
|
|
1277
1317
|
"modalities": {
|
|
1278
1318
|
"description": "模型能力",
|
|
1279
1319
|
"type": "list",
|
|
@@ -17,8 +17,11 @@ async def check_migration_needed_v4(db_helper: BaseDatabase) -> bool:
|
|
|
17
17
|
检查是否需要进行数据库迁移
|
|
18
18
|
如果存在 data_v3.db 并且 preference 中没有 migration_done_v4,则需要进行迁移。
|
|
19
19
|
"""
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
# 仅当 data 目录下存在旧版本数据(data_v3.db 文件)时才考虑迁移
|
|
21
|
+
data_dir = get_astrbot_data_path()
|
|
22
|
+
data_v3_db = os.path.join(data_dir, "data_v3.db")
|
|
23
|
+
|
|
24
|
+
if not os.path.exists(data_v3_db):
|
|
22
25
|
return False
|
|
23
26
|
migration_done = await db_helper.get_preference(
|
|
24
27
|
"global", "global", "migration_done_v4"
|
|
@@ -32,7 +35,7 @@ async def do_migration_v4(
|
|
|
32
35
|
db_helper: BaseDatabase,
|
|
33
36
|
platform_id_map: dict[str, dict[str, str]],
|
|
34
37
|
astrbot_config: AstrBotConfig,
|
|
35
|
-
):
|
|
38
|
+
) -> None:
|
|
36
39
|
"""
|
|
37
40
|
执行数据库迁移
|
|
38
41
|
迁移旧的 webchat_conversation 表到新的 conversation 表。
|
|
@@ -100,8 +100,11 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
100
100
|
abm.raw_message = message
|
|
101
101
|
|
|
102
102
|
if abm.type == MessageType.GROUP_MESSAGE:
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
# 处理所有被 @ 的用户(包括机器人自己,因 at_users 已包含)
|
|
104
|
+
if message.at_users:
|
|
105
|
+
for user in message.at_users:
|
|
106
|
+
if user.dingtalk_id:
|
|
107
|
+
abm.message.append(At(qq=user.dingtalk_id))
|
|
105
108
|
abm.group_id = message.conversation_id
|
|
106
109
|
if self.unique_session:
|
|
107
110
|
abm.session_id = abm.sender.user_id
|
astrbot/core/provider/manager.py
CHANGED
|
@@ -259,6 +259,10 @@ class ProviderManager:
|
|
|
259
259
|
from .sources.whisper_selfhosted_source import (
|
|
260
260
|
ProviderOpenAIWhisperSelfHost as ProviderOpenAIWhisperSelfHost,
|
|
261
261
|
)
|
|
262
|
+
case "xinference_stt":
|
|
263
|
+
from .sources.xinference_stt_provider import (
|
|
264
|
+
ProviderXinferenceSTT as ProviderXinferenceSTT,
|
|
265
|
+
)
|
|
262
266
|
case "openai_tts_api":
|
|
263
267
|
from .sources.openai_tts_api_source import (
|
|
264
268
|
ProviderOpenAITTSAPI as ProviderOpenAITTSAPI,
|
|
@@ -311,6 +315,10 @@ class ProviderManager:
|
|
|
311
315
|
from .sources.vllm_rerank_source import (
|
|
312
316
|
VLLMRerankProvider as VLLMRerankProvider,
|
|
313
317
|
)
|
|
318
|
+
case "xinference_rerank":
|
|
319
|
+
from .sources.xinference_rerank_source import (
|
|
320
|
+
XinferenceRerankProvider as XinferenceRerankProvider,
|
|
321
|
+
)
|
|
314
322
|
except (ImportError, ModuleNotFoundError) as e:
|
|
315
323
|
logger.critical(
|
|
316
324
|
f"加载 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}。可能是因为有未安装的依赖。"
|
|
@@ -68,6 +68,28 @@ class ProviderOpenAIOfficial(Provider):
|
|
|
68
68
|
model = model_config.get("model", "unknown")
|
|
69
69
|
self.set_model(model)
|
|
70
70
|
|
|
71
|
+
def _maybe_inject_xai_search(self, payloads: dict, **kwargs):
|
|
72
|
+
"""当开启 xAI 原生搜索时,向请求体注入 Live Search 参数。
|
|
73
|
+
|
|
74
|
+
- 仅在 provider_config.xai_native_search 为 True 时生效
|
|
75
|
+
- 默认注入 {"mode": "auto"}
|
|
76
|
+
- 允许通过 kwargs 使用 xai_search_mode 覆盖(on/auto/off)
|
|
77
|
+
"""
|
|
78
|
+
if not bool(self.provider_config.get("xai_native_search", False)):
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
mode = kwargs.get("xai_search_mode", "auto")
|
|
82
|
+
mode = str(mode).lower()
|
|
83
|
+
if mode not in ("auto", "on", "off"):
|
|
84
|
+
mode = "auto"
|
|
85
|
+
|
|
86
|
+
# off 时不注入,保持与未开启一致
|
|
87
|
+
if mode == "off":
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
# OpenAI SDK 不识别的字段会在 _query/_query_stream 中放入 extra_body
|
|
91
|
+
payloads["search_parameters"] = {"mode": mode}
|
|
92
|
+
|
|
71
93
|
async def get_models(self):
|
|
72
94
|
try:
|
|
73
95
|
models_str = []
|
|
@@ -271,6 +293,9 @@ class ProviderOpenAIOfficial(Provider):
|
|
|
271
293
|
|
|
272
294
|
payloads = {"messages": context_query, **model_config}
|
|
273
295
|
|
|
296
|
+
# xAI 原生搜索参数(最小侵入地在此处注入)
|
|
297
|
+
self._maybe_inject_xai_search(payloads, **kwargs)
|
|
298
|
+
|
|
274
299
|
return payloads, context_query
|
|
275
300
|
|
|
276
301
|
async def _handle_api_error(
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from xinference_client.client.restful.async_restful_client import (
|
|
2
|
+
AsyncClient as Client,
|
|
3
|
+
)
|
|
4
|
+
from astrbot import logger
|
|
5
|
+
from ..provider import RerankProvider
|
|
6
|
+
from ..register import register_provider_adapter
|
|
7
|
+
from ..entities import ProviderType, RerankResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@register_provider_adapter(
|
|
11
|
+
"xinference_rerank",
|
|
12
|
+
"Xinference Rerank 适配器",
|
|
13
|
+
provider_type=ProviderType.RERANK,
|
|
14
|
+
)
|
|
15
|
+
class XinferenceRerankProvider(RerankProvider):
|
|
16
|
+
def __init__(self, provider_config: dict, provider_settings: dict) -> None:
|
|
17
|
+
super().__init__(provider_config, provider_settings)
|
|
18
|
+
self.provider_config = provider_config
|
|
19
|
+
self.provider_settings = provider_settings
|
|
20
|
+
self.base_url = provider_config.get("rerank_api_base", "http://127.0.0.1:8000")
|
|
21
|
+
self.base_url = self.base_url.rstrip("/")
|
|
22
|
+
self.timeout = provider_config.get("timeout", 20)
|
|
23
|
+
self.model_name = provider_config.get("rerank_model", "BAAI/bge-reranker-base")
|
|
24
|
+
self.api_key = provider_config.get("rerank_api_key")
|
|
25
|
+
self.launch_model_if_not_running = provider_config.get(
|
|
26
|
+
"launch_model_if_not_running", False
|
|
27
|
+
)
|
|
28
|
+
self.client = None
|
|
29
|
+
self.model = None
|
|
30
|
+
self.model_uid = None
|
|
31
|
+
|
|
32
|
+
async def initialize(self):
|
|
33
|
+
if self.api_key:
|
|
34
|
+
logger.info("Xinference Rerank: Using API key for authentication.")
|
|
35
|
+
self.client = Client(self.base_url, api_key=self.api_key)
|
|
36
|
+
else:
|
|
37
|
+
logger.info("Xinference Rerank: No API key provided.")
|
|
38
|
+
self.client = Client(self.base_url)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
running_models = await self.client.list_models()
|
|
42
|
+
for uid, model_spec in running_models.items():
|
|
43
|
+
if model_spec.get("model_name") == self.model_name:
|
|
44
|
+
logger.info(
|
|
45
|
+
f"Model '{self.model_name}' is already running with UID: {uid}"
|
|
46
|
+
)
|
|
47
|
+
self.model_uid = uid
|
|
48
|
+
break
|
|
49
|
+
|
|
50
|
+
if self.model_uid is None:
|
|
51
|
+
if self.launch_model_if_not_running:
|
|
52
|
+
logger.info(f"Launching {self.model_name} model...")
|
|
53
|
+
self.model_uid = await self.client.launch_model(
|
|
54
|
+
model_name=self.model_name, model_type="rerank"
|
|
55
|
+
)
|
|
56
|
+
logger.info("Model launched.")
|
|
57
|
+
else:
|
|
58
|
+
logger.warning(
|
|
59
|
+
f"Model '{self.model_name}' is not running and auto-launch is disabled. Provider will not be available."
|
|
60
|
+
)
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
if self.model_uid:
|
|
64
|
+
self.model = await self.client.get_model(self.model_uid)
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"Failed to initialize Xinference model: {e}")
|
|
68
|
+
logger.debug(
|
|
69
|
+
f"Xinference initialization failed with exception: {e}", exc_info=True
|
|
70
|
+
)
|
|
71
|
+
self.model = None
|
|
72
|
+
|
|
73
|
+
async def rerank(
|
|
74
|
+
self, query: str, documents: list[str], top_n: int | None = None
|
|
75
|
+
) -> list[RerankResult]:
|
|
76
|
+
if not self.model:
|
|
77
|
+
logger.error("Xinference rerank model is not initialized.")
|
|
78
|
+
return []
|
|
79
|
+
try:
|
|
80
|
+
response = await self.model.rerank(documents, query, top_n)
|
|
81
|
+
results = response.get("results", [])
|
|
82
|
+
logger.debug(f"Rerank API response: {response}")
|
|
83
|
+
|
|
84
|
+
if not results:
|
|
85
|
+
logger.warning(
|
|
86
|
+
f"Rerank API returned an empty list. Original response: {response}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return [
|
|
90
|
+
RerankResult(
|
|
91
|
+
index=result["index"],
|
|
92
|
+
relevance_score=result["relevance_score"],
|
|
93
|
+
)
|
|
94
|
+
for result in results
|
|
95
|
+
]
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Xinference rerank failed: {e}")
|
|
98
|
+
logger.debug(f"Xinference rerank failed with exception: {e}", exc_info=True)
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
async def terminate(self) -> None:
|
|
102
|
+
"""关闭客户端会话"""
|
|
103
|
+
if self.client:
|
|
104
|
+
logger.info("Closing Xinference rerank client...")
|
|
105
|
+
try:
|
|
106
|
+
await self.client.close()
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Failed to close Xinference client: {e}", exc_info=True)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import os
|
|
3
|
+
import aiohttp
|
|
4
|
+
from xinference_client.client.restful.async_restful_client import (
|
|
5
|
+
AsyncClient as Client,
|
|
6
|
+
)
|
|
7
|
+
from ..provider import STTProvider
|
|
8
|
+
from ..entities import ProviderType
|
|
9
|
+
from ..register import register_provider_adapter
|
|
10
|
+
from astrbot.core import logger
|
|
11
|
+
from astrbot.core.utils.tencent_record_helper import tencent_silk_to_wav
|
|
12
|
+
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@register_provider_adapter(
|
|
16
|
+
"xinference_stt",
|
|
17
|
+
"Xinference STT",
|
|
18
|
+
provider_type=ProviderType.SPEECH_TO_TEXT,
|
|
19
|
+
)
|
|
20
|
+
class ProviderXinferenceSTT(STTProvider):
|
|
21
|
+
def __init__(self, provider_config: dict, provider_settings: dict) -> None:
|
|
22
|
+
super().__init__(provider_config, provider_settings)
|
|
23
|
+
self.provider_config = provider_config
|
|
24
|
+
self.provider_settings = provider_settings
|
|
25
|
+
self.base_url = provider_config.get("api_base", "http://127.0.0.1:9997")
|
|
26
|
+
self.base_url = self.base_url.rstrip("/")
|
|
27
|
+
self.timeout = provider_config.get("timeout", 180)
|
|
28
|
+
self.model_name = provider_config.get("model", "whisper-large-v3")
|
|
29
|
+
self.api_key = provider_config.get("api_key")
|
|
30
|
+
self.launch_model_if_not_running = provider_config.get(
|
|
31
|
+
"launch_model_if_not_running", False
|
|
32
|
+
)
|
|
33
|
+
self.client = None
|
|
34
|
+
self.model_uid = None
|
|
35
|
+
|
|
36
|
+
async def initialize(self):
|
|
37
|
+
if self.api_key:
|
|
38
|
+
logger.info("Xinference STT: Using API key for authentication.")
|
|
39
|
+
self.client = Client(self.base_url, api_key=self.api_key)
|
|
40
|
+
else:
|
|
41
|
+
logger.info("Xinference STT: No API key provided.")
|
|
42
|
+
self.client = Client(self.base_url)
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
running_models = await self.client.list_models()
|
|
46
|
+
for uid, model_spec in running_models.items():
|
|
47
|
+
if model_spec.get("model_name") == self.model_name:
|
|
48
|
+
logger.info(
|
|
49
|
+
f"Model '{self.model_name}' is already running with UID: {uid}"
|
|
50
|
+
)
|
|
51
|
+
self.model_uid = uid
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
if self.model_uid is None:
|
|
55
|
+
if self.launch_model_if_not_running:
|
|
56
|
+
logger.info(f"Launching {self.model_name} model...")
|
|
57
|
+
self.model_uid = await self.client.launch_model(
|
|
58
|
+
model_name=self.model_name, model_type="audio"
|
|
59
|
+
)
|
|
60
|
+
logger.info("Model launched.")
|
|
61
|
+
else:
|
|
62
|
+
logger.warning(
|
|
63
|
+
f"Model '{self.model_name}' is not running and auto-launch is disabled. Provider will not be available."
|
|
64
|
+
)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error(f"Failed to initialize Xinference model: {e}")
|
|
69
|
+
logger.debug(
|
|
70
|
+
f"Xinference initialization failed with exception: {e}", exc_info=True
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
async def get_text(self, audio_url: str) -> str:
|
|
74
|
+
if not self.model_uid or self.client is None or self.client.session is None:
|
|
75
|
+
logger.error("Xinference STT model is not initialized.")
|
|
76
|
+
return ""
|
|
77
|
+
|
|
78
|
+
audio_bytes = None
|
|
79
|
+
temp_files = []
|
|
80
|
+
is_tencent = False
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
# 1. Get audio bytes
|
|
84
|
+
if audio_url.startswith("http"):
|
|
85
|
+
if "multimedia.nt.qq.com.cn" in audio_url:
|
|
86
|
+
is_tencent = True
|
|
87
|
+
async with aiohttp.ClientSession() as session:
|
|
88
|
+
async with session.get(audio_url, timeout=self.timeout) as resp:
|
|
89
|
+
if resp.status == 200:
|
|
90
|
+
audio_bytes = await resp.read()
|
|
91
|
+
else:
|
|
92
|
+
logger.error(
|
|
93
|
+
f"Failed to download audio from {audio_url}, status: {resp.status}"
|
|
94
|
+
)
|
|
95
|
+
return ""
|
|
96
|
+
else:
|
|
97
|
+
if os.path.exists(audio_url):
|
|
98
|
+
with open(audio_url, "rb") as f:
|
|
99
|
+
audio_bytes = f.read()
|
|
100
|
+
else:
|
|
101
|
+
logger.error(f"File not found: {audio_url}")
|
|
102
|
+
return ""
|
|
103
|
+
|
|
104
|
+
if not audio_bytes:
|
|
105
|
+
logger.error("Audio bytes are empty.")
|
|
106
|
+
return ""
|
|
107
|
+
|
|
108
|
+
# 2. Check for conversion
|
|
109
|
+
needs_conversion = False
|
|
110
|
+
if (
|
|
111
|
+
audio_url.endswith((".amr", ".silk"))
|
|
112
|
+
or is_tencent
|
|
113
|
+
or b"SILK" in audio_bytes[:8]
|
|
114
|
+
):
|
|
115
|
+
needs_conversion = True
|
|
116
|
+
|
|
117
|
+
# 3. Perform conversion if needed
|
|
118
|
+
if needs_conversion:
|
|
119
|
+
logger.info("Audio requires conversion, using temporary files...")
|
|
120
|
+
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
121
|
+
os.makedirs(temp_dir, exist_ok=True)
|
|
122
|
+
|
|
123
|
+
input_path = os.path.join(temp_dir, str(uuid.uuid4()))
|
|
124
|
+
output_path = os.path.join(temp_dir, str(uuid.uuid4()) + ".wav")
|
|
125
|
+
temp_files.extend([input_path, output_path])
|
|
126
|
+
|
|
127
|
+
with open(input_path, "wb") as f:
|
|
128
|
+
f.write(audio_bytes)
|
|
129
|
+
|
|
130
|
+
logger.info("Converting silk/amr file to wav ...")
|
|
131
|
+
await tencent_silk_to_wav(input_path, output_path)
|
|
132
|
+
|
|
133
|
+
with open(output_path, "rb") as f:
|
|
134
|
+
audio_bytes = f.read()
|
|
135
|
+
|
|
136
|
+
# 4. Transcribe
|
|
137
|
+
# 官方asyncCLient的客户端似乎实现有点问题,这里直接用aiohttp实现openai标准兼容请求,提交issue等待官方修复后再改回来
|
|
138
|
+
url = f"{self.base_url}/v1/audio/transcriptions"
|
|
139
|
+
headers = {
|
|
140
|
+
"accept": "application/json",
|
|
141
|
+
}
|
|
142
|
+
if self.client and self.client._headers:
|
|
143
|
+
headers.update(self.client._headers)
|
|
144
|
+
|
|
145
|
+
data = aiohttp.FormData()
|
|
146
|
+
data.add_field("model", self.model_uid)
|
|
147
|
+
data.add_field(
|
|
148
|
+
"file", audio_bytes, filename="audio.wav", content_type="audio/wav"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
async with self.client.session.post(
|
|
152
|
+
url, data=data, headers=headers, timeout=self.timeout
|
|
153
|
+
) as resp:
|
|
154
|
+
if resp.status == 200:
|
|
155
|
+
result = await resp.json()
|
|
156
|
+
text = result.get("text", "")
|
|
157
|
+
logger.debug(f"Xinference STT result: {text}")
|
|
158
|
+
return text
|
|
159
|
+
else:
|
|
160
|
+
error_text = await resp.text()
|
|
161
|
+
logger.error(
|
|
162
|
+
f"Xinference STT transcription failed with status {resp.status}: {error_text}"
|
|
163
|
+
)
|
|
164
|
+
return ""
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.error(f"Xinference STT failed: {e}")
|
|
168
|
+
logger.debug(f"Xinference STT failed with exception: {e}", exc_info=True)
|
|
169
|
+
return ""
|
|
170
|
+
finally:
|
|
171
|
+
# 5. Cleanup
|
|
172
|
+
for temp_file in temp_files:
|
|
173
|
+
try:
|
|
174
|
+
if os.path.exists(temp_file):
|
|
175
|
+
os.remove(temp_file)
|
|
176
|
+
logger.debug(f"Removed temporary file: {temp_file}")
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.error(f"Failed to remove temporary file {temp_file}: {e}")
|
|
179
|
+
|
|
180
|
+
async def terminate(self) -> None:
|
|
181
|
+
"""关闭客户端会话"""
|
|
182
|
+
if self.client:
|
|
183
|
+
logger.info("Closing Xinference STT client...")
|
|
184
|
+
try:
|
|
185
|
+
await self.client.close()
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.error(f"Failed to close Xinference client: {e}", exc_info=True)
|
astrbot/core/star/context.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from asyncio import Queue
|
|
2
|
-
from typing import List, Union
|
|
3
2
|
|
|
4
3
|
from astrbot.core.provider.provider import (
|
|
5
4
|
Provider,
|
|
@@ -11,7 +10,7 @@ from astrbot.core.provider.provider import (
|
|
|
11
10
|
from astrbot.core.provider.entities import ProviderType
|
|
12
11
|
from astrbot.core.db import BaseDatabase
|
|
13
12
|
from astrbot.core.config.astrbot_config import AstrBotConfig
|
|
14
|
-
from astrbot.core.provider.func_tool_manager import FunctionToolManager
|
|
13
|
+
from astrbot.core.provider.func_tool_manager import FunctionToolManager, FunctionTool
|
|
15
14
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
16
15
|
from astrbot.core.message.message_event_result import MessageChain
|
|
17
16
|
from astrbot.core.provider.manager import ProviderManager
|
|
@@ -25,7 +24,8 @@ from .star import star_registry, StarMetadata, star_map
|
|
|
25
24
|
from .star_handler import star_handlers_registry, StarHandlerMetadata, EventType
|
|
26
25
|
from .filter.command import CommandFilter
|
|
27
26
|
from .filter.regex import RegexFilter
|
|
28
|
-
from typing import
|
|
27
|
+
from typing import Any
|
|
28
|
+
from collections.abc import Awaitable, Callable
|
|
29
29
|
from astrbot.core.conversation_mgr import ConversationManager
|
|
30
30
|
from astrbot.core.star.filter.platform_adapter_type import (
|
|
31
31
|
PlatformAdapterType,
|
|
@@ -42,7 +42,7 @@ class Context:
|
|
|
42
42
|
registered_web_apis: list = []
|
|
43
43
|
|
|
44
44
|
# back compatibility
|
|
45
|
-
_register_tasks:
|
|
45
|
+
_register_tasks: list[Awaitable] = []
|
|
46
46
|
_star_manager = None
|
|
47
47
|
|
|
48
48
|
def __init__(
|
|
@@ -78,7 +78,7 @@ class Context:
|
|
|
78
78
|
if star.name == star_name:
|
|
79
79
|
return star
|
|
80
80
|
|
|
81
|
-
def get_all_stars(self) ->
|
|
81
|
+
def get_all_stars(self) -> list[StarMetadata]:
|
|
82
82
|
"""获取当前载入的所有插件 Metadata 的列表"""
|
|
83
83
|
return star_registry
|
|
84
84
|
|
|
@@ -116,19 +116,19 @@ class Context:
|
|
|
116
116
|
prov = self.provider_manager.inst_map.get(provider_id)
|
|
117
117
|
return prov
|
|
118
118
|
|
|
119
|
-
def get_all_providers(self) ->
|
|
119
|
+
def get_all_providers(self) -> list[Provider]:
|
|
120
120
|
"""获取所有用于文本生成任务的 LLM Provider(Chat_Completion 类型)。"""
|
|
121
121
|
return self.provider_manager.provider_insts
|
|
122
122
|
|
|
123
|
-
def get_all_tts_providers(self) ->
|
|
123
|
+
def get_all_tts_providers(self) -> list[TTSProvider]:
|
|
124
124
|
"""获取所有用于 TTS 任务的 Provider。"""
|
|
125
125
|
return self.provider_manager.tts_provider_insts
|
|
126
126
|
|
|
127
|
-
def get_all_stt_providers(self) ->
|
|
127
|
+
def get_all_stt_providers(self) -> list[STTProvider]:
|
|
128
128
|
"""获取所有用于 STT 任务的 Provider。"""
|
|
129
129
|
return self.provider_manager.stt_provider_insts
|
|
130
130
|
|
|
131
|
-
def get_all_embedding_providers(self) ->
|
|
131
|
+
def get_all_embedding_providers(self) -> list[EmbeddingProvider]:
|
|
132
132
|
"""获取所有用于 Embedding 任务的 Provider。"""
|
|
133
133
|
return self.provider_manager.embedding_provider_insts
|
|
134
134
|
|
|
@@ -196,9 +196,7 @@ class Context:
|
|
|
196
196
|
return self._event_queue
|
|
197
197
|
|
|
198
198
|
@deprecated(version="4.0.0", reason="Use get_platform_inst instead")
|
|
199
|
-
def get_platform(
|
|
200
|
-
self, platform_type: Union[PlatformAdapterType, str]
|
|
201
|
-
) -> Platform | None:
|
|
199
|
+
def get_platform(self, platform_type: PlatformAdapterType | str) -> Platform | None:
|
|
202
200
|
"""
|
|
203
201
|
获取指定类型的平台适配器。
|
|
204
202
|
|
|
@@ -231,7 +229,7 @@ class Context:
|
|
|
231
229
|
return platform
|
|
232
230
|
|
|
233
231
|
async def send_message(
|
|
234
|
-
self, session:
|
|
232
|
+
self, session: str | MessageSesion, message_chain: MessageChain
|
|
235
233
|
) -> bool:
|
|
236
234
|
"""
|
|
237
235
|
根据 session(unified_msg_origin) 主动发送消息。
|
|
@@ -258,6 +256,11 @@ class Context:
|
|
|
258
256
|
return True
|
|
259
257
|
return False
|
|
260
258
|
|
|
259
|
+
def add_llm_tools(self, *tools: FunctionTool) -> None:
|
|
260
|
+
"""添加 LLM 工具。"""
|
|
261
|
+
for tool in tools:
|
|
262
|
+
self.provider_manager.llm_tools.func_list.append(tool)
|
|
263
|
+
|
|
261
264
|
"""
|
|
262
265
|
以下的方法已经不推荐使用。请从 AstrBot 文档查看更好的注册方式。
|
|
263
266
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AstrBot
|
|
3
|
-
Version: 4.5.
|
|
3
|
+
Version: 4.5.1
|
|
4
4
|
Summary: 易上手的多平台 LLM 聊天机器人及开发框架
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -54,6 +54,7 @@ Requires-Dist: telegramify-markdown>=0.5.1
|
|
|
54
54
|
Requires-Dist: watchfiles>=1.0.5
|
|
55
55
|
Requires-Dist: websockets>=15.0.1
|
|
56
56
|
Requires-Dist: wechatpy>=1.8.18
|
|
57
|
+
Requires-Dist: xinference-client
|
|
57
58
|
Description-Content-Type: text/markdown
|
|
58
59
|
|
|
59
60
|

|
|
@@ -66,7 +67,7 @@ Description-Content-Type: text/markdown
|
|
|
66
67
|
|
|
67
68
|
<div>
|
|
68
69
|
<a href="https://trendshift.io/repositories/12875" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12875" alt="Soulter%2FAstrBot | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
|
69
|
-
<a href="https://hellogithub.com/repository/AstrBotDevs/AstrBot" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=d127d50cd5e54c5382328acc3bb25483&claim_uid=ZO9by7qCXgSd6Lp" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
|
70
|
+
<a href="https://hellogithub.com/repository/AstrBotDevs/AstrBot" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=d127d50cd5e54c5382328acc3bb25483&claim_uid=ZO9by7qCXgSd6Lp&t=1" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
|
70
71
|
</div>
|
|
71
72
|
|
|
72
73
|
<br>
|
|
@@ -46,11 +46,11 @@ astrbot/core/agent/runners/base.py,sha256=exZS_d2BsrLz-xgeY9ZUPuXikBDUnKxO-dU3ZF
|
|
|
46
46
|
astrbot/core/agent/runners/tool_loop_agent_runner.py,sha256=bg-YKJV2jRNyR2gu3FZNx_aKIFUYr5zjyzJWDlVLVpU,14940
|
|
47
47
|
astrbot/core/config/__init__.py,sha256=0CO_3sKtI3WOwWT0k4i6TleWq1SAWJFfB8KjnYB8Zig,172
|
|
48
48
|
astrbot/core/config/astrbot_config.py,sha256=X-b3c5m4msgJrdYFH2LXic5XKY0ViuUMdNZ335zqSBw,6335
|
|
49
|
-
astrbot/core/config/default.py,sha256=
|
|
49
|
+
astrbot/core/config/default.py,sha256=CsZR9TnL-ZhGKZlCutKLcKJZssjFfmc7DkBZeihJfJg,133409
|
|
50
50
|
astrbot/core/db/__init__.py,sha256=JOAMt7_j0y96CuArXJ-YY_qXisSartOZwyDTKf9ZDn8,8844
|
|
51
51
|
astrbot/core/db/po.py,sha256=NDOJpUXI1i4BF1uymCj07opwXM0gdHBtwCoL16Xj4jc,7798
|
|
52
52
|
astrbot/core/db/sqlite.py,sha256=_5-B2Jlare4twLG0TlO95bVTaLu0HAXsfpX5LOcoVWA,26082
|
|
53
|
-
astrbot/core/db/migration/helper.py,sha256=
|
|
53
|
+
astrbot/core/db/migration/helper.py,sha256=zWh_bjEpINQ2Dm9xF7NEOblG7jFoMNq--7APT0CCDAQ,2078
|
|
54
54
|
astrbot/core/db/migration/migra_3_to_4.py,sha256=I1CesaBbf5wj9agtNWxDl1V-qixmwdURbBQf5Vzagrk,15025
|
|
55
55
|
astrbot/core/db/migration/migra_45_to_46.py,sha256=Fw6DG_eJ2zNZkEs2tmSqvgDDapslzLpm5SaJhGuN32E,1559
|
|
56
56
|
astrbot/core/db/migration/shared_preferences_v3.py,sha256=tE11WIpwT-Q8yVBkw4eveRr1PmFdNRJQSprH4xdO3G4,1245
|
|
@@ -114,7 +114,7 @@ astrbot/core/platform/platform_metadata.py,sha256=efQQrAquLK_8SxJm1h0HLR0k-RKS29
|
|
|
114
114
|
astrbot/core/platform/register.py,sha256=5uLNIbxyMbQDAvTEZ5Iv9k9uT3sI0QASP6yChMcdkNM,1860
|
|
115
115
|
astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py,sha256=PN4h7TjH2UrQZX9bZmI6bGtmXgxHEx5GJREI-Vw0qX4,8036
|
|
116
116
|
astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py,sha256=X5Rk_5ZnQgzKazbnIoLf85gex35qSABDyp0SKzBk7g0,16768
|
|
117
|
-
astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py,sha256=
|
|
117
|
+
astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py,sha256=vlvVkyroRSt7QaJiKaCadpDBvNJXPDn2uNaWqbzlm4o,8703
|
|
118
118
|
astrbot/core/platform/sources/dingtalk/dingtalk_event.py,sha256=CYTVhpiIedNdLQxjKJlK26GxSWZGlUFNbxZZ5i3tM4o,2672
|
|
119
119
|
astrbot/core/platform/sources/discord/client.py,sha256=mNAE81bYJSvdhbSXrR7MDZIqtGEqDYynpRCGZNf9-ag,4592
|
|
120
120
|
astrbot/core/platform/sources/discord/components.py,sha256=QQYKLrdWjvzxMVrNZTxGW-rhdnn4L0CY5wbzxvQ0DY0,3845
|
|
@@ -163,7 +163,7 @@ astrbot/core/provider/__init__.py,sha256=fhD_KB1-KpqJ7woaXDXc7kdlmL3XPQz3xlc5IkF
|
|
|
163
163
|
astrbot/core/provider/entites.py,sha256=-353AdRDA6ST4AS48cQ1RRAXHSy3F7pVS_28hW4cG2U,388
|
|
164
164
|
astrbot/core/provider/entities.py,sha256=CkC-U9nafBKo2n2kLZqzukosDX7RuZWAK4DMBHQkasA,11238
|
|
165
165
|
astrbot/core/provider/func_tool_manager.py,sha256=NuWMmAJaEwoJ3XCSvhwtmzDPdzX4K4BIRKuKgF0FlQk,20881
|
|
166
|
-
astrbot/core/provider/manager.py,sha256=
|
|
166
|
+
astrbot/core/provider/manager.py,sha256=qcu_CpYfcf7BBmyHnR6sHoI3dVgcImG9zBqxf6fvgss,22361
|
|
167
167
|
astrbot/core/provider/provider.py,sha256=I_240hwpzkHudVdeGrCEqJODjVR0yls3n84cviXyquI,9954
|
|
168
168
|
astrbot/core/provider/register.py,sha256=bWAF9zWNnSYQWjmZIXiWgxFaeWIiWjEoEIN_xhmq3mM,1830
|
|
169
169
|
astrbot/core/provider/sources/anthropic_source.py,sha256=VKTd1wc6_6Z5G4l7Sqy3sqEqid-IMbPgi1s7RiYpaBw,15202
|
|
@@ -182,18 +182,20 @@ astrbot/core/provider/sources/gsv_selfhosted_source.py,sha256=7wSQ32AJv4cisjnedE
|
|
|
182
182
|
astrbot/core/provider/sources/gsvi_tts_source.py,sha256=EoYuAf85NVcPPbyRWkE_doWF-7R8IM5o9ozxbbvaFRk,2025
|
|
183
183
|
astrbot/core/provider/sources/minimax_tts_api_source.py,sha256=jNLP_4-UHq_Iekvjn3h7G6YZjTCGuII-hq-1RhzjSlE,5877
|
|
184
184
|
astrbot/core/provider/sources/openai_embedding_source.py,sha256=3KH9WDXZEShESgu1lGgR_8wKzG88qKm0u5yR8lafBHQ,1634
|
|
185
|
-
astrbot/core/provider/sources/openai_source.py,sha256=
|
|
185
|
+
astrbot/core/provider/sources/openai_source.py,sha256=B3PjeCWhNpOIF1MXe5JeU93CAuFw8egFZAG0mKlLOzM,22021
|
|
186
186
|
astrbot/core/provider/sources/openai_tts_api_source.py,sha256=HpLR-hnwNV2u5ohO78phCpkAVRXnBkOSSPLKMeyzGmQ,1624
|
|
187
187
|
astrbot/core/provider/sources/sensevoice_selfhosted_source.py,sha256=2-NUDRiJJs3onxnrovdoVqUMI8bxGu2J2n3ZgwjxEm0,3828
|
|
188
188
|
astrbot/core/provider/sources/vllm_rerank_source.py,sha256=Gv_veniilJ5v9lPGlQG_zmQYmHfhNggYIwj5p02CoLE,2275
|
|
189
189
|
astrbot/core/provider/sources/volcengine_tts.py,sha256=eNS7XUtDTunGuZV8UaI4-ghNyY_fDRW0hiZPssBNsPQ,4143
|
|
190
190
|
astrbot/core/provider/sources/whisper_api_source.py,sha256=qKE9Wx7Nse9pKovbDb__sVBeG7opR_yar5ZsbIYx9e4,2626
|
|
191
191
|
astrbot/core/provider/sources/whisper_selfhosted_source.py,sha256=Qggu2IkNyCYVqNYc5NsonVcVpcLe-t04L-IpqnCzh_A,2636
|
|
192
|
+
astrbot/core/provider/sources/xinference_rerank_source.py,sha256=VB6wHkdWlFexKgQ8udFC1LXDWPM-FdAVsVEfq0_twwQ,4343
|
|
193
|
+
astrbot/core/provider/sources/xinference_stt_provider.py,sha256=Upzf_4ZDKxcJ2Ucrprb86JdYo_vv9RpA85d3ExKCYhI,7653
|
|
192
194
|
astrbot/core/provider/sources/zhipu_source.py,sha256=wUXp9wjMoFjYKqjwkDDUWGduzkrqYvoffjsJch3iNnw,706
|
|
193
195
|
astrbot/core/star/README.md,sha256=LXxqxp3xv_oejO8ocBPOrbmLe0WB4feu43fYDNddHTQ,161
|
|
194
196
|
astrbot/core/star/__init__.py,sha256=ynSwMrdCLyVMN3Q9flS_mcDDjdIGrkLBpfeDVoFj6PM,2080
|
|
195
197
|
astrbot/core/star/config.py,sha256=f4h1YFt1Tn6S2D-LvnhM483qaD8JdWjl-TBRV9CeYBo,3593
|
|
196
|
-
astrbot/core/star/context.py,sha256=
|
|
198
|
+
astrbot/core/star/context.py,sha256=Vhm1-IJy-iZuW09Y28ggx7oSJyOnL9tN9LR8IDdAj_0,13305
|
|
197
199
|
astrbot/core/star/session_llm_manager.py,sha256=c9vPbL454_H--x5c-cjbiH-y1CrcMfTLfbhNmKgSYio,8467
|
|
198
200
|
astrbot/core/star/session_plugin_manager.py,sha256=3vxbqPikZg4ZTHG_STvEKeqNplo78wknILSnFCmHTr0,5291
|
|
199
201
|
astrbot/core/star/star.py,sha256=5jdigpi4WCTLROdg0q15cLOCgER6uNY9x3elqglCa_g,1831
|
|
@@ -251,8 +253,8 @@ astrbot/dashboard/routes/static_file.py,sha256=7KnNcOb1BVqSTft114LhGsDkfg69X2jHE
|
|
|
251
253
|
astrbot/dashboard/routes/t2i.py,sha256=scp05AxoJM9cubrkSMBu1BbIWP1BMS50eFEPZ9S6WKM,8893
|
|
252
254
|
astrbot/dashboard/routes/tools.py,sha256=xVw6sG6xnANZm-M2JXT75ftH_I58MMJ0FqejODnP4Xw,14658
|
|
253
255
|
astrbot/dashboard/routes/update.py,sha256=sWDH_diqNHugUoVzPSxLUHPtb_nv5YIEEIOGjA6VB7o,6713
|
|
254
|
-
astrbot-4.5.
|
|
255
|
-
astrbot-4.5.
|
|
256
|
-
astrbot-4.5.
|
|
257
|
-
astrbot-4.5.
|
|
258
|
-
astrbot-4.5.
|
|
256
|
+
astrbot-4.5.1.dist-info/METADATA,sha256=yx-0Ug1-jT6oqcx0bOxn34bDSoLpUtZM0IWlAiMzugc,10920
|
|
257
|
+
astrbot-4.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
258
|
+
astrbot-4.5.1.dist-info/entry_points.txt,sha256=OEF09YmhBWYuViXrvTLLpstF4ccmNwDL8r7nnFD0pfI,53
|
|
259
|
+
astrbot-4.5.1.dist-info/licenses/LICENSE,sha256=zPfQj5Mq8-gThIiBcxETr7t8gND9bZWOjTGQAr80TQI,34500
|
|
260
|
+
astrbot-4.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|