你让RAG查型号AB-12的维护手册。工具没有报错,返回结果里却同时出现AB 12和ab/12。你给查询加上双引号,认为这下应当“逐字匹配”,结果还是一样。
问题可能不在模型有没有看懂连字符,而在模型之前的那层索引:全文搜索通常匹配分析后的词项,不直接比较原始字符串;短语匹配也不等于完整型号相等。
RAG是先检索资料、再让模型据资料作答的做法。即使它还有向量检索分支,只要关键词分支把型号条件放宽了,错误手册就可能进入候选集合。本篇只追问一个问题:原文确实保存着这个型号,为什么搜索看到的却是另一种表示?
AI原创概念插画:暮色拱廊前,钴蓝瓷板上的红白狐狸马赛克与陶盘里的散片并置,放大镜呈现红色瓷片接缝。它比喻“匹配局部材料”和“确认完整对象”不同,不是索引实现图或真实产品截图。
先把三个层次分开
原文是文档保存的内容,如AB-12。它可用于展示、引用和后续核查。
索引词项是分析器从原文生成的检索单位。分析器可能分词、折叠大小写、处理标点,也可能采用词干、同义词或其他规则。具体行为取决于引擎、字段和配置,不能把某个默认分析器当成所有搜索服务的共同规则。[1][2]
查询语义则决定怎样使用这些单位:查单个词项、查按次序相邻的短语、查前缀,还是比较完整字段值。
因此,“原文里有”“全文索引能命中”“命中的是这个业务型号”是三项不同验收。返回的原文完整,也不能反推索引保留了所有区分。
这里的token是搜索分析器的词项,不是大模型的token。给模型换分词器,不能直接修复数据库全文索引的切分方式。
一个可运行的小实验:双引号没有保住连字符
下面在本机Python 3.13.5、SQLite 3.53.4上实际运行。数据全部人工指定;使用内存数据库和真实FTS5全文索引,没有调用模型、Embedding、Elasticsearch或生产检索服务。
import sqlite3
c = sqlite3.connect(":memory:")
c.execute('CREATE VIRTUAL TABLE docs USING fts5(label, tokenize="unicode61")')
labels = ["AB-12", "AB 12", "ab/12", "AB-120", "检索工程实践"]
c.executemany("INSERT INTO docs(label) VALUES (?)", [(s,) for s in labels])
c.execute("CREATE VIRTUAL TABLE vocab USING fts5vocab(docs, 'instance')")
print(list(c.execute("SELECT term, offset FROM vocab WHERE doc=1 ORDER BY offset")))
def search(query):
return [r[0] for r in c.execute(
"SELECT label FROM docs WHERE docs MATCH ? ORDER BY rowid", (query,)
)]
print(search('"AB-12"'))
print(search("12"))
print(search("12*"))
print(search("工程"))
print([r[0] for r in c.execute(
"SELECT label FROM docs WHERE label = ? COLLATE BINARY", ("AB-12",)
)])实际输出:
[('ab', 0), ('12', 1)]
['AB-12', 'AB 12', 'ab/12']
['AB-12', 'AB 12', 'ab/12']
['AB-12', 'AB 12', 'ab/12', 'AB-120']
[]
['AB-12']先看第一行:fts5vocab读取实际索引,显示AB-12被存成相邻的ab与12。这里的offset是列内词项序号,不是原文字符坐标。[1]
SQLite的unicode61默认按其规则把连续的词项字符组成词项,标点通常作为分隔符,并做大小写折叠。于是本例三串不同原文:
AB-12AB 12ab/12
都产生了相邻的ab和12。
查询中的"AB-12"同样会被分词。双引号让它成为一个短语,但这个短语是“ab之后紧跟12”,不是“原文必须含大写字母与连字符的这一整串”。因此前三份都命中。[1]
加引号可以改变查询语法,却不能把已经丢在分析阶段的区别重新变回来。
再看12与12*:前者查完整词项12,不会命中词项120;后者是前缀查询,于是AB-120也进入结果。全文检索不是天然的任意子串搜索,扩大为前缀也不是没有代价的修复。
最后一行的SQL使用显式BINARY比较完整保存值,只返回AB-12。这证明本地这组数据中,两条查询的语义不同;不是建议用扫描全文来代替生产索引。线上应为型号建立符合业务比较规则、可索引的结构化字段。
参数绑定安全,不代表查询含义正确
上面的SQL使用?绑定参数,没有把输入拼进SQL。但MATCH接到参数后,仍会按FTS5查询语言解释它。
完整实验还实际测试了不加引号的AB-12:本机返回no such column: 12,属于查询语法解释问题,不是“资料不存在”。正确编码FTS5字符串能让这个输入作为短语进入分析,但仍有刚才的分词等价问题。
所以要连续过两道门:
- 输入能否安全、正确地表达为目标搜索语法;
- 这条语法匹配的单位,是否真是任务要求的单位。
SQL参数绑定不替你选择全文语义;全文短语转义也不替你验证型号身份。不要让适配器把语法错误吞成空结果,再让Agent解释“没找到”。
为什么中文短词有时也会消失
本例检索工程实践在unicode61里成为一个连续词项。查询工程没有得到结果,并不说明原文缺这两个字,而是这个配置没有生成独立的工程词项。
这只是本次配置的行为,不是“SQLite不支持中文”或“所有词法检索都不会分中文”。要选择适合语言和任务的切分策略,并实际观察它生成什么,而不是只看功能名写着“全文检索”。
完整实验另外创建了FTS5的trigram索引。它用连续三个字符的单位支持更一般的子串匹配;在同一人工原文上,MATCH '工程实'命中,MATCH '工程'仍不命中。这与官方所列“小于三个Unicode字符的全文查询子串不匹配”限制一致。[1]
这不是通用中文分词方案。短查询、索引规模、排序质量及LIKE/GLOB等其他操作的行为需要分别核对。本篇没有测索引体积或性能,不能把一个命中反例写成某种切分方式全面更好。
换分析器,还是拆字段?先问搜索想忽略什么
完整实验也给另一张FTS5表配置了把连字符纳入词项的tokenchars。在这组数据上,带引号的AB-12只命中第一项。它说明配置确实能改变边界,但不提供完整业务身份保证:大小写折叠、其他符号、字段中额外内容仍是独立问题。短语也仍可在更长字段里出现。
更稳妥的设计通常是按检索用途拆表示,而不是要求一个正文分析器同时负责自然语言和所有精确条件:
| 用途 | 建议保存的表示 | 验收问题 |
|---|---|---|
| 按问题含义找段落 | 正文全文索引,按需要另设向量索引 | 有没有可支持回答的候选证据? |
| 限定型号、版次、厂商 | 可信目录抽取或登记的结构化字段 | 当前比较规则是否确认这个对象? |
| 展示引文与追溯 | 原件、原值和出处 | 能否回到实际版本与原始写法? |
这些是应用建议,不是RAG统一协议。
Elasticsearch提供了对应的机制区分:官方建议用text处理全文,用keyword类字段处理ID等结构化内容;term查询针对精确词项,不要把它当成对已分析text字段原始整串的比较。match会分析查询文本,适合全文检索,却也不自动升级为型号相等判断。[2][3]
keyword也不是单凭名字就完成验收:仍须核对normalizer、长度忽略条件、数组语义和实际映射,不能假定每个字段都存在可用的.keyword子字段。本篇未运行Elasticsearch,相关内容只依据实际读取的官方文档。
索引侧和查询侧也必须一起检查。Elastic文档说多数情况下两侧应生成相同形式的词项,但同时给出了索引前缀、查询保留完整词项的不同分析器示例。因此原则不是“任何场景必须用同一个分析器”,而是两侧变换必须按同一个检索目标有意配合并经过测试。[4]
让Agent少猜一次:把型号条件独立带到验收门
回到手册场景。以下是未执行的应用设计,不是某服务的实测结果:
- 从可信任务中保留“型号必须为
AB-12”这个约束,不把它降成一句可任意改写的搜索提示。 - 正文全文与向量分支负责找候选;有可靠型号元数据时,在合适的检索阶段加入结构化限制。具体前置或后置机制按搜索引擎契约验收。
- 候选回到原件后,再核对型号、厂商、版次与适用范围。未经目录确认的
AB 12只是可能相关项,不能自动当作别名。 - 没有可靠元数据时,明确候选身份尚未确认;先补抽取或登记核验,不因分数高就让模型掩盖缺口。
- 保留实际命中字段、分析器版本及来源定位。若词项规则改变,处理既有索引与新查询的兼容,不能只改查询侧就宣称修复。
检索得到什么与用户有权读取什么还须分别验证;精确型号匹配不是权限门。后置核对也不能补回早已在候选预算中漏掉的正确文档。
可复用检查清单
- 要求的是词项、短语、前缀、子串,还是完整字段值相等?
- 原件字段与检索字段是否分开,实际词项和位置能否观察?
- 型号中的连字符、空格、斜杠、大小写,哪些差别允许忽略,依据是什么?
- 测试是否包含
AB-12、AB 12、ab/12、AB-120这样的近邻反例? - 中文连续文本和短查询是否按当前切分策略实际命中?
- 查询语法错误、零命中与身份未确认是否分别报告?
- 索引侧与查询侧的配置、语言、版本和字段是否相互匹配?
- 最终引用是否仍回查正确对象与版本,而不是仅凭命中分数?
先看索引到底保存了哪些区别,再决定要不要换模型。 如果工具只认得瓷片的颜色,就不要让它替你保证拼出来的是同一只狐狸。
来源与阅读范围
本文由AI创作。以下均为本轮实际打开并补读的官方资料;动态页面未核实独立原始发布日期、未锁定产品发布版本,不冒充最新公告。实验环境版本另列于正文。
- SQLite FTS5官方文档:实际阅读3.1字符串、3.2短语、3.3前缀、4.3.1 Unicode61、4.3.4 trigram与第8节fts5vocab相关正文。文档的Unicode 6.1指该tokenizer规则,不是本机Python的Unicode数据库版本。
- Elasticsearch Term query:实际阅读查询定义、参数及避免在text字段使用term的说明;仅采用分析与查询机制,不引用网页示例中的词干变化作为默认分析器的额外保证。
- Elasticsearch Keyword type family:实际阅读开头、标识符用途及normalizer、ignore_above等参数说明。未运行服务端示例或性能比较。
- Elastic Index and search analysis:实际阅读两侧分析、同形词项与不同search analyzer的前缀示例。产品能力限定于这份动态资料。
检索截止:2026年10月27日21:11 UTC。所有代码与对照只验证人工夹具上的本地词法机制,不报告Agent准确率或真实RAG收益。


留下你的想法 0
欢迎提问、补充和不同观点。评论通过审核后会公开显示。