You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Elasticsearch语义搜索中添加分面、过滤与权重提升

问题描述

已实现基于OpenAI、SpringBoot、Vaadin的Elasticsearch语义搜索并运行正常,现需为查询添加facet(分面)、**filter(过滤)和boost(权重提升)**功能。

Post实体类

@Document(
    indexName="posts"
)
@ToString
public class Post {

    @Id
    private String id;

    @Field(type = FieldType.Text)
    private String title;

    @Field(type = FieldType.Text)
    private String content;

    @Field(type = FieldType.Text)
    private String color;// e.g. red, blue, green

    @Field(type = FieldType.Text)
    private String size;//e.g XS,S,M,L,XL,XXL

    @Field(type = FieldType.Dense_Vector, dims = 1536, index = true)
    private Vector<Double> embedding;

    public Post(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

当前基础查询代码

@Repository
public interface PostRepository extends ElasticsearchRepository<Post, String>{

    @Query("{ " +
            "    \"script_score\": { " +
            "       \"query\": {\"match_all\": {} }, " +
            "       \"script\": { "+
            "           \"source\": \"cosineSimilarity(params.queryVector, 'embedding') +0.1\", "+
            "           \"params\": {\"queryVector\": ?0 } "+
            "       } "+
            "   } "+
            "}")
    List<Post> findBySimilar( String content);
}

正确实现方案

1. 过滤(Filter)功能

前置优化:字段类型调整

color和size是离散枚举值,当前Text类型会被分词,不适合精确匹配。建议修改为Keyword类型:

@Field(type = FieldType.Keyword)
private String color;// e.g. red, blue, green

@Field(type = FieldType.Keyword)
private String size;//e.g XS,S,M,L,XL,XXL

动态过滤查询实现

支持动态传入过滤参数,将过滤条件嵌入bool查询的filter节点(不影响评分):

@Repository
public interface PostRepository extends ElasticsearchRepository<Post, String> {

    @Query("{ " +
            "  \"bool\": { " +
            "    \"must\": { " +
            "      \"script_score\": { " +
            "        \"query\": { \"match_all\": {} }, " +
            "        \"script\": { " +
            "          \"source\": \"cosineSimilarity(params.queryVector, 'embedding') + 0.1\", " +
            "          \"params\": { \"queryVector\": ?0 } " +
            "        } " +
            "      } " +
            "    }, " +
            "    \"filter\": [ " +
            "      { \"term\": { \"color\": ?1 } }, " +
            "      { \"term\": { \"size\": ?2 } } " +
            "    ] " +
            "  } " +
            "}")
    List<Post> findBySimilarWithFilter(Vector<Double> queryVector, String color, String size);
}

若无法修改字段类型,可使用自动生成的keyword子字段匹配:{ "term": { "color.keyword": ?1 } }

2. 权重提升(Boost)功能

方式一:全局评分系数提升

直接在script_score的脚本中加入权重系数,整体提升符合条件结果的评分:

@Query("{ " +
        "  \"bool\": { " +
        "    \"must\": { " +
        "      \"script_score\": { " +
        "        \"query\": { \"match_all\": {} }, " +
        "        \"script\": { " +
        "          \"source\": \"(cosineSimilarity(params.queryVector, 'embedding') + 0.1) * params.boostFactor\", " +
        "          \"params\": { " +
        "            \"queryVector\": ?0, " +
        "            \"boostFactor\": ?1 " +
        "          } " +
        "        } " +
        "      } " +
        "    }, " +
        "    \"filter\": [ { \"term\": { \"color\": ?2 } } ] " +
        "  } " +
        "}")
List<Post> findBySimilarWithBoost(Vector<Double> queryVector, double boostFactor, String color);

方式二:特定字段匹配提升

对标题等核心字段的匹配结果额外加权,结合bool的should节点实现:

@Query("{ " +
        "  \"bool\": { " +
        "    \"should\": [ " +
        "      { " +
        "        \"script_score\": { " +
        "          \"query\": { \"match_all\": {} }, " +
        "          \"script\": { " +
        "            \"source\": \"cosineSimilarity(params.queryVector, 'embedding') + 0.1\", " +
        "            \"params\": { \"queryVector\": ?0 } " +
        "          } " +
        "        } " +
        "      }, " +
        "      { " +
        "        \"match\": { " +
        "          \"title\": { " +
        "            \"query\": ?1, " +
        "            \"boost\": 2.0 " +
        "          } " +
        "        } " +
        "      } " +
        "    ], " +
        "    \"filter\": [ { \"term\": { \"color\": ?2 } } ] " +
        "  } " +
        "}")
List<Post> findBySimilarWithFieldBoost(Vector<Double> queryVector, String titleKeyword, String color);

3. 分面(Facet)功能

分面查询依赖Elasticsearch的聚合能力,需通过ElasticsearchOperations构建查询并解析聚合结果:

服务层实现

@Service
public class PostSearchService {

    private final ElasticsearchOperations elasticsearchOperations;

    public PostSearchService(ElasticsearchOperations elasticsearchOperations) {
        this.elasticsearchOperations = elasticsearchOperations;
    }

    public SearchHits<Post> searchWithFacets(Vector<Double> queryVector, String colorFilter) {
        // 构建基础语义查询+过滤条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.scriptScoreQuery(
                QueryBuilders.matchAllQuery(),
                new Script(ScriptType.INLINE, "painless",
                        "cosineSimilarity(params.queryVector, 'embedding') + 0.1",
                        Map.of("queryVector", queryVector))
        ));
        if (colorFilter != null && !colorFilter.isEmpty()) {
            boolQuery.filter(QueryBuilders.termQuery("color", colorFilter));
        }

        // 添加分面聚合:按color和size分组统计
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQuery)
                .addAggregation(AggregationBuilders.terms("color_facet").field("color"))
                .addAggregation(AggregationBuilders.terms("size_facet").field("size"))
                .build();

        return elasticsearchOperations.search(searchQuery, Post.class);
    }

    // 解析聚合结果为分面统计数据
    public Map<String, Map<String, Long>> getFacetResults(SearchHits<Post> searchHits) {
        Map<String, Map<String, Long>> facets = new HashMap<>();

        // 解析color分面
        Terms colorTerms = searchHits.getAggregations().get("color_facet");
        if (colorTerms != null) {
            Map<String, Long> colorCounts = new HashMap<>();
            for (Terms.Bucket bucket : colorTerms.getBuckets()) {
                colorCounts.put(bucket.getKeyAsString(), bucket.getDocCount());
            }
            facets.put("color", colorCounts);
        }

        // 解析size分面
        Terms sizeTerms = searchHits.getAggregations().get("size_facet");
        if (sizeTerms != null) {
            Map<String, Long> sizeCounts = new HashMap<>();
            for (Terms.Bucket bucket : sizeTerms.getBuckets()) {
                sizeCounts.put(bucket.getKeyAsString(), bucket.getDocCount());
            }
            facets.put("size", sizeCounts);
        }

        return facets;
    }
}

业务代码调用示例

// 执行带分面的语义搜索
SearchHits<Post> searchResults = postSearchService.searchWithFacets(queryEmbedding, "red");
// 获取搜索结果列表
List<Post> posts = searchResults.stream().map(SearchHit::getContent).collect(Collectors.toList());
// 获取分面统计数据(如各color的数量、各size的数量)
Map<String, Map<String, Long>> facets = postSearchService.getFacetResults(searchResults);

内容的提问来源于stack exchange,提问作者Shahid Salim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 07:12:06