25 changed files with 1398 additions and 610 deletions
-
42CLAUDE.md
-
44client/dist/chatbot-sdk.js
-
2client/dist/chatbot-sdk.js.map
-
2client/dist/chatbot-sdk.min.js
-
2client/dist/chatbot-sdk.min.js.map
-
33client/src/api.ts
-
10client/src/chat.ts
-
52src/main/java/com/wok/supportbot/config/ChatModelFactory.java
-
19src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java
-
58src/main/java/com/wok/supportbot/config/DynamicEmbeddingModel.java
-
160src/main/java/com/wok/supportbot/config/EmbeddingConfigFixer.java
-
190src/main/java/com/wok/supportbot/config/EmbeddingModelFactory.java
-
52src/main/java/com/wok/supportbot/config/ModelConfigLoader.java
-
40src/main/java/com/wok/supportbot/controller/AiController.java
-
5src/main/java/com/wok/supportbot/controller/AiModelConfigController.java
-
16src/main/java/com/wok/supportbot/controller/ConversationController.java
-
14src/main/java/com/wok/supportbot/rag/load/InMemoryVectorStoreConfig.java
-
52src/main/java/com/wok/supportbot/rag/load/PgVectorStoreConfig.java
-
13src/main/resources/application.yml
-
58src/main/resources/static/components/ModelConfigManager.js
-
44src/main/resources/static/sdk/chatbot-sdk.js
-
2src/main/resources/static/sdk/chatbot-sdk.js.map
-
2src/main/resources/static/sdk/chatbot-sdk.min.js
-
2src/main/resources/static/sdk/chatbot-sdk.min.js.map
-
1094src/main/resources/static/sdk/test.html
2
client/dist/chatbot-sdk.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
client/dist/chatbot-sdk.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
client/dist/chatbot-sdk.min.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,58 @@ |
|||||
|
package com.wok.supportbot.config; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.ai.document.Document; |
||||
|
import org.springframework.ai.embedding.EmbeddingModel; |
||||
|
import org.springframework.ai.embedding.EmbeddingRequest; |
||||
|
import org.springframework.ai.embedding.EmbeddingResponse; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 动态 EmbeddingModel 代理 |
||||
|
* 实现 EmbeddingModel 接口,每次调用委托给 EmbeddingModelFactory 当前活跃的实例。 |
||||
|
* 这样 PgVectorStore / SimpleVectorStore 无需重建即可在运行时切换向量化模型。 |
||||
|
* |
||||
|
* 注意:每次调用都走 factory(factory 内部有缓存,开销很小, |
||||
|
* 仅在配置变更后第一次调用时才触发新实例创建)。 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class DynamicEmbeddingModel implements EmbeddingModel { |
||||
|
|
||||
|
private final EmbeddingModelFactory embeddingModelFactory; |
||||
|
|
||||
|
public DynamicEmbeddingModel(EmbeddingModelFactory embeddingModelFactory) { |
||||
|
this.embeddingModelFactory = embeddingModelFactory; |
||||
|
} |
||||
|
|
||||
|
private EmbeddingModel getDelegate() { |
||||
|
return embeddingModelFactory.getEmbeddingModel(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public float[] embed(Document document) { |
||||
|
return getDelegate().embed(document); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EmbeddingResponse embedForResponse(List<String> texts) { |
||||
|
return getDelegate().embedForResponse(texts); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EmbeddingResponse call(EmbeddingRequest request) { |
||||
|
return getDelegate().call(request); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int dimensions() { |
||||
|
return getDelegate().dimensions(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 返回当前委托的 EmbeddingModel 实例,供调试/日志使用 |
||||
|
*/ |
||||
|
public EmbeddingModel getCurrentDelegate() { |
||||
|
return getDelegate(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
package com.wok.supportbot.config; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.ai.embedding.EmbeddingModel; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent; |
||||
|
import org.springframework.context.ApplicationListener; |
||||
|
import org.springframework.jdbc.core.JdbcTemplate; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* Embedding 配置检查器 |
||||
|
* 在应用启动完成后,检查 EMBEDDING 配置的合理性并记录日志。 |
||||
|
* 注意:不再强制修正非 DashScope 配置,尊重用户在 DB 中配置的提供商和模型。 |
||||
|
* 新增:检测 EmbeddingModel 返回的实际维度与 yml 配置的向量维度是否一致。 |
||||
|
*/ |
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class EmbeddingConfigFixer implements ApplicationListener<ApplicationReadyEvent> { |
||||
|
|
||||
|
@Autowired |
||||
|
private JdbcTemplate jdbcTemplate; |
||||
|
|
||||
|
@Autowired |
||||
|
private EmbeddingModelFactory embeddingModelFactory; |
||||
|
|
||||
|
@Value("${spring.ai.dashscope.api-key:}") |
||||
|
private String dashscopeApiKey; |
||||
|
|
||||
|
@Value("${knowledge.vector.dimension:1536}") |
||||
|
private int fallbackDimension; |
||||
|
|
||||
|
@Override |
||||
|
public void onApplicationEvent(ApplicationReadyEvent event) { |
||||
|
try { |
||||
|
checkEmbeddingConfig(); |
||||
|
checkDimensionConsistency(); |
||||
|
} catch (Exception e) { |
||||
|
log.warn("Embedding 配置检查异常(不影响启动): {}", e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void checkEmbeddingConfig() { |
||||
|
// 打印当前 EMBEDDING 所有配置 |
||||
|
List<Map<String, Object>> allConfigs = jdbcTemplate.queryForList( |
||||
|
"SELECT id, name, provider, model_name, api_key, base_url, is_active, is_delete " + |
||||
|
"FROM ai_model_config WHERE app_type = 'EMBEDDING' ORDER BY is_active DESC"); |
||||
|
log.info("========== EMBEDDING 配置检查 =========="); |
||||
|
for (Map<String, Object> row : allConfigs) { |
||||
|
String apiKey = (String) row.get("api_key"); |
||||
|
String maskedKey = apiKey != null && apiKey.length() > 8 |
||||
|
? apiKey.substring(0, 4) + "****" + apiKey.substring(apiKey.length() - 4) |
||||
|
: "****"; |
||||
|
log.info(" id={} name={} provider={} model={} apiKey={} baseUrl={} active={} deleted={}", |
||||
|
row.get("id"), row.get("name"), row.get("provider"), |
||||
|
row.get("model_name"), maskedKey, row.get("base_url"), |
||||
|
row.get("is_active"), row.get("is_delete")); |
||||
|
} |
||||
|
|
||||
|
// 查找活跃配置 |
||||
|
Map<String, Object> activeConfig = null; |
||||
|
for (Map<String, Object> row : allConfigs) { |
||||
|
if (Boolean.TRUE.equals(row.get("is_active")) |
||||
|
&& Boolean.FALSE.equals(row.get("is_delete"))) { |
||||
|
activeConfig = row; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (activeConfig == null) { |
||||
|
log.warn("EMBEDDING 类型无活跃配置,请在前端管理页面配置"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
String apiKey = (String) activeConfig.get("api_key"); |
||||
|
String modelName = (String) activeConfig.get("model_name"); |
||||
|
String provider = (String) activeConfig.get("provider"); |
||||
|
String baseUrl = (String) activeConfig.get("base_url"); |
||||
|
|
||||
|
// 仅记录警告,不再强制改写(用户可能有意使用非 DashScope 的 Embedding 提供商) |
||||
|
if (!"dashscope".equalsIgnoreCase(provider)) { |
||||
|
log.info("EMBEDDING 使用非 DashScope 提供商 [{}],model={}。" |
||||
|
+ "请确保前端「向量维度」与实际模型输出一致,否则需重建 vector_store 表", provider, modelName); |
||||
|
} |
||||
|
|
||||
|
// DashScope 用户的 API Key 一致性提示(不强制修正,尊重用户 DB 配置) |
||||
|
if ("dashscope".equalsIgnoreCase(provider) |
||||
|
&& dashscopeApiKey != null && !dashscopeApiKey.isBlank() |
||||
|
&& !dashscopeApiKey.equals(apiKey)) { |
||||
|
log.warn("EMBEDDING DashScope 配置的 API Key 与 application.yml 不一致,以 DB 配置为准。" |
||||
|
+ "如需统一管理,请在管理页面修改。"); |
||||
|
} |
||||
|
|
||||
|
if (baseUrl != null && !baseUrl.isBlank()) { |
||||
|
log.debug("EMBEDDING 配置了自定义 baseUrl={}", baseUrl); |
||||
|
} |
||||
|
|
||||
|
log.info("EMBEDDING 配置检查完成,当前使用 provider={} model={}", provider, modelName); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检测 EmbeddingModel 实际返回的维度与配置的向量维度是否一致。 |
||||
|
* 配置维度优先级:DB extraConfig.dimensions > application.yml knowledge.vector.dimension。 |
||||
|
*/ |
||||
|
private void checkDimensionConsistency() { |
||||
|
try { |
||||
|
int configuredDimension = resolveConfiguredDimension(); |
||||
|
EmbeddingModel model = embeddingModelFactory.getEmbeddingModel(); |
||||
|
int actualDimension = model.dimensions(); |
||||
|
if (actualDimension > 0 && actualDimension != configuredDimension) { |
||||
|
log.warn("========== ⚠️ 向量维度不匹配!=========="); |
||||
|
log.warn("Embedding 模型 {} 返回维度: {}", model, actualDimension); |
||||
|
log.warn("当前配置的向量维度: {}", configuredDimension); |
||||
|
log.warn("请执行以下步骤修复:"); |
||||
|
log.warn(" 1. 在前端「AI 大模型配置管理」中修改 EMBEDDING 配置的向量维度为 {}", actualDimension); |
||||
|
log.warn(" 2. 在 PostgreSQL 中执行: DROP TABLE IF EXISTS vector_store CASCADE"); |
||||
|
log.warn(" 3. 重启服务(PgVectorStore 会自动重建表)"); |
||||
|
log.warn(" 4. 重新上传所有知识库文档"); |
||||
|
log.warn("========================================"); |
||||
|
} else { |
||||
|
log.info("向量维度校验通过: Embedding 模型返回 {} 维,配置 {} 维,一致", |
||||
|
actualDimension, configuredDimension); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.warn("无法校验向量维度(EmbeddingModel 初始化可能失败): {}", e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析配置维度:DB extraConfig 优先,yml 兜底。 |
||||
|
*/ |
||||
|
private int resolveConfiguredDimension() { |
||||
|
try { |
||||
|
List<Map<String, Object>> activeConfigs = jdbcTemplate.queryForList( |
||||
|
"SELECT extra_config FROM ai_model_config WHERE app_type = 'EMBEDDING' AND is_active = true AND is_delete = false LIMIT 1"); |
||||
|
if (!activeConfigs.isEmpty()) { |
||||
|
Map<String, Object> row = activeConfigs.get(0); |
||||
|
String extraConfigJson = (String) row.get("extra_config"); |
||||
|
// 简单解析 JSONB 中的 dimensions 字段 |
||||
|
if (extraConfigJson != null && extraConfigJson.contains("\"dimensions\"")) { |
||||
|
java.util.regex.Pattern p = java.util.regex.Pattern.compile("\"dimensions\"\\s*:\\s*(\\d+)"); |
||||
|
java.util.regex.Matcher m = p.matcher(extraConfigJson); |
||||
|
if (m.find()) { |
||||
|
int dim = Integer.parseInt(m.group(1)); |
||||
|
log.info("从 DB EMBEDDING extraConfig 读取维度: {}", dim); |
||||
|
return dim; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.warn("从 DB 读取 EMBEDDING 维度失败: {}", e.getMessage()); |
||||
|
} |
||||
|
log.info("使用 application.yml fallback 维度: {}", fallbackDimension); |
||||
|
return fallbackDimension; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,190 @@ |
|||||
|
package com.wok.supportbot.config; |
||||
|
|
||||
|
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; |
||||
|
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel; |
||||
|
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; |
||||
|
import com.wok.supportbot.entity.AiModelConfig; |
||||
|
import com.wok.supportbot.service.AiModelConfigService; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.ai.document.MetadataMode; |
||||
|
import org.springframework.ai.embedding.EmbeddingModel; |
||||
|
import org.springframework.ai.openai.OpenAiEmbeddingModel; |
||||
|
import org.springframework.ai.openai.OpenAiEmbeddingOptions; |
||||
|
import org.springframework.ai.openai.api.OpenAiApi; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.retry.backoff.ExponentialBackOffPolicy; |
||||
|
import org.springframework.retry.support.RetryTemplate; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
|
||||
|
/** |
||||
|
* EmbeddingModel 工厂 |
||||
|
* 按 DB 活跃配置(EMBEDDING 类型)动态创建/缓存 EmbeddingModel 实例,支持运行时切换提供商。 |
||||
|
* - DashScope:手动构造 DashScopeApi + DashScopeEmbeddingModel |
||||
|
* - DeepSeek / Kimi / 豆包 / 智谱 / OpenAI 等:通过 spring-ai-openai 模块 + 自定义 baseUrl 创建 |
||||
|
*/ |
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class EmbeddingModelFactory { |
||||
|
|
||||
|
@Autowired |
||||
|
private AiModelConfigService configService; |
||||
|
|
||||
|
/** |
||||
|
* EmbeddingModel 缓存:key = "provider:apiKey:modelName" |
||||
|
*/ |
||||
|
private final ConcurrentHashMap<String, EmbeddingModel> cache = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
/** |
||||
|
* 各提供商默认的 API 基础地址 |
||||
|
*/ |
||||
|
private static final Map<String, String> DEFAULT_BASE_URLS = Map.of( |
||||
|
"deepseek", "https://api.deepseek.com", |
||||
|
"moonshot", "https://api.moonshot.cn/v1", |
||||
|
"volcengine", "https://ark.cn-beijing.volces.com/api/v3", |
||||
|
"zhipu", "https://open.bigmodel.cn/api/paas/v4", |
||||
|
"openai", "https://api.openai.com" |
||||
|
); |
||||
|
|
||||
|
/** |
||||
|
* 各提供商的 Embedding API 路径(相对于 baseUrl) |
||||
|
* OpenAI 默认是 /v1/embeddings,但许多国产厂商在 baseUrl 中已包含版本号 |
||||
|
* (如 /v1、/api/v3、/api/paas/v4),因此只需 /embeddings 即可。 |
||||
|
* 豆包多模态模型需使用 /embeddings/multimodal(与 OpenAI 请求格式不兼容,另行处理)。 |
||||
|
*/ |
||||
|
private static final Map<String, String> EMBEDDINGS_PATHS = Map.of( |
||||
|
"deepseek", "/v1/embeddings", |
||||
|
"moonshot", "/embeddings", // baseUrl 已含 /v1 |
||||
|
"volcengine", "/embeddings", // baseUrl 已含 /api/v3 |
||||
|
"zhipu", "/embeddings", // baseUrl 已含 /api/paas/v4 |
||||
|
"openai", "/v1/embeddings" |
||||
|
); |
||||
|
|
||||
|
/** |
||||
|
* 获取当前活跃的 EmbeddingModel |
||||
|
* 查询 DB 中 app_type=EMBEDDING 的活跃配置,无配置时使用 DashScope text-embedding-v2 作为兜底 |
||||
|
* |
||||
|
* @return EmbeddingModel 实例 |
||||
|
*/ |
||||
|
public EmbeddingModel getEmbeddingModel() { |
||||
|
AiModelConfig config = configService.getActiveConfigWithFullKey("EMBEDDING"); |
||||
|
if (config == null || config.getApiKey() == null || config.getApiKey().isBlank()) { |
||||
|
log.warn("EMBEDDING 类型无活跃配置,请在管理页面配置向量化模型"); |
||||
|
throw new IllegalStateException("EMBEDDING 类型无活跃配置,请在管理页面配置向量化模型"); |
||||
|
} |
||||
|
log.debug("获取 EMBEDDING 活跃配置: provider={}, modelName={}, apiKey前4位={}", |
||||
|
config.getProvider(), config.getModelName(), |
||||
|
config.getApiKey().substring(0, Math.min(4, config.getApiKey().length()))); |
||||
|
return getOrCreateEmbeddingModel(config); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取或创建 EmbeddingModel(带缓存) |
||||
|
* 缓存 key = provider:apiKey:modelName,配置不变则复用实例 |
||||
|
*/ |
||||
|
private EmbeddingModel getOrCreateEmbeddingModel(AiModelConfig config) { |
||||
|
String cacheKey = config.getProvider() + ":" + config.getApiKey() + ":" + config.getModelName(); |
||||
|
return cache.computeIfAbsent(cacheKey, k -> createEmbeddingModel(config)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建 EmbeddingModel 实例 |
||||
|
* - dashscope:手动构造 DashScopeApi + DashScopeEmbeddingModel |
||||
|
* - volcengine 豆包多模态模型:抛出异常提示选用纯文本模型 |
||||
|
* - 其他提供商:通过 OpenAI 兼容 API 创建 |
||||
|
*/ |
||||
|
private EmbeddingModel createEmbeddingModel(AiModelConfig config) { |
||||
|
RetryTemplate retryTemplate = createRetryTemplate(); |
||||
|
|
||||
|
if ("dashscope".equalsIgnoreCase(config.getProvider())) { |
||||
|
log.info("创建 DashScope EmbeddingModel: model={}", config.getModelName()); |
||||
|
DashScopeApi api = DashScopeApi.builder() |
||||
|
.apiKey(config.getApiKey()) |
||||
|
.build(); |
||||
|
DashScopeEmbeddingOptions options = DashScopeEmbeddingOptions.builder() |
||||
|
.withModel(config.getModelName()) |
||||
|
.build(); |
||||
|
return new DashScopeEmbeddingModel(api, MetadataMode.EMBED, options, retryTemplate); |
||||
|
} |
||||
|
|
||||
|
// 豆包多模态模型检测:doubao-embedding-vision* 使用 /embeddings/multimodal 端点, |
||||
|
// 请求/响应格式与 OpenAI 不兼容,无法通过 OpenAiEmbeddingModel 调用。 |
||||
|
// 提示用户改用纯文本模型。 |
||||
|
if ("volcengine".equalsIgnoreCase(config.getProvider()) |
||||
|
&& config.getModelName() != null |
||||
|
&& config.getModelName().contains("vision")) { |
||||
|
throw new IllegalArgumentException( |
||||
|
"豆包多模态向量模型 (" + config.getModelName() + ") 不支持当前使用的 OpenAI 兼容 API 格式。" |
||||
|
+ "请将 EMBEDDING 模型名称改为纯文本模型,如 doubao-embedding-text-240515(2048维)或 doubao-embedding-large(4096维)。" |
||||
|
+ "注意:切换向量维度后需调整 PgVectorStoreConfig.dimensions 并重建 vector_store 表。"); |
||||
|
} |
||||
|
|
||||
|
// OpenAI 兼容提供商 |
||||
|
String baseUrl = resolveBaseUrl(config); |
||||
|
String embeddingsPath = resolveEmbeddingsPath(config); |
||||
|
log.info("创建 OpenAI 兼容 EmbeddingModel: provider={}, baseUrl={}, embeddingsPath={}, model={}", |
||||
|
config.getProvider(), baseUrl, embeddingsPath, config.getModelName()); |
||||
|
|
||||
|
// 维度一致性提示 |
||||
|
log.info("⚠ 请确认模型 [{}] 的向量维度与 PgVectorStoreConfig.dimensions(1536) 一致,否则需调整并重建向量表", |
||||
|
config.getModelName()); |
||||
|
|
||||
|
OpenAiApi api = OpenAiApi.builder() |
||||
|
.apiKey(config.getApiKey()) |
||||
|
.baseUrl(baseUrl) |
||||
|
.embeddingsPath(embeddingsPath) |
||||
|
.build(); |
||||
|
|
||||
|
OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder() |
||||
|
.model(config.getModelName()) |
||||
|
.build(); |
||||
|
|
||||
|
return new OpenAiEmbeddingModel(api, MetadataMode.EMBED, options, retryTemplate); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析 API 基础地址:优先使用 DB 配置的 baseUrl,否则使用提供商默认值 |
||||
|
*/ |
||||
|
private String resolveBaseUrl(AiModelConfig config) { |
||||
|
if (config.getBaseUrl() != null && !config.getBaseUrl().isBlank()) { |
||||
|
return config.getBaseUrl(); |
||||
|
} |
||||
|
String defaultUrl = DEFAULT_BASE_URLS.get(config.getProvider()); |
||||
|
if (defaultUrl != null) { |
||||
|
return defaultUrl; |
||||
|
} |
||||
|
throw new IllegalArgumentException( |
||||
|
"未知提供商 [" + config.getProvider() + "],请在配置中填写 API 基础地址 (baseUrl)"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析 Embedding API 路径:优先使用提供商已知路径,否则使用 OpenAI 默认 /v1/embeddings |
||||
|
*/ |
||||
|
private String resolveEmbeddingsPath(AiModelConfig config) { |
||||
|
String known = EMBEDDINGS_PATHS.get(config.getProvider()); |
||||
|
return known != null ? known : "/v1/embeddings"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建简单的重试模板(最多 3 次,指数退避) |
||||
|
*/ |
||||
|
private RetryTemplate createRetryTemplate() { |
||||
|
RetryTemplate retryTemplate = new RetryTemplate(); |
||||
|
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); |
||||
|
backOffPolicy.setInitialInterval(1000); |
||||
|
backOffPolicy.setMultiplier(2); |
||||
|
backOffPolicy.setMaxInterval(10000); |
||||
|
retryTemplate.setBackOffPolicy(backOffPolicy); |
||||
|
return retryTemplate; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除 EmbeddingModel 缓存(配置变更时调用) |
||||
|
*/ |
||||
|
public void clearCache() { |
||||
|
cache.clear(); |
||||
|
log.info("EmbeddingModel 缓存已清除"); |
||||
|
} |
||||
|
} |
||||
@ -1,20 +1,26 @@ |
|||||
package com.wok.supportbot.rag.load; |
package com.wok.supportbot.rag.load; |
||||
|
|
||||
import org.springframework.ai.embedding.EmbeddingModel; |
|
||||
|
import com.wok.supportbot.config.DynamicEmbeddingModel; |
||||
|
import com.wok.supportbot.config.EmbeddingModelFactory; |
||||
import org.springframework.ai.vectorstore.SimpleVectorStore; |
import org.springframework.ai.vectorstore.SimpleVectorStore; |
||||
import org.springframework.ai.vectorstore.VectorStore; |
import org.springframework.ai.vectorstore.VectorStore; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
/** |
/** |
||||
* 向量数据库配置(初始化基于内存的向量数据库 Bean) |
* 向量数据库配置(初始化基于内存的向量数据库 Bean) |
||||
|
* 使用 DynamicEmbeddingModel 代理,支持运行时切换向量化模型无需重启 |
||||
*/ |
*/ |
||||
@Configuration |
@Configuration |
||||
public class InMemoryVectorStoreConfig { |
public class InMemoryVectorStoreConfig { |
||||
|
|
||||
|
@Autowired |
||||
|
private EmbeddingModelFactory embeddingModelFactory; |
||||
|
|
||||
@Bean |
@Bean |
||||
VectorStore inMemoryVectorStore(EmbeddingModel dashscopeEmbeddingModel) { |
|
||||
SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel).build();; |
|
||||
return simpleVectorStore; |
|
||||
|
VectorStore inMemoryVectorStore() { |
||||
|
DynamicEmbeddingModel dynamicEmbeddingModel = new DynamicEmbeddingModel(embeddingModelFactory); |
||||
|
return SimpleVectorStore.builder(dynamicEmbeddingModel).build(); |
||||
} |
} |
||||
} |
} |
||||
2
src/main/resources/static/sdk/chatbot-sdk.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
src/main/resources/static/sdk/chatbot-sdk.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
src/main/resources/static/sdk/chatbot-sdk.min.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1094
src/main/resources/static/sdk/test.html
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue