Compare commits

...

16 Commits

Author SHA1 Message Date
Soulter ff998fdd8d chore: bump version to 4.5.1 2025-10-31 23:55:40 +08:00
LIghtJUNction d7461ed54c fix(helper.py): 修复了迁移逻辑,现在不再误判 (#3215)
* fix(helper.py): 修复了迁移逻辑,现在不再误判

* fix(helper.py): 没有data_v3 dir
2025-10-31 23:37:37 +08:00
Soulter 3ce577acf9 docs: enhance bug report template with clarity on details
Updated bug report template to emphasize the need for detailed logs and information.
2025-10-31 23:18:15 +08:00
Chris 50b1dccff3 feat: support xAI Grok Live Search config (#3203)
* Add xai_native_search configuration option

* Implement xAI compatibility and search injection

Add support for xAI integration with search parameters injection.

* Refactor xAI handling in openai_source.py

Removed the _is_xai method and updated xAI search injection logic.

* Fix formatting of condition in default.py

* Fix formatting in openai_source.py
2025-10-31 21:48:45 +08:00
Dt8333 c33e7e30d4 chore(requirements): Sync dependencies from pyproject to requirements.txt (#3208)
* chore(requirements): 将pyproject中的dependency同步到requirements.txt

* chore(requirements): 补全遗漏dependency
2025-10-31 15:27:16 +08:00
RC-CHN bc7f01ba36 feat: add Xinference STT provider (#3197)
* feat: add Xinference STT provider

* chore:update comment in xinference_stt_provider

* style: ruff format xinference_stt_provider

* chore: remove unused import of base64 in xinference_stt_provider

* fix: enhance model initialization check in get_text method

---------

Co-authored-by: Soulter <905617992@qq.com>
2025-10-31 01:49:35 +08:00
再吃颗电池吧 2ce653caad perf: modify the at logic in the DingTalk adapter (#3186)
* feat 初次提交

* fix: Modify the At logic in the DingTalk adapter.

* del uv.lock

* 添加at_users为空判断

* 优化钉钉at的处理逻辑,不用重复判断机器人是否is_in_at_list

* fix: refine handling of mentioned users in group messages

---------

Co-authored-by: linyiming <linyiming@example.com>
Co-authored-by: Soulter <905617992@qq.com>
2025-10-30 14:15:01 +08:00
Soulter 0d850d7b22 fix: refine docstring for add_llm_tools method in Context class 2025-10-29 20:16:27 +08:00
Soulter a2be155b8e feat: add method to register LLM tools in Context class 2025-10-29 20:13:15 +08:00
Soulter 68aa107689 docs: update readme 2025-10-29 13:58:58 +08:00
Soulter 23096ed3a5 perf: update extension card page style, add config and view-docs button 2025-10-29 00:38:04 +08:00
RC-CHN 90a65c35c1 feat: add Xinference rerank provider (#3162)
* feat:add Xinference rerank provider

* feat:add default rerank_api_key option for Xinference provider

* style: format code

* fix: refactor XinferenceRerankProvider initialization for better error handling

* fix: update XinferenceRerankProvider to use async client methods for initialization and reranking

* feat: add launch_model_if_not_running option to XinferenceRerankProvider for better control over model initialization

* chore: remove unused asyncio import from xinference_rerank_source.py
2025-10-28 18:23:55 +08:00
a490077 3d88827a95 fix: qq_official_webhook is_sandbox field error (#3167)
* QQ官方机器人增加沙箱模式选项,让本地部署能跳过IP白名单验证

* chore: ruff format

* 修复沙盒配置为字符串判断

* 由于配置类型为字符串,修复为字符串判断

* chore: ruff format

* fix: update is_sandbox configuration to use boolean type

---------

Co-authored-by: 郭鹏 <gp@pp052.top>
Co-authored-by: Soulter <905617992@qq.com>
Co-authored-by: Dt8333 <lb0016@foxmail.com>
2025-10-28 10:15:46 +08:00
Futureppo 40a0a8df5a perf: 优化 /model 切换模型成功的提示 (#3161) 2025-10-28 09:05:42 +08:00
dependabot[bot] 20f7129c0b chore(deps): bump actions/upload-artifact in the github-actions group (#3178)
Bumps the github-actions group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `actions/upload-artifact` from 4 to 5
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-28 08:53:28 +08:00
Soulter 0e962e95dd docs: update plugin information template in YAML 2025-10-27 14:26:59 +08:00
18 changed files with 489 additions and 82 deletions
+4 -3
View File
@@ -26,12 +26,13 @@ body:
value: |
```json
{
"name": "插件名",
"desc": "插件介绍",
"name": "插件名,请以 astrbot_plugin_ 开头",
"display_name": "用于展示的插件名,方便人类阅读",
"desc": "插件的简短介绍",
"author": "作者名",
"repo": "插件仓库链接",
"tags": [],
"social_link": ""
"social_link": "",
}
```
validations:
+3 -3
View File
@@ -6,13 +6,13 @@ body:
- type: markdown
attributes:
value: |
感谢您抽出时间报告问题!请准确解释您的问题。如果可能,请提供一个可复现的片段(这有助于更快地解决问题)。
感谢您抽出时间报告问题!请准确解释您的问题。如果可能,请提供一个可复现的片段(这有助于更快地解决问题)。请注意,不详细 / 没有日志的 issue 会被直接关闭,谢谢理解。
- type: textarea
attributes:
label: 发生了什么
description: 描述你遇到的异常
placeholder: >
一个清晰且具体的描述这个异常是什么。
一个清晰且具体的描述这个异常是什么。请注意,不详细 / 没有日志的 issue 会被直接关闭,谢谢理解。
validations:
required: true
@@ -55,7 +55,7 @@ body:
attributes:
label: 报错日志
description: >
如报错日志、截图等。请提供完整的 Debug 级别的日志,不要介意它很长!
如报错日志、截图等。请提供完整的 Debug 级别的日志,不要介意它很长!请注意,不详细 / 没有日志的 issue 会被直接关闭,谢谢理解。
placeholder: >
请提供完整的报错日志或截图。
validations:
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
zip -r dist.zip dist
- name: Archive production artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: dist-without-markdown
path: |
+1 -1
View File
@@ -8,7 +8,7 @@
<div>
<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>
<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="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<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="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
<br>
+41 -1
View File
@@ -6,7 +6,7 @@ import os
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
VERSION = "4.5.0"
VERSION = "4.5.1"
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
# 默认配置
@@ -324,6 +324,10 @@ CONFIG_METADATA_2 = {
# "type": "string",
# "options": ["fullscreen", "embedded"],
# },
"is_sandbox": {
"description": "沙箱模式",
"type": "bool",
},
"satori_api_base_url": {
"description": "Satori API 终结点",
"type": "string",
@@ -767,6 +771,7 @@ CONFIG_METADATA_2 = {
"timeout": 120,
"model_config": {"model": "grok-2-latest", "temperature": 0.4},
"custom_extra_body": {},
"xai_native_search": False,
"modalities": ["text", "image", "tool_use"],
},
"Anthropic": {
@@ -1258,8 +1263,38 @@ CONFIG_METADATA_2 = {
"rerank_model": "BAAI/bge-reranker-base",
"timeout": 20,
},
"Xinference Rerank": {
"id": "xinference_rerank",
"type": "xinference_rerank",
"provider": "xinference",
"provider_type": "rerank",
"enable": True,
"rerank_api_key": "",
"rerank_api_base": "http://127.0.0.1:9997",
"rerank_model": "BAAI/bge-reranker-base",
"timeout": 20,
"launch_model_if_not_running": False,
},
"Xinference STT": {
"id": "xinference_stt",
"type": "xinference_stt",
"provider": "xinference",
"provider_type": "speech_to_text",
"enable": False,
"api_key": "",
"api_base": "http://127.0.0.1:9997",
"model": "whisper-large-v3",
"timeout": 180,
"launch_model_if_not_running": False,
},
},
"items": {
"xai_native_search": {
"description": "启用原生搜索功能",
"type": "bool",
"hint": "启用后,将通过 xAI 的 Chat Completions 原生 Live Search 进行联网检索(按需计费)。仅对 xAI 提供商生效。",
"condition": {"provider": "xai"},
},
"rerank_api_base": {
"description": "重排序模型 API Base URL",
"type": "string",
@@ -1274,6 +1309,11 @@ CONFIG_METADATA_2 = {
"description": "重排序模型名称",
"type": "string",
},
"launch_model_if_not_running": {
"description": "模型未运行时自动启动",
"type": "bool",
"hint": "如果模型当前未在 Xinference 服务中运行,是否尝试自动启动它。在生产环境中建议关闭。",
},
"modalities": {
"description": "模型能力",
"type": "list",
+6 -3
View File
@@ -17,8 +17,11 @@ async def check_migration_needed_v4(db_helper: BaseDatabase) -> bool:
检查是否需要进行数据库迁移
如果存在 data_v3.db 并且 preference 中没有 migration_done_v4,则需要进行迁移。
"""
data_v3_exists = os.path.exists(get_astrbot_data_path())
if not data_v3_exists:
# 仅当 data 目录下存在旧版本数据(data_v3.db 文件)时才考虑迁移
data_dir = get_astrbot_data_path()
data_v3_db = os.path.join(data_dir, "data_v3.db")
if not os.path.exists(data_v3_db):
return False
migration_done = await db_helper.get_preference(
"global", "global", "migration_done_v4"
@@ -32,7 +35,7 @@ async def do_migration_v4(
db_helper: BaseDatabase,
platform_id_map: dict[str, dict[str, str]],
astrbot_config: AstrBotConfig,
):
) -> None:
"""
执行数据库迁移
迁移旧的 webchat_conversation 表到新的 conversation 表。
@@ -100,8 +100,11 @@ class DingtalkPlatformAdapter(Platform):
abm.raw_message = message
if abm.type == MessageType.GROUP_MESSAGE:
if message.is_in_at_list:
abm.message.append(At(qq=abm.self_id))
# 处理所有被 @ 的用户(包括机器人自己,因 at_users 已包含)
if message.at_users:
for user in message.at_users:
if user.dingtalk_id:
abm.message.append(At(qq=user.dingtalk_id))
abm.group_id = message.conversation_id
if self.unique_session:
abm.session_id = abm.sender.user_id
+8
View File
@@ -259,6 +259,10 @@ class ProviderManager:
from .sources.whisper_selfhosted_source import (
ProviderOpenAIWhisperSelfHost as ProviderOpenAIWhisperSelfHost,
)
case "xinference_stt":
from .sources.xinference_stt_provider import (
ProviderXinferenceSTT as ProviderXinferenceSTT,
)
case "openai_tts_api":
from .sources.openai_tts_api_source import (
ProviderOpenAITTSAPI as ProviderOpenAITTSAPI,
@@ -311,6 +315,10 @@ class ProviderManager:
from .sources.vllm_rerank_source import (
VLLMRerankProvider as VLLMRerankProvider,
)
case "xinference_rerank":
from .sources.xinference_rerank_source import (
XinferenceRerankProvider as XinferenceRerankProvider,
)
except (ImportError, ModuleNotFoundError) as e:
logger.critical(
f"加载 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}。可能是因为有未安装的依赖。"
@@ -68,6 +68,28 @@ class ProviderOpenAIOfficial(Provider):
model = model_config.get("model", "unknown")
self.set_model(model)
def _maybe_inject_xai_search(self, payloads: dict, **kwargs):
"""当开启 xAI 原生搜索时,向请求体注入 Live Search 参数。
- 仅在 provider_config.xai_native_search 为 True 时生效
- 默认注入 {"mode": "auto"}
- 允许通过 kwargs 使用 xai_search_mode 覆盖(on/auto/off
"""
if not bool(self.provider_config.get("xai_native_search", False)):
return
mode = kwargs.get("xai_search_mode", "auto")
mode = str(mode).lower()
if mode not in ("auto", "on", "off"):
mode = "auto"
# off 时不注入,保持与未开启一致
if mode == "off":
return
# OpenAI SDK 不识别的字段会在 _query/_query_stream 中放入 extra_body
payloads["search_parameters"] = {"mode": mode}
async def get_models(self):
try:
models_str = []
@@ -271,6 +293,9 @@ class ProviderOpenAIOfficial(Provider):
payloads = {"messages": context_query, **model_config}
# xAI 原生搜索参数(最小侵入地在此处注入)
self._maybe_inject_xai_search(payloads, **kwargs)
return payloads, context_query
async def _handle_api_error(
@@ -0,0 +1,108 @@
from xinference_client.client.restful.async_restful_client import (
AsyncClient as Client,
)
from astrbot import logger
from ..provider import RerankProvider
from ..register import register_provider_adapter
from ..entities import ProviderType, RerankResult
@register_provider_adapter(
"xinference_rerank",
"Xinference Rerank 适配器",
provider_type=ProviderType.RERANK,
)
class XinferenceRerankProvider(RerankProvider):
def __init__(self, provider_config: dict, provider_settings: dict) -> None:
super().__init__(provider_config, provider_settings)
self.provider_config = provider_config
self.provider_settings = provider_settings
self.base_url = provider_config.get("rerank_api_base", "http://127.0.0.1:8000")
self.base_url = self.base_url.rstrip("/")
self.timeout = provider_config.get("timeout", 20)
self.model_name = provider_config.get("rerank_model", "BAAI/bge-reranker-base")
self.api_key = provider_config.get("rerank_api_key")
self.launch_model_if_not_running = provider_config.get(
"launch_model_if_not_running", False
)
self.client = None
self.model = None
self.model_uid = None
async def initialize(self):
if self.api_key:
logger.info("Xinference Rerank: Using API key for authentication.")
self.client = Client(self.base_url, api_key=self.api_key)
else:
logger.info("Xinference Rerank: No API key provided.")
self.client = Client(self.base_url)
try:
running_models = await self.client.list_models()
for uid, model_spec in running_models.items():
if model_spec.get("model_name") == self.model_name:
logger.info(
f"Model '{self.model_name}' is already running with UID: {uid}"
)
self.model_uid = uid
break
if self.model_uid is None:
if self.launch_model_if_not_running:
logger.info(f"Launching {self.model_name} model...")
self.model_uid = await self.client.launch_model(
model_name=self.model_name, model_type="rerank"
)
logger.info("Model launched.")
else:
logger.warning(
f"Model '{self.model_name}' is not running and auto-launch is disabled. Provider will not be available."
)
return
if self.model_uid:
self.model = await self.client.get_model(self.model_uid)
except Exception as e:
logger.error(f"Failed to initialize Xinference model: {e}")
logger.debug(
f"Xinference initialization failed with exception: {e}", exc_info=True
)
self.model = None
async def rerank(
self, query: str, documents: list[str], top_n: int | None = None
) -> list[RerankResult]:
if not self.model:
logger.error("Xinference rerank model is not initialized.")
return []
try:
response = await self.model.rerank(documents, query, top_n)
results = response.get("results", [])
logger.debug(f"Rerank API response: {response}")
if not results:
logger.warning(
f"Rerank API returned an empty list. Original response: {response}"
)
return [
RerankResult(
index=result["index"],
relevance_score=result["relevance_score"],
)
for result in results
]
except Exception as e:
logger.error(f"Xinference rerank failed: {e}")
logger.debug(f"Xinference rerank failed with exception: {e}", exc_info=True)
return []
async def terminate(self) -> None:
"""关闭客户端会话"""
if self.client:
logger.info("Closing Xinference rerank client...")
try:
await self.client.close()
except Exception as e:
logger.error(f"Failed to close Xinference client: {e}", exc_info=True)
@@ -0,0 +1,187 @@
import uuid
import os
import aiohttp
from xinference_client.client.restful.async_restful_client import (
AsyncClient as Client,
)
from ..provider import STTProvider
from ..entities import ProviderType
from ..register import register_provider_adapter
from astrbot.core import logger
from astrbot.core.utils.tencent_record_helper import tencent_silk_to_wav
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
@register_provider_adapter(
"xinference_stt",
"Xinference STT",
provider_type=ProviderType.SPEECH_TO_TEXT,
)
class ProviderXinferenceSTT(STTProvider):
def __init__(self, provider_config: dict, provider_settings: dict) -> None:
super().__init__(provider_config, provider_settings)
self.provider_config = provider_config
self.provider_settings = provider_settings
self.base_url = provider_config.get("api_base", "http://127.0.0.1:9997")
self.base_url = self.base_url.rstrip("/")
self.timeout = provider_config.get("timeout", 180)
self.model_name = provider_config.get("model", "whisper-large-v3")
self.api_key = provider_config.get("api_key")
self.launch_model_if_not_running = provider_config.get(
"launch_model_if_not_running", False
)
self.client = None
self.model_uid = None
async def initialize(self):
if self.api_key:
logger.info("Xinference STT: Using API key for authentication.")
self.client = Client(self.base_url, api_key=self.api_key)
else:
logger.info("Xinference STT: No API key provided.")
self.client = Client(self.base_url)
try:
running_models = await self.client.list_models()
for uid, model_spec in running_models.items():
if model_spec.get("model_name") == self.model_name:
logger.info(
f"Model '{self.model_name}' is already running with UID: {uid}"
)
self.model_uid = uid
break
if self.model_uid is None:
if self.launch_model_if_not_running:
logger.info(f"Launching {self.model_name} model...")
self.model_uid = await self.client.launch_model(
model_name=self.model_name, model_type="audio"
)
logger.info("Model launched.")
else:
logger.warning(
f"Model '{self.model_name}' is not running and auto-launch is disabled. Provider will not be available."
)
return
except Exception as e:
logger.error(f"Failed to initialize Xinference model: {e}")
logger.debug(
f"Xinference initialization failed with exception: {e}", exc_info=True
)
async def get_text(self, audio_url: str) -> str:
if not self.model_uid or self.client is None or self.client.session is None:
logger.error("Xinference STT model is not initialized.")
return ""
audio_bytes = None
temp_files = []
is_tencent = False
try:
# 1. Get audio bytes
if audio_url.startswith("http"):
if "multimedia.nt.qq.com.cn" in audio_url:
is_tencent = True
async with aiohttp.ClientSession() as session:
async with session.get(audio_url, timeout=self.timeout) as resp:
if resp.status == 200:
audio_bytes = await resp.read()
else:
logger.error(
f"Failed to download audio from {audio_url}, status: {resp.status}"
)
return ""
else:
if os.path.exists(audio_url):
with open(audio_url, "rb") as f:
audio_bytes = f.read()
else:
logger.error(f"File not found: {audio_url}")
return ""
if not audio_bytes:
logger.error("Audio bytes are empty.")
return ""
# 2. Check for conversion
needs_conversion = False
if (
audio_url.endswith((".amr", ".silk"))
or is_tencent
or b"SILK" in audio_bytes[:8]
):
needs_conversion = True
# 3. Perform conversion if needed
if needs_conversion:
logger.info("Audio requires conversion, using temporary files...")
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
os.makedirs(temp_dir, exist_ok=True)
input_path = os.path.join(temp_dir, str(uuid.uuid4()))
output_path = os.path.join(temp_dir, str(uuid.uuid4()) + ".wav")
temp_files.extend([input_path, output_path])
with open(input_path, "wb") as f:
f.write(audio_bytes)
logger.info("Converting silk/amr file to wav ...")
await tencent_silk_to_wav(input_path, output_path)
with open(output_path, "rb") as f:
audio_bytes = f.read()
# 4. Transcribe
# 官方asyncCLient的客户端似乎实现有点问题,这里直接用aiohttp实现openai标准兼容请求,提交issue等待官方修复后再改回来
url = f"{self.base_url}/v1/audio/transcriptions"
headers = {
"accept": "application/json",
}
if self.client and self.client._headers:
headers.update(self.client._headers)
data = aiohttp.FormData()
data.add_field("model", self.model_uid)
data.add_field(
"file", audio_bytes, filename="audio.wav", content_type="audio/wav"
)
async with self.client.session.post(
url, data=data, headers=headers, timeout=self.timeout
) as resp:
if resp.status == 200:
result = await resp.json()
text = result.get("text", "")
logger.debug(f"Xinference STT result: {text}")
return text
else:
error_text = await resp.text()
logger.error(
f"Xinference STT transcription failed with status {resp.status}: {error_text}"
)
return ""
except Exception as e:
logger.error(f"Xinference STT failed: {e}")
logger.debug(f"Xinference STT failed with exception: {e}", exc_info=True)
return ""
finally:
# 5. Cleanup
for temp_file in temp_files:
try:
if os.path.exists(temp_file):
os.remove(temp_file)
logger.debug(f"Removed temporary file: {temp_file}")
except Exception as e:
logger.error(f"Failed to remove temporary file {temp_file}: {e}")
async def terminate(self) -> None:
"""关闭客户端会话"""
if self.client:
logger.info("Closing Xinference STT client...")
try:
await self.client.close()
except Exception as e:
logger.error(f"Failed to close Xinference client: {e}", exc_info=True)
+16 -13
View File
@@ -1,5 +1,4 @@
from asyncio import Queue
from typing import List, Union
from astrbot.core.provider.provider import (
Provider,
@@ -11,7 +10,7 @@ from astrbot.core.provider.provider import (
from astrbot.core.provider.entities import ProviderType
from astrbot.core.db import BaseDatabase
from astrbot.core.config.astrbot_config import AstrBotConfig
from astrbot.core.provider.func_tool_manager import FunctionToolManager
from astrbot.core.provider.func_tool_manager import FunctionToolManager, FunctionTool
from astrbot.core.platform.astr_message_event import MessageSesion
from astrbot.core.message.message_event_result import MessageChain
from astrbot.core.provider.manager import ProviderManager
@@ -25,7 +24,8 @@ from .star import star_registry, StarMetadata, star_map
from .star_handler import star_handlers_registry, StarHandlerMetadata, EventType
from .filter.command import CommandFilter
from .filter.regex import RegexFilter
from typing import Awaitable, Any, Callable
from typing import Any
from collections.abc import Awaitable, Callable
from astrbot.core.conversation_mgr import ConversationManager
from astrbot.core.star.filter.platform_adapter_type import (
PlatformAdapterType,
@@ -42,7 +42,7 @@ class Context:
registered_web_apis: list = []
# back compatibility
_register_tasks: List[Awaitable] = []
_register_tasks: list[Awaitable] = []
_star_manager = None
def __init__(
@@ -78,7 +78,7 @@ class Context:
if star.name == star_name:
return star
def get_all_stars(self) -> List[StarMetadata]:
def get_all_stars(self) -> list[StarMetadata]:
"""获取当前载入的所有插件 Metadata 的列表"""
return star_registry
@@ -116,19 +116,19 @@ class Context:
prov = self.provider_manager.inst_map.get(provider_id)
return prov
def get_all_providers(self) -> List[Provider]:
def get_all_providers(self) -> list[Provider]:
"""获取所有用于文本生成任务的 LLM Provider(Chat_Completion 类型)。"""
return self.provider_manager.provider_insts
def get_all_tts_providers(self) -> List[TTSProvider]:
def get_all_tts_providers(self) -> list[TTSProvider]:
"""获取所有用于 TTS 任务的 Provider。"""
return self.provider_manager.tts_provider_insts
def get_all_stt_providers(self) -> List[STTProvider]:
def get_all_stt_providers(self) -> list[STTProvider]:
"""获取所有用于 STT 任务的 Provider。"""
return self.provider_manager.stt_provider_insts
def get_all_embedding_providers(self) -> List[EmbeddingProvider]:
def get_all_embedding_providers(self) -> list[EmbeddingProvider]:
"""获取所有用于 Embedding 任务的 Provider。"""
return self.provider_manager.embedding_provider_insts
@@ -196,9 +196,7 @@ class Context:
return self._event_queue
@deprecated(version="4.0.0", reason="Use get_platform_inst instead")
def get_platform(
self, platform_type: Union[PlatformAdapterType, str]
) -> Platform | None:
def get_platform(self, platform_type: PlatformAdapterType | str) -> Platform | None:
"""
获取指定类型的平台适配器。
@@ -231,7 +229,7 @@ class Context:
return platform
async def send_message(
self, session: Union[str, MessageSesion], message_chain: MessageChain
self, session: str | MessageSesion, message_chain: MessageChain
) -> bool:
"""
根据 session(unified_msg_origin) 主动发送消息。
@@ -258,6 +256,11 @@ class Context:
return True
return False
def add_llm_tools(self, *tools: FunctionTool) -> None:
"""添加 LLM 工具。"""
for tool in tools:
self.provider_manager.llm_tools.func_list.append(tool)
"""
以下的方法已经不推荐使用。请从 AstrBot 文档查看更好的注册方式。
"""
+7
View File
@@ -0,0 +1,7 @@
## What's Changed
1. 修复:第一次启动时不再错误地弹出迁移提醒
2. 新增:Xinference Rerank Provider, STT Provider
3. 新增: xAI Grok Live Search
4. 优化: 插件卡片左下角恢复 文档 按钮并新增 插件配置 按钮。
5. 优化: 更好地适配 Class 方式注册 LLM Tool。
@@ -202,13 +202,22 @@ const viewReadme = () => {
</v-chip>
</div>
<div class="mt-2" :class="{ 'text-caption': $vuetify.display.xs }" style="overflow-y: auto; height: 80px; font-size: 90%;">
<div class="mt-2" :class="{ 'text-caption': $vuetify.display.xs }" style="overflow-y: auto; height: 70px; font-size: 90%;">
{{ extension.desc }}
</div>
</div>
</div>
</v-card-text>
<v-card-actions class="extension-actions">
<v-btn color="primary" size="small" @click="viewReadme">
{{ tm('buttons.viewDocs') }}
</v-btn>
<v-btn v-if="!marketMode" color="primary" size="small" @click="configure">
{{ tm('card.actions.pluginConfig') }}
</v-btn>
</v-card-actions>
</v-card>
</template>
@@ -238,4 +247,9 @@ const viewReadme = () => {
margin-left: 8px;
}
}
.extension-actions {
margin-top: auto;
gap: 8px;
}
</style>
+3 -3
View File
@@ -740,7 +740,7 @@ onMounted(async () => {
<!-- 卡片视图 -->
<div v-else>
<v-row v-if="filteredPlugins.length === 0" class="text-center">
<v-col cols="12" class="pa-8">
<v-col cols="12" class="pa-2">
<v-icon size="64" color="info" class="mb-4">mdi-puzzle-outline</v-icon>
<div class="text-h5 mb-2">{{ tm('empty.noPlugins') }}</div>
<div class="text-body-1 mb-4">{{ tm('empty.noPluginsDesc') }}</div>
@@ -748,8 +748,8 @@ onMounted(async () => {
</v-row>
<v-row>
<v-col cols="12" md="6" lg="6" v-for="extension in filteredPlugins" :key="extension.name"
class="pb-4">
<v-col cols="12" md="6" lg="4" v-for="extension in filteredPlugins" :key="extension.name"
class="pb-2">
<ExtensionCard :extension="extension" class="rounded-lg" style="background-color: rgb(var(--v-theme-mcpCardBg));"
@configure="openExtensionConfig(extension.name)" @uninstall="uninstallExtension(extension.name)"
@update="updateExtension(extension.name)" @reload="reloadPlugin(extension.name)"
+5 -1
View File
@@ -159,7 +159,11 @@ class ProviderCommands:
message.set_result(
MessageEventResult().message("切换模型未知错误: " + str(e))
)
message.set_result(MessageEventResult().message("切换模型成功。"))
message.set_result(
MessageEventResult().message(
f"切换模型成功。当前提供商: [{prov.meta().id}] 当前模型: [{prov.get_model()}]"
)
)
else:
prov.set_model(idx_or_name)
message.set_result(
+2 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
version = "4.5.0"
version = "4.5.1"
description = "易上手的多平台 LLM 聊天机器人及开发框架"
readme = "README.md"
requires-python = ">=3.10"
@@ -55,6 +55,7 @@ dependencies = [
"rank-bm25>=0.2.2",
"jieba>=0.42.1",
"markitdown-no-magika[docx,xls,xlsx]>=0.1.2",
"xinference-client",
]
[project.scripts]
+52 -49
View File
@@ -1,51 +1,54 @@
aiohttp
pydantic~=2.10.3
psutil>=5.8.0
openai
anthropic
qq-botpy
aiocqhttp>=1.4.4
aiodocker>=0.24.0
aiohttp>=3.11.18
aiocqhttp>=1.4.4
aiodocker>=0.24.0
aiohttp>=3.11.18
aiosqlite>=0.21.0
anthropic>=0.51.0
apscheduler>=3.11.0
beautifulsoup4>=4.13.4
certifi>=2025.4.26
chardet~=5.1.0
Pillow
beautifulsoup4
mi-googlesearch-python
readability-lxml
quart
lxml_html_clean
colorlog
aiocqhttp
pyjwt
apscheduler
docstring_parser
aiodocker
silk-python
lark-oapi
ormsgpack
cryptography
dashscope
python-telegram-bot
wechatpy
dingtalk-stream
defusedxml
mcp
certifi
pip
telegramify-markdown
google-genai
click
filelock
watchfiles
websockets
faiss-cpu
aiosqlite
colorlog>=6.9.0
cryptography>=44.0.3
dashscope>=1.23.2
defusedxml>=0.7.1
deprecated>=1.2.18
dingtalk-stream>=0.22.1
docstring-parser>=0.16
faiss-cpu==1.10.0
filelock>=3.18.0
google-genai>=1.14.0
lark-oapi>=1.4.15
lxml-html-clean>=0.4.2
mcp>=1.8.0
openai>=1.78.0
ormsgpack>=1.9.1
pillow>=11.2.1
pip>=25.1.1
psutil>=5.8.0
py-cord>=2.6.1
slack-sdk
pydub
sqlmodel
deprecated
sqlalchemy[asyncio]
audioop-lts; python_version>='3.13'
pypdf
aiofiles
rank-bm25
jieba
markitdown-no-magika[docx,xls,xlsx]
pydantic~=2.10.3
pydub>=0.25.1
pyjwt>=2.10.1
python-telegram-bot>=22.0
qq-botpy>=1.2.1
quart>=0.20.0
readability-lxml>=0.8.4.1
silk-python>=0.2.6
slack-sdk>=3.35.0
sqlalchemy[asyncio]>=2.0.41
sqlmodel>=0.0.24
telegramify-markdown>=0.5.1
watchfiles>=1.0.5
websockets>=15.0.1
wechatpy>=1.8.18
audioop-lts ; python_full_version >= '3.13'
click>=8.2.1
pypdf>=6.1.1
aiofiles>=25.1.0
rank-bm25>=0.2.2
jieba>=0.42.1
markitdown-no-magika[docx,xls,xlsx]>=0.1.2
xinference-client