# 多模态融合搜索：为每一次查询挑选正确的检索器

Canonical source: https://maguyva.ai/zh-hans/blog/multi-modal-fusion-search

License: text under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/); code samples under [MIT-0](https://opensource.org/license/mit-0); the Maguyva name, logos, brand assets and other code are not covered. Details: [maguyva.ai/license](https://maguyva.ai/license).

Date: 2026-04-15 · Author: Maguyva Team

> 像“parseConfig 是在哪里定义的”这样的查询，和“身份验证是怎么工作的”所需要的搜索方式截然不同。Maguyva 会对查询意图进行分类，据此为四种检索模态分配权重，再用加权的 Reciprocal Rank Fusion（倒数排序融合）把结果融合起来。

> 历史设计说明：这篇 2026 年 4 月的文章描述的是当时的路由设计，并非当前的默认行为。下方 9 月的更正仅针对一项测量声明。关于当前的路由、预设和显式检索模态控制，请参阅[工作原理](https://maguyva.ai/zh-hans/docs/how-it-works)。

搜索查询从来都不是单一的一种东西。

“`parseConfig` 在哪里定义”想要的是一个精确的符号——一个准确的位置，越快越好。“身份验证是如何工作的”想要的是含义——一整片能解释某个概念的相关代码。“如果我改动这个函数会破坏什么”想要的是依赖关系图。“查找字符串 `ECONNREFUSED`”想要的是字面匹配，不需要任何巧思。

Grep 非常擅长字面匹配，对某些引用查找也很有用，但它不是依赖关系图，也不理解含义。嵌入向量覆盖了语义这一侧，但对精确字符串匹配和影响分析来说，它是错误的工具。大多数代码搜索工具只挑一种引擎，然后让每一个查询都受制于这个选择。Maguyva 不做这种取舍。它会先判断你问的是哪一类问题，再按这个问题应得的比例混合四种检索器。

## 四种模态

底层有四种独立的代码查找方式：

- **语义（semantic）**——基于 Voyage 二进制嵌入的向量搜索；按含义查找代码。
- **文本（text）**——trigram 匹配；查找字面量、错误字符串、精确标识符。
- **结构（structural）**——AST 查询；查找定义、签名和语言构造。
- **图（graph）**——依赖关系图；查找调用方、被调用方和影响范围（blast radius）。

每一种模态都擅长应对不同类型的问题。诀窍在于针对眼前这个查询，决定该在多大程度上信任每一种模态。

## 意图分类

在任何检索开始之前，一个轻量级分类器会先把查询归入六种意图之一，并给出一个置信度分数。它被刻意做得很“便宜”——按顺序排列的启发式规则，第一个命中就采用——因为它运行在热路径上，只会额外增加一两毫秒：

- 以 `def `、`class `、`func `、`import ` 开头…… → **find_definition**（置信度 0.95）
- “who calls”、“usages of”、“references to” → **find_references**（0.90）
- “impact”、“blast radius”、“what depends on” → **impact_analysis**（0.90）
- 带引号的 `"string"` 或像 `traceback` 这样的错误标记 → **exact_match**（0.85–0.90）
- `CamelCase` 或 `snake_case` 这样的标识符 → **find_definition**（0.60–0.80）
- “how”、“why”、“explain”、“architecture” → **understand_code**（0.75）
- 什么都不匹配 → **understand_code**，低置信度（0.40）

每种意图在四种模态上都带有一套权重配置。以下是实际数值：

| 意图 | semantic | text | structural (AST) | graph |
|---|---|---|---|---|
| find_definition | 0.2 | 0.1 | **0.6** | 0.1 |
| find_references | 0.1 | 0.2 | 0.2 | **0.5** |
| understand_code | **0.5** | 0.2 | 0.2 | 0.1 |
| find_similar | **0.4** | 0.3 | 0.2 | 0.1 |
| impact_analysis | 0.1 | 0.1 | 0.1 | **0.7** |
| exact_match | 0.0 | **0.9** | 0.1 | 0.0 |

所以“`parseConfig` 在哪里定义”会强烈偏向 AST（0.6）。“身份验证是如何工作的”会偏向语义向量（0.5）。“这个改动会影响到什么”几乎完全落在图模态上（0.7）。“查找 `ECONNREFUSED`”几乎全靠 trigram（0.9），嵌入模型完全被关闭——因为对精确字符串来说，语义相似度恰恰是错误的工具。

## 快速路径与融合路径

当分类器足够自信时——分数 ≥ 0.85——并且查询是一个常规查询，Maguyva 会完全跳过融合，直接路由到那个唯一占主导地位的模态。“X 在哪里定义”不需要四个检索器，它现在就需要 AST 索引。这条直接路径会被回报为 `fusion_strategy: "direct"`。

一切模糊不清的查询都要经过融合。四种（或在默认预设下是三种）模态并行运行，各自返回自己的排序列表，然后我们把它们合并起来。

## 加权倒数排名融合（Weighted Reciprocal Rank Fusion）

融合异构检索器比听起来更难：0.82 的余弦相似度、137 的 trigram 分数和 0.004 的图中心度并不在同一个量纲上，所以不能直接相加。倒数排名融合（Reciprocal Rank Fusion）绕开了这个问题：它丢弃原始分数，只保留每个引擎给出的**排名**。一个结果来自某一模态的贡献值是：

```text
contribution = weight × 1 / (k + rank + 1)
```

其中 `rank` 是它在该模态列表中的位置，`k` 是一个平滑常数。对于被不止一个引擎找到的结果，各模态的贡献会被累加——检索器之间的一致意见自然会浮到最上面。默认预设下我们使用 **k = 40**，在 `thorough` 上使用 60（`quick` 只运行语义检索，所以那里从不会触发融合）。RRF 原始论文对通用检索场景给出的是 k = 60；我们的默认值略微更“锐利”一些，这让排名靠前、被多个模态一致认可的结果获得更多一点的权重——我们不建议手动去调它。

除此之外，结果还会带有一个**图重要性加成**。一个枢纽节点——整个代码库都依赖的一个函数——即便文本相关性相同，也应该排在一个不起眼的叶子节点前面，所以我们会把每个贡献值乘以：

```text
boost = min(1 + 0.3 × ln(1 + centrality), 1.5)
```

中心度来自流水线预先计算好的 PageRank / 度数指标，加成被限制在 1.5 倍以内，这样一个热门函数就不会把一个更相关但不起眼的函数完全埋没。最后，我们会降权来自 vendor、build 和 archive 路径的结果，并按文件对最佳片段去重。

## 仍不完美之处

意图分类器是一组正则表达式，而不是经过训练的模型。它使用启发式方法处理常见的查询形式，遇到含糊的查询时，会回退到 `understand_code` 和偏重语义的混合检索。本文的早期版本曾将无结果查询比例从约 15% 降至低于 5% 描述为已实现的改进。实际上，2025 年 12 月的提案列出的是估计基线和目标，并未提供支持这一结果的查询集或测量记录。我们撤回这项已实现改进的声明；参见[源证据与更正（英文）](https://maguyva.ai/evidence/search-quality.md)。我们尚未用训练过的模型替换该分类器，因为低成本版本对于预期的路由用途而言足够快、也足够好，而且一个自信却错误的分类器，比一个坦诚的回退方案更糟。这些权重是手工选定的先验值，并非从我们没有收集的点击数据中学习而来。

## 问问题，而不是问该用哪个工具

智能体不应该需要知道该用 grep 还是嵌入向量还是调用图——它应该用大白话问出自己的问题，然后得到正确的答案。多模态融合正是让 `find_symbol`、语义搜索和依赖分析能够共处于同一个查询入口背后的原因：系统会读出问题的形状，并悄悄组装出适合它的检索器。给嵌入打分的模型固然重要，但知道*什么时候不该*用它同样重要。为每个查询挑选正确的工具，本身就是一种质量，而这是我们宁愿自己承担、也不愿推给调用方的事情。

`// you bring the question. it brings the tools.`
