多向量检索架构(Multi-Vector Retrieval)¶
本页系统化 Eagle-RAG 的多向量检索架构:单查询如何扇出到多个 (collection, encoder) 嵌入空间、空间内如何做 hybrid dense+sparse、跨空间如何用 RRF 秩序融合。这是 证据聚合 的上游 —— 多向量是检索期的多空间扇出,证据聚合是检索后的证据收敛。
与既有文档的关系
本页是概念正本。逐函数实现细节见 检索 与 路由引擎;架构决策见 ADR-004 多编码器 RRF 融合;插件如何注册编码器/集合见 插件架构。
1. 概念定义¶
Multi-vector retrieval(多向量检索)指一次查询同时检索多个嵌入空间,每个空间有自己的向量维度、编码器与模态,结果跨空间融合。区别于「单编码器、单集合、单空间」的朴素 RAG。
Eagle-RAG 的多向量形态可以一句话概括:
单查询扇出到多个
(collection, encoder)嵌入空间 → 空间内可选 hybrid dense+sparse → 跨空间用 RRF(倒数排名融合,rank-only) 融合,永不混用跨嵌入空间的原始分数。
三个关键约束(来自 ADR-004):
- 不同 encoder 的 raw score 不可比(维度、分布、归一化都不同)→ 跨空间只能比排名,不比分数。
- Hybrid sparse 是空间内增强,对同一 collection 的 dense ANN 结果做词项重叠重排,不是跨空间操作。
- Core 默认路由不自动查专用 collection(G4)—— 只有域插件显式路由才会扇出到域专用空间。
2. 三个层次¶
2.1 空间层:多 collection¶
每个 plugin_namespace(= 一个 Milvus Database)内有一组集合,每个集合是一个独立的嵌入空间:
| 集合 | 维度 | 编码器 | 模态 | 归属 |
|---|---|---|---|---|
eagle_text |
1536 | Qwen text-embedding-v4 |
text | Core 默认 |
eagle_visual |
2048 | Qwen3-VL-Embedding-2B | visual | Core 默认 |
eagle_text_biomed |
768 | PubMedBERT | text | biomed 域 |
eagle_text_medcpt |
768 | MedCPT | text | biomed 域 |
eagle_chemical |
512 | MolFormer | text | biomed 域 |
eagle_medical_radiology |
1024 | MedImageInsight | visual | biomed 域 |
eagle_medical_pathology |
1024 | UNI2 | visual | biomed 域 |
代码: 集合元数据经 CollectionProfile(dim, default_encoder, hybrid_enabled, extra_output_fields) 注册到 EncoderRegistry(eagle_rag/plugins/encoder_registry.py L32-39)。Core 默认集合在 eagle_rag/plugins/core_defaults.py 注册(含 hybrid_enabled=True,L106)。
2.2 编码器层:EncoderRegistry¶
EncoderRegistry 是 encoder↔collection 维度契约的唯一真相源:
# eagle_rag/plugins/encoder_registry.py
class EncoderRegistry:
def register(name, encoder, *, dim, modality="text") -> None: ...
def register_collection(collection, *, dim, default_encoder=None,
hybrid_enabled=False, extra_output_fields=()) -> None: ...
def validate_plan(self, collection: str, encoder_name: str) -> None:
# rerank 编码器跳过;否则 encoder.dim 必须等于 collection.dim
...
关键契约:validate_plan(collection, encoder) 在写入前强制 encoder.dim == collection.dim(L111-118),维度不匹配直接 ValueError。这保证「不会把 2048 维向量写进 1536 维集合」这类静默错误。
查询时:EncoderRegistry.collection_profile(collection) 返回该空间的 default_encoder / hybrid_enabled / extra_output_fields,供 RetrieverOrchestrator 决定用哪个编码器编码 query、是否触发 hybrid、读哪些 output 字段。
2.3 融合层:空间内 hybrid + 跨空间 RRF¶
flowchart LR
Q["query"] --> ROUTE["QueryRouteClassifier.route()"]
ROUTE --> P1["Plan 1<br/>(eagle_text, qwen-emb-v4)"]
ROUTE --> P2["Plan 2<br/>(eagle_text_biomed, pubmedbert)"]
ROUTE --> P3["Plan 3<br/>(eagle_visual, qwen3-vl)"]
P1 --> ANN1["dense ANN"]
P2 --> ANN2["dense ANN"]
P3 --> ANN3["dense ANN"]
ANN1 --> HY1["hybrid_fuse_dense_sparse<br/>(alpha·dense + (1-alpha)·sparse)"]
ANN2 --> HY2["hybrid_fuse_dense_sparse"]
ANN3 --> HY3["(visual 无 sparse)"]
HY1 --> RRF["merge_rrf<br/>rank-only 融合"]
HY2 --> RRF
HY3 --> RRF
RRF --> DEDUP["dedupe_cross_collection<br/>source_chunk_id / (doc_id,path)"]
DEDUP --> SUPP["RRF_POST_MERGE hook<br/>候选注入"]
SUPP --> RR["rerank_merged<br/>RERANK_MERGED / qwen3-rerank"]
RR --> OUT["top_n 证据"]
空间内 hybrid(eagle_rag/retrievers/hybrid_text_retriever.py L73-104):
def hybrid_fuse_dense_sparse(dense_nodes, query, *, alpha=0.6,
extra_sparse_terms=None, rrf_k=60):
# 1. 对 dense ANN 结果按词项重叠做 sparse 排序
sparse_nodes = sparse_rank_nodes(dense_nodes, sparse_query, extra_terms=...)
# 2. alpha 加权融合(alpha=1 纯 dense,alpha=0 纯 sparse)
combined = alpha * dense_score + (1 - alpha) * sparse_score
激活条件(retriever_orchestrator.py L427-434, L519-522):router.hybrid_text_enabled 且 collection 命中 router.hybrid_text_collections、或 CollectionProfile.hybrid_enabled、或为默认 text collection。visual 集合不做 sparse(视觉无词项)。
跨空间 RRF(eagle_rag/router/rerank_fusion.py L37-62):merge_rrf 按 1/(k+rank) 加权融合多 plan 结果,空结果集不贡献幻影排名(G8)。这是跨空间的唯一合法融合方式 —— raw score 跨 encoder 不可比。
跨空间去重(rerank_fusion.py L65-93):dedupe_cross_collection 按 source_chunk_id 或 (document_id, path) 折叠重复逻辑 chunk,保留高排名者(G32),并记 rrf_dedupe 审计事件。
3. 查询路由数据结构¶
多向量扇出的「计划」是插件可插拔的:
# eagle_rag/plugins/routing.py
@dataclass(frozen=True)
class CollectionQueryPlan:
collection: str
encoder: str
top_k: int = 5
@dataclass(frozen=True)
class QueryRouteDecision:
plans: tuple[CollectionQueryPlan, ...] # 一次查询可含多个 plan = 多空间扇出
retrieval_hints: dict[str, Any] = ...
稠密/稀疏查询扩写:域插件可订阅 QUERY_DENSE_EXPAND hook,返回 ExpandedQuery(dense_query, sparse_terms, intent)(routing.py L37-43)。
- biomed 示例:用 UMLS 实体识别改写 dense query(如同义词扩展),同时发
sparse_terms(如药物名、MeSH 词)增强 lexical 召回。 RetrieverOrchestrator._retrieve_generic_milvus(L464-479)先调 hook 拿expanded,再编码 dense_query、把sparse_terms透传给hybrid_fuse_dense_sparse。
4. 关键设计契约¶
| 契约 | 出处 | 含义 |
|---|---|---|
| RRF 是跨空间唯一合法融合 | ADR-004 G8 | 不同 encoder 的 raw score 不可比,跨空间只比排名 |
| Hybrid sparse 是空间内增强 | hybrid_text_retriever.py |
同一 collection 的 dense ANN 结果上做词项重叠,非跨空间 |
| Core 默认不查专用 collection | ADR-004 G4 | Core QueryRouteClassifier 永不自动扇出到域集合,仅 eagle_text(+eagle_visual) |
validate_plan 守 dim 一致 |
encoder_registry.py L111-118 |
写入前强制 encoder.dim == collection.dim |
| 单 plan 失败 best-effort 跳过 | ADR-004 G14 | 某空间检索异常返回 [],不拖垮整查询 |
与单域部署的关系:跨行业靠多实例(每 plugin_namespace 一个 Milvus Database),同 DB 内单 query 可跨多 collection(ADR-002、多租户)。多向量是「单域内多空间」,不是「跨域扇出」。
5. 与多模态融合的关系¶
多模态融合 是多向量架构在 modality 维度的特例:eagle_text(1536)与 eagle_visual(2048)是两个不同维度的嵌入空间,文本节点与视觉瓦片经各自编码器入各自集合,查询时双空间扇出、RRF 融合、VLM 同 prompt 消费。多向量架构把这个模式泛化到「任意多个域专用编码器空间」。
6. Roadmap(未实现)¶
以下均为未实现的设计缺口,非当前能力
- per-collection / per-query 自适应 hybrid alpha:当前
router.hybrid_alpha是全局值(hybrid_fuse_dense_sparse的alpha),不同 collection / 不同 query 类型(实体召回 vs 语义泛化)的理想权重不同。 - late-interaction 多向量(ColBERT 风格 token-level 向量):当前每 chunk 一个稠密向量,无 token 级 late interaction。
- 跨空间 score 归一化对比可视化:当前 RRF 隐藏了各空间 raw score,调试时缺乏「各空间分数分布对比」工具。
- 多向量 ablation 评测脚手架:缺乏「关掉某空间看 QA 指标变化」的系统性 ablation 入口(可经
suppress_collectionsintent 部分实现,但无统一脚手架)。
潜在扩展点:CollectionProfile 增 hybrid_alpha 字段;ExpandedQuery.intent 携带 alpha 提示;新增 MULTI_VECTOR_ABLATION 诊断 hook。