WRITING / 2026.09.19

【最佳实践】ES智能检索在ECHO反馈中的应用

以小米汽车 ECHO 反馈平台为例,拆解 Elasticsearch 标量检索、向量检索与混合检索的实现链路和性能优化。

📌 内容概要:在AI检索时代,用户表达的多样性对传统关键词检索构成挑战。本文以小米汽车ECHO反馈平台为例,通过介绍混合检索架构原理,实践从关键词匹配到语义理解的跨越,为“全听懂”用户真实意图提供高效解决方案。🤝有问题欢迎随时与我们交流:信明权孙冬冬朱诚喆赖慧美王舜陈鹍远非凡

1 ECHO智能检索诉求

1.1 什么是ECHO反馈?

ECHO-小米汽车全渠道全维度收集、响应、分析、解决用户真实反馈,驱动用户体验闭环和产品升级的数字化平台。

旨在以用户体验为中心,以促进汽车持续升级从而实现用户体验闭环为目的,打通用户从反馈数据采集、分析,到反馈响应、处理、运营的完整体验闭环,构建从用户端、业务端与研发端的统一云端数字化平台。

核心目标:全听到、全听懂、全反馈。

其中,“全听懂”是实现闭环的关键——不仅要收集到所有用户的声音,更要真正理解用户表达背后的真实意图与潜在需求

1.2 检索场景挑战:如何“听懂”用户?

🌰以“自定义锁车声音”这一典型需求为例:

  • 用户可能在反馈中使用多种表述方式:

    • “能不能调一下锁车提示音?”
    • “我想设置一个专属的锁车声音”
    • “锁车时能不能放点我喜欢的音乐?”
    • “为什么每次锁车都是一样的‘嘀’声,太没个性了”

传统关键词检索(如termmatch_phrase)依赖精确或模糊匹配,难以识别这些语义相近但表达各异的内容

1.3 传统检索的三大局限

局限点 具体表现
语义理解缺失 仅基于关键词匹配,无法识别“锁车提示音”与“锁车声音”为同义表达
表达形式敏感 对同义词、语序变化、口语化表达(如“嘀一声”)不兼容,召回率低
多模态支持弱 无法有效融合截图、语音等非结构化内容进行联合检索

这使得传统检索在面对真实用户反馈时,难以满足“全听懂”的核心诉求。

1.4 AI 时代搜索的演进:从关键词到语义理解

随着大模型与向量技术的发展,搜索能力正经历从“关键词匹配”向“意图理解”的范式转变:

  • 早期搜索:依赖倒排索引,通过关键词查找信息;
  • 当前趋势:强调语义相似性匹配,理解用户真正想表达的内容;
  • 未来方向:融合多模态、上下文感知、实时反馈的智能检索系统。

但需注意:纯向量搜索虽能提升语义召回,却存在精度不足、计算开销大、结果可解释性弱等问题。

因此,混合检索(Hybrid Search)成为最优解:

将结构化字段标量检索(如时间、车型、反馈类型)与非结构化内容语义检索(如文本意图)相结合,兼顾召回率与精准度。

1.5 ECHO为什么选择 ES 做智能检索

面对智能检索需求,我们评估了多种技术路径,最终选定Elasticsearch 8.14+ 作为核心检索引擎,原因如下:

智能检索的两种方案对比:

  1. 先把非语义检索的部分,通过固定字段的标量检索,确定数据后,全部喂给算法,由算法进行语义检索,得到用户想要的数据。最大的问题是:

    1. 用户检索范围没有限制,大量数据如何与算法进行传输
    2. 即便解决了数据与算法传输的问题,考虑到搜索是一个高频场景,对实时性有要求,算法是否能够高频的处理大量数据?实时性能否保证?
  2. 基于方案一中存在的问题,考虑切换思路,能否在一个数据库里同时做到标量检索和向量检索?当前ECHO反馈列表已经基于ES(全文检索引擎)实现了几十个字段的标量检索,能否基于现有ES,探索同时支持向量检索的可能?

    1. 一方面开始调研 ES 的向量检索能力,欣喜发现ES的全新 8.14 稳定版集群,全面支持向量检索,并且引入了业内最流行的 HNSW 算法作为近似检索索引的算法;
    2. 另一方面也同时与集团 ES 老师开展多次技术交流,得知集团也在推进 ES 集群 8.14 版本的全面升级,全力拥抱 ES 向量检索新特性

最终,我们联合集团 ES 老师搭建了体系部门 ES 8.14 版私有集群,并与算法团队合作,由算法团队采用大模型进行文本数据的 embedding(向量化),研发将向量数据保存到 ES 中,实现了基于 ES 的标量和向量的混合检索,提升检索效率。

2 ES智能检索实现原理:从标量到语义的协同演进

在ECHO平台的智能检索实践中,我们构建了一套基于Elasticsearch 8.14的混合检索架构,融合了传统标量检索与AI驱动的向量语义检索能力。其背后的核心机制,可从三个维度深入解析:标量过滤、向量检索、混合执行流程

下面我尽量由浅入深,带领大家逐层拆解技术原理。

2.1 标量检索:高效过滤的“第一道闸门”

尽管语义检索至关重要,但在实际业务中,结构化数据的快速筛选仍是提升性能的关键前提。

2.1.1 标量数据与索引机制

ES对不同类型标量字段采用差异化索引策略,确保高效查询:

字段类型 索引结构 典型查询场景
数值型(long/double) BKD树 范围查询:price > 100 AND price < 300
关键字(keyword) 倒排索引(Inverted Index) 精确匹配:status: "resolved",前缀匹配:user_id: "100*"
日期(date) 时间戳 → BKD树 时间区间:create_time BETWEEN '2024-01-01' AND '2024-01-31'

✅ 特点:所有标量索引均支持低延迟、高吞吐的过滤操作,适合做“数据预筛”。

🌰倒排索引:记录包含该关键词的文档 ID

以文本字段content:"Elasticsearch uses inverted index and BKD tree"为例,倒排索引会经历以下处理:

  1. 分词:将文本拆分为最小语义单元(如elasticsearchusesinvertedindexbkdtree);
  2. 建立映射:为每个分词创建“词条→文档 ID”的映射表(如下表),同时记录词条在文档中的位置、频率(用于算分)。
词条(Term) 包含该词条的文档 ID 列表(Posting List) 频率 位置
elasticsearch [1, 3, 5] 1 0
uses [1, 4] 1 1
inverted [1, 2, 5] 1 2
index [1, 2, 3, 5] 1 3
bkd [1, 6] 1 4
tree [1, 6] 1 5

特点:

  • 适合处理文本类查询:如“搜索包含inverted index的文档”“模糊匹配bkd相关内容”;
  • 依赖分词器(如 Standard、IK):不同分词器会生成不同的词条,直接影响查询结果;
  • 无法理解语义。

2.1.2 标量过滤的核心原则:先筛后算

在混合检索中,标量过滤承担着“缩小候选集”的角色:

  • 通过BKD树或倒排索引快速定位满足条件的文档ID集合;
  • 将原始数据量从百万级压缩至数千甚至百级;
  • 为后续向量匹配大幅降低计算开销。

📌 关键设计思想:标量过滤是“性价比最高的第一步”,可以前置执行。

2.2 向量检索:语义理解的“大脑引擎”

在AI时代,实现“全听懂”的核心挑战是:如何让机器理解人类语言背后的“真实意图”,而不仅仅是识别关键词。

传统文本匹配方式(如倒排索引、正则表达式)基于“符号等价”进行判断——“锁车” ≠ “关门”,“声音” ≠ “提示音”。

但真实用户表达是高度灵活、多变且充满隐含语义的。例如:

  • “能不能换个锁车声音?”
  • “我想自定义一个锁车提示音”
  • “为什么每次锁车都是一样的‘嘀’一声?太没个性了”

这些句子字面不同,但语义高度一致。

✅ 问题本质:我们需要一种能捕捉“语义相似性”的数学表示方式。

而向量(Vector)正是当前唯一能同时满足“语义表达能力 + 高效计算 + 可扩展性”三大要求的数据结构。

2.2.1 为什么是“向量”?——语义表达的数学基石

向量的本质:将语义“嵌入”到高维空间

向量不是抽象概念,而是一种有明确数学定义的几何实体,在语义建模中,我们通过预训练语言模型(如 BERT、Sentence-BERT、BGE 等)将任意文本映射为一个固定长度的浮点向量。

  • 例如:

    • “锁车声音” → ([0.72, -0.31, 0.88, ..., 0.15])(1024维)
    • “自定义锁车提示音” → ([0.70, -0.33, 0.86, ..., 0.17])(1024维)

虽然数值不同,但整体方向极为接近,夹角极小 → 余弦相似度接近1。

🎯 这正是“语义相近 → 向量相近”的数学根源。

为什么语义相近的词会生成相近的向量?——模型学习的“语义空间”

❓你有没有想过,为什么人工智能能“听懂”我们说的“我想换个锁车的声音”,就知道我们其实想改一下提示音?

这背后的关键,不是因为它记住了“声音”=“提示音”,而是它在“看”了几十亿条人类写的句子之后,偷偷学会了“语言的潜规则”。

🤔一起做一个思想实验:

想象一下:模型在玩“填空游戏”

比如,它看到一句话:“我想要一个不一样的锁车______”,然后被要求猜空格里应该填什么。它不是随便猜,而是根据前面的“锁车”和后面的语境,去推理最可能的词。

它发现:

  • “锁车”后面经常出现“声音”“提示音”“铃声”;
  • “想要”“换”“自定义”这些词,也常常跟“声音”一起出现。

于是,模型慢慢意识到:

👉 “锁车”和“提示音”经常“一起登场”,它们在语义上“关系很近”;

👉 “自定义”和“换”表达的其实是同一个意思,只是说法不同。

时间久了,它就“记住”了这些关系

经过上亿次这样的“填空练习”,模型不再只是记单词,而是学会了用“位置”来表示词的意思。

它把每一个词、每一句话,都变成一个**“坐标点”**——就像地图上的位置一样。

  • “锁车声音”和“自定义提示音”这两个说法,因为总在相似的语境里出现,所以被“挤”到了地图上的相邻位置
  • 而“锁车”和“开门”虽然都和“车”有关,但出现的场合不一样,所以它们的“距离”就远一些。

最终,形成一张“语义地图”

这张地图叫作“语义嵌入空间”(Semantic Embedding Space)——

它不是一张物理地图,而是一个高维空间,每个词都是一个点。

  • 两个点越靠近,说明它们的意思越像;
  • 两个向量之间的夹角越小,说明它们越“说得像”;
  • 甚至可以用“距离”来判断:“我想换锁车声音”和“我要改一下锁车提示音”——这两个句子,向量距离很近,说明它们“意思几乎一样”。

✅ 所以,“锁车声音”和“自定义锁车提示音”之所以向量相近,是因为它们在训练语料中长期共现、语义共现、逻辑相关。

为什么向量优于其他数据结构?——从表达力到效率的全面对比
数据结构 是否支持语义表达 是否支持相似度计算 是否可扩展至多模态 计算效率 适用场景
关键词(String) ❌ 仅字面匹配 ❌ 无语义衡量 ❌ 不支持 ✅ 高 精确搜索
倒排索引(Inverted Index) ❌ 依赖关键词 ❌ 无法衡量语义 ❌ 不支持 ✅ 高 全文检索
向量(Dense Vector) ✅ 强大语义捕捉 ✅ 支持余弦 / L2 距离 ✅ 支持文本/图像/语音统一建模 ✅ 高(HNSW加速) 智能检索、推荐、聚类

🔑 向量是唯一能同时满足以下四点的结构:

  1. 语义表达:捕捉上下文、意图、同义关系;
  2. 相似性度量:提供可量化、可排序的“语义距离”;
  3. 统一建模:支持多模态融合(文本+图像+语音);
  4. 工程高效:结合HNSW等算法实现毫秒级近似检索。

2.2.2 向量生成与存储:从文本到向量

生成:原始文本需经大模型编码为向量。比如采用BGE系列模型对内容进行embedding,输出1024维浮点向量。

"contentVector": [
    0.011500863,
    -0.004322108,   
   ......            
    1024维度            
   ......   
    0.0028650945,
    -0.045998745,
    0.0054406906,
    -0.02370895
]

存储:在ES中,需通过自定义Mapping声明向量字段:

{
    "mappings": {
        "properties": {
            "content_vector": {
                "type": "dense_vector",
                "dims": 1024,
                "index": true,
                "similarity": "dot_product" // 使用点积计算语义接近程度
            },
            "content": {
                "type": "text"
            } // 原始文本,用于展示
        }
    }
}

✅ 特性说明:

  • dense_vector:专用于高维稠密向量存储;
  • index:true:开启向量索引(HNSW),否则无法加速;
  • similarity:cosine:语义匹配计算相似度的方法。

2.2.3 向量索引构建——提效的关键所在

为避免向量检索时的全量遍历(时间复杂度 O (n)),ES 引入了高效的向量索引结构——HNSW(Hierarchical Navigable Small Worlds),这是当前业界性能最优的近似最近邻(ANN)检索算法之一。

HNSW 的核心思想是构建多层“导航图”:底层包含所有向量,上层为底层向量的“稀疏采样”,形成分层结构。检索时,从顶层开始,通过贪心算法快速定位到与目标向量相近的区域,再逐层下探至底层,最终找到相似度最高的 K 个向量(即 K-NN 检索)。相比传统的暴力检索,HNSW 能将检索时间复杂度降至 O (log n),大幅提升向量检索效率。

2.2.4 相似度计算与结果排序:精准匹配语义

当用户发起向量检索请求时(如输入“自定义解锁声音”并转换为目标向量),ES 会基于 HNSW 索引快速筛选出候选向量集合,再通过指定的相似度算法计算目标向量与候选向量的相似度得分:

  • 余弦相似度:衡量两个向量的夹角大小,取值范围 [-1,1],值越接近 1 表示语义越相似,适用于文本语义匹配场景;
  • 欧氏距离(L2):衡量两个向量在高维空间中的直线距离,值越小表示相似度越高,适用于图像、音频等非文本数据场景;
  • **点积(dot_product):**表示向量间的投影关系,与余弦相似度类似,性能高、计算快,常用于实时推荐或搜索

最终根据相似度得分对候选结果进行降序排序,返回 Top-K(如 Top10、Top20)结果。

排序逻辑:

  • 计算目标向量与每个候选向量的相似度得分;
  • 按得分降序排列,返回Top-K结果(如 Top10);
  • 支持动态调整K值,根据业务需求平衡召回率与精度。

2.3 混合检索的核心:协同处理向量匹配与标量过滤

核心目标:在保证语义召回率的前提下,最大化查询性能与资源利用率。

在实际业务中,用户查询往往同时包含:

  • 结构化条件(如“车型:SU7”、“反馈时间:2024年1月”)
  • 非结构化语义诉求(如“我想自定义锁车声音”)

如何高效协同处理这两类查询?关键在于过滤策略的设计。主要有三种模式:前置过滤、后置过滤、多路召回,分别适用于不同场景。

2.3.1 前置过滤

执行流程:

  1. 用户发起混合查询,包含标量条件(如type:SU7) + 向量条件(如content_vector与“自定义锁车声音”相似);
  2. ES 查询引擎优先解析标量过滤条件;
  3. 利用倒排索引等高效结构,快速定位满足条件的文档 ID 集合;
  4. 将该“标量候选集”作为向量匹配的输入;
  5. 在候选集范围内执行 HNSW 检索,返回 Top-K 语义匹配结果。
{
    "knn": [
        {
            "field": "contentVector",
            "query_vector": [
                0.0022726913448423147,
                0.013293873518705368,
                0.029590805992484093,
                0.01181684248149395
            ],
            "num_candidates": 5000,
            "filter": {
                "term": {
                    "channel": 4
                }
            }
        }
    ]
}

2.3.2 后置过滤

执行流程:

  1. 用户输入语义查询(如“锁车声音”);
  2. ES 基于 HNSW 索引,对全量数据执行向量匹配,返回 Top-K 语义相关文档;
  3. 将结果集送入filter子句,执行标量条件校验(如create_time: '2024-01-01');
  4. 只保留同时满足“语义相似”与“标量条件”的文档。
{
    "query": {
        "bool": {
            "must": [
                {
                    "knn": {
                        "field": "contentVector",
                        "query_vector": [
                            0.0022726913448423147,
                            0.013293873518705368,
                            0.01181684248149395
                        ],
                        "num_candidates": 5000
                    }
                }
            ],
            "filter": {
                "term": {
                    "channel": 4
                }
            }
        }
    }
}

2.3.3 多路召回

执行流程:

  1. 将查询拆解为多个独立检索路径(即“召回路”):

    • 路径1:关键词匹配(match查询)→ 快速召回显性表达
    • 路径2:向量语义匹配(knn查询)→ 捕获隐性意图
    • 路径3:标签/分类匹配(term查询)→ 按业务维度筛选
  2. 每条路径独立执行,返回各自的候选结果集;

  3. 分别对各路结果加权融合;

  4. 最终按综合得分排序,返回 Top-K 结果。

{
    "query": {
        "bool": {
            "should": [
                {
                    "match": {
                        "title": {
                            "query": "锁车声音",
                            "boost": 1.0
                        }
                    }
                },
                {
                    "knn": {
                        "field": "embedding",
                        "query_vector": [
                            0.0022726913448423147,
                            0.01926140760287136,
                            0.029590805992484093,
                            0.01181684248149395
                        ],
                        "k": 10,
                        "boost": 2.0
                    }
                },
                {
                    "term": {
                        "channel": 4,
                        "boost": 1.5
                    }
                }
            ]
        }
    }
}

三种过滤策略的对比

策略 适用场景 核心优势
前置过滤 高频、强标量筛选、低延迟要求 优先排除无关数据,大幅缩小候选集,提升检索效率
后置过滤 模糊语义 + 强业务约束 先完成语义匹配,再精细控制结果范围,保障相关性与合规性- 智能推荐中排除已读/已购内容;- 安全审核中过滤敏感关键词
多路召回 复杂洞察、高召回要求、容忍一定延迟 融合多种检索路径,覆盖更多潜在相关结果,提升整体覆盖能力

3 ES智能检索在ECHO反馈中的落地实践

3.1 整体思路

目标:构建一个高召回、低延迟、可扩展的智能反馈检索系统,支撑ECHO平台的智能检索与分析。

维度 要求 技术手段
全采集 支持多渠道、多格式的反馈接入 - 建立统一数据接入网关(echo-access);- 定义标准化反馈结构(JSON Schema);- 消息队列(Kafka)异步解耦写入流程
全理解 理解同义表达、口语化表达、多语言支持 - 采用 BGE模型(支持中英双语+多语言)生成高精度 embedding;- 向量索引使用int8_hnsw,兼顾精度与效率
快响应 准实时查询、低延迟 - ES 标量 + 向量混合检索(KNN + Filter);- 优化分片策略 + 参数调优
可拓展 支持未来图片、语音、视频反馈智能检索 架构预留向量拓展接口;→ 统一向量输入格式(embedding:float[]);→ 支持后续扩展不同模态模型(图像、语音)

3.2 中间件选择

✅ 严格遵循 Spring 官方版本兼容依赖关系,确保长期稳定。

当前 Elasticsearch 集群版本为 8.14,基于上述 ES 版本依赖关系,我们做了如下的版本选型:

  • ES 版本:8.14.0 (升级时公司ES最高支持8.14.0版本,现在已有更高版本)

  • 基础中间件:

    • Spring Data Elasticsearch 5.4.0(简化 ES 客户端操作)
    • JDK:JDK 21
    • Spring 生态:Spring Boot 3.4.0
  • Embedding 模型选型:

    • 文本场景:BGE(语义匹配精度更高,多语言支持更好)

Echo系统原有ES版本为7.17.4,spring-data-elasticsearch 4.4.18,jdk 11, springboot 2.7.3。升级到上述版本踩了一些坑大家可以借鉴:升级问题记录

3.3 实践过程:从数据接入到智能检索的完整链路

3.3.1 数据预处理与Embedding 生成

  • 原始数据清洗:统一大小写、去除首尾空格、合并连续空格等。

  • Embedding 生成(模型部署在算法团队)

    • 接口设计:输入(feedback_text: 反馈文本)→ 输出(embedding: 向量数组)
    • ES数据格式定义(ES 索引 mappings):
{
  "xxxxx@0-000001": {
    "mappings": {
      "properties": {
        "_class": {
          "type": "keyword"
        },
        "vocNo": {
          "type": "keyword"
        },
        "content": {
          "type": "keyword"
        },
        "contentVector": {
          "type": "dense_vector",
          "dims": 1024,
          "index": true,
          "similarity": "dot_product",
          "index_options": {
            "type": "int8_hnsw",
            "m": 16,
            "ef_construction": 100
          }
        },
        "createDate": {
          "type": "date",
          "format": "strict_date_optional_time"
        }
      }
    }
  }
  • ES存储字段样例
{
    "hits": [
        {
            "_score": 1,
            "_source": {
                "contentVector": [
                    0.011500863,
                    -0.004322108,   

                    ......            
                    1024维度            
                    ......   

                    0.0028650945,
                    -0.045998745,
                    0.0054406906,
                    -0.02370895
                ],
                "vocNo": "E041713XXXXX7363",
                "content": "自己设计锁车声音",
                "createDate": "2024-04-15T11:56:33.000+08:00"
            }
        }
}

3.3.2 数据写入ES:批量与增量同步策略

  • 批量写入(历史数据):

    • 场景:ECHO 系统的存量反馈数据,需要通过embedding生成向量数据
    • 策略:分批次处理
    • 实现方式:使用 Spring Data Elasticsearch 的bulkIndex,每次批量写入 1000 条数据
    • 关键点:先删除旧数据(通过vocNo批量删除);然后再批量写入
@Resource
private ElasticsearchOperations elasticsearchOperations;

private void bulkIndex(List<String> vocNos, List<IndexQuery> indexQueries) {
    IndexCoordinates indexCoordinates =
            elasticsearchOperations.getIndexCoordinatesFor(Index.class);
    DeleteQuery deleteQuery =
            DeleteQuery.builder(CriteriaQuery.builder(Criteria.where("vocNo").in(vocNos)).build()).build();
    elasticsearchOperations.delete(deleteQuery, Index.class);
    elasticsearchOperations.bulkIndex(indexQueries, indexCoordinates); //批量更新
}
  • 增量同步(实时数据):

    • 触发方式:当新反馈写入 MySQL ,监听SQL变更后,发送SQL变更的消息,检索分析服务监听自动触发 Embedding 生成与 ES 写入

3.3.3 检索查询实现:标量+向量混合查询

  • 场景特征分析:ECHO的列表检索的特点是高频、强标量筛选、低延迟要求

  • 查询策略:前置过滤 + 向量匹配 + 结果聚合

    • 核心思想:先用标量条件大幅缩小候选集,再进行向量语义匹配,避免全量向量搜索
    • 实现方式:在knn查询中嵌套filter子句

场景示例:精准标量过滤 + 向量语义匹配(如“查询今年5月、渠道是车主APP社区、用户关于‘自定义解锁声音’的反馈”)

  • ES 查询语句示例:
{
    "knn": [
        {
            "field": "contentVector",
            "query_vector": [
                0.0022726913448423147,
                0.013293873518705368,
                ......            
                1024维度            
                ......   
                0.029590805992484093,
                0.01181684248149395
            ],
            "k": 100,
            "similarity": 0.7f,
            "num_candidates": 2000,
            "filter": {
                "bool": {
                    "must": [
                        {
                            "range": {
                                "create_time": {
                                    "gte": "2025-05-01",
                                    "lte": "2025-05-31"
                                }
                            }
                        },
                        {
                            "term": {
                                "channel": 3
                            }
                        }
                    }
                }
            ]
        }
  • 代码实现:Spring Boot + SDE 5.4 最佳实践

✅ 关键点说明:

field-collapse:避免同一vocNo反馈被多次返回;

sort.score.desc:按相似度排序,提升相关性。

// 组装向量+标量查询
List<KnnSearch> knnSearches = new ArrayList<>();
knnSearches.add(KnnSearch.of(k -> k.field("contentVector")
            .queryVector(getContentEmbeddingList()) //向量查询
            .k(esConfig.getKnnKTop())
            .similarity(esConfig.getSimilarity())
            .numCandidates(esConfig.getKnnNumCandidates())
            .filter(query))); //标量查询
// 构建Query
NativeQuery nativeQuery;
nativeQuery = NativeQuery.builder()
            .withKnnSearches(knnSearches)
            .withPageable(offsetPageable)
            .withFieldCollapse(FieldCollapse.of(cf -> cf.field("vocNo")))
            .build();
// 执行查询          
SearchHits<Index> searchHits =
        elasticsearchTemplate.search(nativeQuery, Index.class);

3.4 性能优化:从集群到参数的全方位调优

3.4.1 ES 集群优化

  • 节点配置:私有集群数据节点配置多个(例如3个),避免单点故障
  • 分片策略:索引分为6个主分片(primary shard)+ 1个副本分片(replica shard),确保负载均衡,提升并发效率。(分片数可以按照业务预估的数据量来设置,一般是节点数的倍数,成倍数分布的会均匀些)
  • 压缩策略:修改 index.codec = default,当前是 best_compression (平台创建的默认是这个,会有更高的压缩率,但是读写性能会有一定下降),调整为 default 会有比较明显提升。

3.4.2 向量检索参数优化

  • HNSW 索引参数调优(核心影响召回率与速度):

m:M 决定了 HNSW 图层中每个节点维护的双向链接数量,较高的值会增加图的连通性,通过减少搜索陷入局部最优解的可能性来提高召回率。默认 16(值越大,召回率越高,但搜索时间会更长)

ef_construction:控制在将节点插入图中时探索的候选邻居数量,默认 100(值越大,索引质量越高,越准确,但索引构建时间越长,内存占用更多)

上述参数Echo侧没有调整,大家可以结合业务数据的数量调整,对于准确度和响应时间的敏感程度,找到效率和质量的平衡。

  • knn检索参数

k:返回的Top-k 结果,100

similarity:相似度阈值,适度提高相似度阈值,控制误检率,避免返回过多无关结果,0.7f

num_candidates:搜索时候选节点数,2000

💡 实践问题复盘:为何多次查询,相同查询条件,但结果可能不一致?

原因:HNSW 是近似最近邻算法(ANNS),其搜索过程依赖随机采样与局部最优路径,导致结果存在非确定性。

✅ 解决方案组合:

  1. 索引重建:提高mef_construction,构建更稠密的连接图,能搜索到更多近邻;
  2. 查询增强:提升num_candidates,提升查询召回率;
  3. 结果去重与排序增强:在应用层加入基于vocNo的去重 + 按相似度重新排序(sort+score);

小贴士:对于非强一致性场景(如探索性检索),可接受轻微波动;但对于分析类报表场景,建议增加结果稳定性保障机制。

3.4.3 业务逻辑与查询优化

针对 ECHO 平台“高频+强标量筛选”的查询特征,我们业务代码实施了以下优化策略:

  1. 前置标量过滤(Filter Early)

    • 前置数据预处理、数据去重等
    • 所有rangeterm等标量条件均前置至knn查询的filter子句中
  2. 并行构建多个查询条件

    • 查询条件可以使用CompletableFuture并行组装
    • 对于复杂多条件场景,将条件拆分为独立子查询,通过并行执行后合并结果

3.5 实践效果

1)ECHO实现基于语义的检索功能

🎯 典型场景验证:

  • 查询:“自定义解锁声音” → 成功召回“希望增加自定义锁车音效”、“锁车音效什么时候可以自定义,真的很需要”等相似反馈;
  • 查询:“2025年5月车主APP社区的续航问题” → 准确过滤时间、渠道,并返回“续航”语义相关的反馈;
  • 查询:“车辆声音异常” → 即使无完全匹配关键词,仍召回“车机异响,嗡嗡嗡的声音”、“车辆有杂音”等近义表达。

2)经过参数调整&业务系统代码逻辑优化后,列表查询性能较ES升级前提升约50%左右

接口名称 优化幅度
page接口 ↓ 55.0%
count接口 ↓ 55.0%
base接口 ↓ 53.3%
Vehicle接口 ↓ 54.7%
device接口 ↓ 56.3%
statistics接口 ↓ 46.7%

4 总结

本文系统地阐述了小米汽车用户反馈平台ECHO智能检索系统的构建思路,从传统检索的局限性出发,逐步演进到AI时代的混合检索解决方案,最终实现了一套高效、精准的用户反馈处理体系。

展望未来,ECHO智能检索系统将持续深化多模态融合等能力,并结合业务场景的精细化调优,进一步推动ECHO用户反馈系统的智能化升级。

附宝藏文档:

QCon全球软件开发大会-北京站-基于 Elasticsearch 创建企业 AI 搜索应用实践:https://qcon.infoq.cn/2025/beijing/presentation/6305

Elasticsearch 向量搜索:常见问题与最佳实践:https://tech.xiaomi.com/#/pc/article-detail?id=38150

调参实践:https://xiaomi.f.mioffice.cn/wiki/O0rtw6EslieVnCkARSqkP8Q04oe

如何修改索引模版配置(配置 mapping 字段类型、主分数、副本数、配置分词器等):https://xiaomi.f.mioffice.cn/wiki/J4vpwbOMRiZT78kiC2qk12oK4qe

Elasticsearch:检索增强生成背后的重要思想:https://elasticstack.blog.csdn.net/article/details/142382995

混合检索:https://elasticstack.blog.csdn.net/article/details/145697606