mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
fix: add embedding dimensions send modes (#9245)
* fix: make embedding dimensions request optional * fix: add embedding dimensions send modes * fix: keep siliconflow qwen dimensions in auto mode * fix: harden embedding dimensions auto mode
This commit is contained in:
@@ -1839,6 +1839,7 @@ CONFIG_METADATA_2 = {
|
||||
"embedding_api_base": "",
|
||||
"embedding_model": "",
|
||||
"embedding_dimensions": 1024,
|
||||
"embedding_dimensions_mode": "auto",
|
||||
"timeout": 20,
|
||||
"proxy": "",
|
||||
},
|
||||
@@ -2246,6 +2247,12 @@ CONFIG_METADATA_2 = {
|
||||
"hint": "嵌入向量的维度。根据模型不同,可能需要调整,请参考具体模型的文档。此配置项请务必填写正确,否则将导致向量数据库无法正常工作。",
|
||||
"_special": "get_embedding_dim",
|
||||
},
|
||||
"embedding_dimensions_mode": {
|
||||
"description": "嵌入维度参数发送模式",
|
||||
"type": "string",
|
||||
"options": ["auto", "always", "never"],
|
||||
"hint": "控制是否在 OpenAI 兼容 Embedding 请求中发送 dimensions 参数。auto 会仅对官方 OpenAI embedding-3 模型自动发送;第三方兼容 API 如需该参数可改为 always,报错时改为 never。",
|
||||
},
|
||||
"embedding_model": {
|
||||
"description": "嵌入模型",
|
||||
"type": "string",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
from openai import AsyncOpenAI
|
||||
@@ -66,32 +67,47 @@ class OpenAIEmbeddingProvider(EmbeddingProvider):
|
||||
return [item.embedding for item in embeddings.data]
|
||||
|
||||
def _embedding_kwargs(self) -> dict:
|
||||
"""构建嵌入请求的可选参数"""
|
||||
"""Build optional embedding request parameters."""
|
||||
kwargs = {}
|
||||
if "embedding_dimensions" in self.provider_config:
|
||||
dimensions_mode = self.provider_config.get("embedding_dimensions_mode", "auto")
|
||||
if dimensions_mode not in {"auto", "always", "never"}:
|
||||
logger.warning(
|
||||
f"Unknown embedding_dimensions_mode in embedding configs: '{dimensions_mode}', fallback to 'auto'."
|
||||
)
|
||||
dimensions_mode = "auto"
|
||||
send_dimensions = dimensions_mode == "always"
|
||||
if dimensions_mode == "auto":
|
||||
api_base = _normalize_api_base(
|
||||
self.provider_config.get(
|
||||
"embedding_api_base", "https://api.openai.com/v1"
|
||||
)
|
||||
or "https://api.openai.com/v1"
|
||||
)
|
||||
parsed_api_base = urlparse(api_base)
|
||||
model = (
|
||||
getattr(self, "model", None)
|
||||
or self.provider_config.get("embedding_model")
|
||||
or "text-embedding-3-small"
|
||||
)
|
||||
model_lower = str(model).lower()
|
||||
model_name = model_lower.rsplit("/", 1)[-1]
|
||||
send_dimensions = (
|
||||
parsed_api_base.scheme == "https"
|
||||
and parsed_api_base.hostname == "api.openai.com"
|
||||
and parsed_api_base.path.rstrip("/") == "/v1"
|
||||
and model_name.startswith("text-embedding-3")
|
||||
) or (
|
||||
parsed_api_base.scheme == "https"
|
||||
and parsed_api_base.hostname == "api.siliconflow.cn"
|
||||
and model_name.startswith("qwen")
|
||||
)
|
||||
if send_dimensions and "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."
|
||||
)
|
||||
|
||||
# Fix: SiliconFlow provider does not support dimensions parameter, except for Qwen models.
|
||||
provider_api_base = self.provider_config.get("embedding_api_base")
|
||||
provider_id = self.provider_config.get("id", "unknown_id")
|
||||
if (
|
||||
provider_api_base
|
||||
# Hard-code SiliconFlow API Base Prefix and Model Name, as it's just a temporary workaround.
|
||||
and provider_api_base.strip().startswith("https://api.siliconflow.cn")
|
||||
and not self.model.lower().startswith("qwen")
|
||||
):
|
||||
# For SiliconFlow and Non-Qwen models, dimensions parameter is not supported. so remove it.
|
||||
removed_dimensions = kwargs.pop("dimensions", None)
|
||||
if removed_dimensions is not None:
|
||||
# Log a warning message if dimensions parameter is removed.
|
||||
logger.warning(
|
||||
f"dimensions not supported for model '{self.model}' of provider '{provider_id}' as SiliconFlow does not support this parameter for non-Qwen models: '{removed_dimensions}'."
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_dim(self) -> int:
|
||||
|
||||
@@ -1393,6 +1393,10 @@
|
||||
"description": "Embedding dimensions",
|
||||
"hint": "Embedding vector dimensions. May need adjustment per model; see model documentation. This must be correct or the vector database will not work."
|
||||
},
|
||||
"embedding_dimensions_mode": {
|
||||
"description": "Embedding dimensions mode",
|
||||
"hint": "Controls whether to send the dimensions parameter in OpenAI-compatible embedding requests. auto sends it only for official OpenAI embedding-3 models; use always when a compatible API supports it, or never when it rejects the parameter."
|
||||
},
|
||||
"embedding_model": {
|
||||
"description": "Embedding model",
|
||||
"hint": "Embedding model name."
|
||||
|
||||
@@ -1390,6 +1390,10 @@
|
||||
"description": "Размерность эмбеддингов",
|
||||
"hint": "Размерность векторов эмбеддингов. Зависит от модели. Должно быть указано верно для работы векторной базы данных."
|
||||
},
|
||||
"embedding_dimensions_mode": {
|
||||
"description": "Режим параметра dimensions",
|
||||
"hint": "Управляет отправкой параметра dimensions в OpenAI-совместимых запросах Embedding. auto отправляет его только для официальных моделей OpenAI embedding-3; используйте always, если совместимый API поддерживает параметр, или never, если он отклоняет параметр."
|
||||
},
|
||||
"embedding_model": {
|
||||
"description": "Модель эмбеддингов",
|
||||
"hint": "Имя модели эмбеддингов."
|
||||
|
||||
@@ -1395,6 +1395,10 @@
|
||||
"description": "嵌入维度",
|
||||
"hint": "嵌入向量的维度。根据模型不同,可能需要调整,请参考具体模型的文档。此配置项请务必填写正确,否则将导致向量数据库无法正常工作。"
|
||||
},
|
||||
"embedding_dimensions_mode": {
|
||||
"description": "嵌入维度参数发送模式",
|
||||
"hint": "控制是否在 OpenAI 兼容 Embedding 请求中发送 dimensions 参数。auto 会仅对官方 OpenAI embedding-3 模型自动发送;第三方兼容 API 如需该参数可改为 always,报错时改为 never。"
|
||||
},
|
||||
"embedding_model": {
|
||||
"description": "嵌入模型",
|
||||
"hint": "嵌入模型名称。"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from astrbot.core.provider.sources.openai_embedding_source import _normalize_api_base
|
||||
from astrbot.core.provider.sources.openai_embedding_source import (
|
||||
OpenAIEmbeddingProvider,
|
||||
_normalize_api_base,
|
||||
)
|
||||
|
||||
|
||||
def test_openai_embedding_api_base_keeps_version_suffixes():
|
||||
@@ -16,3 +19,121 @@ def test_openai_embedding_api_base_adds_default_version():
|
||||
assert _normalize_api_base("https://example.test/v1/embeddings") == (
|
||||
"https://example.test/v1"
|
||||
)
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_auto_sends_for_official_openai_embedding_3():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {"embedding_dimensions": 1024}
|
||||
provider.model = "text-embedding-3-small"
|
||||
|
||||
assert provider.get_dim() == 1024
|
||||
assert provider._embedding_kwargs() == {"dimensions": 1024}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_invalid_mode_falls_back_to_auto():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {
|
||||
"embedding_dimensions": 1024,
|
||||
"embedding_dimensions_mode": "foo",
|
||||
}
|
||||
provider.model = "text-embedding-3-small"
|
||||
|
||||
assert provider.get_dim() == 1024
|
||||
assert provider._embedding_kwargs() == {"dimensions": 1024}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_auto_skips_for_official_openai_non_3_model():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {
|
||||
"embedding_api_base": "https://api.openai.com/v1",
|
||||
"embedding_dimensions": 1024,
|
||||
"embedding_dimensions_mode": "auto",
|
||||
}
|
||||
provider.model = "text-embedding-ada-002"
|
||||
|
||||
assert provider._embedding_kwargs() == {}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_auto_skips_custom_api_base():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {
|
||||
"embedding_api_base": "https://api.siliconflow.cn/v1",
|
||||
"embedding_dimensions": 1024,
|
||||
"embedding_dimensions_mode": "auto",
|
||||
}
|
||||
provider.model = "BAAI/bge-m3"
|
||||
|
||||
assert provider._embedding_kwargs() == {}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_auto_sends_for_siliconflow_qwen():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {
|
||||
"embedding_api_base": "https://api.siliconflow.cn/v1",
|
||||
"embedding_dimensions": 1024,
|
||||
"embedding_dimensions_mode": "auto",
|
||||
}
|
||||
provider.model = "Qwen/Qwen3-Embedding-4B"
|
||||
|
||||
assert provider._embedding_kwargs() == {"dimensions": 1024}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_auto_skips_siliconflow_lookalike_host():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {
|
||||
"embedding_api_base": "https://api.siliconflow.cn.evil.test/v1",
|
||||
"embedding_dimensions": 1024,
|
||||
"embedding_dimensions_mode": "auto",
|
||||
}
|
||||
provider.model = "Qwen/Qwen3-Embedding-4B"
|
||||
|
||||
assert provider._embedding_kwargs() == {}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_auto_handles_empty_model():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {"embedding_dimensions": 1024}
|
||||
provider.model = None
|
||||
|
||||
assert provider._embedding_kwargs() == {"dimensions": 1024}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_are_sent_when_mode_is_always():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {
|
||||
"embedding_dimensions": 1024,
|
||||
"embedding_dimensions_mode": "always",
|
||||
}
|
||||
|
||||
assert provider.get_dim() == 1024
|
||||
assert provider._embedding_kwargs() == {"dimensions": 1024}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_always_mode_without_dimensions_sends_nothing():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {"embedding_dimensions_mode": "always"}
|
||||
|
||||
assert provider._embedding_kwargs() == {}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_invalid_value_is_ignored():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {
|
||||
"embedding_dimensions": "not-a-number",
|
||||
"embedding_dimensions_mode": "always",
|
||||
}
|
||||
|
||||
assert provider.get_dim() == 0
|
||||
assert provider._embedding_kwargs() == {}
|
||||
|
||||
|
||||
def test_openai_embedding_dimensions_are_local_when_mode_is_never():
|
||||
provider = OpenAIEmbeddingProvider.__new__(OpenAIEmbeddingProvider)
|
||||
provider.provider_config = {
|
||||
"embedding_dimensions": 1024,
|
||||
"embedding_dimensions_mode": "never",
|
||||
}
|
||||
provider.model = "text-embedding-3-small"
|
||||
|
||||
assert provider.get_dim() == 1024
|
||||
assert provider._embedding_kwargs() == {}
|
||||
|
||||
Reference in New Issue
Block a user