You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
7.5 KiB
160 lines
7.5 KiB
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:1024}")
|
|
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;
|
|
}
|
|
}
|