From 090cb156acbfcaec460415180a5d4af37b1c515f Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl <15071461069@163.com> Date: Sun, 15 Mar 2026 17:09:46 +0800 Subject: [PATCH] tune(search): improve semantic reranking quality --- .../search/HashingSearchEmbeddingService.java | 23 +++++++++++++++---- .../PostgresFullTextIndexService.java | 1 + .../HashingSearchEmbeddingServiceTest.java | 11 +++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/server/skillhub-search/src/main/java/com/iflytek/skillhub/search/HashingSearchEmbeddingService.java b/server/skillhub-search/src/main/java/com/iflytek/skillhub/search/HashingSearchEmbeddingService.java index 1ed4a72c..b92efe24 100644 --- a/server/skillhub-search/src/main/java/com/iflytek/skillhub/search/HashingSearchEmbeddingService.java +++ b/server/skillhub-search/src/main/java/com/iflytek/skillhub/search/HashingSearchEmbeddingService.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service; public class HashingSearchEmbeddingService implements SearchEmbeddingService { private static final Pattern TOKEN_SPLITTER = Pattern.compile("[^\\p{L}\\p{N}_]+"); private static final int DIMENSIONS = 64; + private static final double NGRAM_WEIGHT = 0.35D; @Override public String embed(String text) { @@ -47,16 +48,30 @@ public class HashingSearchEmbeddingService implements SearchEmbeddingService { .map(String::trim) .filter(token -> !token.isBlank()) .forEach(token -> { - int hash = token.hashCode(); - int index = Math.floorMod(hash, DIMENSIONS); - double weight = 1D + Math.min(token.length(), 12) / 12D; - vector[index] += weight; + addTokenWeight(vector, token, 1D + Math.min(token.length(), 12) / 12D); + addCharacterNgrams(vector, token); }); normalize(vector); return vector; } + private void addTokenWeight(double[] vector, String token, double weight) { + int hash = token.hashCode(); + int index = Math.floorMod(hash, DIMENSIONS); + vector[index] += weight; + } + + private void addCharacterNgrams(double[] vector, String token) { + if (token.length() < 3) { + return; + } + for (int i = 0; i <= token.length() - 3; i++) { + String trigram = token.substring(i, i + 3); + addTokenWeight(vector, trigram, NGRAM_WEIGHT); + } + } + private double[] parseVector(String serializedVector) { String[] parts = serializedVector.split(","); double[] vector = new double[parts.length]; diff --git a/server/skillhub-search/src/main/java/com/iflytek/skillhub/search/postgres/PostgresFullTextIndexService.java b/server/skillhub-search/src/main/java/com/iflytek/skillhub/search/postgres/PostgresFullTextIndexService.java index 7db8bcca..b856b82a 100644 --- a/server/skillhub-search/src/main/java/com/iflytek/skillhub/search/postgres/PostgresFullTextIndexService.java +++ b/server/skillhub-search/src/main/java/com/iflytek/skillhub/search/postgres/PostgresFullTextIndexService.java @@ -75,6 +75,7 @@ public class PostgresFullTextIndexService implements SearchIndexService { private String buildSemanticVector(SkillSearchDocument document) { return searchEmbeddingService.embed(String.join("\n", + safe(document.title()), safe(document.title()), safe(document.summary()), safe(document.keywords()), diff --git a/server/skillhub-search/src/test/java/com/iflytek/skillhub/search/HashingSearchEmbeddingServiceTest.java b/server/skillhub-search/src/test/java/com/iflytek/skillhub/search/HashingSearchEmbeddingServiceTest.java index d916cc8d..c0412a16 100644 --- a/server/skillhub-search/src/test/java/com/iflytek/skillhub/search/HashingSearchEmbeddingServiceTest.java +++ b/server/skillhub-search/src/test/java/com/iflytek/skillhub/search/HashingSearchEmbeddingServiceTest.java @@ -26,4 +26,15 @@ class HashingSearchEmbeddingServiceTest { assertThat(relevant).isGreaterThan(noisy); } + + @Test + void similarityShouldHandleSingularAndPluralForms() { + String pluralVector = service.embed("build strong habits with daily practice"); + String unrelatedVector = service.embed("research company profiles on the web"); + + double pluralMatch = service.similarity("habit", pluralVector); + double unrelatedMatch = service.similarity("habit", unrelatedVector); + + assertThat(pluralMatch).isGreaterThan(unrelatedMatch); + } }