Skip to content

Commit 69f2fb2

Browse files
authored
fix: cannot automatically get embedding dim when create embedding provider (#5442)
* fix(dashboard): 强化 API Key 复制临时节点清理逻辑 * fix(embedding): 自动检测改为探测 OpenAI embedding 最大可用维度 * fix: normalize openai embedding base url and add hint key * i18n: add embedding_api_base hint translations * i18n: localize provider embedding/proxy metadata hints * fix: show provider-specific embedding API Base URL hint as field subtitle * fix(embedding): cap OpenAI detect_dim probes with early short-circuit * fix(dashboard): return generic error on provider adapter import failure * 回退检测逻辑
1 parent 78660da commit 69f2fb2

File tree

7 files changed

+91
-13
lines changed

7 files changed

+91
-13
lines changed

astrbot/core/config/default.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,7 @@ class ChatProviderTemplate(TypedDict):
14631463
"type": "openai_embedding",
14641464
"provider": "openai",
14651465
"provider_type": "embedding",
1466+
"hint": "provider_group.provider.openai_embedding.hint",
14661467
"enable": True,
14671468
"embedding_api_key": "",
14681469
"embedding_api_base": "",
@@ -1476,6 +1477,7 @@ class ChatProviderTemplate(TypedDict):
14761477
"type": "gemini_embedding",
14771478
"provider": "google",
14781479
"provider_type": "embedding",
1480+
"hint": "provider_group.provider.gemini_embedding.hint",
14791481
"enable": True,
14801482
"embedding_api_key": "",
14811483
"embedding_api_base": "",
@@ -2192,9 +2194,9 @@ class ChatProviderTemplate(TypedDict):
21922194
"type": "string",
21932195
},
21942196
"proxy": {
2195-
"description": "代理地址",
2197+
"description": "provider_group.provider.proxy.description",
21962198
"type": "string",
2197-
"hint": "HTTP/HTTPS 代理地址,格式如 http://127.0.0.1:7890。仅对该提供商的 API 请求生效,不影响 Docker 内网通信。",
2199+
"hint": "provider_group.provider.proxy.hint",
21982200
},
21992201
"model": {
22002202
"description": "模型 ID",

astrbot/core/provider/sources/openai_embedding_source.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@ def __init__(self, provider_config: dict, provider_settings: dict) -> None:
2323
if proxy:
2424
logger.info(f"[OpenAI Embedding] 使用代理: {proxy}")
2525
http_client = httpx.AsyncClient(proxy=proxy)
26+
api_base = provider_config.get("embedding_api_base", "").strip()
27+
if not api_base:
28+
api_base = "https://api.openai.com/v1"
29+
else:
30+
api_base = api_base.removesuffix("/")
31+
if not api_base.endswith("/v1"):
32+
api_base = f"{api_base}/v1"
2633
self.client = AsyncOpenAI(
2734
api_key=provider_config.get("embedding_api_key"),
28-
base_url=provider_config.get(
29-
"embedding_api_base",
30-
"https://api.openai.com/v1",
31-
),
35+
base_url=api_base,
3236
timeout=int(provider_config.get("timeout", 20)),
3337
http_client=http_client,
3438
)

astrbot/dashboard/routes/config.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,22 @@ async def get_embedding_dim(self):
754754
if not provider_type:
755755
return Response().error("provider_config 缺少 type 字段").__dict__
756756

757+
# 首次添加某类提供商时,provider_cls_map 可能尚未注册该适配器
758+
if provider_type not in provider_cls_map:
759+
try:
760+
self.core_lifecycle.provider_manager.dynamic_import_provider(
761+
provider_type,
762+
)
763+
except ImportError:
764+
logger.error(traceback.format_exc())
765+
return (
766+
Response()
767+
.error(
768+
"提供商适配器加载失败,请检查提供商类型配置或查看服务端日志"
769+
)
770+
.__dict__
771+
)
772+
757773
# 获取对应的 provider 类
758774
if provider_type not in provider_cls_map:
759775
return (
@@ -779,7 +795,7 @@ async def get_embedding_dim(self):
779795
if inspect.iscoroutinefunction(init_fn):
780796
await init_fn()
781797

782-
# 获取嵌入向量维度
798+
# 通过实际请求验证当前 embedding_dimensions 是否可用
783799
vec = await inst.get_embedding("echo")
784800
dim = len(vec)
785801

dashboard/src/components/shared/AstrBotConfig.vue

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,40 @@ const filteredIterable = computed(() => {
4848
return rest
4949
})
5050
51+
const providerHint = computed(() => {
52+
const hint = props.iterable?.hint
53+
if (typeof hint !== 'string' || !hint) return ''
54+
55+
if (
56+
hint === 'provider_group.provider.openai_embedding.hint'
57+
|| hint === 'provider_group.provider.gemini_embedding.hint'
58+
) {
59+
return ''
60+
}
61+
62+
return hint
63+
})
64+
65+
const getItemHint = (itemKey, itemMeta) => {
66+
if (itemMeta?.hint) return itemMeta.hint
67+
68+
if (itemKey !== 'embedding_api_base') return ''
69+
70+
const providerType = props.iterable?.type
71+
if (providerType === 'openai_embedding') {
72+
return getRaw('provider_group.provider.openai_embedding.hint')
73+
? 'provider_group.provider.openai_embedding.hint'
74+
: ''
75+
}
76+
if (providerType === 'gemini_embedding') {
77+
return getRaw('provider_group.provider.gemini_embedding.hint')
78+
? 'provider_group.provider.gemini_embedding.hint'
79+
: ''
80+
}
81+
82+
return ''
83+
}
84+
5185
const dialog = ref(false)
5286
const currentEditingKey = ref('')
5387
const currentEditingLanguage = ref('json')
@@ -153,14 +187,14 @@ function hasVisibleItemsAfter(items, currentIndex) {
153187
<div v-if="metadata[metadataKey]?.type === 'object' || metadata[metadataKey]?.config_template" class="object-config">
154188
<!-- Provider-level hint -->
155189
<v-alert
156-
v-if="iterable.hint && !isEditing"
190+
v-if="providerHint"
157191
type="info"
158192
variant="tonal"
159193
class="mb-4"
160194
border="start"
161195
density="compact"
162196
>
163-
{{ iterable.hint }}
197+
{{ translateIfKey(providerHint) }}
164198
</v-alert>
165199
166200
<div v-for="(val, key, index) in filteredIterable" :key="key" class="config-item">
@@ -218,9 +252,9 @@ function hasVisibleItemsAfter(items, currentIndex) {
218252
</v-list-item-title>
219253
220254
<v-list-item-subtitle class="property-hint">
221-
<span v-if="metadata[metadataKey].items[key]?.obvious_hint && metadata[metadataKey].items[key]?.hint"
255+
<span v-if="metadata[metadataKey].items[key]?.obvious_hint && getItemHint(key, metadata[metadataKey].items[key])"
222256
class="important-hint">‼️</span>
223-
{{ translateIfKey(metadata[metadataKey].items[key]?.hint) }}
257+
{{ translateIfKey(getItemHint(key, metadata[metadataKey].items[key])) }}
224258
</v-list-item-subtitle>
225259
</v-list-item>
226260
</v-col>

dashboard/src/i18n/locales/en-US/features/config-metadata.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,12 @@
10861086
"embedding_api_base": {
10871087
"description": "API Base URL"
10881088
},
1089+
"openai_embedding": {
1090+
"hint": "OpenAI Embedding automatically appends /v1 at request time."
1091+
},
1092+
"gemini_embedding": {
1093+
"hint": "Gemini Embedding does not require manually adding /v1beta."
1094+
},
10891095
"volcengine_cluster": {
10901096
"description": "Volcengine cluster",
10911097
"hint": "For voice cloning models, choose volcano_icl or volcano_icl_concurr; default is volcano_tts."
@@ -1313,6 +1319,10 @@
13131319
"api_base": {
13141320
"description": "API Base URL"
13151321
},
1322+
"proxy": {
1323+
"description": "Proxy address",
1324+
"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."
1325+
},
13161326
"model": {
13171327
"description": "Model ID",
13181328
"hint": "Model name, e.g., gpt-4o-mini, deepseek-chat."

dashboard/src/i18n/locales/zh-CN/features/config-metadata.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,12 @@
10891089
"embedding_api_base": {
10901090
"description": "API Base URL"
10911091
},
1092+
"openai_embedding": {
1093+
"hint": "OpenAI Embedding 会在请求时自动补上 /v1。"
1094+
},
1095+
"gemini_embedding": {
1096+
"hint": "Gemini Embedding 无需手动添加 /v1beta。"
1097+
},
10921098
"volcengine_cluster": {
10931099
"description": "火山引擎集群",
10941100
"hint": "若使用语音复刻大模型,可选volcano_icl或volcano_icl_concurr,默认使用volcano_tts"
@@ -1316,6 +1322,10 @@
13161322
"api_base": {
13171323
"description": "API Base URL"
13181324
},
1325+
"proxy": {
1326+
"description": "代理地址",
1327+
"hint": "HTTP/HTTPS 代理地址,格式如 http://127.0.0.1:7890。仅对该提供商的 API 请求生效,不影响 Docker 内网通信。"
1328+
},
13191329
"model": {
13201330
"description": "模型 ID",
13211331
"hint": "模型名称,如 gpt-4o-mini, deepseek-chat。"

dashboard/src/views/Settings.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ const loadApiKeys = async () => {
336336
const tryExecCommandCopy = (text) => {
337337
let textArea = null;
338338
try {
339-
if (typeof document === 'undefined') return false;
339+
if (typeof document === 'undefined' || !document.body) return false;
340340
textArea = document.createElement('textarea');
341341
textArea.value = text;
342342
textArea.setAttribute('readonly', '');
@@ -353,7 +353,9 @@ const tryExecCommandCopy = (text) => {
353353
return false;
354354
} finally {
355355
try {
356-
textArea?.remove?.();
356+
if (textArea?.parentNode) {
357+
textArea.parentNode.removeChild(textArea);
358+
}
357359
} catch (_) {
358360
// ignore cleanup errors
359361
}

0 commit comments

Comments
 (0)