From b13bf369c40383020d41057265fc73c0b454eb1f Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 15:06:45 +0800 Subject: [PATCH 1/9] =?UTF-8?q?fix(dashboard):=20=E5=BC=BA=E5=8C=96=20API?= =?UTF-8?q?=20Key=20=E5=A4=8D=E5=88=B6=E4=B8=B4=E6=97=B6=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=B8=85=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/views/Settings.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dashboard/src/views/Settings.vue b/dashboard/src/views/Settings.vue index 5260c4eedd..8ec447dac2 100644 --- a/dashboard/src/views/Settings.vue +++ b/dashboard/src/views/Settings.vue @@ -336,7 +336,7 @@ const loadApiKeys = async () => { const tryExecCommandCopy = (text) => { let textArea = null; try { - if (typeof document === 'undefined') return false; + if (typeof document === 'undefined' || !document.body) return false; textArea = document.createElement('textarea'); textArea.value = text; textArea.setAttribute('readonly', ''); @@ -353,7 +353,9 @@ const tryExecCommandCopy = (text) => { return false; } finally { try { - textArea?.remove?.(); + if (textArea?.parentNode) { + textArea.parentNode.removeChild(textArea); + } } catch (_) { // ignore cleanup errors } From 64ae12013c13387b1d3ea76dccadaf9a2053208c Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 15:30:25 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix(embedding):=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E6=94=B9=E4=B8=BA=E6=8E=A2=E6=B5=8B=20OpenAI?= =?UTF-8?q?=20embedding=20=E6=9C=80=E5=A4=A7=E5=8F=AF=E7=94=A8=E7=BB=B4?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/provider.py | 4 ++ .../sources/gemini_embedding_source.py | 13 +++++ .../sources/openai_embedding_source.py | 58 +++++++++++++++++++ astrbot/dashboard/routes/config.py | 15 ++++- 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/astrbot/core/provider/provider.py b/astrbot/core/provider/provider.py index 901efd0052..2a56dff770 100644 --- a/astrbot/core/provider/provider.py +++ b/astrbot/core/provider/provider.py @@ -305,6 +305,10 @@ def get_dim(self) -> int: """获取向量的维度""" ... + async def detect_dim(self) -> int: + """探测模型原生向量维度(默认实现)""" + return len(await self.get_embedding("astrbot")) + async def test(self) -> None: await self.get_embedding("astrbot") diff --git a/astrbot/core/provider/sources/gemini_embedding_source.py b/astrbot/core/provider/sources/gemini_embedding_source.py index 61ba9cadbe..4dc34399b1 100644 --- a/astrbot/core/provider/sources/gemini_embedding_source.py +++ b/astrbot/core/provider/sources/gemini_embedding_source.py @@ -78,6 +78,19 @@ async def get_embeddings(self, text: list[str]) -> list[list[float]]: except APIError as e: raise Exception(f"Gemini Embedding API批量请求失败: {e.message}") + async def detect_dim(self) -> int: + """探测模型原生向量维度(不传 output_dimensionality)""" + try: + result = await self.client.models.embed_content( + model=self.model, + contents="echo", + ) + assert result.embeddings is not None + assert result.embeddings[0].values is not None + return len(result.embeddings[0].values) + except APIError as e: + raise Exception(f"Gemini Embedding 维度探测失败: {e.message}") + def get_dim(self) -> int: """获取向量的维度""" return int(self.provider_config.get("embedding_dimensions", 768)) diff --git a/astrbot/core/provider/sources/openai_embedding_source.py b/astrbot/core/provider/sources/openai_embedding_source.py index b686a6ee6b..d02e5559a7 100644 --- a/astrbot/core/provider/sources/openai_embedding_source.py +++ b/astrbot/core/provider/sources/openai_embedding_source.py @@ -52,6 +52,64 @@ async def get_embeddings(self, text: list[str]) -> list[list[float]]: ) return [item.embedding for item in embeddings.data] + async def detect_dim(self) -> int: + """探测模型可用的最大向量维度""" + + async def _request_dim(dimensions: int | None) -> int: + kwargs = { + "input": "echo", + "model": self.model, + } + if dimensions is not None: + kwargs["dimensions"] = dimensions + embedding = await self.client.embeddings.create(**kwargs) + return len(embedding.data[0].embedding) + + # 1) 默认调用,获取当前默认维度 + base_dim = await _request_dim(None) + + # 2) 先判断 dimensions 参数是否可调 + probe_dim = base_dim + 1 + try: + probe_result = await _request_dim(probe_dim) + if probe_result != probe_dim: + return base_dim + except Exception: + return base_dim + + # 3) 可调时探测上界:指数扩张 + 二分 + max_cap = 32768 + low = probe_dim + high = max(base_dim * 2, probe_dim + 1) + if high > max_cap: + high = max_cap + + while high < max_cap: + try: + result_dim = await _request_dim(high) + if result_dim != high: + break + low = high + high = min(high * 2, max_cap) + except Exception: + break + + left = low + 1 + right = high - 1 + while left <= right: + mid = (left + right) // 2 + try: + result_dim = await _request_dim(mid) + if result_dim == mid: + low = mid + left = mid + 1 + else: + right = mid - 1 + except Exception: + right = mid - 1 + + return low + def get_dim(self) -> int: """获取向量的维度""" return int(self.provider_config.get("embedding_dimensions", 1024)) diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index 6d60fb6de0..ccc77a1622 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -754,6 +754,16 @@ async def get_embedding_dim(self): if not provider_type: return Response().error("provider_config 缺少 type 字段").__dict__ + # 首次添加某类提供商时,provider_cls_map 可能尚未注册该适配器 + if provider_type not in provider_cls_map: + try: + self.core_lifecycle.provider_manager.dynamic_import_provider( + provider_type, + ) + except ImportError as e: + logger.error(traceback.format_exc()) + return Response().error(f"动态导入提供商适配器失败: {e!s}").__dict__ + # 获取对应的 provider 类 if provider_type not in provider_cls_map: return ( @@ -779,9 +789,8 @@ async def get_embedding_dim(self): if inspect.iscoroutinefunction(init_fn): await init_fn() - # 获取嵌入向量维度 - vec = await inst.get_embedding("echo") - dim = len(vec) + # 探测嵌入向量维度(优先使用 provider 的原生探测逻辑) + dim = await inst.detect_dim() logger.info( f"检测到 {provider_config.get('id', 'unknown')} 的嵌入向量维度为 {dim}", From 5b31476808bbd30b7ed9368a3a5f09add9f6d5ac Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 15:53:09 +0800 Subject: [PATCH 3/9] fix: normalize openai embedding base url and add hint key --- astrbot/core/config/default.py | 1 + .../core/provider/sources/openai_embedding_source.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index c03027f3b2..0a74849d9c 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -1771,6 +1771,7 @@ class ChatProviderTemplate(TypedDict): "embedding_api_base": { "description": "API Base URL", "type": "string", + "hint": "provider_group.provider.embedding_api_base.hint", }, "volcengine_cluster": { "type": "string", diff --git a/astrbot/core/provider/sources/openai_embedding_source.py b/astrbot/core/provider/sources/openai_embedding_source.py index d02e5559a7..1992350d63 100644 --- a/astrbot/core/provider/sources/openai_embedding_source.py +++ b/astrbot/core/provider/sources/openai_embedding_source.py @@ -23,12 +23,16 @@ def __init__(self, provider_config: dict, provider_settings: dict) -> None: if proxy: logger.info(f"[OpenAI Embedding] 使用代理: {proxy}") http_client = httpx.AsyncClient(proxy=proxy) + api_base = provider_config.get("embedding_api_base", "").strip() + if not api_base: + api_base = "https://api.openai.com/v1" + else: + api_base = api_base.removesuffix("/") + if not api_base.endswith("/v1"): + api_base = f"{api_base}/v1" self.client = AsyncOpenAI( api_key=provider_config.get("embedding_api_key"), - base_url=provider_config.get( - "embedding_api_base", - "https://api.openai.com/v1", - ), + base_url=api_base, timeout=int(provider_config.get("timeout", 20)), http_client=http_client, ) From fd3c3377359b9acb8634abfa945634569e3d14e4 Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 15:53:18 +0800 Subject: [PATCH 4/9] i18n: add embedding_api_base hint translations --- dashboard/src/i18n/locales/en-US/features/config-metadata.json | 3 ++- dashboard/src/i18n/locales/zh-CN/features/config-metadata.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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 9665e893a2..4db147e555 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -1084,7 +1084,8 @@ "description": "API Key" }, "embedding_api_base": { - "description": "API Base URL" + "description": "API Base URL", + "hint": "OpenAI Embedding automatically appends /v1 at request time. For Gemini Embedding, use https://generativelanguage.googleapis.com and do not add /v1beta manually." }, "volcengine_cluster": { "description": "Volcengine cluster", 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 de7e81bcd2..e9edf463de 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -1087,7 +1087,8 @@ "description": "API Key" }, "embedding_api_base": { - "description": "API Base URL" + "description": "API Base URL", + "hint": "OpenAI Embedding 会在请求时自动补上 /v1。Gemini Embedding 建议填写 https://generativelanguage.googleapis.com,无需手动添加 /v1beta。" }, "volcengine_cluster": { "description": "火山引擎集群", From 0e5946a1e54fed07fe7803d82c435454c0122992 Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 16:02:17 +0800 Subject: [PATCH 5/9] i18n: localize provider embedding/proxy metadata hints --- astrbot/core/config/default.py | 4 ++-- .../src/i18n/locales/en-US/features/config-metadata.json | 4 ++++ .../src/i18n/locales/zh-CN/features/config-metadata.json | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 0a74849d9c..da57858597 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -2193,9 +2193,9 @@ class ChatProviderTemplate(TypedDict): "type": "string", }, "proxy": { - "description": "代理地址", + "description": "provider_group.provider.proxy.description", "type": "string", - "hint": "HTTP/HTTPS 代理地址,格式如 http://127.0.0.1:7890。仅对该提供商的 API 请求生效,不影响 Docker 内网通信。", + "hint": "provider_group.provider.proxy.hint", }, "model": { "description": "模型 ID", 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 4db147e555..2fd6723d80 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -1314,6 +1314,10 @@ "api_base": { "description": "API Base URL" }, + "proxy": { + "description": "Proxy address", + "hint": "HTTP/HTTPS proxy URL, e.g. http://127.0.0.1:7890. Applies only to this provider's API requests and does not affect Docker internal networking." + }, "model": { "description": "Model ID", "hint": "Model name, e.g., gpt-4o-mini, deepseek-chat." 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 e9edf463de..80d4ce8eb2 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -1317,6 +1317,10 @@ "api_base": { "description": "API Base URL" }, + "proxy": { + "description": "代理地址", + "hint": "HTTP/HTTPS 代理地址,格式如 http://127.0.0.1:7890。仅对该提供商的 API 请求生效,不影响 Docker 内网通信。" + }, "model": { "description": "模型 ID", "hint": "模型名称,如 gpt-4o-mini, deepseek-chat。" From 4763cb52a1f234fe76befd90a624634e5a0d83b3 Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 16:29:13 +0800 Subject: [PATCH 6/9] fix: show provider-specific embedding API Base URL hint as field subtitle --- astrbot/core/config/default.py | 3 +- .../src/components/shared/AstrBotConfig.vue | 42 +++++++++++++++++-- .../en-US/features/config-metadata.json | 9 +++- .../zh-CN/features/config-metadata.json | 9 +++- 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index da57858597..4930bc9caa 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -1463,6 +1463,7 @@ class ChatProviderTemplate(TypedDict): "type": "openai_embedding", "provider": "openai", "provider_type": "embedding", + "hint": "provider_group.provider.openai_embedding.hint", "enable": True, "embedding_api_key": "", "embedding_api_base": "", @@ -1476,6 +1477,7 @@ class ChatProviderTemplate(TypedDict): "type": "gemini_embedding", "provider": "google", "provider_type": "embedding", + "hint": "provider_group.provider.gemini_embedding.hint", "enable": True, "embedding_api_key": "", "embedding_api_base": "", @@ -1771,7 +1773,6 @@ class ChatProviderTemplate(TypedDict): "embedding_api_base": { "description": "API Base URL", "type": "string", - "hint": "provider_group.provider.embedding_api_base.hint", }, "volcengine_cluster": { "type": "string", diff --git a/dashboard/src/components/shared/AstrBotConfig.vue b/dashboard/src/components/shared/AstrBotConfig.vue index 12cb0bee1c..bc1c86bdfc 100644 --- a/dashboard/src/components/shared/AstrBotConfig.vue +++ b/dashboard/src/components/shared/AstrBotConfig.vue @@ -48,6 +48,40 @@ const filteredIterable = computed(() => { return rest }) +const providerHint = computed(() => { + const hint = props.iterable?.hint + if (typeof hint !== 'string' || !hint) return '' + + if ( + hint === 'provider_group.provider.openai_embedding.hint' + || hint === 'provider_group.provider.gemini_embedding.hint' + ) { + return '' + } + + return hint +}) + +const getItemHint = (itemKey, itemMeta) => { + if (itemMeta?.hint) return itemMeta.hint + + if (itemKey !== 'embedding_api_base') return '' + + const providerType = props.iterable?.type + if (providerType === 'openai_embedding') { + return getRaw('provider_group.provider.openai_embedding.hint') + ? 'provider_group.provider.openai_embedding.hint' + : '' + } + if (providerType === 'gemini_embedding') { + return getRaw('provider_group.provider.gemini_embedding.hint') + ? 'provider_group.provider.gemini_embedding.hint' + : '' + } + + return '' +} + const dialog = ref(false) const currentEditingKey = ref('') const currentEditingLanguage = ref('json') @@ -153,14 +187,14 @@ function hasVisibleItemsAfter(items, currentIndex) {
- {{ iterable.hint }} + {{ translateIfKey(providerHint) }}
@@ -218,9 +252,9 @@ function hasVisibleItemsAfter(items, currentIndex) { - ‼️ - {{ translateIfKey(metadata[metadataKey].items[key]?.hint) }} + {{ translateIfKey(getItemHint(key, metadata[metadataKey].items[key])) }} 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 2fd6723d80..cad25e835c 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -1084,8 +1084,13 @@ "description": "API Key" }, "embedding_api_base": { - "description": "API Base URL", - "hint": "OpenAI Embedding automatically appends /v1 at request time. For Gemini Embedding, use https://generativelanguage.googleapis.com and do not add /v1beta manually." + "description": "API Base URL" + }, + "openai_embedding": { + "hint": "OpenAI Embedding automatically appends /v1 at request time." + }, + "gemini_embedding": { + "hint": "Gemini Embedding does not require manually adding /v1beta." }, "volcengine_cluster": { "description": "Volcengine cluster", 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 80d4ce8eb2..e5eea63fd0 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -1087,8 +1087,13 @@ "description": "API Key" }, "embedding_api_base": { - "description": "API Base URL", - "hint": "OpenAI Embedding 会在请求时自动补上 /v1。Gemini Embedding 建议填写 https://generativelanguage.googleapis.com,无需手动添加 /v1beta。" + "description": "API Base URL" + }, + "openai_embedding": { + "hint": "OpenAI Embedding 会在请求时自动补上 /v1。" + }, + "gemini_embedding": { + "hint": "Gemini Embedding 无需手动添加 /v1beta。" }, "volcengine_cluster": { "description": "火山引擎集群", From e28a75fa3fd4138b44bc844234f3f6dea9a82699 Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 16:51:23 +0800 Subject: [PATCH 7/9] fix(embedding): cap OpenAI detect_dim probes with early short-circuit --- .../sources/openai_embedding_source.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/astrbot/core/provider/sources/openai_embedding_source.py b/astrbot/core/provider/sources/openai_embedding_source.py index 1992350d63..dd3679d387 100644 --- a/astrbot/core/provider/sources/openai_embedding_source.py +++ b/astrbot/core/provider/sources/openai_embedding_source.py @@ -59,7 +59,17 @@ async def get_embeddings(self, text: list[str]) -> list[list[float]]: async def detect_dim(self) -> int: """探测模型可用的最大向量维度""" + class _DetectBudgetExceeded(Exception): + pass + + max_probe_calls = 12 + probe_calls = 0 + async def _request_dim(dimensions: int | None) -> int: + nonlocal probe_calls + if probe_calls >= max_probe_calls: + raise _DetectBudgetExceeded + probe_calls += 1 kwargs = { "input": "echo", "model": self.model, @@ -78,6 +88,12 @@ async def _request_dim(dimensions: int | None) -> int: probe_result = await _request_dim(probe_dim) if probe_result != probe_dim: return base_dim + except _DetectBudgetExceeded: + logger.warning( + f"[OpenAI Embedding] 维度探测达到调用上限({max_probe_calls})," + f"返回当前已确认维度 {base_dim}", + ) + return base_dim except Exception: return base_dim @@ -95,6 +111,12 @@ async def _request_dim(dimensions: int | None) -> int: break low = high high = min(high * 2, max_cap) + except _DetectBudgetExceeded: + logger.warning( + f"[OpenAI Embedding] 维度探测达到调用上限({max_probe_calls})," + f"短路返回当前已确认维度 {low}", + ) + return low except Exception: break @@ -109,6 +131,12 @@ async def _request_dim(dimensions: int | None) -> int: left = mid + 1 else: right = mid - 1 + except _DetectBudgetExceeded: + logger.warning( + f"[OpenAI Embedding] 维度探测达到调用上限({max_probe_calls})," + f"短路返回当前已确认维度 {low}", + ) + return low except Exception: right = mid - 1 From 6620f2b04f03d51cfdaba364ff34003997ab2d4a Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 16:51:28 +0800 Subject: [PATCH 8/9] fix(dashboard): return generic error on provider adapter import failure --- astrbot/dashboard/routes/config.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index ccc77a1622..0d27f80816 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -760,9 +760,15 @@ async def get_embedding_dim(self): self.core_lifecycle.provider_manager.dynamic_import_provider( provider_type, ) - except ImportError as e: + except ImportError: logger.error(traceback.format_exc()) - return Response().error(f"动态导入提供商适配器失败: {e!s}").__dict__ + return ( + Response() + .error( + "提供商适配器加载失败,请检查提供商类型配置或查看服务端日志" + ) + .__dict__ + ) # 获取对应的 provider 类 if provider_type not in provider_cls_map: From 227eb1af1ed70427176ee3cb49ad8a9fabeebb79 Mon Sep 17 00:00:00 2001 From: xiaoxi68 <3520824673@qq.com> Date: Wed, 25 Feb 2026 17:16:21 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E5=9B=9E=E9=80=80=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/provider.py | 4 - .../sources/gemini_embedding_source.py | 13 --- .../sources/openai_embedding_source.py | 86 ------------------- astrbot/dashboard/routes/config.py | 5 +- 4 files changed, 3 insertions(+), 105 deletions(-) diff --git a/astrbot/core/provider/provider.py b/astrbot/core/provider/provider.py index 2a56dff770..901efd0052 100644 --- a/astrbot/core/provider/provider.py +++ b/astrbot/core/provider/provider.py @@ -305,10 +305,6 @@ def get_dim(self) -> int: """获取向量的维度""" ... - async def detect_dim(self) -> int: - """探测模型原生向量维度(默认实现)""" - return len(await self.get_embedding("astrbot")) - async def test(self) -> None: await self.get_embedding("astrbot") diff --git a/astrbot/core/provider/sources/gemini_embedding_source.py b/astrbot/core/provider/sources/gemini_embedding_source.py index 4dc34399b1..61ba9cadbe 100644 --- a/astrbot/core/provider/sources/gemini_embedding_source.py +++ b/astrbot/core/provider/sources/gemini_embedding_source.py @@ -78,19 +78,6 @@ async def get_embeddings(self, text: list[str]) -> list[list[float]]: except APIError as e: raise Exception(f"Gemini Embedding API批量请求失败: {e.message}") - async def detect_dim(self) -> int: - """探测模型原生向量维度(不传 output_dimensionality)""" - try: - result = await self.client.models.embed_content( - model=self.model, - contents="echo", - ) - assert result.embeddings is not None - assert result.embeddings[0].values is not None - return len(result.embeddings[0].values) - except APIError as e: - raise Exception(f"Gemini Embedding 维度探测失败: {e.message}") - def get_dim(self) -> int: """获取向量的维度""" return int(self.provider_config.get("embedding_dimensions", 768)) diff --git a/astrbot/core/provider/sources/openai_embedding_source.py b/astrbot/core/provider/sources/openai_embedding_source.py index dd3679d387..8bf92ef4d5 100644 --- a/astrbot/core/provider/sources/openai_embedding_source.py +++ b/astrbot/core/provider/sources/openai_embedding_source.py @@ -56,92 +56,6 @@ async def get_embeddings(self, text: list[str]) -> list[list[float]]: ) return [item.embedding for item in embeddings.data] - async def detect_dim(self) -> int: - """探测模型可用的最大向量维度""" - - class _DetectBudgetExceeded(Exception): - pass - - max_probe_calls = 12 - probe_calls = 0 - - async def _request_dim(dimensions: int | None) -> int: - nonlocal probe_calls - if probe_calls >= max_probe_calls: - raise _DetectBudgetExceeded - probe_calls += 1 - kwargs = { - "input": "echo", - "model": self.model, - } - if dimensions is not None: - kwargs["dimensions"] = dimensions - embedding = await self.client.embeddings.create(**kwargs) - return len(embedding.data[0].embedding) - - # 1) 默认调用,获取当前默认维度 - base_dim = await _request_dim(None) - - # 2) 先判断 dimensions 参数是否可调 - probe_dim = base_dim + 1 - try: - probe_result = await _request_dim(probe_dim) - if probe_result != probe_dim: - return base_dim - except _DetectBudgetExceeded: - logger.warning( - f"[OpenAI Embedding] 维度探测达到调用上限({max_probe_calls})," - f"返回当前已确认维度 {base_dim}", - ) - return base_dim - except Exception: - return base_dim - - # 3) 可调时探测上界:指数扩张 + 二分 - max_cap = 32768 - low = probe_dim - high = max(base_dim * 2, probe_dim + 1) - if high > max_cap: - high = max_cap - - while high < max_cap: - try: - result_dim = await _request_dim(high) - if result_dim != high: - break - low = high - high = min(high * 2, max_cap) - except _DetectBudgetExceeded: - logger.warning( - f"[OpenAI Embedding] 维度探测达到调用上限({max_probe_calls})," - f"短路返回当前已确认维度 {low}", - ) - return low - except Exception: - break - - left = low + 1 - right = high - 1 - while left <= right: - mid = (left + right) // 2 - try: - result_dim = await _request_dim(mid) - if result_dim == mid: - low = mid - left = mid + 1 - else: - right = mid - 1 - except _DetectBudgetExceeded: - logger.warning( - f"[OpenAI Embedding] 维度探测达到调用上限({max_probe_calls})," - f"短路返回当前已确认维度 {low}", - ) - return low - except Exception: - right = mid - 1 - - return low - def get_dim(self) -> int: """获取向量的维度""" return int(self.provider_config.get("embedding_dimensions", 1024)) diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index 0d27f80816..08b8c12b83 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -795,8 +795,9 @@ async def get_embedding_dim(self): if inspect.iscoroutinefunction(init_fn): await init_fn() - # 探测嵌入向量维度(优先使用 provider 的原生探测逻辑) - dim = await inst.detect_dim() + # 通过实际请求验证当前 embedding_dimensions 是否可用 + vec = await inst.get_embedding("echo") + dim = len(vec) logger.info( f"检测到 {provider_config.get('id', 'unknown')} 的嵌入向量维度为 {dim}",