一个内部知识库既有中文文档也有英文文档,用户用中文提问"如何配置超时",而答案在一份英文的《Timeout Configuration》里。检索返回空——因为分词器按语言切词,中文问句被切成中文 token,而英文文档的索引里没有这些 token。
这个区分很重要,因为它决定了"该改哪一层":模型能翻译,但检索发生在翻译之前——如果索引里根本没有对应的 token,再强的模型也救不回来。
多语言检索的问题不在"模型不懂两种语言",而在索引与查询的切词规则不匹配。而这三种不匹配,各有不同的修法。
1. 三种不匹配
| 不匹配 | 表现 |
|---|---|
| 切词规则不同 | 中文查不到英文 |
| 词形变化 | "timeout"查不到"timeouts" |
| 同义/术语差异 | "超时"vs"timeout" |
第二项在英文内部也很常见:没有词干化(stemming)时,单复数与动词变形搜不到。第一项(切词规则)在中文上更极端:中文没有空格,切词器决定了"哪些字组合成词",而英文按空格切——两者无法"自然对齐"。
第三项是"跨语言"的核心:语义相同但字面完全不同,只能靠"词典/向量"解决,不能靠"匹配"。
2. 四种修法与代价
| 修法 | 效果 |
|---|---|
| 双语同义词表 | 中(覆盖已知术语) |
| 查询改写(翻译后检索) | 中高(需模型) |
| 多字段索引(原文 + 译文) | 高(离线翻译成本) |
| 向量检索(跨语言模型) | 高(成本与调优) |
第一项的维护方式建议是"从零结果日志里长出来":用户搜了什么、改写成了什么,直接给出"该补哪一对"。这比让架构师拍一份术语表更符合真实用法。
第一项最便宜也很有效:把"超时 / timeout / ttl / 有效期"这类核心术语成对维护,几十对就能覆盖大部分查询。
第三项最稳:入库时把英文文档译成中文(或反之)并存两份第三项还有个"质量"问题:机器翻译会把标识符改坏(如把 READ_TIMEOUT 译成"读取超时"),于是"按标识符搜"变得困难。所以原文必须保留,译文只作为补充字段。
,检索任一份都能命中。代价是"离线翻译成本 + 双份索引"。
// 多字段索引:原文与译文各一列,检索时都查
mappings:
properties:
title_zh: { type: text, analyzer: ik_max_word }
title_en: { type: text, analyzer: english }
body_zh: { type: text, analyzer: ik_max_word }
body_en: { type: text, analyzer: english }
// 查询时 multi_match 覆盖四个字段,按分数合并
3. 查询改写:最实用的折中
| 做法 | 说明 |
|---|---|
| 规则改写(术语表) | 无需模型,延迟为零 |
| 模型翻译再检索 | 质量高,多一次调用 |
| 双语并行检索 | 两个查询各查一半,合并 |
第三项在"不确定语言"时最稳:同一问题同时用中英查询各跑一次,把结果合并去重。代价是双倍检索开销,但召回提升明显。
第二项的关键是"翻译要保留术语":模型可能把"TTL"译成"生存时间",而库里写的是 "TTL"——于是翻译反而降低了召回。
// 双语并行检索(合并去重)
const [zh, en] = await Promise.all([
search({ q, lang: "zh" }),
search({ q: translate(q), lang: "en" }),
]);
const merged = dedupByDocId([...zh, ...en]).sort(byScore).slice(0, 10);
// 注意:去重按 doc_id,而不是按内容(同一文档可能两路都命中)
"查询语言识别"本身也有坑:中英混排的查询("怎么设 timeout")无法被简单判定为某一种语言。所以实践上常常"不做语言判定,直接双路检索"——因为误判的代价大于多跑一次的代价。
// 三种策略的取舍
判定语言 → 只查一路:省一次检索,但混排查询会漏
总是双路 → 查两路合并:多一次开销,但不会漏
按字符占比判定(中文 > 30% → 双路):折中,实现简单
// 推荐:总是双路(或字符占比法),因为检索本身很便宜
4. 词形与大小写
| 处理 | 效果 |
|---|---|
| 词干化(stemming) | timeouts → timeout |
| 小写化 | Timeout → timeout |
| 同义词(synonym) | 缩写与全称互相匹配 |
| 拼音/首字母 | 中文场景补充 |
第四项在中文里很实用:"chaoshi"能匹配"超时",前提是索引里存了拼音字段。第一项(词干化)在"搜代码/API 名"时反而是负担:把 OrderService 词干化后可能变成 orderserv,于是"按原名搜"失败。所以代码字段通常用 keyword 类型,不做分析。
而它的成本是"多一个字段 + 分析器"。
第二项在"标识符搜索"上是双刃:小写化让 Timeout 与 timeout 都能命中,但让"大小写敏感"的键名搜索失真。所以"代码/配置键搜索"通常需要"区分大小写的字段"。
5. 混合语言的文档
| 现象 | 原因 |
|---|---|
| 中文文档里夹英文术语 | 分词器可能把术语切碎 |
| 英文文档里有中文注释 | 英文分析器丢掉中文 |
| 表格/代码块内文本 | 未参与索引或切分异常 |
第一项的修法是"多字段 + 多分析器":同一字段用两种分析器各建一份索引,查询时都试。
第三项(表格/代码块)在实际语料里占比不小:切分时若把代码块当普通文本,标识符会被切碎,于是"按函数名搜"命中率极低。
第二项要检查"分析器是否会丢弃非 ASCII"——某些英文分析器会把中文当作分隔符丢掉,于是"中英混排"文档的中文部分完全搜不到。
// 中英混排的三种索引策略
1 单一 ik_max_word(能切中文,英文按字母序列)→ 中英都能搜,但英文词形不处理
2 双字段(zh + en)→ 召回最好,索引体积翻倍
3 统一小写 + ngram → 近似匹配强,精确性差
// 推荐:2(双字段),代价可算,行为可解释
"跨语言"还有一个"反向"问题:英文查询命中中文文档时,摘要展示是中文,而用户读不懂——于是"搜到了但没用"。所以多语言检索不只是"召回",还包括"呈现":按查询语言生成摘要,或提供原文 + 译文两段。
// 召回之后的三种呈现
1 原文 + 机器译文(推荐:可核对)
2 只给模型生成的摘要(快,但可能失真)
3 只给原文(对读不懂的用户等于没召回)
6. 排错:五种"跨语言搜不到"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 中文问英文文档 0 结果 | 切词不匹配 | 双语索引/翻译 |
| 单复数搜不到 | 无词干化 | 加 stemmer |
| 缩写搜不到 | 无同义词 | 术语表 |
| 中英混排只命中一半 | 分析器丢字符 | 双字段 |
| 结果相关性差 | 语言权重未调 | 按语言分权重 |
第五项在"双字段"里很常见:英文命中的分数天然更高(因为英文文档更短),于是中文结果被压下去。第二项(单复数)的修法有个"语言"差异:中文没有复数变化,所以这条只影响英文语料——而它对"英文技术文档库"的召回影响很大。
解法是"按查询语言给字段加权"。
最后一个"落地顺序":先做术语表,再做双字段索引,最后才考虑向量。因为术语表的成本是"几小时",双字段是"几天",向量是"几周 + 调优"——而多数团队的实际问题在"术语表没维护",不在"缺向量检索"。
// 三步走(按投入产出)
1 术语表(50 对术语):成本小时级,覆盖多数"术语不一致"
2 双字段索引(zh + en):成本天级,覆盖"语言不同"
3 跨语言向量:成本周级,覆盖"语义相同但表述完全不同"
// 多数团队在第 2 步就已经"够用"
7. 边界:什么情况不必做多语言
| 场景 | 为什么 | 做法 |
|---|---|---|
| 语料单一语言 | 无跨语言需求 | 单分析器 |
| 术语表已覆盖 | 规则改写够用 | 不做双语索引 |
| 用户能用对方语言提问 | 无需改写 | 不做 |
| 数据量极小 | 可人工浏览 | 不做 |
最后一条与"评测"有关的边界:多语言检索必须"分语言评测"。用整体指标看,中文查询的低召回会被英文查询的高分掩盖——于是"看起来 78% 可用",而中文用户几乎搜不到东西。
判断口径一句话:"用户用的语言"与"语料的语言"是否一致。一致,单语言索引就够;不一致,就必须回答"怎么把两种语言接起来"。
8. 自测
- 三种不匹配里,哪一种"靠匹配永远解决不了"?为什么它只能靠词典或向量?
- 四种修法里,哪一种最便宜也很有效?哪一种最稳但成本最高?
- 为什么"模型翻译"可能降低召回?翻译时该保留什么?
- "双语并行检索"为什么要按 doc_id 去重,而不是按内容?
- 中英混排文档为什么"只命中一半"?双字段方案的代价是什么?
- 判断"是否需要多语言方案"的那个问题是什么?