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 * 回退检测逻辑
This commit is contained in:
@@ -1463,6 +1463,7 @@ CONFIG_METADATA_2 = {
|
||||
"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 @@ CONFIG_METADATA_2 = {
|
||||
"type": "gemini_embedding",
|
||||
"provider": "google",
|
||||
"provider_type": "embedding",
|
||||
"hint": "provider_group.provider.gemini_embedding.hint",
|
||||
"enable": True,
|
||||
"embedding_api_key": "",
|
||||
"embedding_api_base": "",
|
||||
@@ -2192,9 +2194,9 @@ CONFIG_METADATA_2 = {
|
||||
"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",
|
||||
|
||||
@@ -23,12 +23,16 @@ class OpenAIEmbeddingProvider(EmbeddingProvider):
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -754,6 +754,22 @@ class ConfigRoute(Route):
|
||||
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:
|
||||
logger.error(traceback.format_exc())
|
||||
return (
|
||||
Response()
|
||||
.error(
|
||||
"提供商适配器加载失败,请检查提供商类型配置或查看服务端日志"
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
|
||||
# 获取对应的 provider 类
|
||||
if provider_type not in provider_cls_map:
|
||||
return (
|
||||
@@ -779,7 +795,7 @@ class ConfigRoute(Route):
|
||||
if inspect.iscoroutinefunction(init_fn):
|
||||
await init_fn()
|
||||
|
||||
# 获取嵌入向量维度
|
||||
# 通过实际请求验证当前 embedding_dimensions 是否可用
|
||||
vec = await inst.get_embedding("echo")
|
||||
dim = len(vec)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
<div v-if="metadata[metadataKey]?.type === 'object' || metadata[metadataKey]?.config_template" class="object-config">
|
||||
<!-- Provider-level hint -->
|
||||
<v-alert
|
||||
v-if="iterable.hint && !isEditing"
|
||||
v-if="providerHint"
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
border="start"
|
||||
density="compact"
|
||||
>
|
||||
{{ iterable.hint }}
|
||||
{{ translateIfKey(providerHint) }}
|
||||
</v-alert>
|
||||
|
||||
<div v-for="(val, key, index) in filteredIterable" :key="key" class="config-item">
|
||||
@@ -218,9 +252,9 @@ function hasVisibleItemsAfter(items, currentIndex) {
|
||||
</v-list-item-title>
|
||||
|
||||
<v-list-item-subtitle class="property-hint">
|
||||
<span v-if="metadata[metadataKey].items[key]?.obvious_hint && metadata[metadataKey].items[key]?.hint"
|
||||
<span v-if="metadata[metadataKey].items[key]?.obvious_hint && getItemHint(key, metadata[metadataKey].items[key])"
|
||||
class="important-hint">‼️</span>
|
||||
{{ translateIfKey(metadata[metadataKey].items[key]?.hint) }}
|
||||
{{ translateIfKey(getItemHint(key, metadata[metadataKey].items[key])) }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
|
||||
@@ -1086,6 +1086,12 @@
|
||||
"embedding_api_base": {
|
||||
"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",
|
||||
"hint": "For voice cloning models, choose volcano_icl or volcano_icl_concurr; default is volcano_tts."
|
||||
@@ -1313,6 +1319,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."
|
||||
|
||||
@@ -1089,6 +1089,12 @@
|
||||
"embedding_api_base": {
|
||||
"description": "API Base URL"
|
||||
},
|
||||
"openai_embedding": {
|
||||
"hint": "OpenAI Embedding 会在请求时自动补上 /v1。"
|
||||
},
|
||||
"gemini_embedding": {
|
||||
"hint": "Gemini Embedding 无需手动添加 /v1beta。"
|
||||
},
|
||||
"volcengine_cluster": {
|
||||
"description": "火山引擎集群",
|
||||
"hint": "若使用语音复刻大模型,可选volcano_icl或volcano_icl_concurr,默认使用volcano_tts"
|
||||
@@ -1316,6 +1322,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。"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user