|
|
@@ -18,6 +18,7 @@ import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample;
|
|
|
import com.tzld.videoVector.service.DeconstructService;
|
|
|
import com.tzld.videoVector.service.EmbeddingService;
|
|
|
import com.tzld.videoVector.service.VectorStoreService;
|
|
|
+import com.tzld.videoVector.util.Md5Util;
|
|
|
import com.tzld.videoVector.util.OdpsUtil;
|
|
|
import com.tzld.videoVector.util.VectorUtils;
|
|
|
import com.xxl.job.core.biz.model.ReturnT;
|
|
|
@@ -123,6 +124,17 @@ public class VideoVectorJob {
|
|
|
try {
|
|
|
// 4.1 查询哪些 videoId 在该配置下已有向量(数据库层已做 DISTINCT video_id)
|
|
|
Set<Long> existingVideoIds = vectorStoreService.existsByIds(configCode, auditPassedIds);
|
|
|
+
|
|
|
+ // 4.1.1 检查已存在记录中 text 为空的,删除后重新向量化
|
|
|
+ if (!existingVideoIds.isEmpty()) {
|
|
|
+ Set<Long> emptyTextIds = vectorStoreService.findVideoIdsWithEmptyText(configCode, existingVideoIds);
|
|
|
+ if (!emptyTextIds.isEmpty()) {
|
|
|
+ log.info("配置 {} 下发现 {} 个 text 为空的记录,删除后重新向量化: {}", configCode, emptyTextIds.size(), emptyTextIds);
|
|
|
+ vectorStoreService.deleteBatch(configCode, emptyTextIds);
|
|
|
+ existingVideoIds.removeAll(emptyTextIds);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 4.2 过滤出需要处理的 videoId(排除已有向量的)
|
|
|
List<Long> needProcessIds = auditPassedIds.stream()
|
|
|
.filter(id -> !existingVideoIds.contains(id))
|
|
|
@@ -394,7 +406,8 @@ public class VideoVectorJob {
|
|
|
if (maxLength != null && maxLength > 0 && text.length() > maxLength) {
|
|
|
text = text.substring(0, maxLength);
|
|
|
}
|
|
|
- List<Float> vector = embeddingService.embed(text, config);
|
|
|
+ // 优先通过 text_hash 复用已有 embedding,避免重复调用 API
|
|
|
+ List<Float> vector = getOrEmbed(text, config);
|
|
|
if (vector == null || vector.isEmpty()) {
|
|
|
log.warn("videoId={} 配置 {} 第{}个文本向量化失败", videoId, configCode, i);
|
|
|
continue;
|
|
|
@@ -420,7 +433,8 @@ public class VideoVectorJob {
|
|
|
if (maxLength != null && maxLength > 0 && text.length() > maxLength) {
|
|
|
text = text.substring(0, maxLength);
|
|
|
}
|
|
|
- List<Float> vector = embeddingService.embed(text, config);
|
|
|
+ // 优先通过 text_hash 复用已有 embedding,避免重复调用 API
|
|
|
+ List<Float> vector = getOrEmbed(text, config);
|
|
|
if (vector == null || vector.isEmpty()) {
|
|
|
log.warn("videoId={} 配置 {} 文本向量化失败", videoId, configCode);
|
|
|
return 0;
|
|
|
@@ -431,6 +445,28 @@ public class VideoVectorJob {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 优先通过 text_hash 复用已有 embedding,未命中则调用 embedding API
|
|
|
+ *
|
|
|
+ * @param text 文本内容
|
|
|
+ * @param config 向量化配置
|
|
|
+ * @return 向量数据
|
|
|
+ */
|
|
|
+ private List<Float> getOrEmbed(String text, DeconstructVectorConfig config) {
|
|
|
+ String configCode = config.getConfigCode();
|
|
|
+ // 计算 text_hash,查询是否已有相同文本的 embedding
|
|
|
+ String textHash = Md5Util.encoderByMd5(text);
|
|
|
+ if (StringUtils.hasText(textHash)) {
|
|
|
+ List<Float> cached = vectorStoreService.getVectorByTextHash(textHash, configCode);
|
|
|
+ if (cached != null && !cached.isEmpty()) {
|
|
|
+ log.debug("命中 text_hash 缓存,复用 embedding,hash={}, configCode={}", textHash, configCode);
|
|
|
+ return cached;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 未命中缓存,调用 embedding API
|
|
|
+ return embeddingService.embed(text, config);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 分页查询 videoId 列表
|
|
|
* @param pageNum 页码(从0开始)
|
|
|
@@ -526,6 +562,17 @@ public class VideoVectorJob {
|
|
|
try {
|
|
|
// 5.1 查询该配置下已有向量的 videoId,排除已处理过的(数据库层已做 DISTINCT video_id)
|
|
|
Set<Long> existingVideoIds = vectorStoreService.existsByIds(configCode, auditPassedIds);
|
|
|
+
|
|
|
+ // 5.1.1 检查已存在记录中 text 为空的,删除后重新向量化
|
|
|
+ if (!existingVideoIds.isEmpty()) {
|
|
|
+ Set<Long> emptyTextIds = vectorStoreService.findVideoIdsWithEmptyText(configCode, existingVideoIds);
|
|
|
+ if (!emptyTextIds.isEmpty()) {
|
|
|
+ log.info("配置 {} 下发现 {} 个 text 为空的记录,删除后重新向量化: {}", configCode, emptyTextIds.size(), emptyTextIds);
|
|
|
+ vectorStoreService.deleteBatch(configCode, emptyTextIds);
|
|
|
+ existingVideoIds.removeAll(emptyTextIds);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
List<Long> needProcessIds = auditPassedIds.stream()
|
|
|
.filter(id -> !existingVideoIds.contains(id))
|
|
|
.collect(Collectors.toList());
|
|
|
@@ -904,6 +951,17 @@ public class VideoVectorJob {
|
|
|
try {
|
|
|
// 5.1 已向量化过滤
|
|
|
Set<Long> existingVideoIds = vectorStoreService.existsByIds(configCode, auditPassedIds);
|
|
|
+
|
|
|
+ // 5.1.1 检查已存在记录中 text 为空的,删除后重新向量化
|
|
|
+ if (!existingVideoIds.isEmpty()) {
|
|
|
+ Set<Long> emptyTextIds = vectorStoreService.findVideoIdsWithEmptyText(configCode, existingVideoIds);
|
|
|
+ if (!emptyTextIds.isEmpty()) {
|
|
|
+ log.info("配置 {} 下发现 {} 个 text 为空的记录,删除后重新向量化: {}", configCode, emptyTextIds.size(), emptyTextIds);
|
|
|
+ vectorStoreService.deleteBatch(configCode, emptyTextIds);
|
|
|
+ existingVideoIds.removeAll(emptyTextIds);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
List<Long> needProcessIds = auditPassedIds.stream()
|
|
|
.filter(id -> !existingVideoIds.contains(id))
|
|
|
.collect(Collectors.toList());
|