多模态融合搜索:为每一次查询挑选正确的检索器
> 像“parseConfig 是在哪里定义的”这样的查询,和“身份验证是怎么工作的”所需要的搜索方式截然不同。Maguyva 会对查询意图进行分类,据此为四种检索模态分配权重,再用加权的 Reciprocal Rank Fusion(倒数排序融合)把结果融合起来。
历史设计说明:这篇 2026 年 4 月的文章描述的是当时的路由设计,并非当前的默认行为。下方 9 月的更正仅针对一项测量声明。关于当前的路由、预设和显式检索模态控制,请参阅工作原理。
搜索查询从来都不是单一的一种东西。
“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)绕开了这个问题:它丢弃原始分数,只保留每个引擎给出的排名。一个结果来自某一模态的贡献值是:
contribution = weight × 1 / (k + rank + 1)
其中 rank 是它在该模态列表中的位置,k 是一个平滑常数。对于被不止一个引擎找到的结果,各模态的贡献会被累加——检索器之间的一致意见自然会浮到最上面。默认预设下我们使用 k = 40,在 thorough 上使用 60(quick 只运行语义检索,所以那里从不会触发融合)。RRF 原始论文对通用检索场景给出的是 k = 60;我们的默认值略微更“锐利”一些,这让排名靠前、被多个模态一致认可的结果获得更多一点的权重——我们不建议手动去调它。
除此之外,结果还会带有一个图重要性加成。一个枢纽节点——整个代码库都依赖的一个函数——即便文本相关性相同,也应该排在一个不起眼的叶子节点前面,所以我们会把每个贡献值乘以:
boost = min(1 + 0.3 × ln(1 + centrality), 1.5)
中心度来自流水线预先计算好的 PageRank / 度数指标,加成被限制在 1.5 倍以内,这样一个热门函数就不会把一个更相关但不起眼的函数完全埋没。最后,我们会降权来自 vendor、build 和 archive 路径的结果,并按文件对最佳片段去重。
仍不完美之处
意图分类器是一组正则表达式,而不是经过训练的模型。它使用启发式方法处理常见的查询形式,遇到含糊的查询时,会回退到 understand_code 和偏重语义的混合检索。本文的早期版本曾将无结果查询比例从约 15% 降至低于 5% 描述为已实现的改进。实际上,2025 年 12 月的提案列出的是估计基线和目标,并未提供支持这一结果的查询集或测量记录。我们撤回这项已实现改进的声明;参见源证据与更正(英文)。我们尚未用训练过的模型替换该分类器,因为低成本版本对于预期的路由用途而言足够快、也足够好,而且一个自信却错误的分类器,比一个坦诚的回退方案更糟。这些权重是手工选定的先验值,并非从我们没有收集的点击数据中学习而来。
问问题,而不是问该用哪个工具
智能体不应该需要知道该用 grep 还是嵌入向量还是调用图——它应该用大白话问出自己的问题,然后得到正确的答案。多模态融合正是让 find_symbol、语义搜索和依赖分析能够共处于同一个查询入口背后的原因:系统会读出问题的形状,并悄悄组装出适合它的检索器。给嵌入打分的模型固然重要,但知道什么时候不该用它同样重要。为每个查询挑选正确的工具,本身就是一种质量,而这是我们宁愿自己承担、也不愿推给调用方的事情。
// you bring the question. it brings the tools.
相关阅读
更多来自 Maguyva 开发日志的内容
我们为什么把代码搜索升级到了 voyage-4-large_
我们把代码嵌入模型迁移到了 voyage-4-large——目前公开的 RTEB 代码检索排行榜上排名第一的模型。诚实的版本是这样的:我们做出的取舍、我们实际索引的内容,以及我们为什么愿意为高端嵌入模型付费。
智能体可观测性:Hooks、Alloy 与 Grafana_
我们用 OpenTelemetry 和 Alloy,把 Claude Code 与 Codex 接入同一套 Grafana 技术栈,再借助 Trace 和日志从源头定位并修复智能体的行为问题。
挖掘闭环:变更如何沉淀为组织记忆_
Git 提交会变成结构化的变更日志条目和架构决策记录,再反哺回 AI 智能体,成为可查询的组织记忆。