Elasticsearch 混合检索实验:高亮不是装饰,是回跳证据
关键词检索、字段过滤和高亮片段要同时返回,才能让全文索引在 AI 检索链路里承担证据定位责任。
Elasticsearch 混合检索实验:高亮不是装饰,是回跳证据
关键词检索、字段过滤和高亮片段要同时返回,才能让全文索引在 AI 检索链路里承担证据定位责任。
文章属于行业研究与技术科普,不替代项目设计、合规审查或招投标技术文件;引用时应保留来源、标题和原文地址。
Elasticsearch 混合检索实验:高亮不是装饰,是回跳证据
很多团队一讨论 AI 检索,就急着把全文索引放到向量库后面,好像 Elasticsearch 只是上一代检索工具。这个判断太粗。档案检索里,编号、文号、责任者、年度、全宗、开放状态、页码和原文位置都很重要。向量检索擅长语义近邻,全文索引擅长字段过滤、精确匹配和高亮定位。两者应该组合,而不是互相替代。

这篇只做一个最小实验:建立一组页级档案文本索引,查询时同时返回字段、权限过滤结果、高亮片段和原文回跳字段。实验目标实验目标是证明证明一件事:检索结果必须能解释“为什么命中、命中了哪里、能不能打开原文”。
不要把所有内容塞进一个 body 字段
如果索引里只有 body,后面会很难做过滤和回跳。档案文本至少要拆出几类字段。
{
"archive_id": "A001-2026-0007",
"fonds_no": "A001",
"archive_no": "QZ-2026-0007",
"title": "关于接口联调复测情况的说明",
"year": 2026,
"open_state": "open",
"page_no": 3,
"component_id": "C02",
"source_url": "/archive/A001-2026-0007/page/3",
"body": "本次接口联调复测重点检查归档包接收、四性检测结果回传和退回记录..."
}
这里的 body 只是正文。真正决定项目可用性的,是 archive_id、page_no、open_state、source_url 这些字段。没有它们,检索结果只能返回一段文字;有了它们,前端可以打开原文页,AI 可以引用片段,审计可以记录用户看了哪份材料。
mapping 也要区分字段类型。档号、全宗号、开放状态用 keyword,年份用数值,正文用 text。中文分词器可以根据项目环境选择,试验阶段先不纠结最优分词,先把字段边界设计清楚。
这里有一个判断很重要:全文索引不是把 OCR 文本倒进去就结束。OCR 文本只是内容,档案对象才是检索单位。一个页面命中以后,系统要知道它属于哪份档案、哪个组件、哪一页、是否开放、是否经过人工复核。否则命中再准,也只能停在“找到一段文字”,无法进入可引用证据。
很多项目后期补不动,就是因为早期 mapping 太随意。先把所有东西塞到一个字段,前端上线后再要求按门类过滤、按年度筛选、按开放状态限制、按页码回跳,就会发现索引结构不支持。重建索引不是难事,难的是业务系统、前端、日志和测试用例都已经按旧结构写完了。
所以最小实验也要带一点“未来感”:不必一次做全,但要把以后肯定会用到的对象位置留出来。尤其是 archive_id、component_id、page_no、open_state、source_url,这几个字段越早稳定,后面接 AI、接权限、接审计越省事。
查询里必须把相关性和权限分开
一个常见错误,是把所有条件都写成关键词查询。比如用户查“接口复测”,同时只允许看开放档案、只查某全宗。如果把这些都混在 must 里,相关性排序和安全过滤会变得不清楚。
更稳的写法是:正文命中放在 must,权限、开放状态、全宗、年度放在 filter。filter 不参与相关性评分,更适合表达边界。
{
"query": {
"bool": {
"must": [
{"match": {"body": "接口复测"}}
],
"filter": [
{"term": {"open_state": "open"}},
{"term": {"fonds_no": "A001"}},
{"range": {"year": {"gte": 2024}}}
]
}
},
"highlight": {
"fields": {
"body": {
"fragment_size": 80,
"number_of_fragments": 2
}
}
},
"_source": ["archive_id", "archive_no", "title", "page_no", "open_state", "source_url"]
}
这个查询返回的返回结果应包含一组可复核命中。它能告诉系统:这些结果符合权限边界,正文命中了关键词,片段可以高亮,用户可以从 source_url 回到原文。
高亮不是为了好看
高亮片段常被当前端效果处理,其实它更像证据摘要。复核人员看到高亮,能快速判断命中是否真的相关。AI 模型拿到高亮,能减少引用整页文本带来的噪声。项目负责人看到高亮,也能理解为什么这条结果排在前面。
高亮片段至少要和三类信息一起返回:档案对象、页码、原文回跳。只返回 接口复测 没有意义;返回“这段文字来自 A001-2026-0007 第 3 页,可打开原文”,才有证据价值。
可以把检索返回结构约定成这样:
{
"query_id": "q-20260617-001",
"hits": [
{
"archive_id": "A001-2026-0007",
"archive_no": "QZ-2026-0007",
"title": "关于接口联调复测情况的说明",
"page_no": 3,
"open_state": "open",
"source_url": "/archive/A001-2026-0007/page/3",
"score": 8.73,
"highlight": ["本次接口联调复测重点检查归档包接收..."]
}
]
}
后续如果接入大模型,这个结构可以作为 evidence 输入,而不是把 ES 结果先拼成一大段散文。模型可以基于 highlight 生成回答,但回答旁边仍然保留 source_url。这样用户不只看答案,还能回到原文。
混合检索不是简单相加
全文索引和向量检索结合时,不建议一开始就追求复杂排序。可以先做两段式:第一步用权限、开放状态、门类、年度缩小候选范围;第二步在候选范围内分别跑关键词和向量;第三步把结果合并,并保留每条结果来自哪个通道。
这样做有两个好处。第一,权限在召回前就生效,避免模型接触不该看的文本。第二,结果可解释。如果一条记录因为关键词命中排前面,就看高亮;如果因为语义相近排前面,就看向量相似度和关联片段。两种证据不要混成一个神秘分数。
档案项目里,很多查询不是纯语义问题。用户可能输入档号、文号、人名、责任者、项目名称、日期范围。全文索引对这些精确条件更可靠。向量检索可以补足同义表达、口语问题和跨段关联,但不能替代字段过滤和原文回跳。
还有一种情况更常见:用户的问题很口语,但答案必须落到精确字段。比如“去年那个接口验收没过的材料在哪里”,语义上需要向量召回相关表达,但最后仍要筛选年度、项目、检测状态和页级证据。如果只有向量检索,可能返回相似段落,却说不清为什么这条结果符合“去年”和“验收没过”;如果只有关键词检索,又可能漏掉“复测未通过”“检测退回”这类同义表达。
混合检索的价值正在这里。全文索引让边界清楚,向量检索让表达更宽,二者合起来让系统既能找得出来,也能说得清楚。档案场景不能只追求召回率,还要看证据可解释性。宁可少返回几条,但每条都能回跳,也比返回一堆相似摘要更可靠。
失败样本要进入检索测试
很多检索实验只测成功问题,比如输入一个明显出现在正文里的词,看系统能不能命中。这个测试太容易通过。更有价值的是准备失败样本。
第一类是权限失败:有命中文本,但当前用户不能看。系统应该在召回前过滤,或者返回明确的权限不足状态,而不是把片段交给模型再遮挡。
第二类是字段失败:用户输入档号或文号,正文里没有,但结构化字段里有。系统应该能通过字段命中,而不是误以为没有结果。
第三类是高亮失败:结果命中了整页,但高亮片段不包含关键表达,说明分词、字段选择或查询写法可能不对。
把这些失败样本写进测试,才能防止后续升级把边界改坏。ES 版本、分词器、mapping、权限规则、前端展示和模型摘要都可能变化,测试样本是保证检索链路不退化的最小保险。
失败样本还应该和真实业务问题绑定。比如“同名人员过多导致误命中”“文号里有括号和年份导致分词异常”“OCR 把 0 识别成 O”“用户输入简称但目录里是全称”“原文未开放但题名可检索”。这些问题比普通技术样例更贴近档案场景。只要测试集里没有它们,系统就会在演示时表现很好,在真实利用时不断被用户打断。
检索测试不需要一次做很大。先准备 20 个问题就足够暴露问题:5 个精确字段题,5 个全文命中题,5 个权限边界题,5 个回跳复核题。每个问题写清期望命中、期望过滤、期望高亮和期望 source_url。以后改索引、改分词、改权限或接入大模型,都用同一批问题回归。
高亮片段也要防止误导
高亮不是万能证据。它只能说明某段文本命中了查询条件,不能自动证明这段文本就是答案。比如用户查“接口复测通过了吗”,高亮里出现“接口复测”,但同一页后面可能写的是“未通过,需退回整改”。如果系统只把高亮片段交给模型,模型可能会忽略上下文。
因此高亮片段最好带一点前后文,并保留页码和原文链接。复核人员可以快速扫片段,但最终判断仍要能打开原文。对 AI 检索来说,高亮是缩小证据范围,不是替代原文。
还有一种误导来自多片段拼接。ES 可能从同一页或多页返回多个 fragment,前端展示时要标明它们来自同一页还是不同页。否则用户会以为几段文字在原文里连续出现。档案证据很讲究语境,片段展示越短,越要保留回跳。
最小验证清单
| 验证项 | 应看到的结果 | 风险信号 |
|---|---|---|
| 字段过滤 | open_state、fonds_no、year 能限制结果 | 所有条件都靠正文匹配 |
| 高亮片段 | 每条命中带页级片段 | 只返回整页文本或自然语言摘要 |
| 原文回跳 | source_url 能打开对应页 | 命中了文字,但无法定位原文 |
| 权限边界 | 无权限内容在召回前被过滤 | 前端或模型再做事后遮挡 |
| 可复测 | 同一 query_id 可查日志和参数 | 只保存最终答案 |
这个清单比“检索效果不错”更有用。它能让团队判断问题出在字段设计、权限过滤、分词、索引更新、前端展示还是模型回答。
如果要把验证做得再硬一点,可以为每个测试问题保留 query_id。日志里记录查询词、用户角色、过滤条件、命中数量、最终返回数量、高亮片段、耗时和调用版本。这样当用户反馈“昨天能查到,今天查不到”时,运维人员能复现同一路径,而不是从界面截图猜。
检索系统最怕不可复盘。它不像单个按钮,点了没反应很容易定位。检索问题可能出在 OCR、索引更新、权限过滤、字段 mapping、分词器、排序、前端展示甚至用户输入。没有 query 日志,所有问题都会被归结为“搜索不准”。有了日志,团队才能知道到底该修哪一层。
领至科技会怎样用这类实验
领至科技在做档案 AI 检索方案时,不会把 ES、向量库和大模型写成互相替代的关系。更稳的架构是:全文索引负责精确命中、字段过滤和高亮定位;向量库负责语义召回;业务系统负责权限、利用日志和原文回跳;大模型只在证据集合内组织回答。
这种分工看起来保守,但适合档案场景。因为档案利用最怕“答得像真的,却找不到来源”。高亮片段不是装饰,source_url 不是附属字段,权限过滤也不是后补逻辑。它们共同决定一条检索结果能不能被人复核。
如果一个最小 ES 实验能跑通 mapping、查询 DSL、高亮返回、权限过滤和回跳字段,后续再接向量库和模型会更稳。需要查看相关方案资料,可以通过文末原文进入领至科技官网。
高亮字段要和原文位置对应
Elasticsearch 返回高亮片段后,还要能回到原文位置。只显示一句高亮文本,用户仍然不知道它来自哪份材料、哪一页、哪一段。比较稳的做法是让索引文档带上 archive_id、component_id、page_no 和段落编号。
高亮不是页面效果,它是复核入口。用户点击高亮后能看到原图、页码和上下文,检索结果才不会停在摘要层。