JAVA 构建多 Index 召回集成架构,实现复杂场景语义增强检索链优化

JAVA 构建多 Index 召回集成架构,实现复杂场景语义增强检索链优化

大家好,今天我们来聊聊如何使用 Java 构建一个多 Index 召回集成架构,并针对复杂场景进行语义增强检索链的优化。 在实际的业务场景中,特别是涉及到电商、内容平台等领域,用户查询的复杂性日益增加,单一的检索策略往往难以满足需求。我们需要结合多种召回策略,并利用语义增强技术来提升检索的准确性和召回率。

一、多 Index 召回架构概述

多 Index 召回架构的核心思想是将数据按照不同的维度或特征进行索引,然后针对用户的查询,并行地从多个索引中召回候选结果,最后进行合并、排序和过滤,得到最终的检索结果。 这种架构的优势在于:

  • 提高召回率: 不同的 Index 可以覆盖不同的数据子集,从而提高整体的召回率。
  • 灵活适应复杂查询: 可以根据查询的不同特征,选择不同的 Index 进行检索。
  • 提高检索效率: 并行检索多个 Index 可以缩短整体的检索时间。

1.1 架构设计

一个典型的多 Index 召回架构包含以下几个核心组件:

  • 数据预处理: 对原始数据进行清洗、转换和特征提取,为构建 Index 做好准备。
  • Index 构建: 根据不同的维度或特征,构建多个 Index,例如:

    • 全文 Index: 基于文本内容进行索引,支持关键词检索。
    • 结构化 Index: 基于结构化数据(例如商品属性)进行索引,支持属性过滤和排序。
    • 向量 Index: 基于向量化表示的数据进行索引,支持语义相似度检索。
  • 查询解析: 对用户的查询进行解析,提取关键词、属性条件、语义信息等。
  • Index 选择: 根据查询的特征,选择合适的 Index 进行检索。
  • 并行检索: 并行地从多个 Index 中召回候选结果。
  • 结果合并: 将多个 Index 召回的结果进行合并,去除重复项。
  • 排序和过滤: 根据相关性、业务规则等对合并后的结果进行排序和过滤。
  • 结果返回: 将最终的检索结果返回给用户。

1.2 架构图示

graph LR
    A[用户查询] --> B(查询解析);
    B --> C{Index 选择};
    C --> D[全文 Index];
    C --> E[结构化 Index];
    C --> F[向量 Index];
    D --> G(检索结果1);
    E --> H(检索结果2);
    F --> I(检索结果3);
    G --> J(结果合并);
    H --> J;
    I --> J;
    J --> K(排序和过滤);
    K --> L[最终结果];

二、Java 实现多 Index 召回架构

接下来,我们使用 Java 代码来实现一个简单的多 Index 召回架构。这里我们使用 Elasticsearch 作为 Index 的存储引擎。

2.1 依赖引入

首先,我们需要在 Maven 项目中引入 Elasticsearch 的 Java 客户端:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.17.6</version>
</dependency>

2.2 数据预处理

假设我们有一个商品数据集,包含以下字段:

字段名 类型 描述
id String 商品 ID
title String 商品标题
description String 商品描述
category String 商品分类
price Double 商品价格
attributes Map<String, Object> 商品属性(例如颜色、尺寸)
embedding float[] 标题的向量化表示

我们需要对这些数据进行预处理,例如:

  • 文本清洗: 去除 HTML 标签、特殊字符等。
  • 分词: 将文本内容分割成词语。
  • 向量化: 将文本内容转换为向量表示,可以使用 Word2Vec、BERT 等模型。
  • 类型转换: 将数据转换为 Elasticsearch 支持的类型。
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;

import java.io.IOException;
import java.util.Map;

public class ProductDocument {

    private String id;
    private String title;
    private String description;
    private String category;
    private Double price;
    private Map<String, Object> attributes;
    private float[] embedding;

    // Getters and setters

    public XContentBuilder toXContent() throws IOException {
        XContentBuilder builder = XContentFactory.jsonBuilder();
        builder.startObject();
        builder.field("id", id);
        builder.field("title", title);
        builder.field("description", description);
        builder.field("category", category);
        builder.field("price", price);
        builder.field("attributes", attributes);
        builder.field("embedding", embedding);
        builder.endObject();
        return builder;
    }
}

2.3 Index 构建

我们构建三个 Index:

  • product_title_index 全文 Index,基于商品标题进行索引。
  • product_category_index 结构化 Index,基于商品分类和价格进行索引。
  • product_embedding_index 向量 Index,基于商品标题的向量表示进行索引。
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.mapper.MapperService;

import java.io.IOException;

public class IndexCreator {

    private final RestHighLevelClient client;

    public IndexCreator(RestHighLevelClient client) {
        this.client = client;
    }

    public void createTitleIndex(String indexName) throws IOException {
        CreateIndexRequest request = new CreateIndexRequest(indexName);
        Settings.Builder settingsBuilder = Settings.builder()
                .put("index.number_of_shards", 3)
                .put("index.number_of_replicas", 1);
        request.settings(settingsBuilder);

        XContentBuilder mappingBuilder = XContentFactory.jsonBuilder();
        mappingBuilder.startObject();
        mappingBuilder.startObject("properties");
        mappingBuilder.startObject("title");
        mappingBuilder.field("type", "text");
        mappingBuilder.field("analyzer", "ik_max_word"); // 使用IK分词器
        mappingBuilder.endObject();
        mappingBuilder.endObject();
        mappingBuilder.endObject();

        request.mapping(mappingBuilder);

        client.indices().create(request, RequestOptions.DEFAULT);
    }

    public void createCategoryIndex(String indexName) throws IOException {
        CreateIndexRequest request = new CreateIndexRequest(indexName);
        Settings.Builder settingsBuilder = Settings.builder()
                .put("index.number_of_shards", 3)
                .put("index.number_of_replicas", 1);
        request.settings(settingsBuilder);

        XContentBuilder mappingBuilder = XContentFactory.jsonBuilder();
        mappingBuilder.startObject();
        mappingBuilder.startObject("properties");
        mappingBuilder.startObject("category");
        mappingBuilder.field("type", "keyword");
        mappingBuilder.endObject();
        mappingBuilder.startObject("price");
        mappingBuilder.field("type", "double");
        mappingBuilder.endObject();
        mappingBuilder.endObject();
        mappingBuilder.endObject();

        request.mapping(mappingBuilder);

        client.indices().create(request, RequestOptions.DEFAULT);
    }

    public void createEmbeddingIndex(String indexName, int dimension) throws IOException {
        CreateIndexRequest request = new CreateIndexRequest(indexName);
        Settings.Builder settingsBuilder = Settings.builder()
                .put("index.number_of_shards", 3)
                .put("index.number_of_replicas", 1);
        request.settings(settingsBuilder);

        XContentBuilder mappingBuilder = XContentFactory.jsonBuilder();
        mappingBuilder.startObject();
        mappingBuilder.startObject("properties");
        mappingBuilder.startObject("embedding");
        mappingBuilder.field("type", "dense_vector");
        mappingBuilder.field("dims", dimension);
        mappingBuilder.field("index", true);
        mappingBuilder.field("similarity", "cosine"); // 使用余弦相似度
        mappingBuilder.endObject();
        mappingBuilder.endObject();
        mappingBuilder.endObject();

        request.mapping(mappingBuilder);

        client.indices().create(request, RequestOptions.DEFAULT);
    }

    public static void main(String[] args) throws IOException {
        RestHighLevelClient client = new RestHighLevelClientBuilder().build(); // 假设已经初始化
        IndexCreator creator = new IndexCreator(client);
        creator.createTitleIndex("product_title_index");
        creator.createCategoryIndex("product_category_index");
        creator.createEmbeddingIndex("product_embedding_index", 128); // 假设向量维度为128
        client.close();
    }
}

2.4 查询解析

查询解析器的作用是将用户的查询转换为 Elasticsearch 的查询语句。

import java.util.HashMap;
import java.util.Map;

public class QueryParser {

    public Map<String, Object> parse(String query) {
        Map<String, Object> parsedQuery = new HashMap<>();

        // 简单的关键词提取
        parsedQuery.put("keywords", query);

        // 模拟提取分类
        if (query.contains("手机")) {
            parsedQuery.put("category", "手机");
        }

        // 模拟提取价格范围 (实际应根据更复杂的逻辑处理)
        if (query.contains("1000元")) {
            parsedQuery.put("price_range", "0-1000");
        }

        return parsedQuery;
    }
}

2.5 Index 选择

根据查询的特征,选择合适的 Index 进行检索。

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class IndexSelector {

    public List<String> select(Map<String, Object> parsedQuery) {
        List<String> selectedIndices = new ArrayList<>();

        // 如果有关键词,选择全文 Index
        if (parsedQuery.containsKey("keywords") && parsedQuery.get("keywords") != null && !parsedQuery.get("keywords").toString().isEmpty()) {
            selectedIndices.add("product_title_index");
        }

        // 如果有分类或价格范围,选择结构化 Index
        if (parsedQuery.containsKey("category") || parsedQuery.containsKey("price_range")) {
            selectedIndices.add("product_category_index");
        }

        // 总是选择向量 Index,进行语义召回
        selectedIndices.add("product_embedding_index");

        return selectedIndices;
    }
}

2.6 并行检索

并行地从多个 Index 中召回候选结果。

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Searcher {

    private final RestHighLevelClient client;
    private final ExecutorService executor = Executors.newFixedThreadPool(3); // 使用线程池

    public Searcher(RestHighLevelClient client) {
        this.client = client;
    }

    public CompletableFuture<List<SearchHit>> searchTitleIndex(String keywords) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                SearchRequest searchRequest = new SearchRequest("product_title_index");
                SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
                MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", keywords);
                sourceBuilder.query(matchQueryBuilder);
                searchRequest.source(sourceBuilder);

                SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
                List<SearchHit> hits = new ArrayList<>();
                for (SearchHit hit : searchResponse.getHits().getHits()) {
                    hits.add(hit);
                }
                return hits;
            } catch (IOException e) {
                e.printStackTrace();
                return new ArrayList<>(); // 处理异常,返回空列表
            }
        }, executor);
    }

    public CompletableFuture<List<SearchHit>> searchCategoryIndex(Map<String, Object> parsedQuery) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                SearchRequest searchRequest = new SearchRequest("product_category_index");
                SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
                BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

                if (parsedQuery.containsKey("category")) {
                    String category = (String) parsedQuery.get("category");
                    boolQueryBuilder.must(QueryBuilders.termQuery("category", category));
                }

                if (parsedQuery.containsKey("price_range")) {
                    String priceRange = (String) parsedQuery.get("price_range");
                    String[] prices = priceRange.split("-");
                    double from = Double.parseDouble(prices[0]);
                    double to = Double.parseDouble(prices[1]);
                    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price").gte(from).lte(to);
                    boolQueryBuilder.filter(rangeQueryBuilder);
                }

                sourceBuilder.query(boolQueryBuilder);
                searchRequest.source(sourceBuilder);

                SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
                List<SearchHit> hits = new ArrayList<>();
                for (SearchHit hit : searchResponse.getHits().getHits()) {
                    hits.add(hit);
                }
                return hits;
            } catch (IOException e) {
                e.printStackTrace();
                return new ArrayList<>(); // 处理异常,返回空列表
            }
        }, executor);
    }

     public CompletableFuture<List<SearchHit>> searchEmbeddingIndex(String keywords) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                // 获取查询语句的 embedding 向量
                float[] queryVector = getEmbeddingVector(keywords);

                SearchRequest searchRequest = new SearchRequest("product_embedding_index");
                SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

                // 使用 Elasticsearch 的 knn 搜索
                Map<String, Object> knn = new HashMap<>();
                knn.put("field", "embedding");
                knn.put("k", 10);
                knn.put("num_candidates", 100);
                knn.put("query_vector", queryVector);

                sourceBuilder.knnQuery(knn);
                searchRequest.source(sourceBuilder);

                SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
                List<SearchHit> hits = new ArrayList<>();
                for (SearchHit hit : searchResponse.getHits().getHits()) {
                    hits.add(hit);
                }
                return hits;
            } catch (IOException e) {
                e.printStackTrace();
                return new ArrayList<>(); // 处理异常,返回空列表
            }
        }, executor);
    }

    // 模拟获取 embedding 向量的方法 (实际应调用 embedding 模型)
    private float[] getEmbeddingVector(String text) {
        // 假设向量维度为128
        float[] vector = new float[128];
        for (int i = 0; i < 128; i++) {
            vector[i] = (float) Math.random(); // 随机生成向量
        }
        return vector;
    }
}

2.7 结果合并、排序和过滤

将多个 Index 召回的结果进行合并、排序和过滤,得到最终的检索结果。

import org.elasticsearch.search.SearchHit;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ResultMerger {

    public List<SearchHit> merge(List<List<SearchHit>> results) {
        Set<String> seenIds = new HashSet<>();
        List<SearchHit> mergedResults = new ArrayList<>();

        for (List<SearchHit> resultList : results) {
            for (SearchHit hit : resultList) {
                String id = hit.getId(); // Assuming documents have an "id" field.

                if (!seenIds.contains(id)) {
                    mergedResults.add(hit);
                    seenIds.add(id);
                }
            }
        }

        // Sort by score (descending).
        mergedResults.sort(Comparator.comparing(SearchHit::getScore).reversed());

        // Apply business rules for filtering (example: filter out items with price > 1000).
        mergedResults.removeIf(hit -> {
            Map<String, Object> source = hit.getSourceAsMap();
            if (source.containsKey("price")) {
                double price = (double) source.get("price");
                return price > 1000;
            }
            return false;
        });

        return mergedResults;
    }
}

2.8 完整检索流程

import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.SearchHit;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

public class SearchService {

    private final QueryParser queryParser = new QueryParser();
    private final IndexSelector indexSelector = new IndexSelector();
    private final Searcher searcher;
    private final ResultMerger resultMerger = new ResultMerger();

    public SearchService(RestHighLevelClient client) {
        this.searcher = new Searcher(client);
    }

    public List<SearchHit> search(String query) throws ExecutionException, InterruptedException {
        // 1. 查询解析
        Map<String, Object> parsedQuery = queryParser.parse(query);

        // 2. Index 选择
        List<String> selectedIndices = indexSelector.select(parsedQuery);

        // 3. 并行检索
        List<CompletableFuture<List<SearchHit>>> futures = selectedIndices.stream().map(indexName -> {
            if (indexName.equals("product_title_index")) {
                return searcher.searchTitleIndex((String) parsedQuery.get("keywords"));
            } else if (indexName.equals("product_category_index")) {
                return searcher.searchCategoryIndex(parsedQuery);
            } else if (indexName.equals("product_embedding_index")) {
                return searcher.searchEmbeddingIndex((String) parsedQuery.get("keywords"));
            } else {
                return CompletableFuture.completedFuture(List.of()); // 处理未知 Index
            }
        }).collect(Collectors.toList());

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); // 等待所有任务完成

        // 4. 结果合并
        List<List<SearchHit>> results = futures.stream().map(future -> {
            try {
                return future.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                return List.of(); // 处理异常,返回空列表
            }
        }).collect(Collectors.toList());

        // 5. 排序和过滤
        return resultMerger.merge(results);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RestHighLevelClient client = new RestHighLevelClientBuilder().build(); // 假设已经初始化
        SearchService searchService = new SearchService(client);
        List<SearchHit> results = searchService.search("新款手机 1000元");
        System.out.println("查询结果数量:" + results.size());

        client.close();
    }
}

三、语义增强检索链优化

为了提高检索的准确性和召回率,我们可以使用语义增强技术来优化检索链。

3.1 Query 理解和改写

  • Query 意图识别: 分析用户的查询意图,例如是想购买商品、查找信息还是进行导航。
  • Query 纠错: 自动纠正用户输入的错误拼写或语法。
  • Query 扩展: 将用户的查询扩展为更丰富的语义表达,例如使用同义词、近义词、上位词等。

3.2 语义相似度计算

使用 Word2Vec、BERT 等模型计算文本的语义相似度,例如:

  • 计算查询和文档的语义相似度: 将查询和文档都转换为向量表示,然后计算它们的余弦相似度或点积。
  • 计算查询和属性值的语义相似度: 将查询和属性值都转换为向量表示,然后计算它们的相似度,用于属性过滤。

3.3 个性化推荐

根据用户的历史行为、兴趣偏好等信息,对检索结果进行个性化排序和推荐。

四、性能优化

  • Index 优化: 合理设置 Index 的分片数、副本数、刷新间隔等参数。
  • 查询优化: 避免使用复杂的查询语句,尽量使用缓存。
  • 缓存: 对热点数据进行缓存,减少对 Index 的访问。
  • 并发: 使用多线程或异步编程来提高检索的并发能力。

五、代码示例:使用 BERT 进行语义相似度计算

这里我们使用 Hugging Face 的 Transformers 库来加载 BERT 模型,并计算查询和文档的语义相似度。

import ai.djl.ModelException;
import ai.djl.inference.Predictor;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.translate.TranslateException;
import ai.djl.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class BertSimilarity {

    private static final Logger logger = LoggerFactory.getLogger(BertSimilarity.class);

    private Predictor<String[], float[]> predictor;

    public BertSimilarity() throws ModelException, IOException {
        Criteria<String[], float[]> criteria = Criteria.builder()
                .setTypes(String[].class, float[].class)
                .optModelUrls("djl://ai.djl.huggingface.pytorch/sentence-transformers/all-MiniLM-L6-v2") // 使用轻量级模型
                .optTranslatorFactory(new SentenceEmbeddingTranslatorFactory())
                .optEngine("PyTorch") // 或者 "TensorFlow"
                .build();
        ZooModel<String[], float[]> model = criteria.loadModel();
        this.predictor = model.newPredictor();
    }

    public double calculateSimilarity(String text1, String text2) throws TranslateException {
        float[] embedding1 = predictor.predict(new String[]{text1});
        float[] embedding2 = predictor.predict(new String[]{text2});

        return cosineSimilarity(embedding1, embedding2);
    }

    private double cosineSimilarity(float[] vectorA, float[] vectorB) {
        double dotProduct = 0.0;
        double normA = 0.0;
        double normB = 0.0;
        for (int i = 0; i < vectorA.length; i++) {
            dotProduct += vectorA[i] * vectorB[i];
            normA += Math.pow(vectorA[i], 2);
            normB += Math.pow(vectorB[i], 2);
        }
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }

    public static void main(String[] args) throws ModelException, IOException, TranslateException {
        BertSimilarity bertSimilarity = new BertSimilarity();
        String text1 = "新款 手机";
        String text2 = "智能 手机 最新 款式";
        double similarity = bertSimilarity.calculateSimilarity(text1, text2);
        System.out.println("Similarity between "" + text1 + "" and "" + text2 + "" is: " + similarity);
    }

    // 翻译器类,用于处理模型输入输出
    static class SentenceEmbeddingTranslatorFactory implements ai.djl.translate.TranslatorFactory {
        @Override
        public ai.djl.translate.Translator<String[], float[]> newInstance(ai.djl.Model model, ai.djl.repository.zoo.ModelZoo.ModelLoadingContext context) {
            return new SentenceEmbeddingTranslator();
        }
    }

    static class SentenceEmbeddingTranslator implements ai.djl.translate.Translator<String[], float[]> {

        @Override
        public ai.djl.ndarray.NDList processInput(ai.djl.translate.TranslatorContext ctx, String[] input) {
            ai.djl.ndarray.NDManager manager = ctx.getNDManager();
            long[] shape = {1, input.length}; // batch size 1
            ai.djl.ndarray.NDArray ndArray = manager.create(input).reshape(shape);
            return new ai.djl.ndarray.NDList(ndArray);
        }

        @Override
        public float[] processOutput(ai.djl.translate.TranslatorContext ctx, ai.djl.ndarray.NDList list) {
            ai.djl.ndarray.NDArray ndArray = list.get(0);
            float[] result = ndArray.toFloatArray();
            return result;
        }

        @Override
        public ai.djl.translate.Batchifier getBatchifier() {
            return null;
        }
    }
}

请注意,你需要添加 DJL(Deep Java Library) 的依赖到你的项目中:

<dependency>
    <groupId>ai.djl</groupId>
    <artifactId>api</artifactId>
    <version>0.23.0</version>
</dependency>
<dependency>
    <groupId>ai.djl.pytorch</groupId>
    <artifactId>pytorch-engine</artifactId>
    <version>0.23.0</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>ai.djl.basicdataset</groupId>
    <artifactId>basicdataset</artifactId>
    <version>0.23.0</version>
</dependency>
<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
    </dependency>

这段代码使用了一个轻量级的 BERT 模型 all-MiniLM-L6-v2,因为它速度快,而且效果也不错。你也可以尝试其他模型。

六、总结

本文介绍了如何使用 Java 构建一个多 Index 召回集成架构,并针对复杂场景进行语义增强检索链的优化。通过合理地选择 Index、并行检索、结果合并、排序和过滤,以及使用语义增强技术,可以有效地提高检索的准确性和召回率。 随着业务的不断发展,我们可以不断地优化和完善这个架构,以满足不断变化的需求。

七、一些关键点的概括

  • 数据预处理和索引构建是基础,为后续的检索提供数据支持。
  • 查询解析和 Index 选择是核心,决定了使用哪些 Index 进行检索。
  • 并行检索和结果合并是关键,提高了检索的效率和召回率。
  • 语义增强是亮点,可以提高检索的准确性和用户体验。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注