|
|
@@ -7,22 +7,27 @@ import com.tzld.videoVector.api.VideoApiService;
|
|
|
import com.tzld.videoVector.common.constant.VectorConstants;
|
|
|
import com.tzld.videoVector.common.enums.Modality;
|
|
|
import com.tzld.videoVector.dao.mapper.pgVector.DeconstructVectorConfigMapper;
|
|
|
+import com.tzld.videoVector.dao.mapper.pgVector.ext.ArticleDeconstructResultMapperExt;
|
|
|
import com.tzld.videoVector.dao.mapper.pgVector.ext.MaterialDeconstructResultMapperExt;
|
|
|
+import com.tzld.videoVector.model.entity.ArticleMatch;
|
|
|
import com.tzld.videoVector.model.entity.MaterialMatch;
|
|
|
import com.tzld.videoVector.model.entity.VideoDetail;
|
|
|
import com.tzld.videoVector.model.param.MatchTopNVideoParam;
|
|
|
import com.tzld.videoVector.model.param.recall.MatchByTextParam;
|
|
|
import com.tzld.videoVector.model.param.recall.MatchByVideoIdParam;
|
|
|
+import com.tzld.videoVector.model.po.pgVector.ArticleDeconstructResult;
|
|
|
import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfig;
|
|
|
import com.tzld.videoVector.model.po.pgVector.DeconstructVectorConfigExample;
|
|
|
import com.tzld.videoVector.model.po.pgVector.MaterialDeconstructResult;
|
|
|
import com.tzld.videoVector.model.vo.VideoMatchResult;
|
|
|
import com.tzld.videoVector.model.vo.recall.AIUnderstandingVO;
|
|
|
import com.tzld.videoVector.model.vo.recall.DeconstructPointsVO;
|
|
|
+import com.tzld.videoVector.model.vo.recall.ArticleDetailVO;
|
|
|
import com.tzld.videoVector.model.vo.recall.MaterialDetailVO;
|
|
|
import com.tzld.videoVector.model.vo.recall.RecallResultVO;
|
|
|
import com.tzld.videoVector.model.vo.recall.VideoBasicVO;
|
|
|
import com.tzld.videoVector.model.vo.recall.VideoMatchEnrichedVO;
|
|
|
+import com.tzld.videoVector.service.ArticleVectorStoreService;
|
|
|
import com.tzld.videoVector.service.EmbeddingService;
|
|
|
import com.tzld.videoVector.service.MaterialVectorStoreService;
|
|
|
import com.tzld.videoVector.service.VideoSearchService;
|
|
|
@@ -76,6 +81,12 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
@Autowired
|
|
|
private MaterialDeconstructResultMapperExt materialDeconstructResultMapperExt;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private ArticleVectorStoreService articleVectorStoreService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ArticleDeconstructResultMapperExt articleDeconstructResultMapperExt;
|
|
|
+
|
|
|
@Autowired
|
|
|
private DeconstructVectorConfigMapper deconstructVectorConfigMapper;
|
|
|
|
|
|
@@ -150,12 +161,15 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
int videoTopN = param.getVideoTopN() != null && param.getVideoTopN() > 0 ? param.getVideoTopN() : defaultTopN;
|
|
|
int materialTopN = param.getMaterialTopN() != null && param.getMaterialTopN() > 0
|
|
|
? param.getMaterialTopN() : defaultTopN;
|
|
|
+ int articleTopN = param.getArticleTopN() != null && param.getArticleTopN() > 0
|
|
|
+ ? param.getArticleTopN() : defaultTopN;
|
|
|
String configCode = StringUtils.hasText(param.getConfigCode())
|
|
|
? param.getConfigCode() : VectorConstants.DEFAULT_CONFIG_CODE;
|
|
|
|
|
|
- // 并行召回:视频、素材各自独立 topN
|
|
|
+ // 并行召回:视频、素材、文章各自独立 topN
|
|
|
final int finalVideoTopN = videoTopN;
|
|
|
final int finalMaterialTopN = materialTopN;
|
|
|
+ final int finalArticleTopN = articleTopN;
|
|
|
final String finalConfigCode = configCode;
|
|
|
CompletableFuture<List<VideoMatchResult>> videoFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
try {
|
|
|
@@ -175,8 +189,13 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
() -> recallMaterialItems(param.getQueryText(), finalConfigCode, finalMaterialTopN),
|
|
|
RECALL_EXECUTOR);
|
|
|
|
|
|
+ CompletableFuture<List<VideoMatchEnrichedVO>> articleFuture = CompletableFuture.supplyAsync(
|
|
|
+ () -> recallArticleItems(param.getQueryText(), finalConfigCode, finalArticleTopN),
|
|
|
+ RECALL_EXECUTOR);
|
|
|
+
|
|
|
List<VideoMatchResult> videoMatches;
|
|
|
List<VideoMatchEnrichedVO> materialItems;
|
|
|
+ List<VideoMatchEnrichedVO> articleItems;
|
|
|
try {
|
|
|
videoMatches = videoFuture.get(30, TimeUnit.SECONDS);
|
|
|
} catch (Exception e) {
|
|
|
@@ -189,9 +208,15 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
log.error("素材召回等待超时/异常: {}", e.getMessage(), e);
|
|
|
materialItems = Collections.emptyList();
|
|
|
}
|
|
|
+ try {
|
|
|
+ articleItems = articleFuture.get(30, TimeUnit.SECONDS);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("文章召回等待超时/异常: {}", e.getMessage(), e);
|
|
|
+ articleItems = Collections.emptyList();
|
|
|
+ }
|
|
|
|
|
|
List<VideoMatchEnrichedVO> videoItems = enrichVideoMatches(videoMatches, configCode);
|
|
|
- return buildResult(videoItems, materialItems);
|
|
|
+ return buildResult(videoItems, materialItems, articleItems);
|
|
|
}
|
|
|
|
|
|
private List<VideoMatchResult> limitVideoMatchesByScore(List<VideoMatchResult> matches, int topN) {
|
|
|
@@ -246,7 +271,27 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
log.info("素材召回(rawVector) 无结果, configCode={}", configCode);
|
|
|
return Collections.emptyList();
|
|
|
}
|
|
|
- log.info("素材召回 text_hash 缓存未命中, textHash={}, 降级到 embedding API", textHash);
|
|
|
+ log.info("素材召回 rawVector 缓存未命中, textHash={}, 尝试 parsed vector 缓存", textHash);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.hasText(textHash)) {
|
|
|
+ List<Float> cachedVector = materialVectorStoreService.getVectorByTextHash(textHash, configCode);
|
|
|
+ if (cachedVector != null && !cachedVector.isEmpty()) {
|
|
|
+ log.info("素材召回 使用缓存的 parsed vector, dim={}", cachedVector.size());
|
|
|
+ List<MaterialMatch> raw = materialVectorStoreService.searchTopN(configCode, cachedVector, candidate);
|
|
|
+ List<MaterialMatch> matches = deduplicateMaterialMatches(raw, topN);
|
|
|
+ if (!CollectionUtils.isEmpty(matches)) {
|
|
|
+ List<String> matchSample = new ArrayList<>();
|
|
|
+ for (MaterialMatch m : matches) {
|
|
|
+ matchSample.add(m.getMaterialId() + ":" + String.format("%.4f", m.getScore()));
|
|
|
+ }
|
|
|
+ log.info("素材召回(parsed vector缓存) 去重后({}条): {}, configCode={}",
|
|
|
+ matches.size(), matchSample, configCode);
|
|
|
+ return limitEnrichedItemsByScore(enrichMaterialMatches(matches, configCode), topN);
|
|
|
+ }
|
|
|
+ log.info("素材召回(parsed vector缓存) 无结果, configCode={}", configCode);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 降级:embedding API → Float 向量 → 搜索(非缓存路径,容忍精度损失)
|
|
|
@@ -289,19 +334,6 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
log.info("resolveQueryVectorForMaterial: queryText={}, configCode={}, model={}, dim={}",
|
|
|
queryText, configCode, config.getEmbeddingModel(), config.getDimension());
|
|
|
|
|
|
- // 1. 先查 material_vectors 的 text_hash 缓存
|
|
|
- String textHash = Md5Util.encoderByMd5(queryText);
|
|
|
- if (StringUtils.hasText(textHash)) {
|
|
|
- log.info("resolveQueryVectorForMaterial textHash={}, 开始查 text_hash 缓存", textHash);
|
|
|
- List<Float> cached = materialVectorStoreService.getVectorByTextHash(textHash, configCode);
|
|
|
- if (cached != null && !cached.isEmpty()) {
|
|
|
- log.info("resolveQueryVectorForMaterial 命中 text_hash 缓存,dim={}", cached.size());
|
|
|
- return cached;
|
|
|
- }
|
|
|
- log.info("resolveQueryVectorForMaterial text_hash 缓存未命中,降级到 embedding API");
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 调用 embedding API(与入库时相同的 model / dimension)
|
|
|
try {
|
|
|
log.info("resolveQueryVectorForMaterial 调用 embedding API: text={}, model={}, dim={}",
|
|
|
queryText, config.getEmbeddingModel(), config.getDimension());
|
|
|
@@ -467,6 +499,245 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
+ // ====================================================================
|
|
|
+ // 文章召回(对称素材召回)
|
|
|
+ // ====================================================================
|
|
|
+
|
|
|
+ private List<VideoMatchEnrichedVO> recallArticleItems(String queryText, String configCode, int topN) {
|
|
|
+ try {
|
|
|
+ int candidate = Math.max(topN * VectorConstants.MULTI_POINT_RECALL_CANDIDATE_FACTOR,
|
|
|
+ VectorConstants.MULTI_POINT_RECALL_MIN_CANDIDATES);
|
|
|
+
|
|
|
+ String textHash = Md5Util.encoderByMd5(queryText);
|
|
|
+ if (StringUtils.hasText(textHash)) {
|
|
|
+ String rawVector = articleVectorStoreService.getRawVectorByTextHash(textHash, configCode);
|
|
|
+ if (rawVector != null && !rawVector.isEmpty()) {
|
|
|
+ log.info("文章召回 使用缓存的原始向量字符串, configCode={}", configCode);
|
|
|
+ List<ArticleMatch> raw = articleVectorStoreService.searchTopNByRawVector(
|
|
|
+ configCode, rawVector, candidate);
|
|
|
+ List<ArticleMatch> matches = deduplicateArticleMatches(raw, topN);
|
|
|
+ if (!CollectionUtils.isEmpty(matches)) {
|
|
|
+ List<String> matchSample = new ArrayList<>();
|
|
|
+ for (ArticleMatch m : matches) {
|
|
|
+ matchSample.add(m.getArticleId() + ":" + String.format("%.4f", m.getScore()));
|
|
|
+ }
|
|
|
+ log.info("文章召回(rawVector) 去重后({}条): {}, configCode={}",
|
|
|
+ matches.size(), matchSample, configCode);
|
|
|
+ return limitEnrichedItemsByScore(enrichArticleMatches(matches, configCode), topN);
|
|
|
+ }
|
|
|
+ log.info("文章召回(rawVector) 无结果, configCode={}", configCode);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ log.info("文章召回 rawVector 缓存未命中, textHash={}, 尝试 parsed vector 缓存", textHash);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.hasText(textHash)) {
|
|
|
+ List<Float> cachedVector = articleVectorStoreService.getVectorByTextHash(textHash, configCode);
|
|
|
+ if (cachedVector != null && !cachedVector.isEmpty()) {
|
|
|
+ log.info("文章召回 使用缓存的 parsed vector, dim={}", cachedVector.size());
|
|
|
+ List<ArticleMatch> raw = articleVectorStoreService.searchTopN(configCode, cachedVector, candidate);
|
|
|
+ List<ArticleMatch> matches = deduplicateArticleMatches(raw, topN);
|
|
|
+ if (!CollectionUtils.isEmpty(matches)) {
|
|
|
+ List<String> matchSample = new ArrayList<>();
|
|
|
+ for (ArticleMatch m : matches) {
|
|
|
+ matchSample.add(m.getArticleId() + ":" + String.format("%.4f", m.getScore()));
|
|
|
+ }
|
|
|
+ log.info("文章召回(parsed vector缓存) 去重后({}条): {}, configCode={}",
|
|
|
+ matches.size(), matchSample, configCode);
|
|
|
+ return limitEnrichedItemsByScore(enrichArticleMatches(matches, configCode), topN);
|
|
|
+ }
|
|
|
+ log.info("文章召回(parsed vector缓存) 无结果, configCode={}", configCode);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Float> queryVector = resolveQueryVectorForArticle(queryText, configCode);
|
|
|
+ if (queryVector == null || queryVector.isEmpty()) {
|
|
|
+ log.info("文章召回: 无法获取查询向量, queryText={}", queryText);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ log.info("文章召回 使用 embedding API 向量, dim={}", queryVector.size());
|
|
|
+ List<ArticleMatch> raw = articleVectorStoreService.searchTopN(configCode, queryVector, candidate);
|
|
|
+ List<ArticleMatch> matches = deduplicateArticleMatches(raw, topN);
|
|
|
+ if (CollectionUtils.isEmpty(matches)) {
|
|
|
+ log.info("文章召回 article_vectors 无结果, configCode={}", configCode);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<String> matchSample = new ArrayList<>();
|
|
|
+ for (ArticleMatch m : matches) {
|
|
|
+ matchSample.add(m.getArticleId() + ":" + String.format("%.4f", m.getScore()));
|
|
|
+ }
|
|
|
+ log.info("文章召回(embedding API) 去重后({}条): {}, configCode={}", matches.size(), matchSample, configCode);
|
|
|
+ return limitEnrichedItemsByScore(enrichArticleMatches(matches, configCode), topN);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("文章召回 article_vectors 异常: {}", e.getMessage(), e);
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Float> resolveQueryVectorForArticle(String queryText, String configCode) {
|
|
|
+ if (!StringUtils.hasText(queryText)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ DeconstructVectorConfig config = getVectorConfigByCode(configCode);
|
|
|
+ if (config == null) {
|
|
|
+ config = new DeconstructVectorConfig();
|
|
|
+ config.setConfigCode(configCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ log.info("resolveQueryVectorForArticle 调用 embedding API: text={}, model={}, dim={}",
|
|
|
+ queryText, config.getEmbeddingModel(), config.getDimension());
|
|
|
+ return embeddingService.embed(queryText, config);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("文章召回 embedding 失败: queryText={}, error={}", queryText, e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<ArticleMatch> deduplicateArticleMatches(List<ArticleMatch> matches, int topN) {
|
|
|
+ if (CollectionUtils.isEmpty(matches)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ Map<String, ArticleMatch> deduped = new LinkedHashMap<>();
|
|
|
+ for (ArticleMatch m : matches) {
|
|
|
+ if (m == null || !StringUtils.hasText(m.getArticleId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ ArticleMatch existing = deduped.get(m.getArticleId());
|
|
|
+ if (existing == null || m.getScore() > existing.getScore()) {
|
|
|
+ deduped.put(m.getArticleId(), m);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return deduped.values().stream().limit(topN).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<VideoMatchEnrichedVO> enrichArticleMatches(List<ArticleMatch> matches, String requestConfigCode) {
|
|
|
+ if (CollectionUtils.isEmpty(matches)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<String> articleIds = matches.stream()
|
|
|
+ .map(ArticleMatch::getArticleId)
|
|
|
+ .filter(java.util.Objects::nonNull)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ Map<String, ArticleDeconstructResult> rowByArticleId = loadArticleDeconstructRows(articleIds);
|
|
|
+
|
|
|
+ List<VideoMatchEnrichedVO> items = new ArrayList<>(matches.size());
|
|
|
+ for (ArticleMatch m : matches) {
|
|
|
+ if (m == null || m.getArticleId() == null) continue;
|
|
|
+ VideoMatchEnrichedVO vo = new VideoMatchEnrichedVO();
|
|
|
+ vo.setModality(Modality.ARTICLE);
|
|
|
+ vo.setConfigCode(requestConfigCode);
|
|
|
+ vo.setScore(m.getScore());
|
|
|
+
|
|
|
+ ArticleDeconstructResult row = rowByArticleId.get(m.getArticleId());
|
|
|
+ JSONObject raw = parseArticleResultJson(row);
|
|
|
+ ArticleBasicMeta basic = raw != null ? extractArticleBasicMeta(raw) : null;
|
|
|
+ Map<String, Object> deconstructFlat = raw != null ? buildDeconstructFromRaw(raw) : null;
|
|
|
+
|
|
|
+ String displayArticleId = (basic != null && StringUtils.hasText(basic.articleId))
|
|
|
+ ? basic.articleId : m.getArticleId();
|
|
|
+ vo.setArticleId(displayArticleId);
|
|
|
+ try {
|
|
|
+ vo.setId(Long.parseLong(displayArticleId));
|
|
|
+ } catch (NumberFormatException ignored) {
|
|
|
+ }
|
|
|
+
|
|
|
+ if (basic != null) {
|
|
|
+ vo.setTitle(basic.title);
|
|
|
+ vo.setCover(basic.cover);
|
|
|
+ if (basic.images != null && !basic.images.isEmpty()) {
|
|
|
+ vo.setImageList(basic.images);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ArticleDetailVO detail = new ArticleDetailVO();
|
|
|
+ if (basic != null) {
|
|
|
+ detail.setTitle(basic.title);
|
|
|
+ detail.setSummary(basic.summary);
|
|
|
+ detail.setCover(basic.cover);
|
|
|
+ detail.setImages(basic.images);
|
|
|
+ }
|
|
|
+ detail.setDeconstruct(deconstructFlat);
|
|
|
+ vo.setArticleDetail(detail);
|
|
|
+
|
|
|
+ applyCompatibilityFields(vo);
|
|
|
+ items.add(vo);
|
|
|
+ }
|
|
|
+ return items;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, ArticleDeconstructResult> loadArticleDeconstructRows(List<String> articleIds) {
|
|
|
+ if (CollectionUtils.isEmpty(articleIds)) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ Map<String, ArticleDeconstructResult> result = new HashMap<>();
|
|
|
+ try {
|
|
|
+ List<ArticleDeconstructResult> rows = articleDeconstructResultMapperExt
|
|
|
+ .selectResultsByArticleIds(SOURCE_AIGC, articleIds);
|
|
|
+ if (CollectionUtils.isEmpty(rows)) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ for (ArticleDeconstructResult row : rows) {
|
|
|
+ if (row == null || !StringUtils.hasText(row.getArticleId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ result.putIfAbsent(row.getArticleId(), row);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量加载 article_deconstruct_result 失败: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private JSONObject parseArticleResultJson(ArticleDeconstructResult row) {
|
|
|
+ if (row == null || !StringUtils.hasText(row.getResult())) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return JSON.parseObject(row.getResult());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.info("解析 article_deconstruct_result.result 失败 articleId={}: {}",
|
|
|
+ row.getArticleId(), e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private ArticleBasicMeta extractArticleBasicMeta(JSONObject raw) {
|
|
|
+ if (raw == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ JSONObject targetPost = raw.getJSONObject("target_post");
|
|
|
+ if (targetPost == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ ArticleBasicMeta meta = new ArticleBasicMeta();
|
|
|
+ meta.title = targetPost.getString("title");
|
|
|
+ String bodyText = targetPost.getString("body_text");
|
|
|
+ if (StringUtils.hasText(bodyText)) {
|
|
|
+ meta.summary = bodyText.length() > 120 ? bodyText.substring(0, 120) : bodyText;
|
|
|
+ }
|
|
|
+ meta.articleId = targetPost.getString("channel_content_id");
|
|
|
+
|
|
|
+ JSONArray imagesArr = targetPost.getJSONArray("images");
|
|
|
+ if (imagesArr != null && !imagesArr.isEmpty()) {
|
|
|
+ meta.cover = imagesArr.getString(0);
|
|
|
+ meta.images = new ArrayList<>(imagesArr.size());
|
|
|
+ for (int i = 0; i < imagesArr.size(); i++) {
|
|
|
+ String img = imagesArr.getString(i);
|
|
|
+ if (StringUtils.hasText(img)) {
|
|
|
+ meta.images.add(img);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!StringUtils.hasText(meta.title)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return meta;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 批量加载 material_deconstruct_result 原始行(保留 source_type / result)
|
|
|
*/
|
|
|
@@ -665,21 +936,26 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 组装返回结果:视频 + 素材合并为 items。
|
|
|
- * 各模态在前置链路已按 videoTopN / materialTopN 各自截断,此处仅拼接 + 计数,不做合并截断。
|
|
|
+ * 组装返回结果:视频 + 素材 + 文章合并为 items。
|
|
|
+ * 各模态在前置链路已按各自 topN 截断,此处仅拼接 + 计数,不做合并截断。
|
|
|
*/
|
|
|
private RecallResultVO buildResult(List<VideoMatchEnrichedVO> videoItems,
|
|
|
- List<VideoMatchEnrichedVO> materialItems) {
|
|
|
+ List<VideoMatchEnrichedVO> materialItems,
|
|
|
+ List<VideoMatchEnrichedVO> articleItems) {
|
|
|
if (videoItems == null) {
|
|
|
videoItems = Collections.emptyList();
|
|
|
}
|
|
|
if (materialItems == null) {
|
|
|
materialItems = Collections.emptyList();
|
|
|
}
|
|
|
+ if (articleItems == null) {
|
|
|
+ articleItems = Collections.emptyList();
|
|
|
+ }
|
|
|
|
|
|
- List<VideoMatchEnrichedVO> all = new ArrayList<>(videoItems.size() + materialItems.size());
|
|
|
+ List<VideoMatchEnrichedVO> all = new ArrayList<>(videoItems.size() + materialItems.size() + articleItems.size());
|
|
|
all.addAll(videoItems);
|
|
|
all.addAll(materialItems);
|
|
|
+ all.addAll(articleItems);
|
|
|
|
|
|
int videoCount = 0;
|
|
|
int materialCount = 0;
|
|
|
@@ -743,7 +1019,7 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
String configCode = StringUtils.hasText(param.getConfigCode())
|
|
|
? param.getConfigCode() : VectorConstants.DEFAULT_CONFIG_CODE;
|
|
|
List<VideoMatchEnrichedVO> videoItems = enrichVideoMatches(rawMatches, configCode);
|
|
|
- return buildResult(videoItems, Collections.emptyList());
|
|
|
+ return buildResult(videoItems, Collections.emptyList(), Collections.emptyList());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -882,4 +1158,14 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
|
|
|
String title;
|
|
|
String imagesJson;
|
|
|
}
|
|
|
+
|
|
|
+ private static class ArticleBasicMeta {
|
|
|
+ String articleId;
|
|
|
+ String title;
|
|
|
+ String summary;
|
|
|
+ List<String> tags;
|
|
|
+ String cover;
|
|
|
+ String url;
|
|
|
+ List<String> images;
|
|
|
+ }
|
|
|
}
|