Procházet zdrojové kódy

搜索改成 title 关键字 + 向量召回 并集,池内 substring 与向量交集 union dedupe;同 video_id 重叠用向量 score 升级排序;sort 仍为 score DESC, sceneSumRov DESC。修复向量服务空返回时纯关键字命中也会被丢弃的问题。

刘立冬 před 7 hodinami
rodič
revize
a541ea8c19

+ 41 - 22
api-module/src/main/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImpl.java

@@ -2025,7 +2025,7 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     }
 
     /**
-     * 搜索:在当前入口的 demand 白名单(prior+posterior 池,rov>=0.02)内对向量召回结果做交集 + 排序。
+     * 搜索:在当前入口的 demand 白名单(prior+posterior 池,rov>=0.02)内做 title LIKE 关键字命中 + 向量召回,并集去重后统一排序。
      * 门控:
      *   - 用户身份 type ∈ {2 自营, 3 代理},否则返回空
      *   - 渠道 ∈ {小程序投流-稳定, 公众号投流-稳定},否则返回空
@@ -2033,10 +2033,10 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
      * 流程:
      *   1. demand 白名单(channel + crowdSegment/channelLevel3 + demand_strategy IN (人群需求,优质相似) + rov>=0.02)
      *   2. 同 video_id 取 sceneSumRov 最大的代表行
-     *   3. 向量召回 topN=500
-     *   4. 交集:仅保留向量召回中出现在白名单的 video_id
-     *   5. 排序:向量 score DESC,缺失/相同时 sceneSumRov DESC
-     *   6. 一次性全量返回(不分页;白名单+召回交集量级小)
+     *   3. Pass1: 池内 title.toLowerCase().contains(query) 关键字命中,score=null
+     *   4. Pass2: 向量召回 topN=500,池内交集;同 video_id 已被关键字命中则用向量 score 升级
+     *   5. 排序:score DESC(向量 score 优先),缺失/相同时 sceneSumRov DESC
+     *   6. 一次性算好全量,按 pageNum/pageSize 切片返回(翻页不再调向量服务)
      */
     private Page<VideoContentItemVO> searchByTitleInDemandPool(VideoContentListParam param, ContentPlatformAccount user) {
         Page<VideoContentItemVO> empty = new Page<>(param.getPageNum(), param.getPageSize());
@@ -2075,26 +2075,45 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
             }
         }
 
-        JSONObject vectorData = managerApiService.recallVideoWithScore(param.getTitle(), SEARCH_VECTOR_TOP_N);
-        if (vectorData == null) return empty;
-        JSONArray items = vectorData.getJSONArray("items");
-        if (items == null || items.isEmpty()) return empty;
-
-        // 交集:按向量召回顺序遍历,留下 whitelist 命中的 video_id;同 video_id 去重
-        List<VideoContentItemVO> hits = new ArrayList<>();
-        Set<Long> seen = new HashSet<>();
-        for (int i = 0; i < items.size(); i++) {
-            JSONObject item = items.getJSONObject(i);
-            Long videoId = item.getLong("videoId");
-            if (videoId == null || !bestPerVideo.containsKey(videoId) || !seen.add(videoId)) continue;
-            ContentPlatformDemandVideo demand = bestPerVideo.get(videoId);
+        // Pass 1: title LIKE 关键字命中(池内 substring,大小写不敏感),score=null 兜底 sceneSumRov
+        String kw = param.getTitle().toLowerCase();
+        Map<Long, VideoContentItemVO> byVid = new LinkedHashMap<>();
+        for (ContentPlatformDemandVideo demand : bestPerVideo.values()) {
+            if (demand.getTitle() == null) continue;
+            if (!demand.getTitle().toLowerCase().contains(kw)) continue;
             VideoContentItemVO vo = buildDemandVideoContentItemVOList(Collections.singletonList(demand)).get(0);
-            vo.setSearchSource("vector");
-            vo.setScore(item.getDouble("score"));  // 用向量 score 覆盖 demand.score,主排序键
-            hits.add(vo);
+            vo.setSearchSource("keyword");
+            vo.setScore(null);
+            byVid.put(demand.getVideoId(), vo);
+        }
+
+        // Pass 2: 向量召回,池内交集;若已被 title 命中,用向量 score 升级以提升排序
+        JSONObject vectorData = managerApiService.recallVideoWithScore(param.getTitle(), SEARCH_VECTOR_TOP_N);
+        JSONArray items = vectorData == null ? null : vectorData.getJSONArray("items");
+        if (items != null) {
+            for (int i = 0; i < items.size(); i++) {
+                JSONObject item = items.getJSONObject(i);
+                Long videoId = item.getLong("videoId");
+                if (videoId == null || !bestPerVideo.containsKey(videoId)) continue;
+                Double vScore = item.getDouble("score");
+                VideoContentItemVO existing = byVid.get(videoId);
+                if (existing != null) {
+                    existing.setSearchSource("both");
+                    existing.setScore(vScore);
+                } else {
+                    ContentPlatformDemandVideo demand = bestPerVideo.get(videoId);
+                    VideoContentItemVO vo = buildDemandVideoContentItemVOList(Collections.singletonList(demand)).get(0);
+                    vo.setSearchSource("vector");
+                    vo.setScore(vScore);
+                    byVid.put(videoId, vo);
+                }
+            }
         }
 
-        // 排序:向量 score DESC,缺失/相同时 sceneSumRov DESC
+        if (byVid.isEmpty()) return empty;
+        List<VideoContentItemVO> hits = new ArrayList<>(byVid.values());
+
+        // 排序:score DESC(向量 score 优先),缺失/相同时 sceneSumRov DESC
         hits.sort((a, b) -> {
             double sa = a.getScore() == null ? 0d : a.getScore();
             double sb = b.getScore() == null ? 0d : b.getScore();