From ce33c1c6521d8464820af504ee811151f061342a Mon Sep 17 00:00:00 2001 From: matevip Date: Sun, 17 May 2026 09:25:09 +0800 Subject: [PATCH] feat(llm): add UI to configure embedding models from any provider (#79) --- .../llm/embedding/EmbeddingModelFactory.java | 24 +- mateclaw-ui/src/i18n/locales/en-US.ts | 30 ++ mateclaw-ui/src/i18n/locales/zh-CN.ts | 30 ++ .../Models/EmbeddingModelsSection.vue | 306 +++++++++++++++--- 4 files changed, 343 insertions(+), 47 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/llm/embedding/EmbeddingModelFactory.java b/mateclaw-server/src/main/java/vip/mate/llm/embedding/EmbeddingModelFactory.java index a8436f95..4d13b4ca 100644 --- a/mateclaw-server/src/main/java/vip/mate/llm/embedding/EmbeddingModelFactory.java +++ b/mateclaw-server/src/main/java/vip/mate/llm/embedding/EmbeddingModelFactory.java @@ -22,6 +22,7 @@ import vip.mate.llm.model.ModelProviderEntity; import vip.mate.llm.service.ModelProviderService; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; /** * Embedding 模型工厂 @@ -55,6 +56,14 @@ public class EmbeddingModelFactory { /** 构造 API 时共享的 retry template(用 Spring AI 默认) */ private static final RetryTemplate DEFAULT_RETRY = RetryUtils.DEFAULT_RETRY_TEMPLATE; + /** + * Trailing "/v{digits}" segment in a base URL — the OpenAI-compatible + * convention (/v1 OpenAI, /v3 Volcano Ark, /v4 Zhipu). When the base URL + * already carries a version segment, the default "/v1/embeddings" path + * would build a broken URL like ".../api/paas/v4/v1/embeddings". + */ + private static final Pattern BASE_URL_VERSION_SUFFIX = Pattern.compile(".*/v\\d+$"); + /** 按 modelConfig.id 缓存 EmbeddingModel,config 变更时调用 {@link #evict} 清除 */ private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); @@ -163,7 +172,7 @@ public class EmbeddingModelFactory { OpenAiApi api = OpenAiApi.builder() .baseUrl(baseUrl) .apiKey(apiKey.trim()) - .embeddingsPath("/v1/embeddings") + .embeddingsPath(resolveEmbeddingsPath(baseUrl)) .build(); OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder() @@ -181,4 +190,17 @@ public class EmbeddingModelFactory { if (u.endsWith("/v1")) u = u.substring(0, u.length() - 3); return u; } + + /** + * Resolve the embeddings path. Defaults to {@code /v1/embeddings}; when the + * base URL already ends with a version segment (Zhipu {@code /v4}, Volcano + * Ark {@code /v3}, …), drop the leading {@code /v1} so the request hits + * {@code /embeddings} instead of a 404 at {@code /v1/embeddings}. + */ + private String resolveEmbeddingsPath(String baseUrl) { + if (baseUrl != null && BASE_URL_VERSION_SUFFIX.matcher(baseUrl).matches()) { + return "/embeddings"; + } + return "/v1/embeddings"; + } } diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 8ccf18b6..1e096c41 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -658,6 +658,36 @@ export default { editTitle: 'Provider Settings', manageTitle: 'Manage Models', addModel: 'Add Model', + embedding: { + title: 'Embedding Models', + hint: 'Used for knowledge base semantic search; shares the provider API key with chat models', + loading: 'Loading...', + empty: 'No embedding models yet — add one with the form below.', + defaultBadge: 'Default', + builtinBadge: 'Built-in', + test: 'Test', + testing: 'Testing...', + testPassed: 'Passed · {dim} dimensions', + setDefault: 'Set as default', + delete: 'Delete', + deleteConfirm: 'Delete embedding model "{name}"?', + deleteFailed: 'Failed to delete', + deleted: 'Embedding model deleted', + addTitle: 'Add Embedding Model', + addHint: 'Pick a configured provider and enter an embedding model name it supports (e.g. text-embedding-3-small, bge-m3).', + provider: 'Provider', + selectProvider: 'Select a provider', + modelName: 'Model Name', + modelNamePlaceholder: 'e.g. text-embedding-3-small', + displayName: 'Display Name', + displayNamePlaceholder: 'Defaults to the model name', + add: 'Add', + adding: 'Adding...', + added: 'Embedding model added', + addFailed: 'Failed to add', + nameRequired: 'Select a provider and enter a model name', + noProviders: 'Enable and configure at least one provider above first.', + }, // Inline API Key on the provider card — primary entry point for the // 90% case (paste a key, hit save). Modal stays for advanced fields. inlineApiKeyTitle: 'API Key', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 691c03db..5de4234d 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -538,6 +538,36 @@ export default { editTitle: '提供商配置', manageTitle: '管理模型', addModel: '新增模型', + embedding: { + title: 'Embedding 模型', + hint: '知识库语义检索使用,与 Chat 模型共享 Provider 的 API Key', + loading: '加载中...', + empty: '暂无 Embedding 模型,使用下方表单添加。', + defaultBadge: '默认', + builtinBadge: '内置', + test: '测试连通性', + testing: '测试中...', + testPassed: '测试通过 · 维度 {dim}', + setDefault: '设为系统默认', + delete: '删除', + deleteConfirm: '确认删除 Embedding 模型“{name}”?', + deleteFailed: '删除失败', + deleted: 'Embedding 模型已删除', + addTitle: '新增 Embedding 模型', + addHint: '选择一个已配置的提供商,填入它支持的 Embedding 模型名称(如 text-embedding-3-small、bge-m3)。', + provider: '提供商', + selectProvider: '请选择提供商', + modelName: '模型名称', + modelNamePlaceholder: '例如 text-embedding-3-small', + displayName: '显示名称', + displayNamePlaceholder: '留空则使用模型名称', + add: '添加', + adding: '添加中...', + added: 'Embedding 模型已添加', + addFailed: '添加失败', + nameRequired: '请选择提供商并填写模型名称', + noProviders: '请先在上方启用并配置至少一个提供商。', + }, // Inline API Key on the provider card — primary entry point for the // 90% case (paste a key, hit save). Modal stays for advanced fields. inlineApiKeyTitle: 'API Key', diff --git a/mateclaw-ui/src/views/Settings/Models/EmbeddingModelsSection.vue b/mateclaw-ui/src/views/Settings/Models/EmbeddingModelsSection.vue index 1605bfca..77f949ce 100644 --- a/mateclaw-ui/src/views/Settings/Models/EmbeddingModelsSection.vue +++ b/mateclaw-ui/src/views/Settings/Models/EmbeddingModelsSection.vue @@ -9,60 +9,128 @@ - Embedding 模型 - 知识库语义检索使用,与 Chat 模型共享 Provider 的 API Key + {{ t('settings.model.embedding.title') }} + {{ t('settings.model.embedding.hint') }} -
加载中...
-
- 暂无可用的 Embedding 模型。系统已预置 DashScope Text Embedding v3/v2, - 请在"云端模型"下的 DashScope Provider 中配置 API Key。 -
+
{{ t('settings.model.embedding.loading') }}
-
-
-
-
- {{ model.name }} - 默认 +