From 59a79a9c29aadebc313bf1355d5333a3081af9f6 Mon Sep 17 00:00:00 2001 From: LLsetnow <70004500@qq.com> Date: Sun, 24 May 2026 21:05:26 +0800 Subject: [PATCH 1/2] feat(provider): add DashScope text-embedding-v4 embedding provider - Create dashscope_embedding_source.py with OpenAI-compatible AsyncOpenAI client - Register the provider in manager.py dynamic_import_provider() - Add default config template with text-embedding-v4 model in default.py - Add i18n hint entries for en-US, zh-CN, ru-RU Closes #8067 Co-Authored-By: Claude Opus 4.7 --- astrbot/core/config/default.py | 14 +++ astrbot/core/provider/manager.py | 4 + .../sources/dashscope_embedding_source.py | 88 +++++++++++++++++++ .../en-US/features/config-metadata.json | 3 + .../ru-RU/features/config-metadata.json | 3 + .../zh-CN/features/config-metadata.json | 3 + 6 files changed, 115 insertions(+) create mode 100644 astrbot/core/provider/sources/dashscope_embedding_source.py diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index dec98692bc..6058b06884 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -1825,6 +1825,20 @@ "timeout": 60, "proxy": "", }, + "阿里云百炼 Embedding": { + "id": "dashscope_embedding", + "type": "dashscope_embedding", + "provider": "dashscope", + "provider_type": "embedding", + "hint": "provider_group.provider.dashscope_embedding.hint", + "enable": True, + "embedding_api_key": "", + "embedding_api_base": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "embedding_model": "text-embedding-v4", + "embedding_dimensions": 1024, + "timeout": 20, + "proxy": "", + }, "vLLM Rerank": { "id": "vllm_rerank", "type": "vllm_rerank", diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index 5aa452bbd5..7bff69b183 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -477,6 +477,10 @@ def dynamic_import_provider(self, type: str) -> None: from .sources.ollama_embedding_source import ( OllamaEmbeddingProvider as OllamaEmbeddingProvider, ) + case "dashscope_embedding": + from .sources.dashscope_embedding_source import ( + DashscopeEmbeddingProvider as DashscopeEmbeddingProvider, + ) case "vllm_rerank": from .sources.vllm_rerank_source import ( VLLMRerankProvider as VLLMRerankProvider, diff --git a/astrbot/core/provider/sources/dashscope_embedding_source.py b/astrbot/core/provider/sources/dashscope_embedding_source.py new file mode 100644 index 0000000000..a19a7d3260 --- /dev/null +++ b/astrbot/core/provider/sources/dashscope_embedding_source.py @@ -0,0 +1,88 @@ +import httpx +from openai import AsyncOpenAI + +from astrbot import logger + +from ..entities import ProviderType +from ..provider import EmbeddingProvider +from ..register import register_provider_adapter + + +@register_provider_adapter( + "dashscope_embedding", + "DashScope text-embedding-v4 Embedding Provider", + provider_type=ProviderType.EMBEDDING, +) +class DashscopeEmbeddingProvider(EmbeddingProvider): + 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 + proxy = provider_config.get("proxy", "") + provider_id = provider_config.get("id", "unknown_id") + http_client = None + if proxy: + logger.info(f"[DashScope Embedding] {provider_id} Using proxy: {proxy}") + http_client = httpx.AsyncClient(proxy=proxy) + api_base = ( + provider_config.get( + "embedding_api_base", + "https://dashscope.aliyuncs.com/compatible-mode/v1", + ) + .strip() + .removesuffix("/") + .removesuffix("/embeddings") + ) + if api_base and not api_base.endswith("/v1") and not api_base.endswith("/v4"): + api_base = api_base + "/v1" + logger.info(f"[DashScope Embedding] {provider_id} Using API Base: {api_base}") + self.client = AsyncOpenAI( + api_key=provider_config.get("embedding_api_key"), + base_url=api_base, + timeout=int(provider_config.get("timeout", 20)), + http_client=http_client, + ) + self.model = provider_config.get("embedding_model", "text-embedding-v4") + + async def get_embedding(self, text: str) -> list[float]: + kwargs = self._embedding_kwargs() + embedding = await self.client.embeddings.create( + input=text, + model=self.model, + **kwargs, + ) + return embedding.data[0].embedding + + async def get_embeddings(self, text: list[str]) -> list[list[float]]: + kwargs = self._embedding_kwargs() + embeddings = await self.client.embeddings.create( + input=text, + model=self.model, + **kwargs, + ) + return [item.embedding for item in embeddings.data] + + def _embedding_kwargs(self) -> dict: + kwargs = {} + if "embedding_dimensions" in self.provider_config: + try: + kwargs["dimensions"] = int(self.provider_config["embedding_dimensions"]) + except (ValueError, TypeError): + logger.warning( + f"embedding_dimensions in embedding configs is not a valid integer: '{self.provider_config['embedding_dimensions']}', ignored." + ) + return kwargs + + def get_dim(self) -> int: + if "embedding_dimensions" in self.provider_config: + try: + return int(self.provider_config["embedding_dimensions"]) + except (ValueError, TypeError): + logger.warning( + f"embedding_dimensions in embedding configs is not a valid integer: '{self.provider_config['embedding_dimensions']}', ignored." + ) + return 0 + + async def terminate(self): + if self.client: + await self.client.close() diff --git a/dashboard/src/i18n/locales/en-US/features/config-metadata.json b/dashboard/src/i18n/locales/en-US/features/config-metadata.json index 6363b71e31..d642b0727a 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -1321,6 +1321,9 @@ "gemini_embedding": { "hint": "Gemini Embedding does not require manually adding /v1beta." }, + "dashscope_embedding": { + "hint": "text-embedding-v4 supports multiple dimensions: 64, 128, 256, 512, 768, 1024 (default), 1536, 2048. API base defaults to DashScope compatible mode." + }, "volcengine_cluster": { "description": "Volcengine cluster", "hint": "For voice cloning models, choose volcano_icl or volcano_icl_concurr; default is volcano_tts." diff --git a/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json index 028bff8675..9d14a35e1b 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json +++ b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json @@ -1318,6 +1318,9 @@ "gemini_embedding": { "hint": "Gemini Embedding не требует ручного добавления /v1beta." }, + "dashscope_embedding": { + "hint": "text-embedding-v4 поддерживает несколько размеров: 64, 128, 256, 512, 768, 1024 (по умолчанию), 1536, 2048. API base по умолчанию использует совместимый режим DashScope." + }, "volcengine_cluster": { "description": "Кластер Volcengine", "hint": "Для моделей клонирования голоса выберите volcano_icl или volcano_icl_concurr; по умолчанию volcano_tts." diff --git a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json index 70f4fa5c79..eb5574f3f4 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -1323,6 +1323,9 @@ "gemini_embedding": { "hint": "Gemini Embedding 无需手动添加 /v1beta。" }, + "dashscope_embedding": { + "hint": "text-embedding-v4 支持多种维度: 64、128、256、512、768、1024(默认)、1536、2048。API Base 默认使用 DashScope 兼容模式。" + }, "volcengine_cluster": { "description": "火山引擎集群", "hint": "若使用语音复刻大模型,可选volcano_icl或volcano_icl_concurr,默认使用volcano_tts" From 54bfa45f309152c514f54bf35b0cc935aac3ef05 Mon Sep 17 00:00:00 2001 From: LLsetnow <70004500@qq.com> Date: Sun, 24 May 2026 21:29:12 +0800 Subject: [PATCH 2/2] fix(provider): address review comments for DashScope embedding provider - Remove redundant self.provider_config / self.provider_settings assignments - Add empty response guard in get_embedding() - Change get_dim() default from 0 to 1024 for text-embedding-v4 Co-Authored-By: Claude Opus 4.7 --- astrbot/core/provider/sources/dashscope_embedding_source.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/astrbot/core/provider/sources/dashscope_embedding_source.py b/astrbot/core/provider/sources/dashscope_embedding_source.py index a19a7d3260..9c03b19da2 100644 --- a/astrbot/core/provider/sources/dashscope_embedding_source.py +++ b/astrbot/core/provider/sources/dashscope_embedding_source.py @@ -16,8 +16,6 @@ class DashscopeEmbeddingProvider(EmbeddingProvider): 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 proxy = provider_config.get("proxy", "") provider_id = provider_config.get("id", "unknown_id") http_client = None @@ -51,6 +49,8 @@ async def get_embedding(self, text: str) -> list[float]: model=self.model, **kwargs, ) + if not embedding.data: + raise Exception("DashScope API returned no embedding data.") return embedding.data[0].embedding async def get_embeddings(self, text: list[str]) -> list[list[float]]: @@ -81,7 +81,7 @@ def get_dim(self) -> int: logger.warning( f"embedding_dimensions in embedding configs is not a valid integer: '{self.provider_config['embedding_dimensions']}', ignored." ) - return 0 + return 1024 async def terminate(self): if self.client: